Skip to main content

tldr_cli/commands/remaining/
error.rs

1//! Error types for remaining commands
2//!
3//! This module defines the error types used across all remaining analysis
4//! commands (todo, explain, secure, definition, diff, diff_impact, api_check,
5//! equivalence, vuln).
6
7use std::path::PathBuf;
8use thiserror::Error;
9
10/// Errors for remaining commands.
11#[derive(Debug, Error)]
12pub enum RemainingError {
13    /// File not found.
14    #[error("file not found: {}", path.display())]
15    FileNotFound { path: PathBuf },
16
17    /// Function/symbol not found.
18    #[error("symbol '{}' not found in {}", symbol, file.display())]
19    SymbolNotFound { symbol: String, file: PathBuf },
20
21    /// Parse error.
22    #[error("parse error in {}: {message}", file.display())]
23    ParseError { file: PathBuf, message: String },
24
25    /// Invalid arguments.
26    #[error("invalid argument: {message}")]
27    InvalidArgument { message: String },
28
29    /// File too large.
30    #[error("file too large: {} ({bytes} bytes)", path.display())]
31    FileTooLarge { path: PathBuf, bytes: u64 },
32
33    /// Path traversal blocked.
34    #[error("path traversal blocked: {}", path.display())]
35    PathTraversal { path: PathBuf },
36
37    /// Unsupported language.
38    #[error("unsupported language: {language}")]
39    UnsupportedLanguage { language: String },
40
41    /// Analysis error.
42    #[error("analysis error: {message}")]
43    AnalysisError { message: String },
44
45    /// Findings detected (for vuln/api-check - special exit code).
46    #[error("{count} findings detected")]
47    FindingsDetected { count: u32 },
48
49    /// Timeout.
50    #[error("analysis timed out after {seconds}s")]
51    Timeout { seconds: u64 },
52
53    /// IO error.
54    #[error("IO error: {0}")]
55    Io(#[from] std::io::Error),
56
57    /// JSON error.
58    #[error("JSON error: {0}")]
59    Json(#[from] serde_json::Error),
60}
61
62impl RemainingError {
63    /// Create a FileNotFound error
64    pub fn file_not_found(path: impl Into<PathBuf>) -> Self {
65        Self::FileNotFound { path: path.into() }
66    }
67
68    /// Create a SymbolNotFound error
69    pub fn symbol_not_found(symbol: impl Into<String>, file: impl Into<PathBuf>) -> Self {
70        Self::SymbolNotFound {
71            symbol: symbol.into(),
72            file: file.into(),
73        }
74    }
75
76    /// Create a ParseError
77    pub fn parse_error(file: impl Into<PathBuf>, message: impl Into<String>) -> Self {
78        Self::ParseError {
79            file: file.into(),
80            message: message.into(),
81        }
82    }
83
84    /// Create an InvalidArgument error
85    pub fn invalid_argument(message: impl Into<String>) -> Self {
86        Self::InvalidArgument {
87            message: message.into(),
88        }
89    }
90
91    /// Create a FileTooLarge error
92    pub fn file_too_large(path: impl Into<PathBuf>, bytes: u64) -> Self {
93        Self::FileTooLarge {
94            path: path.into(),
95            bytes,
96        }
97    }
98
99    /// Create a PathTraversal error
100    pub fn path_traversal(path: impl Into<PathBuf>) -> Self {
101        Self::PathTraversal { path: path.into() }
102    }
103
104    /// Create an UnsupportedLanguage error
105    pub fn unsupported_language(language: impl Into<String>) -> Self {
106        Self::UnsupportedLanguage {
107            language: language.into(),
108        }
109    }
110
111    /// Create an AnalysisError
112    pub fn analysis_error(message: impl Into<String>) -> Self {
113        Self::AnalysisError {
114            message: message.into(),
115        }
116    }
117
118    /// Create a FindingsDetected error
119    pub fn findings_detected(count: u32) -> Self {
120        Self::FindingsDetected { count }
121    }
122
123    /// Create a Timeout error
124    pub fn timeout(seconds: u64) -> Self {
125        Self::Timeout { seconds }
126    }
127
128    /// Get the appropriate exit code for this error
129    pub fn exit_code(&self) -> i32 {
130        match self {
131            Self::FindingsDetected { .. } => 2, // Special exit code for findings
132            _ => 1,                             // General error
133        }
134    }
135}
136
137/// Result type alias for remaining commands
138pub type RemainingResult<T> = Result<T, RemainingError>;
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_file_not_found_error() {
146        let err = RemainingError::file_not_found("/path/to/file.py");
147        assert!(err.to_string().contains("file not found"));
148        assert!(err.to_string().contains("file.py"));
149    }
150
151    #[test]
152    fn test_symbol_not_found_error() {
153        let err = RemainingError::symbol_not_found("my_function", "/path/to/file.py");
154        assert!(err.to_string().contains("my_function"));
155        assert!(err.to_string().contains("not found"));
156    }
157
158    #[test]
159    fn test_exit_codes() {
160        assert_eq!(RemainingError::file_not_found("/foo").exit_code(), 1);
161        assert_eq!(RemainingError::findings_detected(5).exit_code(), 2);
162    }
163}