rhi

Coordinated trade for connected markets
git clone https://radroots.dev/git/rhi.git
Log | Files | Refs | README | LICENSE

commit 299f6a9e4b98ebfe20e1c6d3a99d099ffb70a8ba
parent dd68ec93b38e0acbfa8f0a33afa94d5e0fe8d4aa
Author: triesap <tyson@radroots.org>
Date:   Thu,  9 Apr 2026 03:55:32 +0000

rhi: extract runtime paths module

Diffstat:
Msrc/config.rs | 167++++++-------------------------------------------------------------------------
Msrc/features/trade_listing/state.rs | 2+-
Msrc/identity_storage.rs | 2+-
Msrc/lib.rs | 1+
Msrc/main.rs | 15+++++++--------
Asrc/paths.rs | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 174 insertions(+), 165 deletions(-)

diff --git a/src/config.rs b/src/config.rs @@ -1,21 +1,12 @@ -use anyhow::{Context, Result, bail}; +use anyhow::{Context, Result}; use radroots_nostr::prelude::RadrootsNostrMetadata; use radroots_runtime::{BackoffConfig, RadrootsNostrServiceConfig}; -use radroots_runtime_paths::{ - DEFAULT_CONFIG_FILE_NAME, DEFAULT_SERVICE_IDENTITY_FILE_NAME, RadrootsPathOverrides, - RadrootsPathProfile, RadrootsPathResolver, RadrootsRuntimeNamespace, -}; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; -const RHI_RUNTIME_ID: &str = "rhi"; -const SUBSCRIBER_STATE_DIR_NAME: &str = "trade-listing"; -const SUBSCRIBER_STATE_FILE_NAME: &str = "state.json"; -const RHI_PATHS_PROFILE_ENV: &str = "RHI_PATHS_PROFILE"; -const RHI_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RHI_PATHS_REPO_LOCAL_ROOT"; -const RHI_DEFAULT_SHARED_SECRET_BACKEND: &str = "encrypted_file"; -const RHI_ALLOWED_PROFILES: [&str; 3] = ["interactive_user", "service_host", "repo_local"]; -const RHI_ALLOWED_SHARED_SECRET_BACKENDS: [&str; 1] = ["encrypted_file"]; +use crate::paths::{ + RhiRuntimePaths, default_subscriber_state_path_for_process, resolve_runtime_paths_with_resolver, +}; fn default_replay_window_secs() -> u64 { 24 * 60 * 60 @@ -25,26 +16,6 @@ fn default_replay_overlap_secs() -> u64 { 5 * 60 } -#[derive(Debug, Clone, PartialEq, Eq)] -struct RhiRuntimePaths { - config_path: PathBuf, - logs_dir: PathBuf, - identity_path: PathBuf, - subscriber_state_path: PathBuf, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -pub struct RhiRuntimeContractOutput { - pub active_profile: String, - pub allowed_profiles: Vec<String>, - pub default_shared_secret_backend: String, - pub allowed_shared_secret_backends: Vec<String>, - pub canonical_config_path: PathBuf, - pub canonical_logs_dir: PathBuf, - pub canonical_identity_path: PathBuf, - pub canonical_subscriber_state_path: PathBuf, -} - #[derive(Debug, Deserialize, Clone, Default)] struct RawServiceConfig { #[serde(default)] @@ -185,125 +156,10 @@ pub struct Settings { pub config: Configuration, } -fn parse_path_profile(value: &str) -> Result<RadrootsPathProfile> { - match value { - "interactive_user" => Ok(RadrootsPathProfile::InteractiveUser), - "service_host" => Ok(RadrootsPathProfile::ServiceHost), - "repo_local" => Ok(RadrootsPathProfile::RepoLocal), - _ => bail!( - "{RHI_PATHS_PROFILE_ENV} must be `interactive_user`, `service_host`, or `repo_local`" - ), - } -} - -fn process_path_selection() -> Result<(RadrootsPathProfile, Option<PathBuf>)> { - let profile = match std::env::var(RHI_PATHS_PROFILE_ENV) { - Ok(value) => parse_path_profile(&value)?, - Err(std::env::VarError::NotPresent) => RadrootsPathProfile::InteractiveUser, - Err(std::env::VarError::NotUnicode(_)) => { - bail!("{RHI_PATHS_PROFILE_ENV} must be valid utf-8 when set") - } - }; - let repo_local_root = std::env::var_os(RHI_PATHS_REPO_LOCAL_ROOT_ENV).map(PathBuf::from); - Ok((profile, repo_local_root)) -} - -fn path_overrides_for( - profile: RadrootsPathProfile, - repo_local_root: Option<&Path>, -) -> Result<RadrootsPathOverrides> { - match profile { - RadrootsPathProfile::RepoLocal => { - let repo_local_root = repo_local_root.context(format!( - "{RHI_PATHS_REPO_LOCAL_ROOT_ENV} must be set when {RHI_PATHS_PROFILE_ENV}=repo_local" - ))?; - Ok(RadrootsPathOverrides::repo_local(repo_local_root)) - } - _ => Ok(RadrootsPathOverrides::default()), - } -} - -fn resolve_runtime_paths_with_resolver( - resolver: &RadrootsPathResolver, - profile: RadrootsPathProfile, - repo_local_root: Option<&Path>, -) -> Result<RhiRuntimePaths> { - let namespace = RadrootsRuntimeNamespace::worker(RHI_RUNTIME_ID) - .map_err(|error| anyhow::anyhow!("resolve rhi namespace: {error}"))?; - let overrides = path_overrides_for(profile, repo_local_root)?; - let namespaced = resolver - .resolve(profile, &overrides) - .map_err(|error| anyhow::anyhow!("resolve rhi runtime paths: {error}"))? - .namespaced(&namespace); - Ok(RhiRuntimePaths { - config_path: namespaced.config.join(DEFAULT_CONFIG_FILE_NAME), - logs_dir: namespaced.logs, - identity_path: namespaced.secrets.join(DEFAULT_SERVICE_IDENTITY_FILE_NAME), - subscriber_state_path: namespaced - .data - .join(SUBSCRIBER_STATE_DIR_NAME) - .join(SUBSCRIBER_STATE_FILE_NAME), - }) -} - -fn default_runtime_paths_for_process() -> Result<RhiRuntimePaths> { - let (profile, repo_local_root) = process_path_selection()?; - resolve_runtime_paths_with_resolver( - &RadrootsPathResolver::current(), - profile, - repo_local_root.as_deref(), - ) -} - -pub fn default_config_path_for_process() -> Result<PathBuf> { - Ok(default_runtime_paths_for_process()?.config_path) -} - -pub fn default_identity_path_for_process() -> Result<PathBuf> { - Ok(default_runtime_paths_for_process()?.identity_path) -} - -pub fn default_subscriber_state_path_for_process() -> Result<PathBuf> { - Ok(default_runtime_paths_for_process()?.subscriber_state_path) -} - -pub fn runtime_contract_for_process() -> Result<RhiRuntimeContractOutput> { - let (profile, repo_local_root) = process_path_selection()?; - runtime_contract_with_resolver( - &RadrootsPathResolver::current(), - profile, - repo_local_root.as_deref(), - ) -} - -fn runtime_contract_with_resolver( - resolver: &RadrootsPathResolver, - profile: RadrootsPathProfile, - repo_local_root: Option<&Path>, -) -> Result<RhiRuntimeContractOutput> { - let paths = resolve_runtime_paths_with_resolver(resolver, profile, repo_local_root)?; - Ok(RhiRuntimeContractOutput { - active_profile: profile.to_string(), - allowed_profiles: RHI_ALLOWED_PROFILES - .into_iter() - .map(str::to_owned) - .collect(), - default_shared_secret_backend: RHI_DEFAULT_SHARED_SECRET_BACKEND.to_owned(), - allowed_shared_secret_backends: RHI_ALLOWED_SHARED_SECRET_BACKENDS - .into_iter() - .map(str::to_owned) - .collect(), - canonical_config_path: paths.config_path, - canonical_logs_dir: paths.logs_dir, - canonical_identity_path: paths.identity_path, - canonical_subscriber_state_path: paths.subscriber_state_path, - }) -} - fn load_settings_from_path_with_resolver( path: &Path, - resolver: &RadrootsPathResolver, - profile: RadrootsPathProfile, + resolver: &radroots_runtime_paths::RadrootsPathResolver, + profile: radroots_runtime_paths::RadrootsPathProfile, repo_local_root: Option<&Path>, ) -> Result<Settings> { let paths = resolve_runtime_paths_with_resolver(resolver, profile, repo_local_root)?; @@ -315,10 +171,10 @@ fn load_settings_from_path_with_resolver( } pub fn load_settings_from_path(path: &Path) -> Result<Settings> { - let (profile, repo_local_root) = process_path_selection()?; + let (profile, repo_local_root) = crate::paths::process_path_selection()?; load_settings_from_path_with_resolver( path, - &RadrootsPathResolver::current(), + &radroots_runtime_paths::RadrootsPathResolver::current(), profile, repo_local_root.as_deref(), ) @@ -326,9 +182,10 @@ pub fn load_settings_from_path(path: &Path) -> Result<Settings> { #[cfg(test)] mod tests { - use super::{ - default_subscriber_state_path_for_process, load_settings_from_path_with_resolver, - resolve_runtime_paths_with_resolver, runtime_contract_with_resolver, + use super::load_settings_from_path_with_resolver; + use crate::paths::{ + default_subscriber_state_path_for_process, resolve_runtime_paths_with_resolver, + runtime_contract_with_resolver, }; use radroots_runtime_paths::{ RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, diff --git a/src/features/trade_listing/state.rs b/src/features/trade_listing/state.rs @@ -83,7 +83,7 @@ struct PersistedTradeListingState { impl Default for TradeListingRuntimeConfig { fn default() -> Self { Self { - state_path: crate::config::default_subscriber_state_path_for_process() + state_path: crate::paths::default_subscriber_state_path_for_process() .expect("resolve canonical rhi trade-listing state path"), replay_window_secs: 24 * 60 * 60, replay_overlap_secs: 5 * 60, diff --git a/src/identity_storage.rs b/src/identity_storage.rs @@ -41,7 +41,7 @@ pub fn load_encrypted_identity(path: impl AsRef<Path>) -> Result<RadrootsIdentit fn resolved_identity_path(path: Option<&Path>) -> PathBuf { path.map(Path::to_path_buf).unwrap_or_else(|| { - crate::config::default_identity_path_for_process() + crate::paths::default_identity_path_for_process() .expect("resolve canonical rhi identity path") }) } diff --git a/src/lib.rs b/src/lib.rs @@ -5,6 +5,7 @@ pub mod cli; pub mod config; pub mod features; pub mod identity_storage; +pub mod paths; pub mod rhi; pub use cli::Args as cli_args; diff --git a/src/main.rs b/src/main.rs @@ -5,7 +5,7 @@ use anyhow::Context; use anyhow::Result; #[cfg(not(test))] use clap::Parser; -use rhi::{cli_args, config, run_rhi}; +use rhi::{cli_args, config, paths, run_rhi}; use std::path::PathBuf; use std::process::ExitCode; use tracing::info; @@ -78,7 +78,7 @@ fn load_args_and_settings() -> Result<(cli_args, config::Settings)> { .config .clone() .map(Ok) - .unwrap_or_else(config::default_config_path_for_process)?; + .unwrap_or_else(paths::default_config_path_for_process)?; let settings = config::load_settings_from_path(&config_path).context("load configuration")?; radroots_runtime::init_with_logs_dir( @@ -92,7 +92,7 @@ fn load_args_and_settings() -> Result<(cli_args, config::Settings)> { fn runtime_startup_report( args: &cli_args, settings: &config::Settings, - contract: &config::RhiRuntimeContractOutput, + contract: &paths::RhiRuntimeContractOutput, ) -> RhiRuntimeStartupReport { RhiRuntimeStartupReport { active_profile: contract.active_profile.clone(), @@ -140,8 +140,7 @@ async fn run() -> Result<()> { #[cfg(not(test))] { - let contract = - config::runtime_contract_for_process().context("resolve runtime contract")?; + let contract = paths::runtime_contract_for_process().context("resolve runtime contract")?; let report = runtime_startup_report(&args, &settings, &contract); log_runtime_startup_report(&report); } @@ -160,7 +159,7 @@ mod tests { }; use radroots_nostr::prelude::{RadrootsNostrClient, RadrootsNostrKeys}; use rhi::features::trade_listing::state::TradeListingRuntime; - use rhi::{cli_args, config}; + use rhi::{cli_args, config, paths}; use std::path::PathBuf; use std::process::ExitCode; @@ -182,8 +181,8 @@ mod tests { } } - fn sample_runtime_contract() -> config::RhiRuntimeContractOutput { - config::RhiRuntimeContractOutput { + fn sample_runtime_contract() -> paths::RhiRuntimeContractOutput { + paths::RhiRuntimeContractOutput { active_profile: "interactive_user".to_string(), allowed_profiles: vec![ "interactive_user".to_string(), diff --git a/src/paths.rs b/src/paths.rs @@ -0,0 +1,152 @@ +use std::path::{Path, PathBuf}; + +use anyhow::{Context, Result, bail}; +use radroots_runtime_paths::{ + DEFAULT_CONFIG_FILE_NAME, DEFAULT_SERVICE_IDENTITY_FILE_NAME, RadrootsPathOverrides, + RadrootsPathProfile, RadrootsPathResolver, RadrootsRuntimeNamespace, +}; +use serde::Serialize; + +const RHI_RUNTIME_ID: &str = "rhi"; +const SUBSCRIBER_STATE_DIR_NAME: &str = "trade-listing"; +const SUBSCRIBER_STATE_FILE_NAME: &str = "state.json"; +const RHI_PATHS_PROFILE_ENV: &str = "RHI_PATHS_PROFILE"; +const RHI_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RHI_PATHS_REPO_LOCAL_ROOT"; +const RHI_DEFAULT_SHARED_SECRET_BACKEND: &str = "encrypted_file"; +const RHI_ALLOWED_PROFILES: [&str; 3] = ["interactive_user", "service_host", "repo_local"]; +const RHI_ALLOWED_SHARED_SECRET_BACKENDS: [&str; 1] = ["encrypted_file"]; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct RhiRuntimePaths { + pub(crate) config_path: PathBuf, + pub(crate) logs_dir: PathBuf, + pub(crate) identity_path: PathBuf, + pub(crate) subscriber_state_path: PathBuf, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct RhiRuntimeContractOutput { + pub active_profile: String, + pub allowed_profiles: Vec<String>, + pub default_shared_secret_backend: String, + pub allowed_shared_secret_backends: Vec<String>, + pub canonical_config_path: PathBuf, + pub canonical_logs_dir: PathBuf, + pub canonical_identity_path: PathBuf, + pub canonical_subscriber_state_path: PathBuf, +} + +fn parse_path_profile(value: &str) -> Result<RadrootsPathProfile> { + match value { + "interactive_user" => Ok(RadrootsPathProfile::InteractiveUser), + "service_host" => Ok(RadrootsPathProfile::ServiceHost), + "repo_local" => Ok(RadrootsPathProfile::RepoLocal), + _ => bail!( + "{RHI_PATHS_PROFILE_ENV} must be `interactive_user`, `service_host`, or `repo_local`" + ), + } +} + +pub(crate) fn process_path_selection() -> Result<(RadrootsPathProfile, Option<PathBuf>)> { + let profile = match std::env::var(RHI_PATHS_PROFILE_ENV) { + Ok(value) => parse_path_profile(&value)?, + Err(std::env::VarError::NotPresent) => RadrootsPathProfile::InteractiveUser, + Err(std::env::VarError::NotUnicode(_)) => { + bail!("{RHI_PATHS_PROFILE_ENV} must be valid utf-8 when set") + } + }; + let repo_local_root = std::env::var_os(RHI_PATHS_REPO_LOCAL_ROOT_ENV).map(PathBuf::from); + Ok((profile, repo_local_root)) +} + +fn path_overrides_for( + profile: RadrootsPathProfile, + repo_local_root: Option<&Path>, +) -> Result<RadrootsPathOverrides> { + match profile { + RadrootsPathProfile::RepoLocal => { + let repo_local_root = repo_local_root.context(format!( + "{RHI_PATHS_REPO_LOCAL_ROOT_ENV} must be set when {RHI_PATHS_PROFILE_ENV}=repo_local" + ))?; + Ok(RadrootsPathOverrides::repo_local(repo_local_root)) + } + _ => Ok(RadrootsPathOverrides::default()), + } +} + +pub(crate) fn resolve_runtime_paths_with_resolver( + resolver: &RadrootsPathResolver, + profile: RadrootsPathProfile, + repo_local_root: Option<&Path>, +) -> Result<RhiRuntimePaths> { + let namespace = RadrootsRuntimeNamespace::worker(RHI_RUNTIME_ID) + .map_err(|error| anyhow::anyhow!("resolve rhi namespace: {error}"))?; + let overrides = path_overrides_for(profile, repo_local_root)?; + let namespaced = resolver + .resolve(profile, &overrides) + .map_err(|error| anyhow::anyhow!("resolve rhi runtime paths: {error}"))? + .namespaced(&namespace); + Ok(RhiRuntimePaths { + config_path: namespaced.config.join(DEFAULT_CONFIG_FILE_NAME), + logs_dir: namespaced.logs, + identity_path: namespaced.secrets.join(DEFAULT_SERVICE_IDENTITY_FILE_NAME), + subscriber_state_path: namespaced + .data + .join(SUBSCRIBER_STATE_DIR_NAME) + .join(SUBSCRIBER_STATE_FILE_NAME), + }) +} + +pub(crate) fn default_runtime_paths_for_process() -> Result<RhiRuntimePaths> { + let (profile, repo_local_root) = process_path_selection()?; + resolve_runtime_paths_with_resolver( + &RadrootsPathResolver::current(), + profile, + repo_local_root.as_deref(), + ) +} + +pub fn default_config_path_for_process() -> Result<PathBuf> { + Ok(default_runtime_paths_for_process()?.config_path) +} + +pub fn default_identity_path_for_process() -> Result<PathBuf> { + Ok(default_runtime_paths_for_process()?.identity_path) +} + +pub fn default_subscriber_state_path_for_process() -> Result<PathBuf> { + Ok(default_runtime_paths_for_process()?.subscriber_state_path) +} + +pub fn runtime_contract_for_process() -> Result<RhiRuntimeContractOutput> { + let (profile, repo_local_root) = process_path_selection()?; + runtime_contract_with_resolver( + &RadrootsPathResolver::current(), + profile, + repo_local_root.as_deref(), + ) +} + +pub(crate) fn runtime_contract_with_resolver( + resolver: &RadrootsPathResolver, + profile: RadrootsPathProfile, + repo_local_root: Option<&Path>, +) -> Result<RhiRuntimeContractOutput> { + let paths = resolve_runtime_paths_with_resolver(resolver, profile, repo_local_root)?; + Ok(RhiRuntimeContractOutput { + active_profile: profile.to_string(), + allowed_profiles: RHI_ALLOWED_PROFILES + .into_iter() + .map(str::to_owned) + .collect(), + default_shared_secret_backend: RHI_DEFAULT_SHARED_SECRET_BACKEND.to_owned(), + allowed_shared_secret_backends: RHI_ALLOWED_SHARED_SECRET_BACKENDS + .into_iter() + .map(str::to_owned) + .collect(), + canonical_config_path: paths.config_path, + canonical_logs_dir: paths.logs_dir, + canonical_identity_path: paths.identity_path, + canonical_subscriber_state_path: paths.subscriber_state_path, + }) +}