1use crate::errors::{CfgdError, ConfigError};
31
32#[repr(i32)]
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ExitCode {
35 Success = 0,
36 Error = 1,
37 UpdateAvailable = 2,
38 NoConfig = 3,
39 ConfigInvalid = 4,
40 DriftDetected = 5,
41 NotFound = 6,
42}
43
44impl ExitCode {
45 pub const fn as_i32(self) -> i32 {
46 self as i32
47 }
48
49 pub fn exit(self) -> ! {
51 std::process::exit(self.as_i32())
52 }
53}
54
55impl From<ExitCode> for i32 {
56 fn from(code: ExitCode) -> i32 {
57 code as i32
58 }
59}
60
61pub fn exit_code_for_error(err: &CfgdError) -> ExitCode {
69 match err {
70 CfgdError::Config(ConfigError::NotFound { .. }) => ExitCode::NoConfig,
71 CfgdError::Config(_) => ExitCode::ConfigInvalid,
72 _ => ExitCode::Error,
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use std::path::PathBuf;
80
81 #[test]
82 fn distinct_integer_codes() {
83 let codes = [
84 ExitCode::Success.as_i32(),
85 ExitCode::Error.as_i32(),
86 ExitCode::UpdateAvailable.as_i32(),
87 ExitCode::NoConfig.as_i32(),
88 ExitCode::ConfigInvalid.as_i32(),
89 ExitCode::DriftDetected.as_i32(),
90 ExitCode::NotFound.as_i32(),
91 ];
92 let mut seen = std::collections::HashSet::new();
93 for c in codes {
94 assert!(seen.insert(c), "duplicate exit code {}", c);
95 }
96 }
97
98 #[test]
99 fn stable_wire_values() {
100 assert_eq!(ExitCode::Success.as_i32(), 0);
104 assert_eq!(ExitCode::Error.as_i32(), 1);
105 assert_eq!(ExitCode::UpdateAvailable.as_i32(), 2);
106 assert_eq!(ExitCode::NoConfig.as_i32(), 3);
107 assert_eq!(ExitCode::ConfigInvalid.as_i32(), 4);
108 assert_eq!(ExitCode::DriftDetected.as_i32(), 5);
109 assert_eq!(ExitCode::NotFound.as_i32(), 6);
110 }
111
112 #[test]
113 fn config_not_found_maps_to_no_config() {
114 let err = CfgdError::Config(ConfigError::NotFound {
115 path: PathBuf::from("/nonexistent/cfgd.yaml"),
116 });
117 assert_eq!(exit_code_for_error(&err), ExitCode::NoConfig);
118 }
119
120 #[test]
121 fn config_invalid_maps_to_config_invalid() {
122 let err = CfgdError::Config(ConfigError::Invalid {
123 message: "missing apiVersion".into(),
124 });
125 assert_eq!(exit_code_for_error(&err), ExitCode::ConfigInvalid);
126 let err = CfgdError::Config(ConfigError::ProfileNotFound { name: "dev".into() });
127 assert_eq!(exit_code_for_error(&err), ExitCode::ConfigInvalid);
128 }
129
130 #[test]
131 fn non_config_error_maps_to_generic() {
132 let err = CfgdError::Io(std::io::Error::other("boom"));
133 assert_eq!(exit_code_for_error(&err), ExitCode::Error);
134 }
135
136 #[test]
137 fn i32_conversion_matches_as_i32() {
138 let code: i32 = ExitCode::DriftDetected.into();
139 assert_eq!(code, ExitCode::DriftDetected.as_i32());
140 }
141}