1use crate::RoutingMode;
6use crate::types::{CacheCapacity, ConfigPath, Port, ThreadCount};
7use clap::Parser;
8
9fn parse_port(s: &str) -> Result<Port, String> {
11 let port: u16 = s
12 .parse()
13 .map_err(|e| format!("Invalid port number: {}", e))?;
14 Port::try_new(port).map_err(|e| format!("Invalid port: {}", e))
15}
16
17#[derive(Parser, Debug, Clone)]
21pub struct CommonArgs {
22 #[arg(short, long, env, value_parser = parse_port)]
24 pub port: Option<Port>,
25
26 #[arg(long, env)]
28 pub host: Option<String>,
29
30 #[arg(
36 short = 'm',
37 long = "routing-mode",
38 value_enum,
39 default_value = "hybrid",
40 env
41 )]
42 pub routing_mode: RoutingMode,
43
44 #[arg(short, long, default_value = "config.toml", env)]
46 pub config: ConfigPath,
47
48 #[arg(short, long, env)]
50 pub threads: Option<ThreadCount>,
51}
52
53impl CommonArgs {
54 const DEFAULT_PORT: u16 = 8119;
56
57 const DEFAULT_HOST: &'static str = "0.0.0.0";
59
60 #[must_use]
68 pub fn listen_addr(&self, config_port: Option<Port>) -> String {
69 let port = self
70 .effective_port(config_port)
71 .map_or(Self::DEFAULT_PORT, |p| p.get());
72
73 format!("{}:{}", self.effective_host(), port)
74 }
75
76 #[must_use]
78 pub fn effective_port(&self, config_port: Option<Port>) -> Option<Port> {
79 self.port.or(config_port)
80 }
81
82 #[must_use]
84 pub fn effective_host(&self) -> &str {
85 self.host.as_deref().unwrap_or(Self::DEFAULT_HOST)
86 }
87}
88
89#[derive(Parser, Debug, Clone)]
91pub struct CacheArgs {
92 #[arg(long, default_value = "67108864", env)]
94 pub cache_capacity: CacheCapacity,
95
96 #[arg(long, default_value = "3600", env)]
98 pub cache_ttl: u64,
99}
100
101impl CacheArgs {
102 #[must_use]
104 pub const fn ttl(&self) -> std::time::Duration {
105 std::time::Duration::from_secs(self.cache_ttl)
106 }
107
108 #[must_use]
110 pub fn capacity(&self) -> usize {
111 self.cache_capacity.get() as usize
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_common_args_defaults() {
121 let args = CommonArgs {
122 port: None,
123 host: None,
124 routing_mode: RoutingMode::Hybrid,
125 config: ConfigPath::new("config.toml").unwrap(),
126 threads: None,
127 };
128
129 assert_eq!(args.listen_addr(None), "0.0.0.0:8119");
130 assert_eq!(args.effective_host(), "0.0.0.0");
131 assert!(args.effective_port(None).is_none());
132 }
133
134 #[test]
135 fn test_common_args_with_port() {
136 let args = CommonArgs {
137 port: Some(Port::try_new(9119).unwrap()),
138 host: None,
139 routing_mode: RoutingMode::Hybrid,
140 config: ConfigPath::new("config.toml").unwrap(),
141 threads: None,
142 };
143
144 assert_eq!(args.listen_addr(None), "0.0.0.0:9119");
145 assert_eq!(
146 args.effective_port(None),
147 Some(Port::try_new(9119).unwrap())
148 );
149 }
150
151 #[test]
152 fn test_common_args_with_host() {
153 let args = CommonArgs {
154 port: None,
155 host: Some("127.0.0.1".to_string()),
156 routing_mode: RoutingMode::Hybrid,
157 config: ConfigPath::new("config.toml").unwrap(),
158 threads: None,
159 };
160
161 assert_eq!(args.listen_addr(None), "127.0.0.1:8119");
162 assert_eq!(args.effective_host(), "127.0.0.1");
163 }
164
165 #[test]
166 fn test_common_args_port_override() {
167 let args = CommonArgs {
168 port: Some(Port::try_new(9119).unwrap()),
169 host: None,
170 routing_mode: RoutingMode::Hybrid,
171 config: ConfigPath::new("config.toml").unwrap(),
172 threads: None,
173 };
174
175 let config_port = Some(Port::try_new(7119).unwrap());
176
177 assert_eq!(args.listen_addr(config_port), "0.0.0.0:9119");
179 assert_eq!(
180 args.effective_port(config_port),
181 Some(Port::try_new(9119).unwrap())
182 );
183 }
184
185 #[test]
186 fn test_common_args_config_port_fallback() {
187 let args = CommonArgs {
188 port: None,
189 host: None,
190 routing_mode: RoutingMode::Hybrid,
191 config: ConfigPath::new("config.toml").unwrap(),
192 threads: None,
193 };
194
195 let config_port = Some(Port::try_new(7119).unwrap());
196
197 assert_eq!(args.listen_addr(config_port), "0.0.0.0:7119");
199 assert_eq!(
200 args.effective_port(config_port),
201 Some(Port::try_new(7119).unwrap())
202 );
203 }
204
205 #[test]
206 fn test_common_args_custom_host_and_port() {
207 let args = CommonArgs {
208 port: Some(Port::try_new(9119).unwrap()),
209 host: Some("192.168.1.1".to_string()),
210 routing_mode: RoutingMode::Hybrid,
211 config: ConfigPath::new("config.toml").unwrap(),
212 threads: None,
213 };
214
215 assert_eq!(args.listen_addr(None), "192.168.1.1:9119");
216 assert_eq!(args.effective_host(), "192.168.1.1");
217 assert_eq!(
218 args.effective_port(None),
219 Some(Port::try_new(9119).unwrap())
220 );
221 }
222
223 #[test]
224 fn test_cache_args_defaults() {
225 let args = CacheArgs {
226 cache_capacity: CacheCapacity::try_new(67108864).unwrap(), cache_ttl: 3600,
228 };
229
230 assert_eq!(args.capacity(), 67108864);
231 assert_eq!(args.cache_ttl, 3600);
232 assert_eq!(args.ttl(), std::time::Duration::from_secs(3600));
233 }
234
235 #[test]
236 fn test_cache_args_custom_values() {
237 let args = CacheArgs {
238 cache_capacity: CacheCapacity::try_new(134217728).unwrap(), cache_ttl: 7200,
240 };
241
242 assert_eq!(args.capacity(), 134217728);
243 assert_eq!(args.cache_ttl, 7200);
244 assert_eq!(args.ttl(), std::time::Duration::from_secs(7200));
245 }
246
247 #[test]
248 fn test_routing_modes() {
249 let hybrid = CommonArgs {
250 routing_mode: RoutingMode::Hybrid,
251 ..default_args()
252 };
253 assert_eq!(hybrid.routing_mode, RoutingMode::Hybrid);
254
255 let stateful = CommonArgs {
256 routing_mode: RoutingMode::Stateful,
257 ..default_args()
258 };
259 assert_eq!(stateful.routing_mode, RoutingMode::Stateful);
260
261 let per_command = CommonArgs {
262 routing_mode: RoutingMode::PerCommand,
263 ..default_args()
264 };
265 assert_eq!(per_command.routing_mode, RoutingMode::PerCommand);
266 }
267
268 #[test]
269 fn test_thread_count() {
270 let default_threads = CommonArgs {
271 threads: None,
272 ..default_args()
273 };
274 assert!(default_threads.threads.is_none());
275
276 let single_thread = CommonArgs {
277 threads: Some(ThreadCount::new(1).unwrap()),
278 ..default_args()
279 };
280 assert_eq!(single_thread.threads, Some(ThreadCount::new(1).unwrap()));
281
282 let multi_thread = CommonArgs {
283 threads: Some(ThreadCount::new(4).unwrap()),
284 ..default_args()
285 };
286 assert_eq!(multi_thread.threads, Some(ThreadCount::new(4).unwrap()));
287 }
288
289 fn default_args() -> CommonArgs {
291 CommonArgs {
292 port: None,
293 host: None,
294 routing_mode: RoutingMode::Hybrid,
295 config: ConfigPath::new("config.toml").unwrap(),
296 threads: None,
297 }
298 }
299}