1use crate::reconnect::ReconnectPolicy;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct SidecarConfig {
11 pub name: String,
13
14 #[serde(default)]
17 pub command: Option<String>,
18
19 #[serde(default)]
21 pub socket: Option<String>,
22
23 #[serde(default = "default_shm_size")]
25 pub shm_size: u64,
26
27 #[serde(default = "default_health_interval")]
29 pub health_interval_ms: u64,
30
31 #[serde(default = "default_timeout")]
33 pub timeout_ms: u64,
34
35 #[serde(default)]
37 pub retry: u8,
38
39 #[serde(default = "default_pool_size")]
41 pub pool_size: usize,
42
43 #[serde(default = "default_max_in_flight")]
45 pub max_in_flight: u64,
46
47 #[serde(default)]
49 pub auth_token: Option<String>,
50
51 #[serde(default)]
53 pub failover: Option<FailoverConfig>,
54
55 #[serde(default = "default_reconnect_policy")]
57 pub reconnect_policy: ReconnectPolicy,
58}
59
60impl SidecarConfig {
61 pub fn new(name: impl Into<String>) -> Self {
63 Self {
64 name: name.into(),
65 command: None,
66 socket: None,
67 shm_size: default_shm_size(),
68 health_interval_ms: default_health_interval(),
69 timeout_ms: default_timeout(),
70 retry: 0,
71 pool_size: default_pool_size(),
72 max_in_flight: default_max_in_flight(),
73 auth_token: None,
74 failover: None,
75 reconnect_policy: default_reconnect_policy(),
76 }
77 }
78
79 pub fn command(mut self, cmd: impl Into<String>) -> Self {
81 self.command = Some(cmd.into());
82 self
83 }
84
85 pub fn timeout(mut self, ms: u64) -> Self {
87 self.timeout_ms = ms;
88 self
89 }
90
91 pub fn shm_size(mut self, bytes: u64) -> Self {
93 self.shm_size = bytes;
94 self
95 }
96
97 pub fn pool_size(mut self, n: usize) -> Self {
99 self.pool_size = n;
100 self
101 }
102
103 pub fn max_in_flight(mut self, n: u64) -> Self {
105 self.max_in_flight = n;
106 self
107 }
108
109 pub fn reconnect_max_retries(mut self, n: u32) -> Self {
111 self.reconnect_policy.max_retries = n;
112 self
113 }
114
115 pub fn reconnect_backoff_ms(mut self, base: u64, max: u64) -> Self {
117 self.reconnect_policy.base_backoff_ms = base;
118 self.reconnect_policy.max_backoff_ms = max;
119 self
120 }
121
122 pub fn socket_path(&self) -> String {
124 self.socket
125 .clone()
126 .unwrap_or_else(|| crate::transport::socket_path(&self.name))
127 }
128
129 pub fn shm_path(&self) -> String {
131 crate::transport::shm_path(&self.name)
132 }
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct FailoverConfig {
138 #[serde(default)]
140 pub backup: Option<String>,
141
142 #[serde(default)]
144 pub fallback_wasm: Option<String>,
145
146 #[serde(default = "default_cb_threshold")]
148 pub failure_threshold: u32,
149
150 #[serde(default = "default_cb_cooldown")]
152 pub cooldown_secs: u32,
153}
154
155fn default_shm_size() -> u64 {
156 64 * 1024 * 1024 }
158fn default_health_interval() -> u64 {
159 5000
160}
161fn default_timeout() -> u64 {
162 30000
163}
164fn default_pool_size() -> usize {
165 4
166}
167fn default_max_in_flight() -> u64 {
168 1000
169}
170fn default_reconnect_policy() -> ReconnectPolicy {
171 ReconnectPolicy::default()
172}
173fn default_cb_threshold() -> u32 {
174 5
175}
176fn default_cb_cooldown() -> u32 {
177 30
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_config_defaults() {
186 let cfg = SidecarConfig::new("fraud-checker");
187 assert_eq!(cfg.name, "fraud-checker");
188 assert_eq!(cfg.shm_size, 64 * 1024 * 1024);
189 assert_eq!(cfg.timeout_ms, 30000);
190 assert_eq!(cfg.pool_size, 4);
191 assert_eq!(cfg.max_in_flight, 1000);
192 assert_eq!(cfg.reconnect_policy.max_retries, 10);
193 assert_eq!(cfg.reconnect_policy.base_backoff_ms, 100);
194 assert_eq!(cfg.reconnect_policy.max_backoff_ms, 30000);
195 assert!(cfg.command.is_none());
196 }
197
198 #[test]
199 fn test_config_builder() {
200 let cfg = SidecarConfig::new("ml-engine")
201 .command("python -m ml_service")
202 .timeout(60000)
203 .shm_size(256 * 1024 * 1024);
204
205 assert_eq!(cfg.command.as_deref(), Some("python -m ml_service"));
206 assert_eq!(cfg.timeout_ms, 60000);
207 assert_eq!(cfg.shm_size, 256 * 1024 * 1024);
208 }
209
210 #[test]
211 fn test_pool_and_reconnect_builder() {
212 let cfg = SidecarConfig::new("ml-engine")
213 .pool_size(8)
214 .max_in_flight(5000)
215 .reconnect_max_retries(20)
216 .reconnect_backoff_ms(200, 60000);
217
218 assert_eq!(cfg.pool_size, 8);
219 assert_eq!(cfg.max_in_flight, 5000);
220 assert_eq!(cfg.reconnect_policy.max_retries, 20);
221 assert_eq!(cfg.reconnect_policy.base_backoff_ms, 200);
222 assert_eq!(cfg.reconnect_policy.max_backoff_ms, 60000);
223 }
224
225 #[test]
226 fn test_socket_path_resolution() {
227 let cfg = SidecarConfig::new("fraud");
228 assert_eq!(cfg.socket_path(), "/tmp/vil_sidecar_fraud.sock");
229
230 let cfg2 = SidecarConfig {
231 socket: Some("/custom/path.sock".into()),
232 ..SidecarConfig::new("fraud")
233 };
234 assert_eq!(cfg2.socket_path(), "/custom/path.sock");
235 }
236
237 #[test]
238 fn test_yaml_deserialize() {
239 let yaml = r#"
240name: fraud-checker
241command: "python -m fraud_service"
242shm_size: 67108864
243timeout_ms: 30000
244retry: 3
245pool_size: 4
246failover:
247 backup: fraud-checker-2
248 fallback_wasm: fraud_basic.wasm
249 failure_threshold: 5
250 cooldown_secs: 30
251"#;
252 let cfg: SidecarConfig = serde_yaml::from_str(yaml).unwrap();
253 assert_eq!(cfg.name, "fraud-checker");
254 assert_eq!(cfg.retry, 3);
255 assert_eq!(cfg.pool_size, 4);
256 assert!(cfg.failover.is_some());
257 let fo = cfg.failover.unwrap();
258 assert_eq!(fo.backup.as_deref(), Some("fraud-checker-2"));
259 assert_eq!(fo.fallback_wasm.as_deref(), Some("fraud_basic.wasm"));
260 }
261}