Skip to main content

rustfs_cli/
exit_code.rs

1//! Exit code definitions for rc CLI
2//!
3//! This file is protected by CI. Any modifications require the Breaking Change process:
4//! 1. Update version number
5//! 2. Provide migration plan
6//! 3. Update CHANGELOG
7//! 4. Mark PR as BREAKING
8
9/// Exit codes for the rc CLI application.
10///
11/// These codes follow a consistent convention to allow scripts and automation
12/// to handle different error scenarios appropriately.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[repr(i32)]
15pub enum ExitCode {
16    /// Operation completed successfully
17    Success = 0,
18
19    /// General/unspecified error
20    GeneralError = 1,
21
22    /// User input error: invalid arguments, malformed path, etc.
23    UsageError = 2,
24
25    /// Retryable network error: timeout, connection reset, 503, etc.
26    NetworkError = 3,
27
28    /// Authentication or permission failure
29    AuthError = 4,
30
31    /// Resource not found: bucket or object does not exist
32    NotFound = 5,
33
34    /// Conflict or precondition failure: version conflict, if-match failed, etc.
35    Conflict = 6,
36
37    /// Backend does not support this feature
38    UnsupportedFeature = 7,
39
40    /// Operation was interrupted (e.g., Ctrl+C)
41    Interrupted = 130,
42}
43
44impl ExitCode {
45    /// Convert exit code to i32 for use with std::process::exit
46    #[inline]
47    pub const fn as_i32(self) -> i32 {
48        self as i32
49    }
50
51    /// Create exit code from i32 value
52    ///
53    /// Returns None if the value doesn't correspond to a known exit code.
54    pub const fn from_i32(code: i32) -> Option<Self> {
55        match code {
56            0 => Some(Self::Success),
57            1 => Some(Self::GeneralError),
58            2 => Some(Self::UsageError),
59            3 => Some(Self::NetworkError),
60            4 => Some(Self::AuthError),
61            5 => Some(Self::NotFound),
62            6 => Some(Self::Conflict),
63            7 => Some(Self::UnsupportedFeature),
64            130 => Some(Self::Interrupted),
65            _ => None,
66        }
67    }
68
69    /// Get a human-readable description of the exit code
70    pub const fn description(self) -> &'static str {
71        match self {
72            Self::Success => "Operation completed successfully",
73            Self::GeneralError => "General error",
74            Self::UsageError => "Invalid arguments or path format",
75            Self::NetworkError => "Network error (retryable)",
76            Self::AuthError => "Authentication or permission failure",
77            Self::NotFound => "Resource not found",
78            Self::Conflict => "Conflict or precondition failure",
79            Self::UnsupportedFeature => "Feature not supported by backend",
80            Self::Interrupted => "Operation interrupted",
81        }
82    }
83}
84
85impl From<ExitCode> for i32 {
86    fn from(code: ExitCode) -> Self {
87        code.as_i32()
88    }
89}
90
91impl std::fmt::Display for ExitCode {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        write!(f, "{} ({})", self.description(), self.as_i32())
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_exit_code_values() {
103        assert_eq!(ExitCode::Success.as_i32(), 0);
104        assert_eq!(ExitCode::GeneralError.as_i32(), 1);
105        assert_eq!(ExitCode::UsageError.as_i32(), 2);
106        assert_eq!(ExitCode::NetworkError.as_i32(), 3);
107        assert_eq!(ExitCode::AuthError.as_i32(), 4);
108        assert_eq!(ExitCode::NotFound.as_i32(), 5);
109        assert_eq!(ExitCode::Conflict.as_i32(), 6);
110        assert_eq!(ExitCode::UnsupportedFeature.as_i32(), 7);
111        assert_eq!(ExitCode::Interrupted.as_i32(), 130);
112    }
113
114    #[test]
115    fn test_exit_code_from_i32() {
116        assert_eq!(ExitCode::from_i32(0), Some(ExitCode::Success));
117        assert_eq!(ExitCode::from_i32(1), Some(ExitCode::GeneralError));
118        assert_eq!(ExitCode::from_i32(2), Some(ExitCode::UsageError));
119        assert_eq!(ExitCode::from_i32(3), Some(ExitCode::NetworkError));
120        assert_eq!(ExitCode::from_i32(4), Some(ExitCode::AuthError));
121        assert_eq!(ExitCode::from_i32(5), Some(ExitCode::NotFound));
122        assert_eq!(ExitCode::from_i32(6), Some(ExitCode::Conflict));
123        assert_eq!(ExitCode::from_i32(7), Some(ExitCode::UnsupportedFeature));
124        assert_eq!(ExitCode::from_i32(130), Some(ExitCode::Interrupted));
125        assert_eq!(ExitCode::from_i32(99), None);
126    }
127
128    #[test]
129    fn test_exit_code_into_i32() {
130        let code: i32 = ExitCode::Success.into();
131        assert_eq!(code, 0);
132
133        let code: i32 = ExitCode::NotFound.into();
134        assert_eq!(code, 5);
135    }
136
137    #[test]
138    fn test_exit_code_display() {
139        let display = format!("{}", ExitCode::Success);
140        assert!(display.contains("0"));
141        assert!(display.contains("successfully"));
142
143        let display = format!("{}", ExitCode::NotFound);
144        assert!(display.contains("5"));
145        assert!(display.contains("not found"));
146    }
147}