Skip to main content

eggress_pproxy_compat/
exit_codes.rs

1/// Stable process exit codes shared by the `eggress` and `pproxy` binaries.
2/// This module is the single owner of the numeric process contract; the
3/// `eggress-cli` crate re-exports these constants instead of defining its
4/// own overlapping copies.
5pub const EXIT_SUCCESS: i32 = 0;
6pub const EXIT_RUNTIME_FAILURE: i32 = 1;
7pub const EXIT_CLI_PARSE_ERROR: i32 = 2;
8pub const EXIT_CONFIG_VALIDATION: i32 = 3;
9pub const EXIT_BIND_FAILURE: i32 = 4;
10pub const EXIT_UNSUPPORTED_FEATURE: i32 = 5;
11pub const EXIT_PLATFORM_MISSING: i32 = 6;
12pub const EXIT_EXTERNAL_DEPENDENCY: i32 = 7;
13pub const EXIT_SIGINT: i32 = 130;
14pub const EXIT_SIGTERM: i32 = 143;
15
16/// Typed process outcome. Prefer returning this from command handlers and
17/// converting to a numeric code once at the process boundary over threading
18/// raw `i32` constants through business logic.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ProcessExit {
21    /// Clean run / check passed.
22    Success,
23    /// Runtime failure (supervisor, connection drain, unexpected I/O).
24    RuntimeFailure,
25    /// CLI/flag parse error (including strict-gate rejection of unknown flags
26    /// and invalid values for closed option domains).
27    CliParseError,
28    /// Configuration validation failure (file, TOML, or translated config).
29    ConfigValidation,
30    /// Listener bind failure (address in use, permission denied).
31    BindFailure,
32    /// Unsupported feature / composition refused fail-closed.
33    UnsupportedFeature,
34    /// Required platform facility unavailable (e.g. Linux-only `--daemon`
35    /// requested on another OS, prebuilt updater on an unsupported target).
36    PlatformMissing,
37    /// External dependency unavailable (e.g. `curl` missing for download,
38    /// release metadata unreachable, checksum tool absent).
39    ExternalDependency,
40    /// Terminated by SIGINT (Ctrl-C).
41    Sigint,
42    /// Terminated by SIGTERM.
43    Sigterm,
44}
45
46impl ProcessExit {
47    /// Numeric exit code for this outcome.
48    pub const fn code(self) -> i32 {
49        match self {
50            ProcessExit::Success => EXIT_SUCCESS,
51            ProcessExit::RuntimeFailure => EXIT_RUNTIME_FAILURE,
52            ProcessExit::CliParseError => EXIT_CLI_PARSE_ERROR,
53            ProcessExit::ConfigValidation => EXIT_CONFIG_VALIDATION,
54            ProcessExit::BindFailure => EXIT_BIND_FAILURE,
55            ProcessExit::UnsupportedFeature => EXIT_UNSUPPORTED_FEATURE,
56            ProcessExit::PlatformMissing => EXIT_PLATFORM_MISSING,
57            ProcessExit::ExternalDependency => EXIT_EXTERNAL_DEPENDENCY,
58            ProcessExit::Sigint => EXIT_SIGINT,
59            ProcessExit::Sigterm => EXIT_SIGTERM,
60        }
61    }
62
63    /// Stable snake_case name for this outcome (matches `exit_code_name`).
64    pub const fn name(self) -> &'static str {
65        match self {
66            ProcessExit::Success => "success",
67            ProcessExit::RuntimeFailure => "runtime_failure",
68            ProcessExit::CliParseError => "cli_parse_error",
69            ProcessExit::ConfigValidation => "config_validation",
70            ProcessExit::BindFailure => "bind_failure",
71            ProcessExit::UnsupportedFeature => "unsupported_feature",
72            ProcessExit::PlatformMissing => "platform_missing",
73            ProcessExit::ExternalDependency => "external_dependency",
74            ProcessExit::Sigint => "interrupted_by_sigint",
75            ProcessExit::Sigterm => "terminated_by_sigterm",
76        }
77    }
78}
79
80impl From<ProcessExit> for i32 {
81    fn from(exit: ProcessExit) -> Self {
82        exit.code()
83    }
84}
85
86pub fn exit_code_name(code: i32) -> &'static str {
87    match code {
88        EXIT_SUCCESS => "success",
89        EXIT_RUNTIME_FAILURE => "runtime_failure",
90        EXIT_CLI_PARSE_ERROR => "cli_parse_error",
91        EXIT_CONFIG_VALIDATION => "config_validation",
92        EXIT_BIND_FAILURE => "bind_failure",
93        EXIT_UNSUPPORTED_FEATURE => "unsupported_feature",
94        EXIT_PLATFORM_MISSING => "platform_missing",
95        EXIT_EXTERNAL_DEPENDENCY => "external_dependency",
96        EXIT_SIGINT => "interrupted_by_sigint",
97        EXIT_SIGTERM => "terminated_by_sigterm",
98        _ => "unknown",
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn typed_outcomes_match_numeric_contract() {
108        let cases = [
109            (ProcessExit::Success, EXIT_SUCCESS),
110            (ProcessExit::RuntimeFailure, EXIT_RUNTIME_FAILURE),
111            (ProcessExit::CliParseError, EXIT_CLI_PARSE_ERROR),
112            (ProcessExit::ConfigValidation, EXIT_CONFIG_VALIDATION),
113            (ProcessExit::BindFailure, EXIT_BIND_FAILURE),
114            (ProcessExit::UnsupportedFeature, EXIT_UNSUPPORTED_FEATURE),
115            (ProcessExit::PlatformMissing, EXIT_PLATFORM_MISSING),
116            (ProcessExit::ExternalDependency, EXIT_EXTERNAL_DEPENDENCY),
117            (ProcessExit::Sigint, EXIT_SIGINT),
118            (ProcessExit::Sigterm, EXIT_SIGTERM),
119        ];
120        for (outcome, code) in cases {
121            assert_eq!(outcome.code(), code);
122            assert_eq!(i32::from(outcome), code);
123            assert_eq!(outcome.name(), exit_code_name(code));
124        }
125    }
126
127    #[test]
128    fn signal_exits_remain_stable() {
129        assert_eq!(ProcessExit::Sigint.code(), 130);
130        assert_eq!(ProcessExit::Sigterm.code(), 143);
131    }
132}