lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 6df2858a4101aa78008a3fe64a100c548f4f17b6
parent ba8d92e530c5576d6c2ad3ca0ec43a92a16fbe7e
Author: triesap <tyson@radroots.org>
Date:   Tue, 14 Apr 2026 04:52:09 +0000

config: prefer root env for local sdk defaults

Diffstat:
Mcrates/sdk/src/config.rs | 67++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mcrates/sdk/tests/config.rs | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 125 insertions(+), 5 deletions(-)

diff --git a/crates/sdk/src/config.rs b/crates/sdk/src/config.rs @@ -2,7 +2,7 @@ use alloc::{string::String, vec::Vec}; use core::fmt; #[cfg(feature = "std")] -use std::{string::String, vec::Vec}; +use std::{env, string::String, vec::Vec}; pub const RADROOTS_SDK_PRODUCTION_RELAY_URL: &str = "wss://radroots.org"; pub const RADROOTS_SDK_STAGING_RELAY_URL: &str = "wss://staging.radroots.org"; @@ -15,6 +15,19 @@ pub const RADROOTS_SDK_LOCAL_RADROOTSD_ENDPOINT: &str = "http://127.0.0.1:7070"; pub const RADROOTS_SDK_DEFAULT_TIMEOUT_MS: u64 = 10_000; +#[cfg(feature = "std")] +const LOCAL_RELAY_SCHEME_ENV: &str = "NOSTR_RS_RELAY_PUBLIC_SCHEME"; +#[cfg(feature = "std")] +const LOCAL_RELAY_HOST_ENV: &str = "NOSTR_RS_RELAY_PUBLIC_HOST"; +#[cfg(feature = "std")] +const LOCAL_RELAY_PORT_ENV: &str = "NOSTR_RS_RELAY_PUBLIC_PORT"; +#[cfg(feature = "std")] +const LOCAL_RADROOTSD_ENDPOINT_ENV: &str = "RADROOTSD_RPC_URL"; +#[cfg(feature = "std")] +const LOCAL_RADROOTSD_HOST_ENV: &str = "RADROOTSD_RPC_HOST"; +#[cfg(feature = "std")] +const LOCAL_RADROOTSD_PORT_ENV: &str = "RADROOTSD_RPC_PORT"; + #[derive(Debug, Clone, PartialEq, Eq)] pub struct RadrootsSdkConfig { pub environment: SdkEnvironment, @@ -113,6 +126,12 @@ impl RelayConfig { environment: SdkEnvironment, ) -> Result<Vec<String>, SdkConfigError> { if self.urls.is_empty() { + if environment == SdkEnvironment::Local { + #[cfg(feature = "std")] + if let Some(local_url) = resolve_local_relay_url_from_env() { + return Ok(vec![normalize_relay_url(local_url.as_str())?]); + } + } return environment .default_relay_urls() .ok_or(SdkConfigError::MissingCustomRelayUrls); @@ -132,10 +151,19 @@ impl RadrootsdConfig { pub fn resolved_endpoint(&self, environment: SdkEnvironment) -> Result<String, SdkConfigError> { match self.endpoint.as_deref() { Some(endpoint) => normalize_radrootsd_endpoint(endpoint), - None => environment - .default_radrootsd_endpoint() - .map(str::to_owned) - .ok_or(SdkConfigError::MissingCustomRadrootsdEndpoint), + None => { + if environment == SdkEnvironment::Local { + #[cfg(feature = "std")] + if let Some(endpoint) = resolve_local_radrootsd_endpoint_from_env() { + return normalize_radrootsd_endpoint(endpoint.as_str()); + } + } + + environment + .default_radrootsd_endpoint() + .map(str::to_owned) + .ok_or(SdkConfigError::MissingCustomRadrootsdEndpoint) + } } } } @@ -265,3 +293,32 @@ fn normalize_radrootsd_endpoint(value: &str) -> Result<String, SdkConfigError> { } Ok(trimmed.to_owned()) } + +#[cfg(feature = "std")] +fn resolve_local_relay_url_from_env() -> Option<String> { + let scheme = read_trimmed_env(LOCAL_RELAY_SCHEME_ENV)?; + let host = read_trimmed_env(LOCAL_RELAY_HOST_ENV)?; + let port = read_trimmed_env(LOCAL_RELAY_PORT_ENV)?; + Some(format!("{scheme}://{host}:{port}")) +} + +#[cfg(feature = "std")] +fn resolve_local_radrootsd_endpoint_from_env() -> Option<String> { + if let Some(endpoint) = read_trimmed_env(LOCAL_RADROOTSD_ENDPOINT_ENV) { + return Some(endpoint); + } + + let host = read_trimmed_env(LOCAL_RADROOTSD_HOST_ENV)?; + let port = read_trimmed_env(LOCAL_RADROOTSD_PORT_ENV)?; + Some(format!("http://{host}:{port}")) +} + +#[cfg(feature = "std")] +fn read_trimmed_env(key: &str) -> Option<String> { + let value = env::var(key).ok()?; + let trimmed = value.trim(); + if trimmed.is_empty() { + return None; + } + Some(trimmed.to_owned()) +} diff --git a/crates/sdk/tests/config.rs b/crates/sdk/tests/config.rs @@ -4,6 +4,43 @@ use radroots_sdk::{ RADROOTS_SDK_STAGING_RADROOTSD_ENDPOINT, RADROOTS_SDK_STAGING_RELAY_URL, RadrootsSdkConfig, RadrootsdAuth, SdkConfigError, SdkEnvironment, SdkTransportMode, SignerConfig, }; +use std::sync::{Mutex, OnceLock}; + +fn sdk_env_lock() -> &'static Mutex<()> { + static LOCK: OnceLock<Mutex<()>> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())) +} + +fn with_local_sdk_env<F>(pairs: &[(&str, &str)], test: F) +where + F: FnOnce(), +{ + let _guard = sdk_env_lock().lock().expect("sdk env lock"); + let saved = pairs + .iter() + .map(|(key, _)| (key.to_string(), std::env::var(key).ok())) + .collect::<Vec<_>>(); + + for (key, value) in pairs { + // The global lock keeps env mutation single-threaded for this test file. + unsafe { + std::env::set_var(key, value); + } + } + + test(); + + for (key, original) in saved { + match original { + Some(value) => unsafe { + std::env::set_var(&key, value); + }, + None => unsafe { + std::env::remove_var(&key); + }, + } + } +} #[test] fn default_config_uses_production_relay_direct_draft_only() { @@ -65,6 +102,32 @@ fn local_environment_resolves_localhost_defaults() { } #[test] +fn local_environment_prefers_root_env_contract_when_present() { + with_local_sdk_env( + &[ + ("NOSTR_RS_RELAY_PUBLIC_SCHEME", "ws"), + ("NOSTR_RS_RELAY_PUBLIC_HOST", "127.0.0.1"), + ("NOSTR_RS_RELAY_PUBLIC_PORT", "18080"), + ("RADROOTSD_RPC_URL", "http://127.0.0.1:17070/jsonrpc"), + ], + || { + let config = RadrootsSdkConfig::local(); + + assert_eq!( + config.resolved_relay_urls().expect("relay defaults"), + vec!["ws://127.0.0.1:18080".to_owned()] + ); + assert_eq!( + config + .resolved_radrootsd_endpoint() + .expect("radrootsd endpoint"), + "http://127.0.0.1:17070/jsonrpc" + ); + }, + ); +} + +#[test] fn explicit_coordinates_override_environment_defaults_exactly() { let mut config = RadrootsSdkConfig::production(); config.relay.urls = vec![