Skip to main content

sqry_nl/
error.rs

1//! Error types for the sqry-nl crate.
2//!
3//! Uses `thiserror` for ergonomic error handling with automatic
4//! `std::error::Error` implementation.
5
6use thiserror::Error;
7
8/// Result type alias for sqry-nl operations.
9pub type NlResult<T> = Result<T, NlError>;
10
11/// Top-level error type for sqry-nl operations.
12#[derive(Error, Debug)]
13pub enum NlError {
14    /// Preprocessing failed (Unicode normalization, input validation)
15    #[error("Preprocessing failed: {0}")]
16    Preprocess(#[from] PreprocessError),
17
18    /// Entity extraction failed
19    #[error("Entity extraction failed: {0}")]
20    Extractor(#[from] ExtractorError),
21
22    /// Intent classification failed
23    #[error("Classification failed: {0}")]
24    Classifier(#[from] ClassifierError),
25
26    /// Command assembly failed
27    #[error("Assembly failed: {0}")]
28    Assembler(#[from] AssemblerError),
29
30    /// Validation failed (safety checks)
31    #[error("Validation failed: {0}")]
32    Validator(#[from] ValidatorError),
33
34    /// Cache operation failed
35    #[error("Cache error: {0}")]
36    Cache(#[from] CacheError),
37
38    /// Configuration error
39    #[error("Configuration error: {0}")]
40    Config(String),
41
42    /// I/O error
43    #[error("I/O error: {0}")]
44    Io(#[from] std::io::Error),
45}
46
47/// Errors from the preprocessing stage.
48#[derive(Error, Debug, Clone, PartialEq, Eq)]
49pub enum PreprocessError {
50    /// Input exceeds maximum length
51    #[error("Input too long: {len} bytes (max: {max})")]
52    InputTooLong { len: usize, max: usize },
53
54    /// Input contains only whitespace or is empty
55    #[error("Input is empty or contains only whitespace")]
56    EmptyInput,
57
58    /// Homoglyph attack detected
59    #[error("Suspicious character detected: possible homoglyph attack")]
60    HomoglyphDetected,
61
62    /// Invalid UTF-8 encoding
63    #[error("Invalid UTF-8 encoding")]
64    InvalidUtf8,
65}
66
67/// Errors from the entity extraction stage.
68#[derive(Error, Debug, Clone, PartialEq, Eq)]
69pub enum ExtractorError {
70    /// No symbols found in input
71    #[error("No symbol or pattern found in query")]
72    NoSymbolFound,
73
74    /// Ambiguous symbol reference
75    #[error("Ambiguous symbol reference: multiple interpretations possible")]
76    AmbiguousSymbol,
77
78    /// Invalid language specified
79    #[error("Unknown language: {0}")]
80    UnknownLanguage(String),
81
82    /// Invalid symbol kind specified
83    #[error("Unknown symbol kind: {0}")]
84    UnknownKind(String),
85
86    /// Regex compilation error
87    #[error("Pattern compilation failed: {0}")]
88    RegexError(String),
89}
90
91/// Errors from the intent classification stage.
92#[derive(Error, Debug)]
93pub enum ClassifierError {
94    /// Model file not found
95    #[error("Model not found at: {0}")]
96    ModelNotFound(String),
97
98    /// Model checksum mismatch
99    #[error("Model checksum mismatch: expected {expected}, got {actual}")]
100    ChecksumMismatch { expected: String, actual: String },
101
102    /// Tokenization failed
103    #[error("Tokenization failed: {0}")]
104    TokenizationFailed(String),
105
106    /// ONNX Runtime error
107    #[error("ONNX Runtime error: {0}")]
108    OnnxError(String),
109
110    /// Model version incompatible
111    #[error("Model version {model_version} incompatible with sqry-nl {crate_version}")]
112    VersionMismatch {
113        model_version: String,
114        crate_version: String,
115    },
116
117    /// Inference timeout
118    #[error("Classification timed out after {timeout_ms}ms")]
119    Timeout { timeout_ms: u64 },
120}
121
122/// Errors from the command assembly stage.
123#[derive(Error, Debug, Clone, PartialEq, Eq)]
124pub enum AssemblerError {
125    /// Required symbol not provided
126    #[error("Missing required symbol for this command type")]
127    MissingSymbol,
128
129    /// Missing from/to symbols for trace-path
130    #[error("Trace-path requires both 'from' and 'to' symbols")]
131    MissingTracePath,
132
133    /// Intent is ambiguous and cannot be assembled
134    #[error("Cannot assemble command: intent is ambiguous")]
135    AmbiguousIntent,
136
137    /// Generated command exceeds length limit
138    #[error("Generated command too long: {len} chars (max: {max})")]
139    CommandTooLong { len: usize, max: usize },
140
141    /// Template not found for intent
142    #[error("No template found for intent: {0}")]
143    NoTemplate(String),
144}
145
146/// Errors from the validation stage.
147#[derive(Error, Debug, Clone, PartialEq, Eq)]
148pub enum ValidatorError {
149    /// Command doesn't match any allowed template
150    #[error("Command rejected: doesn't match any allowed template")]
151    TemplateMismatch,
152
153    /// Dangerous shell metacharacters detected
154    #[error("Command rejected: contains shell metacharacters")]
155    MetacharDetected,
156
157    /// Environment variable expansion detected
158    #[error("Command rejected: contains environment variable")]
159    EnvVarDetected,
160
161    /// Path traversal attempt detected
162    #[error("Command rejected: path traversal detected")]
163    PathTraversal,
164
165    /// Absolute path detected
166    #[error("Command rejected: absolute paths not allowed")]
167    AbsolutePath,
168
169    /// Write-mode operation detected
170    #[error("Command rejected: write operations not allowed via NL")]
171    WriteOperation,
172
173    /// Command too long
174    #[error("Command rejected: exceeds maximum length")]
175    CommandTooLong,
176}
177
178/// Errors from the cache operations.
179#[derive(Error, Debug, Clone, PartialEq, Eq)]
180pub enum CacheError {
181    /// Cache is disabled
182    #[error("Cache is disabled")]
183    Disabled,
184
185    /// Cache entry expired
186    #[error("Cache entry has expired")]
187    Expired,
188
189    /// Cache key generation failed
190    #[error("Failed to generate cache key: {0}")]
191    KeyGenerationFailed(String),
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[test]
199    fn test_error_display() {
200        let err = PreprocessError::InputTooLong {
201            len: 5000,
202            max: 4096,
203        };
204        assert!(err.to_string().contains("5000"));
205        assert!(err.to_string().contains("4096"));
206    }
207
208    #[test]
209    fn test_error_conversion() {
210        let preprocess_err = PreprocessError::EmptyInput;
211        let nl_err: NlError = preprocess_err.into();
212        assert!(matches!(nl_err, NlError::Preprocess(_)));
213    }
214
215    #[test]
216    fn test_errors_implement_std_error() {
217        fn assert_error<T: std::error::Error>() {}
218
219        assert_error::<NlError>();
220        assert_error::<PreprocessError>();
221        assert_error::<ExtractorError>();
222        assert_error::<ClassifierError>();
223        assert_error::<AssemblerError>();
224        assert_error::<ValidatorError>();
225        assert_error::<CacheError>();
226    }
227}