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