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}