Skip to main content

decy_oracle/
error.rs

1//! Oracle error types
2
3use thiserror::Error;
4
5/// Oracle error type
6#[derive(Debug, Error)]
7pub enum OracleError {
8    /// Failed to load patterns file
9    #[error("Failed to load patterns from {path}: {source}")]
10    LoadError {
11        path: String,
12        #[source]
13        source: std::io::Error,
14    },
15
16    /// Failed to save patterns file
17    #[error("Failed to save patterns to {path}: {source}")]
18    SaveError {
19        path: String,
20        #[source]
21        source: std::io::Error,
22    },
23
24    /// Invalid pattern format
25    #[error("Invalid pattern format: {0}")]
26    InvalidPattern(String),
27
28    /// Pattern store error (from entrenar)
29    #[error("Pattern store error: {0}")]
30    PatternStoreError(String),
31
32    /// Configuration error
33    #[error("Configuration error: {0}")]
34    ConfigError(String),
35
36    /// Diff application failed
37    #[error("Failed to apply diff: {0}")]
38    DiffError(String),
39
40    /// Export error
41    #[error("Export error: {0}")]
42    ExportError(String),
43}
44
45impl From<std::io::Error> for OracleError {
46    fn from(e: std::io::Error) -> Self {
47        Self::LoadError { path: "<unknown>".into(), source: e }
48    }
49}
50
51impl From<toml::de::Error> for OracleError {
52    fn from(e: toml::de::Error) -> Self {
53        Self::ConfigError(e.to_string())
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    // ============================================================================
62    // ERROR CONSTRUCTION TESTS
63    // ============================================================================
64
65    #[test]
66    fn test_load_error_construction() {
67        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
68        let err = OracleError::LoadError { path: "/tmp/test.apr".into(), source: io_err };
69        assert!(matches!(err, OracleError::LoadError { .. }));
70    }
71
72    #[test]
73    fn test_save_error_construction() {
74        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
75        let err = OracleError::SaveError { path: "/etc/test.apr".into(), source: io_err };
76        assert!(matches!(err, OracleError::SaveError { .. }));
77    }
78
79    #[test]
80    fn test_invalid_pattern_error() {
81        let err = OracleError::InvalidPattern("malformed pattern syntax".into());
82        assert!(matches!(err, OracleError::InvalidPattern(_)));
83    }
84
85    #[test]
86    fn test_pattern_store_error() {
87        let err = OracleError::PatternStoreError("store corruption detected".into());
88        assert!(matches!(err, OracleError::PatternStoreError(_)));
89    }
90
91    #[test]
92    fn test_config_error() {
93        let err = OracleError::ConfigError("invalid threshold value".into());
94        assert!(matches!(err, OracleError::ConfigError(_)));
95    }
96
97    #[test]
98    fn test_diff_error() {
99        let err = OracleError::DiffError("hunk mismatch at line 42".into());
100        assert!(matches!(err, OracleError::DiffError(_)));
101    }
102
103    #[test]
104    fn test_export_error() {
105        let err = OracleError::ExportError("failed to write parquet".into());
106        assert!(matches!(err, OracleError::ExportError(_)));
107    }
108
109    // ============================================================================
110    // DISPLAY FORMATTING TESTS
111    // ============================================================================
112
113    #[test]
114    fn test_load_error_display() {
115        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
116        let err = OracleError::LoadError { path: "/tmp/patterns.apr".into(), source: io_err };
117        let msg = format!("{}", err);
118        assert!(msg.contains("/tmp/patterns.apr"));
119        assert!(msg.contains("file not found"));
120    }
121
122    #[test]
123    fn test_save_error_display() {
124        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
125        let err = OracleError::SaveError { path: "/etc/patterns.apr".into(), source: io_err };
126        let msg = format!("{}", err);
127        assert!(msg.contains("/etc/patterns.apr"));
128        assert!(msg.contains("access denied"));
129    }
130
131    #[test]
132    fn test_invalid_pattern_display() {
133        let err = OracleError::InvalidPattern("missing error_code field".into());
134        let msg = format!("{}", err);
135        assert!(msg.contains("Invalid pattern format"));
136        assert!(msg.contains("missing error_code field"));
137    }
138
139    #[test]
140    fn test_pattern_store_error_display() {
141        let err = OracleError::PatternStoreError("index corruption".into());
142        let msg = format!("{}", err);
143        assert!(msg.contains("Pattern store error"));
144        assert!(msg.contains("index corruption"));
145    }
146
147    #[test]
148    fn test_config_error_display() {
149        let err = OracleError::ConfigError("invalid TOML syntax".into());
150        let msg = format!("{}", err);
151        assert!(msg.contains("Configuration error"));
152        assert!(msg.contains("invalid TOML syntax"));
153    }
154
155    #[test]
156    fn test_diff_error_display() {
157        let err = OracleError::DiffError("context mismatch".into());
158        let msg = format!("{}", err);
159        assert!(msg.contains("Failed to apply diff"));
160        assert!(msg.contains("context mismatch"));
161    }
162
163    #[test]
164    fn test_export_error_display() {
165        let err = OracleError::ExportError("arrow schema error".into());
166        let msg = format!("{}", err);
167        assert!(msg.contains("Export error"));
168        assert!(msg.contains("arrow schema error"));
169    }
170
171    // ============================================================================
172    // FROM TRAIT IMPLEMENTATION TESTS
173    // ============================================================================
174
175    #[test]
176    fn test_from_io_error() {
177        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "no such file");
178        let oracle_err: OracleError = io_err.into();
179
180        match oracle_err {
181            OracleError::LoadError { path, source } => {
182                assert_eq!(path, "<unknown>");
183                assert_eq!(source.kind(), std::io::ErrorKind::NotFound);
184            }
185            _ => panic!("Expected LoadError variant"),
186        }
187    }
188
189    #[test]
190    fn test_from_io_error_preserves_error_kind() {
191        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
192        let oracle_err: OracleError = io_err.into();
193
194        if let OracleError::LoadError { source, .. } = oracle_err {
195            assert_eq!(source.kind(), std::io::ErrorKind::PermissionDenied);
196        } else {
197            panic!("Expected LoadError variant");
198        }
199    }
200
201    #[test]
202    fn test_from_toml_error() {
203        let toml_result: Result<toml::Value, _> = toml::from_str("invalid = [toml");
204        let toml_err = toml_result.unwrap_err();
205        let oracle_err: OracleError = toml_err.into();
206
207        match oracle_err {
208            OracleError::ConfigError(msg) => {
209                assert!(!msg.is_empty());
210            }
211            _ => panic!("Expected ConfigError variant"),
212        }
213    }
214
215    // ============================================================================
216    // DEBUG FORMATTING TESTS
217    // ============================================================================
218
219    #[test]
220    fn test_debug_formatting() {
221        let err = OracleError::InvalidPattern("test".into());
222        let debug_str = format!("{:?}", err);
223        assert!(debug_str.contains("InvalidPattern"));
224    }
225
226    // ============================================================================
227    // ERROR SOURCE CHAIN TESTS
228    // ============================================================================
229
230    #[test]
231    fn test_load_error_source_chain() {
232        use std::error::Error;
233
234        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "underlying error");
235        let err = OracleError::LoadError { path: "/test".into(), source: io_err };
236
237        // thiserror should provide source() method
238        assert!(err.source().is_some());
239    }
240
241    #[test]
242    fn test_save_error_source_chain() {
243        use std::error::Error;
244
245        let io_err = std::io::Error::new(std::io::ErrorKind::Other, "disk full");
246        let err = OracleError::SaveError { path: "/test".into(), source: io_err };
247
248        assert!(err.source().is_some());
249    }
250
251    #[test]
252    fn test_simple_errors_no_source() {
253        use std::error::Error;
254
255        let err = OracleError::InvalidPattern("test".into());
256        assert!(err.source().is_none());
257
258        let err = OracleError::PatternStoreError("test".into());
259        assert!(err.source().is_none());
260
261        let err = OracleError::ConfigError("test".into());
262        assert!(err.source().is_none());
263
264        let err = OracleError::DiffError("test".into());
265        assert!(err.source().is_none());
266
267        let err = OracleError::ExportError("test".into());
268        assert!(err.source().is_none());
269    }
270}