Skip to main content

nut_shell/
error.rs

1//! Error types for CLI operations.
2//!
3//! The `CliError` enum represents all possible error conditions during
4//! command processing, with security-conscious error messages.
5
6use core::fmt;
7
8/// CLI error type.
9///
10/// Represents all possible error conditions during command processing.
11/// Error messages are designed to be user-friendly while maintaining security
12/// (e.g., `InvalidPath` for both non-existent and inaccessible paths).
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum CliError {
15    /// Command not found in tree
16    CommandNotFound,
17
18    /// Path doesn't exist OR user lacks access (intentionally ambiguous for security)
19    ///
20    /// SECURITY: Never reveal whether path exists vs. access denied
21    InvalidPath,
22
23    /// Wrong number of arguments
24    InvalidArgumentCount {
25        /// Minimum expected arguments
26        expected_min: usize,
27        /// Maximum expected arguments
28        expected_max: usize,
29        /// Number of arguments received
30        received: usize,
31    },
32
33    /// Invalid argument format/type (e.g., expected integer, got string)
34    InvalidArgumentFormat {
35        /// Which argument (0-indexed)
36        arg_index: usize,
37        /// What was expected (e.g., "integer", "IP address")
38        expected: heapless::String<32>,
39    },
40
41    /// Buffer capacity exceeded
42    BufferFull,
43
44    /// Path exceeds MAX_PATH_DEPTH
45    PathTooDeep,
46
47    /// Authentication failed - wrong credentials
48    #[cfg(feature = "authentication")]
49    AuthenticationFailed,
50
51    /// Tried to execute command while logged out
52    #[cfg(feature = "authentication")]
53    NotAuthenticated,
54
55    /// I/O error occurred
56    IoError,
57
58    /// Async command called from sync context
59    #[cfg(feature = "async")]
60    AsyncInSyncContext,
61
62    /// Operation timed out
63    Timeout,
64
65    /// Command executed but reported failure
66    CommandFailed(heapless::String<128>),
67
68    /// Generic error with message
69    Other(heapless::String<128>),
70}
71
72impl fmt::Display for CliError {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        match self {
75            CliError::CommandNotFound => write!(f, "Command not found"),
76            CliError::InvalidPath => write!(f, "Invalid path"),
77            CliError::InvalidArgumentCount {
78                expected_min,
79                expected_max,
80                received,
81            } => {
82                if expected_min == expected_max {
83                    write!(f, "Expected {} arguments, got {}", expected_min, received)
84                } else {
85                    write!(
86                        f,
87                        "Expected {}-{} arguments, got {}",
88                        expected_min, expected_max, received
89                    )
90                }
91            }
92            CliError::InvalidArgumentFormat {
93                arg_index,
94                expected,
95            } => {
96                write!(f, "Argument {}: expected {}", arg_index + 1, expected)
97            }
98            CliError::BufferFull => write!(f, "Buffer full"),
99            CliError::PathTooDeep => write!(f, "Path too deep"),
100            #[cfg(feature = "authentication")]
101            CliError::AuthenticationFailed => write!(f, "Authentication failed"),
102            #[cfg(feature = "authentication")]
103            CliError::NotAuthenticated => write!(f, "Not authenticated"),
104            CliError::IoError => write!(f, "I/O error"),
105            #[cfg(feature = "async")]
106            CliError::AsyncInSyncContext => write!(f, "Async command requires async context"),
107            CliError::Timeout => write!(f, "Timeout"),
108            CliError::CommandFailed(msg) => write!(f, "{}", msg),
109            CliError::Other(msg) => write!(f, "{}", msg),
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    extern crate std;
118    use std::format;
119
120    #[test]
121    fn test_error_display() {
122        assert_eq!(
123            format!("{}", CliError::CommandNotFound),
124            "Command not found"
125        );
126        assert_eq!(format!("{}", CliError::InvalidPath), "Invalid path");
127
128        let err = CliError::InvalidArgumentCount {
129            expected_min: 2,
130            expected_max: 2,
131            received: 1,
132        };
133        assert_eq!(format!("{}", err), "Expected 2 arguments, got 1");
134
135        let err = CliError::InvalidArgumentCount {
136            expected_min: 1,
137            expected_max: 3,
138            received: 4,
139        };
140        assert_eq!(format!("{}", err), "Expected 1-3 arguments, got 4");
141
142        let mut expected = heapless::String::new();
143        expected.push_str("integer").unwrap();
144        let err = CliError::InvalidArgumentFormat {
145            arg_index: 0,
146            expected,
147        };
148        assert_eq!(format!("{}", err), "Argument 1: expected integer");
149
150        let mut expected = heapless::String::new();
151        expected.push_str("IP address").unwrap();
152        let err = CliError::InvalidArgumentFormat {
153            arg_index: 2,
154            expected,
155        };
156        assert_eq!(format!("{}", err), "Argument 3: expected IP address");
157    }
158}