1use crate::error::Error;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9use std::str::FromStr;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub enum SettingKey {
36 DefaultTimeoutSecs,
38 AgentDefaultsJsonErrors,
40 RetryDefaultsMaxAttempts,
42 RetryDefaultsInitialDelayMs,
44 RetryDefaultsMaxDelayMs,
46}
47
48impl SettingKey {
49 pub const ALL: &'static [Self] = &[
51 Self::DefaultTimeoutSecs,
52 Self::AgentDefaultsJsonErrors,
53 Self::RetryDefaultsMaxAttempts,
54 Self::RetryDefaultsInitialDelayMs,
55 Self::RetryDefaultsMaxDelayMs,
56 ];
57
58 #[must_use]
60 pub const fn as_str(&self) -> &'static str {
61 match self {
62 Self::DefaultTimeoutSecs => "default_timeout_secs",
63 Self::AgentDefaultsJsonErrors => "agent_defaults.json_errors",
64 Self::RetryDefaultsMaxAttempts => "retry_defaults.max_attempts",
65 Self::RetryDefaultsInitialDelayMs => "retry_defaults.initial_delay_ms",
66 Self::RetryDefaultsMaxDelayMs => "retry_defaults.max_delay_ms",
67 }
68 }
69
70 #[must_use]
72 pub const fn type_name(&self) -> &'static str {
73 match self {
74 Self::DefaultTimeoutSecs
75 | Self::RetryDefaultsMaxAttempts
76 | Self::RetryDefaultsInitialDelayMs
77 | Self::RetryDefaultsMaxDelayMs => "integer",
78 Self::AgentDefaultsJsonErrors => "boolean",
79 }
80 }
81
82 #[must_use]
84 pub const fn description(&self) -> &'static str {
85 match self {
86 Self::DefaultTimeoutSecs => "Default timeout for API requests in seconds",
87 Self::AgentDefaultsJsonErrors => "Output errors as JSON by default",
88 Self::RetryDefaultsMaxAttempts => "Maximum retry attempts (0 = disabled)",
89 Self::RetryDefaultsInitialDelayMs => "Initial delay between retries in milliseconds",
90 Self::RetryDefaultsMaxDelayMs => "Maximum delay cap in milliseconds",
91 }
92 }
93
94 #[must_use]
96 pub const fn default_value_str(&self) -> &'static str {
97 match self {
98 Self::DefaultTimeoutSecs => "30",
99 Self::AgentDefaultsJsonErrors => "false",
100 Self::RetryDefaultsMaxAttempts => "0",
101 Self::RetryDefaultsInitialDelayMs => "500",
102 Self::RetryDefaultsMaxDelayMs => "30000",
103 }
104 }
105
106 #[must_use]
108 pub const fn value_from_config(&self, config: &super::models::GlobalConfig) -> SettingValue {
109 match self {
110 Self::DefaultTimeoutSecs => SettingValue::U64(config.default_timeout_secs),
111 Self::AgentDefaultsJsonErrors => SettingValue::Bool(config.agent_defaults.json_errors),
112 Self::RetryDefaultsMaxAttempts => {
113 SettingValue::U64(config.retry_defaults.max_attempts as u64)
114 }
115 Self::RetryDefaultsInitialDelayMs => {
116 SettingValue::U64(config.retry_defaults.initial_delay_ms)
117 }
118 Self::RetryDefaultsMaxDelayMs => SettingValue::U64(config.retry_defaults.max_delay_ms),
119 }
120 }
121}
122
123impl fmt::Display for SettingKey {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(f, "{}", self.as_str())
126 }
127}
128
129impl FromStr for SettingKey {
130 type Err = Error;
131
132 fn from_str(s: &str) -> Result<Self, Self::Err> {
133 match s {
134 "default_timeout_secs" => Ok(Self::DefaultTimeoutSecs),
135 "agent_defaults.json_errors" => Ok(Self::AgentDefaultsJsonErrors),
136 "retry_defaults.max_attempts" => Ok(Self::RetryDefaultsMaxAttempts),
137 "retry_defaults.initial_delay_ms" => Ok(Self::RetryDefaultsInitialDelayMs),
138 "retry_defaults.max_delay_ms" => Ok(Self::RetryDefaultsMaxDelayMs),
139 _ => Err(Error::unknown_setting_key(s)),
140 }
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
146pub enum SettingValue {
147 U64(u64),
149 Bool(bool),
151}
152
153const MAX_TIMEOUT_SECS: u64 = 365 * 24 * 60 * 60;
156
157const MAX_RETRY_ATTEMPTS: u64 = 10;
159
160const MAX_INITIAL_DELAY_MS: u64 = 60_000;
162
163const MAX_DELAY_CAP_MS: u64 = 300_000;
165
166impl SettingValue {
167 pub fn parse_for_key(key: SettingKey, value: &str) -> Result<Self, Error> {
174 match key {
175 SettingKey::DefaultTimeoutSecs => {
176 let parsed = value
177 .parse::<u64>()
178 .map_err(|_| Error::invalid_setting_value(key, value))?;
179
180 if parsed == 0 {
182 return Err(Error::setting_value_out_of_range(
183 key,
184 value,
185 "timeout must be greater than 0",
186 ));
187 }
188 if parsed > MAX_TIMEOUT_SECS {
189 return Err(Error::setting_value_out_of_range(
190 key,
191 value,
192 &format!("timeout cannot exceed {MAX_TIMEOUT_SECS} seconds (1 year)"),
193 ));
194 }
195
196 Ok(Self::U64(parsed))
197 }
198 SettingKey::AgentDefaultsJsonErrors => {
199 let parsed = match value.to_lowercase().as_str() {
200 "true" | "1" | "yes" | "on" => true,
201 "false" | "0" | "no" | "off" => false,
202 _ => return Err(Error::invalid_setting_value(key, value)),
203 };
204 Ok(Self::Bool(parsed))
205 }
206 SettingKey::RetryDefaultsMaxAttempts => {
207 let parsed = value
208 .parse::<u64>()
209 .map_err(|_| Error::invalid_setting_value(key, value))?;
210
211 if parsed > MAX_RETRY_ATTEMPTS {
212 return Err(Error::setting_value_out_of_range(
213 key,
214 value,
215 &format!("max_attempts cannot exceed {MAX_RETRY_ATTEMPTS}"),
216 ));
217 }
218
219 Ok(Self::U64(parsed))
220 }
221 SettingKey::RetryDefaultsInitialDelayMs => {
222 let parsed = value
223 .parse::<u64>()
224 .map_err(|_| Error::invalid_setting_value(key, value))?;
225
226 if parsed == 0 {
227 return Err(Error::setting_value_out_of_range(
228 key,
229 value,
230 "initial_delay_ms must be greater than 0",
231 ));
232 }
233 if parsed > MAX_INITIAL_DELAY_MS {
234 return Err(Error::setting_value_out_of_range(
235 key,
236 value,
237 &format!(
238 "initial_delay_ms cannot exceed {MAX_INITIAL_DELAY_MS}ms (1 minute)"
239 ),
240 ));
241 }
242
243 Ok(Self::U64(parsed))
244 }
245 SettingKey::RetryDefaultsMaxDelayMs => {
246 let parsed = value
247 .parse::<u64>()
248 .map_err(|_| Error::invalid_setting_value(key, value))?;
249
250 if parsed == 0 {
251 return Err(Error::setting_value_out_of_range(
252 key,
253 value,
254 "max_delay_ms must be greater than 0",
255 ));
256 }
257 if parsed > MAX_DELAY_CAP_MS {
258 return Err(Error::setting_value_out_of_range(
259 key,
260 value,
261 &format!("max_delay_ms cannot exceed {MAX_DELAY_CAP_MS}ms (5 minutes)"),
262 ));
263 }
264
265 Ok(Self::U64(parsed))
266 }
267 }
268 }
269
270 #[must_use]
272 pub const fn as_u64(&self) -> Option<u64> {
273 match self {
274 Self::U64(v) => Some(*v),
275 Self::Bool(_) => None,
276 }
277 }
278
279 #[must_use]
281 pub const fn as_bool(&self) -> Option<bool> {
282 match self {
283 Self::Bool(v) => Some(*v),
284 Self::U64(_) => None,
285 }
286 }
287}
288
289impl fmt::Display for SettingValue {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 match self {
292 Self::U64(v) => write!(f, "{v}"),
293 Self::Bool(v) => write!(f, "{v}"),
294 }
295 }
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct SettingInfo {
301 pub key: String,
303 pub value: String,
305 #[serde(rename = "type")]
307 pub type_name: String,
308 pub description: String,
310 pub default: String,
312}
313
314impl SettingInfo {
315 #[must_use]
317 pub fn new(key: SettingKey, current_value: &SettingValue) -> Self {
318 Self {
319 key: key.as_str().to_string(),
320 value: current_value.to_string(),
321 type_name: key.type_name().to_string(),
322 description: key.description().to_string(),
323 default: key.default_value_str().to_string(),
324 }
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_setting_key_from_str_valid() {
334 assert_eq!(
335 "default_timeout_secs".parse::<SettingKey>().unwrap(),
336 SettingKey::DefaultTimeoutSecs
337 );
338 assert_eq!(
339 "agent_defaults.json_errors".parse::<SettingKey>().unwrap(),
340 SettingKey::AgentDefaultsJsonErrors
341 );
342 }
343
344 #[test]
345 fn test_setting_key_from_str_invalid() {
346 let result = "unknown_key".parse::<SettingKey>();
347 assert!(result.is_err());
348 }
349
350 #[test]
351 fn test_setting_key_as_str() {
352 assert_eq!(
353 SettingKey::DefaultTimeoutSecs.as_str(),
354 "default_timeout_secs"
355 );
356 assert_eq!(
357 SettingKey::AgentDefaultsJsonErrors.as_str(),
358 "agent_defaults.json_errors"
359 );
360 }
361
362 #[test]
363 fn test_setting_value_parse_u64_valid() {
364 let value = SettingValue::parse_for_key(SettingKey::DefaultTimeoutSecs, "60").unwrap();
365 assert_eq!(value, SettingValue::U64(60));
366 }
367
368 #[test]
369 fn test_setting_value_parse_u64_invalid() {
370 let result = SettingValue::parse_for_key(SettingKey::DefaultTimeoutSecs, "abc");
371 assert!(result.is_err());
372 }
373
374 #[test]
375 fn test_setting_value_parse_bool_valid() {
376 let key = SettingKey::AgentDefaultsJsonErrors;
377
378 assert_eq!(
379 SettingValue::parse_for_key(key, "true").unwrap(),
380 SettingValue::Bool(true)
381 );
382 assert_eq!(
383 SettingValue::parse_for_key(key, "false").unwrap(),
384 SettingValue::Bool(false)
385 );
386 assert_eq!(
387 SettingValue::parse_for_key(key, "1").unwrap(),
388 SettingValue::Bool(true)
389 );
390 assert_eq!(
391 SettingValue::parse_for_key(key, "0").unwrap(),
392 SettingValue::Bool(false)
393 );
394 assert_eq!(
395 SettingValue::parse_for_key(key, "yes").unwrap(),
396 SettingValue::Bool(true)
397 );
398 assert_eq!(
399 SettingValue::parse_for_key(key, "no").unwrap(),
400 SettingValue::Bool(false)
401 );
402 }
403
404 #[test]
405 fn test_setting_value_parse_bool_invalid() {
406 let result = SettingValue::parse_for_key(SettingKey::AgentDefaultsJsonErrors, "maybe");
407 assert!(result.is_err());
408 }
409
410 #[test]
411 fn test_setting_value_display() {
412 assert_eq!(SettingValue::U64(30).to_string(), "30");
413 assert_eq!(SettingValue::Bool(true).to_string(), "true");
414 assert_eq!(SettingValue::Bool(false).to_string(), "false");
415 }
416
417 #[test]
418 fn test_setting_info_new() {
419 let info = SettingInfo::new(SettingKey::DefaultTimeoutSecs, &SettingValue::U64(60));
420 assert_eq!(info.key, "default_timeout_secs");
421 assert_eq!(info.value, "60");
422 assert_eq!(info.type_name, "integer");
423 assert_eq!(info.default, "30");
424 }
425
426 #[test]
427 fn test_setting_value_parse_timeout_zero_rejected() {
428 let result = SettingValue::parse_for_key(SettingKey::DefaultTimeoutSecs, "0");
429 assert!(result.is_err());
430 }
431
432 #[test]
433 fn test_setting_value_parse_timeout_max_boundary() {
434 let result = SettingValue::parse_for_key(
436 SettingKey::DefaultTimeoutSecs,
437 &super::MAX_TIMEOUT_SECS.to_string(),
438 );
439 assert!(result.is_ok());
440 }
441
442 #[test]
443 fn test_setting_value_parse_timeout_over_max_rejected() {
444 let over_max = super::MAX_TIMEOUT_SECS + 1;
446 let result =
447 SettingValue::parse_for_key(SettingKey::DefaultTimeoutSecs, &over_max.to_string());
448 assert!(result.is_err());
449 }
450
451 #[test]
452 fn test_retry_settings_from_str() {
453 assert_eq!(
454 "retry_defaults.max_attempts".parse::<SettingKey>().unwrap(),
455 SettingKey::RetryDefaultsMaxAttempts
456 );
457 assert_eq!(
458 "retry_defaults.initial_delay_ms"
459 .parse::<SettingKey>()
460 .unwrap(),
461 SettingKey::RetryDefaultsInitialDelayMs
462 );
463 assert_eq!(
464 "retry_defaults.max_delay_ms".parse::<SettingKey>().unwrap(),
465 SettingKey::RetryDefaultsMaxDelayMs
466 );
467 }
468
469 #[test]
470 fn test_retry_max_attempts_valid_range() {
471 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsMaxAttempts, "0");
473 assert_eq!(result.unwrap(), SettingValue::U64(0));
474
475 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsMaxAttempts, "3");
477 assert_eq!(result.unwrap(), SettingValue::U64(3));
478
479 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsMaxAttempts, "10");
481 assert_eq!(result.unwrap(), SettingValue::U64(10));
482
483 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsMaxAttempts, "11");
485 assert!(result.is_err());
486 }
487
488 #[test]
489 fn test_retry_initial_delay_ms_valid_range() {
490 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsInitialDelayMs, "0");
492 assert!(result.is_err());
493
494 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsInitialDelayMs, "100");
496 assert_eq!(result.unwrap(), SettingValue::U64(100));
497
498 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsInitialDelayMs, "60000");
500 assert_eq!(result.unwrap(), SettingValue::U64(60000));
501
502 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsInitialDelayMs, "60001");
504 assert!(result.is_err());
505 }
506
507 #[test]
508 fn test_retry_max_delay_ms_valid_range() {
509 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsMaxDelayMs, "0");
511 assert!(result.is_err());
512
513 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsMaxDelayMs, "1000");
515 assert_eq!(result.unwrap(), SettingValue::U64(1000));
516
517 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsMaxDelayMs, "300000");
519 assert_eq!(result.unwrap(), SettingValue::U64(300_000));
520
521 let result = SettingValue::parse_for_key(SettingKey::RetryDefaultsMaxDelayMs, "300001");
523 assert!(result.is_err());
524 }
525
526 #[test]
527 fn test_retry_settings_descriptions() {
528 assert_eq!(
529 SettingKey::RetryDefaultsMaxAttempts.description(),
530 "Maximum retry attempts (0 = disabled)"
531 );
532 assert_eq!(
533 SettingKey::RetryDefaultsInitialDelayMs.description(),
534 "Initial delay between retries in milliseconds"
535 );
536 assert_eq!(
537 SettingKey::RetryDefaultsMaxDelayMs.description(),
538 "Maximum delay cap in milliseconds"
539 );
540 }
541
542 #[test]
543 fn test_retry_settings_defaults() {
544 assert_eq!(
545 SettingKey::RetryDefaultsMaxAttempts.default_value_str(),
546 "0"
547 );
548 assert_eq!(
549 SettingKey::RetryDefaultsInitialDelayMs.default_value_str(),
550 "500"
551 );
552 assert_eq!(
553 SettingKey::RetryDefaultsMaxDelayMs.default_value_str(),
554 "30000"
555 );
556 }
557}