Skip to main content

orcs_runtime/config/
resolver.rs

1//! Configuration resolver trait.
2//!
3//! # Architecture
4//!
5//! The `ConfigResolver` trait abstracts configuration resolution.
6//! Each call to `resolve()` produces a fully-merged `OrcsConfig`
7//! from all available sources (files, env, CLI flags, etc.).
8//!
9//! The application layer holds a resolver and controls **when** to
10//! call `resolve()`, enabling hot-reload without process restart.
11//!
12//! ```text
13//! impl ConfigResolver (CLI / test / GUI / ...)
14//!          │
15//!          ▼
16//!   resolve() → Result<OrcsConfig>
17//!          │
18//!          ▼
19//!   OrcsApp holds resolver → reload_config() at any time
20//! ```
21//!
22//! # Example
23//!
24//! ```ignore
25//! use orcs_runtime::config::{ConfigResolver, ConfigLoader, OrcsConfig, ConfigError};
26//!
27//! struct MyResolver;
28//!
29//! impl ConfigResolver for MyResolver {
30//!     fn resolve(&self) -> Result<OrcsConfig, ConfigError> {
31//!         ConfigLoader::new().load()
32//!     }
33//! }
34//! ```
35
36use super::{ConfigError, OrcsConfig};
37
38/// Trait for resolving configuration from all sources.
39///
40/// Each invocation of `resolve()` returns a fresh, fully-merged
41/// `OrcsConfig`. This allows the application to re-resolve at any
42/// time (e.g., after a config file edit) without restarting.
43pub trait ConfigResolver: Send + Sync {
44    /// Resolves and returns the current configuration.
45    ///
46    /// Implementations should merge all layers (file, env, flags)
47    /// and return the final result.
48    fn resolve(&self) -> Result<OrcsConfig, ConfigError>;
49}
50
51/// No-op resolver that returns default configuration.
52///
53/// Useful as a fallback or for testing.
54#[derive(Debug, Clone, Copy, Default)]
55pub struct NoOpResolver;
56
57impl ConfigResolver for NoOpResolver {
58    fn resolve(&self) -> Result<OrcsConfig, ConfigError> {
59        Ok(OrcsConfig::default())
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn noop_resolver_returns_default() {
69        let config = NoOpResolver
70            .resolve()
71            .expect("NoOpResolver should return default config");
72        assert_eq!(config, OrcsConfig::default());
73    }
74
75    #[test]
76    fn custom_resolver() {
77        struct TestResolver {
78            debug: bool,
79        }
80
81        impl ConfigResolver for TestResolver {
82            fn resolve(&self) -> Result<OrcsConfig, ConfigError> {
83                Ok(OrcsConfig {
84                    debug: self.debug,
85                    ..OrcsConfig::default()
86                })
87            }
88        }
89
90        let config = TestResolver { debug: true }
91            .resolve()
92            .expect("custom TestResolver should return config with debug=true");
93        assert!(config.debug);
94    }
95}