worldinterface_host/
config.rs1use std::num::NonZeroUsize;
4use std::path::PathBuf;
5use std::sync::Arc;
6use std::time::Duration;
7
8use actionqueue_runtime::config::{BackoffStrategyConfig, RuntimeConfig};
9use worldinterface_core::metrics::{MetricsRecorder, NoopMetricsRecorder};
10use worldinterface_flowspec::CompilerConfig;
11
12use crate::error::HostError;
13
14#[derive(Clone)]
16pub struct HostConfig {
17 pub aq_data_dir: PathBuf,
19
20 pub context_store_path: PathBuf,
22
23 pub tick_interval: Duration,
26
27 pub dispatch_concurrency: NonZeroUsize,
30
31 pub lease_timeout_secs: u64,
34
35 pub compiler_config: CompilerConfig,
37
38 pub shutdown_timeout: Duration,
41
42 pub metrics: Arc<dyn MetricsRecorder>,
45}
46
47impl Default for HostConfig {
48 fn default() -> Self {
49 Self {
50 aq_data_dir: PathBuf::from("data/aq"),
51 context_store_path: PathBuf::from("data/context.db"),
52 tick_interval: Duration::from_millis(50),
53 dispatch_concurrency: NonZeroUsize::new(4).unwrap(),
54 lease_timeout_secs: 300,
55 compiler_config: CompilerConfig::default(),
56 shutdown_timeout: Duration::from_secs(30),
57 metrics: Arc::new(NoopMetricsRecorder),
58 }
59 }
60}
61
62impl std::fmt::Debug for HostConfig {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.debug_struct("HostConfig")
65 .field("aq_data_dir", &self.aq_data_dir)
66 .field("context_store_path", &self.context_store_path)
67 .field("tick_interval", &self.tick_interval)
68 .field("dispatch_concurrency", &self.dispatch_concurrency)
69 .field("lease_timeout_secs", &self.lease_timeout_secs)
70 .field("compiler_config", &self.compiler_config)
71 .field("shutdown_timeout", &self.shutdown_timeout)
72 .finish()
73 }
74}
75
76impl HostConfig {
77 pub fn validate(&self) -> Result<(), HostError> {
79 if self.aq_data_dir.as_os_str().is_empty() {
80 return Err(HostError::InvalidConfig("aq_data_dir must be non-empty".into()));
81 }
82 if self.context_store_path.as_os_str().is_empty() {
83 return Err(HostError::InvalidConfig("context_store_path must be non-empty".into()));
84 }
85 if self.tick_interval.is_zero() {
86 return Err(HostError::InvalidConfig("tick_interval must be > 0".into()));
87 }
88 if self.lease_timeout_secs < 30 {
89 return Err(HostError::InvalidConfig("lease_timeout_secs must be >= 30".into()));
90 }
91 if self.shutdown_timeout.is_zero() {
92 return Err(HostError::InvalidConfig("shutdown_timeout must be > 0".into()));
93 }
94 Ok(())
95 }
96
97 pub(crate) fn to_runtime_config(&self) -> RuntimeConfig {
99 RuntimeConfig {
100 data_dir: self.aq_data_dir.clone(),
101 backoff_strategy: BackoffStrategyConfig::Fixed { interval: Duration::from_secs(5) },
102 dispatch_concurrency: self.dispatch_concurrency,
103 lease_timeout_secs: self.lease_timeout_secs,
104 tick_interval: self.tick_interval,
105 snapshot_event_threshold: Some(10_000),
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn default_config_is_valid() {
116 assert!(HostConfig::default().validate().is_ok());
117 }
118
119 #[test]
120 fn rejects_zero_tick_interval() {
121 let config = HostConfig { tick_interval: Duration::ZERO, ..Default::default() };
122 let err = config.validate().unwrap_err();
123 assert!(matches!(err, HostError::InvalidConfig(_)));
124 }
125
126 #[test]
127 fn rejects_low_lease_timeout() {
128 let config = HostConfig { lease_timeout_secs: 10, ..Default::default() };
129 let err = config.validate().unwrap_err();
130 assert!(matches!(err, HostError::InvalidConfig(_)));
131 }
132
133 #[test]
134 fn rejects_zero_shutdown_timeout() {
135 let config = HostConfig { shutdown_timeout: Duration::ZERO, ..Default::default() };
136 let err = config.validate().unwrap_err();
137 assert!(matches!(err, HostError::InvalidConfig(_)));
138 }
139
140 #[test]
141 fn to_runtime_config_preserves_values() {
142 let config = HostConfig {
143 aq_data_dir: PathBuf::from("/tmp/aq"),
144 tick_interval: Duration::from_millis(100),
145 dispatch_concurrency: NonZeroUsize::new(8).unwrap(),
146 lease_timeout_secs: 600,
147 ..Default::default()
148 };
149 let rc = config.to_runtime_config();
150 assert_eq!(rc.data_dir, PathBuf::from("/tmp/aq"));
151 assert_eq!(rc.tick_interval, Duration::from_millis(100));
152 assert_eq!(rc.dispatch_concurrency, NonZeroUsize::new(8).unwrap());
153 assert_eq!(rc.lease_timeout_secs, 600);
154 }
155}