Skip to main content

aprender_shell/
error.rs

1//! Error types for aprender-shell
2//!
3//! Follows Toyota Way principle *Jidoka* (Build quality in):
4//! Stop and fix problems immediately; never pass defects downstream.
5
6use std::fmt;
7use std::path::PathBuf;
8
9/// Errors that can occur in aprender-shell operations.
10///
11/// Each variant provides specific context and actionable hints for resolution.
12/// This eliminates the Cloudflare-class defects caused by unwrap()/expect().
13#[derive(Debug)]
14pub enum ShellError {
15    /// Model file was not found at the expected path
16    ModelNotFound { path: PathBuf, hint: String },
17
18    /// Model file exists but is corrupted (checksum mismatch, invalid format)
19    ModelCorrupted { path: PathBuf, hint: String },
20
21    /// Generic model loading failure
22    ModelLoadFailed { path: PathBuf, cause: String },
23
24    /// Invalid input provided to a command
25    InvalidInput { message: String },
26
27    /// Error parsing history file
28    HistoryParseError {
29        path: PathBuf,
30        line: usize,
31        cause: String,
32    },
33
34    /// Security violation detected (sensitive command blocked)
35    SecurityViolation { command: String, reason: String },
36}
37
38impl fmt::Display for ShellError {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            Self::ModelNotFound { path, hint } => {
42                write!(
43                    f,
44                    "Error: Model not found at '{}'\nHint: {}",
45                    path.display(),
46                    hint
47                )
48            }
49            Self::ModelCorrupted { path, hint } => {
50                write!(
51                    f,
52                    "Error: Model corrupted at '{}'\nHint: {}",
53                    path.display(),
54                    hint
55                )
56            }
57            Self::ModelLoadFailed { path, cause } => {
58                write!(
59                    f,
60                    "Error: Failed to load model '{}': {}",
61                    path.display(),
62                    cause
63                )
64            }
65            Self::InvalidInput { message } => {
66                write!(f, "Error: {message}")
67            }
68            Self::HistoryParseError { path, line, cause } => {
69                write!(
70                    f,
71                    "Error: Failed to parse {} at line {}: {}",
72                    path.display(),
73                    line,
74                    cause
75                )
76            }
77            Self::SecurityViolation { command, reason } => {
78                write!(f, "Security: Blocked '{}' - {}", command, reason)
79            }
80        }
81    }
82}
83
84impl std::error::Error for ShellError {}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_model_not_found_display() {
92        let err = ShellError::ModelNotFound {
93            path: PathBuf::from("/path/to/model.bin"),
94            hint: "Run 'aprender-shell train' to create a model".into(),
95        };
96        let msg = err.to_string();
97        assert!(msg.contains("not found"));
98        assert!(msg.contains("/path/to/model.bin"));
99        assert!(msg.contains("Hint:"));
100    }
101
102    #[test]
103    fn test_model_corrupted_display() {
104        let err = ShellError::ModelCorrupted {
105            path: PathBuf::from("/path/to/model.bin"),
106            hint: "Run 'aprender-shell train' to rebuild".into(),
107        };
108        let msg = err.to_string();
109        assert!(msg.contains("corrupted"));
110        assert!(msg.contains("rebuild"));
111    }
112
113    #[test]
114    fn test_invalid_input_display() {
115        let err = ShellError::InvalidInput {
116            message: "Empty prefix".into(),
117        };
118        assert_eq!(err.to_string(), "Error: Empty prefix");
119    }
120
121    #[test]
122    fn test_security_violation_display() {
123        let err = ShellError::SecurityViolation {
124            command: "export SECRET=abc".into(),
125            reason: "Contains sensitive data".into(),
126        };
127        let msg = err.to_string();
128        assert!(msg.contains("Security:"));
129        assert!(msg.contains("Blocked"));
130    }
131}