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}