daedalus_runtime/
config.rs

1use crate::NodeError;
2use crate::io::NodeIo;
3use daedalus_data::model::Value;
4
5/// Policy for invalid configuration values.
6///
7/// ```
8/// use daedalus_runtime::config::ConfigPolicy;
9/// let policy = ConfigPolicy::Clamp;
10/// assert_eq!(policy, ConfigPolicy::Clamp);
11/// ```
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ConfigPolicy {
14    Clamp,
15    Error,
16}
17
18/// Record of a configuration value change after sanitization.
19///
20/// ```
21/// use daedalus_runtime::config::{ConfigChange, ConfigPolicy};
22/// use daedalus_data::model::Value;
23/// let change = ConfigChange {
24///     port: "threshold",
25///     previous: Value::Int(0),
26///     next: Value::Int(1),
27///     policy: ConfigPolicy::Clamp,
28/// };
29/// assert_eq!(change.port, "threshold");
30/// ```
31#[derive(Debug, Clone, PartialEq)]
32pub struct ConfigChange {
33    pub port: &'static str,
34    pub previous: Value,
35    pub next: Value,
36    pub policy: ConfigPolicy,
37}
38
39/// Sanitized configuration plus any changes applied.
40///
41/// ```
42/// use daedalus_runtime::config::Sanitized;
43/// let sanitized = Sanitized { value: 3u8, changes: vec![] };
44/// assert_eq!(sanitized.value, 3);
45/// ```
46#[derive(Debug, Clone, PartialEq)]
47pub struct Sanitized<T> {
48    pub value: T,
49    pub changes: Vec<ConfigChange>,
50}
51
52/// Error returned from config validation or sanitization.
53///
54/// ```
55/// use daedalus_runtime::config::ConfigError;
56/// let err = ConfigError::for_port("radius", "too small");
57/// assert_eq!(err.port, Some("radius"));
58/// ```
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct ConfigError {
61    pub port: Option<&'static str>,
62    pub message: String,
63}
64
65impl ConfigError {
66    /// Create a new config error with no port context.
67    pub fn new(message: impl Into<String>) -> Self {
68        Self {
69            port: None,
70            message: message.into(),
71        }
72    }
73
74    /// Create a new config error scoped to a specific port.
75    pub fn for_port(port: &'static str, message: impl Into<String>) -> Self {
76        Self {
77            port: Some(port),
78            message: message.into(),
79        }
80    }
81}
82
83impl std::fmt::Display for ConfigError {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        if let Some(port) = self.port {
86            write!(f, "{port}: {}", self.message)
87        } else {
88            write!(f, "{}", self.message)
89        }
90    }
91}
92
93impl std::error::Error for ConfigError {}
94
95/// Trait implemented by config structs generated by `#[derive(NodeConfig)]`.
96///
97/// ```
98/// use daedalus_runtime::config::{ConfigError, NodeConfig, Sanitized};
99/// use daedalus_runtime::{NodeError};
100/// use daedalus_runtime::io::NodeIo;
101///
102/// #[derive(Default)]
103/// struct DemoConfig {
104///     threshold: i64,
105/// }
106///
107/// impl NodeConfig for DemoConfig {
108///     fn ports() -> Vec<daedalus_registry::store::Port> { vec![] }
109///     fn from_io(_io: &NodeIo) -> Result<Self, NodeError> { Ok(DemoConfig::default()) }
110///     fn sanitize(self) -> Result<Sanitized<Self>, ConfigError> { Ok(Sanitized { value: self, changes: vec![] }) }
111///     fn validate(&self) -> Result<(), ConfigError> { Ok(()) }
112/// }
113///
114/// let cfg = DemoConfig::default();
115/// cfg.validate().unwrap();
116/// ```
117pub trait NodeConfig: Sized {
118    fn ports() -> Vec<daedalus_registry::store::Port>;
119    fn metadata() -> std::collections::BTreeMap<String, Value> {
120        std::collections::BTreeMap::new()
121    }
122    fn from_io(io: &NodeIo) -> Result<Self, NodeError>;
123    fn sanitize(self) -> Result<Sanitized<Self>, ConfigError>;
124    fn validate(&self) -> Result<(), ConfigError>;
125}
126
127/// Emit warnings for config changes applied by sanitization.
128///
129/// ```
130/// use daedalus_runtime::config::{ConfigChange, ConfigPolicy, log_config_changes};
131/// use daedalus_data::model::Value;
132/// let changes = vec![ConfigChange {
133///     port: "value",
134///     previous: Value::Int(0),
135///     next: Value::Int(1),
136///     policy: ConfigPolicy::Clamp,
137/// }];
138/// log_config_changes("node", &changes);
139/// ```
140pub fn log_config_changes(node_id: &str, changes: &[ConfigChange]) {
141    for change in changes {
142        log::warn!(
143            "node={} port={} policy={:?} previous={:?} next={:?}",
144            node_id,
145            change.port,
146            change.policy,
147            change.previous,
148            change.next
149        );
150    }
151}