Skip to main content

rusmes_server/
cli.rs

1//! Command-line interface for `rusmes-server`.
2//!
3//! The CLI uses `clap` derive to expose:
4//! - `-c/--config <PATH>` — explicit config flag (preferred).
5//! - A positional `[CONFIG]` fallback — preserved for one release for backwards
6//!   compatibility with older invocations such as `rusmes-server rusmes.toml`.
7//!   The fallback emits a deprecation warning to stderr when used.
8//! - `--check-config` — load + validate the configuration, then exit. No
9//!   sockets are opened, no servers are spawned.
10//!
11//! Resolution rules:
12//! - If `--config` is provided, it wins (even if a positional argument is also
13//!   present — the positional is silently ignored to keep the meaning of the
14//!   flag unambiguous).
15//! - Otherwise, the positional is used (with a deprecation warning).
16//! - Otherwise, the default `rusmes.toml` in the current directory is used.
17
18use clap::Parser;
19use std::path::PathBuf;
20
21/// `rusmes-server` — RusMES mail-server orchestrator.
22#[derive(Debug, Clone, Parser)]
23#[command(name = "rusmes-server", version, about, long_about = None)]
24pub struct Cli {
25    /// Path to the configuration file (TOML or YAML).
26    #[arg(short = 'c', long = "config", value_name = "PATH")]
27    pub config: Option<PathBuf>,
28
29    /// Validate the configuration file and exit without starting any servers.
30    ///
31    /// Exits 0 on success, 1 on any validation error. Diagnostics are printed
32    /// to stderr.
33    #[arg(long = "check-config", default_value_t = false)]
34    pub check_config: bool,
35
36    /// Deprecated: positional path to the configuration file. Use `--config`
37    /// instead. Will be removed in the next minor release.
38    #[arg(value_name = "CONFIG")]
39    pub positional_config: Option<PathBuf>,
40}
41
42impl Cli {
43    /// Resolve the effective configuration file path according to the
44    /// precedence rules documented at the module level.
45    ///
46    /// Returns `(path, used_positional_fallback)`. Callers should emit a
47    /// deprecation warning if `used_positional_fallback` is `true`.
48    pub fn resolve_config_path(&self) -> (PathBuf, bool) {
49        if let Some(ref path) = self.config {
50            return (path.clone(), false);
51        }
52        if let Some(ref path) = self.positional_config {
53            return (path.clone(), true);
54        }
55        (PathBuf::from("rusmes.toml"), false)
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn resolves_explicit_config_flag() {
65        let cli = Cli::parse_from(["rusmes-server", "-c", "/etc/rusmes/rusmes.toml"]);
66        let (path, fallback) = cli.resolve_config_path();
67        assert_eq!(path, PathBuf::from("/etc/rusmes/rusmes.toml"));
68        assert!(!fallback);
69        assert!(!cli.check_config);
70    }
71
72    #[test]
73    fn resolves_long_form_config_flag() {
74        let cli = Cli::parse_from(["rusmes-server", "--config", "x.toml"]);
75        let (path, fallback) = cli.resolve_config_path();
76        assert_eq!(path, PathBuf::from("x.toml"));
77        assert!(!fallback);
78    }
79
80    #[test]
81    fn resolves_positional_fallback_with_deprecation_flag() {
82        let cli = Cli::parse_from(["rusmes-server", "rusmes.toml"]);
83        let (path, fallback) = cli.resolve_config_path();
84        assert_eq!(path, PathBuf::from("rusmes.toml"));
85        assert!(fallback);
86    }
87
88    #[test]
89    fn flag_takes_precedence_over_positional() {
90        let cli = Cli::parse_from(["rusmes-server", "-c", "flag.toml", "positional.toml"]);
91        let (path, fallback) = cli.resolve_config_path();
92        assert_eq!(path, PathBuf::from("flag.toml"));
93        assert!(!fallback);
94    }
95
96    #[test]
97    fn resolves_default_when_nothing_supplied() {
98        let cli = Cli::parse_from(["rusmes-server"]);
99        let (path, fallback) = cli.resolve_config_path();
100        assert_eq!(path, PathBuf::from("rusmes.toml"));
101        assert!(!fallback);
102    }
103
104    #[test]
105    fn check_config_flag_parses() {
106        let cli = Cli::parse_from(["rusmes-server", "--check-config", "-c", "x.toml"]);
107        assert!(cli.check_config);
108    }
109}