Skip to main content

ferra_agent/
config.rs

1use std::time::Duration;
2
3use clap::Parser;
4
5#[derive(Debug, Clone, Parser)]
6#[command(name = "ferra-agent", version, about = "Ferra sidecar agent")]
7pub struct Args {
8    /// Ferra server endpoint, e.g. http://ferra-server.ferra.svc.cluster.local:8080
9    #[arg(long, env = "FERRA_SERVER")]
10    pub server: String,
11
12    /// Prefix(es) to watch. Can be repeated; or pass a comma-separated list
13    /// via FERRA_PREFIX. The agent loads a snapshot and watches each prefix
14    /// independently; service requests for keys not under any of these
15    /// prefixes return 404.
16    #[arg(long, env = "FERRA_PREFIX", value_delimiter = ',', num_args = 0..)]
17    pub prefix: Vec<String>,
18
19    /// Local listen address for the agent's HTTP API.
20    #[arg(long, env = "FERRA_AGENT_LISTEN", default_value = "127.0.0.1:9999")]
21    pub listen: String,
22
23    /// Minimum reconnect backoff after a watch transport error.
24    #[arg(long, env = "FERRA_MIN_BACKOFF", default_value = "500ms",
25          value_parser = parse_dur)]
26    pub min_backoff: Duration,
27
28    /// Maximum reconnect backoff (ceiling for exponential growth).
29    #[arg(long, env = "FERRA_MAX_BACKOFF", default_value = "30s",
30          value_parser = parse_dur)]
31    pub max_backoff: Duration,
32}
33
34fn parse_dur(s: &str) -> Result<Duration, String> {
35    humantime::parse_duration(s).map_err(|e| format!("invalid duration {s:?}: {e}"))
36}
37
38#[cfg(test)]
39mod tests {
40    use super::Args;
41    use clap::Parser;
42    use std::time::Duration;
43
44    #[test]
45    fn parses_required_args() {
46        let a = Args::try_parse_from([
47            "ferra-agent",
48            "--server",
49            "http://x:8080",
50            "--prefix",
51            "services/payment/",
52        ])
53        .unwrap();
54        assert_eq!(a.server, "http://x:8080");
55        assert_eq!(a.prefix, vec!["services/payment/"]);
56        assert_eq!(a.listen, "127.0.0.1:9999");
57        assert_eq!(a.min_backoff, Duration::from_millis(500));
58        assert_eq!(a.max_backoff, Duration::from_secs(30));
59    }
60
61    #[test]
62    fn parses_multiple_prefixes() {
63        let a = Args::try_parse_from([
64            "ferra-agent",
65            "--server",
66            "http://x:8080",
67            "--prefix",
68            "services/payment/",
69            "--prefix",
70            "flags/global/",
71        ])
72        .unwrap();
73        assert_eq!(a.prefix, vec!["services/payment/", "flags/global/"]);
74    }
75
76    #[test]
77    fn parses_durations() {
78        let a = Args::try_parse_from([
79            "ferra-agent",
80            "--server",
81            "http://x:8080",
82            "--prefix",
83            "p/",
84            "--min-backoff",
85            "1s",
86            "--max-backoff",
87            "1m",
88        ])
89        .unwrap();
90        assert_eq!(a.min_backoff, Duration::from_secs(1));
91        assert_eq!(a.max_backoff, Duration::from_secs(60));
92    }
93}