oxify_connect_vision/
errors.rs

1//! Error types for vision/OCR operations.
2
3use crate::diagnostics::ErrorDiagnostic;
4use thiserror::Error;
5
6/// Errors that can occur during vision/OCR processing.
7#[derive(Debug, Error)]
8pub enum VisionError {
9    /// Failed to load the model.
10    #[error("Failed to load model: {0}")]
11    ModelLoad(String),
12
13    /// Model is not loaded.
14    #[error("Model is not loaded. Call load_model() first.")]
15    ModelNotLoaded,
16
17    /// Failed to process the image.
18    #[error("Image processing error: {0}")]
19    ImageProcessing(String),
20
21    /// Invalid image format.
22    #[error("Invalid image format: {0}")]
23    InvalidFormat(String),
24
25    /// Image decoding error.
26    #[error("Failed to decode image: {0}")]
27    ImageDecode(String),
28
29    /// OCR engine error.
30    #[error("OCR engine error: {0}")]
31    OcrEngine(String),
32
33    /// ONNX runtime error.
34    #[error("ONNX runtime error: {0}")]
35    OnnxRuntime(String),
36
37    /// Tesseract error.
38    #[error("Tesseract error: {0}")]
39    Tesseract(String),
40
41    /// Configuration error.
42    #[error("Configuration error: {0}")]
43    Config(String),
44
45    /// Unsupported provider.
46    #[error("Unsupported provider: {0}")]
47    UnsupportedProvider(String),
48
49    /// Timeout error.
50    #[error("Processing timeout after {0}ms")]
51    Timeout(u64),
52
53    /// Resource exhaustion.
54    #[error("Resource exhaustion: {0}")]
55    ResourceExhaustion(String),
56
57    /// I/O error.
58    #[error("I/O error: {0}")]
59    Io(#[from] std::io::Error),
60
61    /// Other error.
62    #[error("{0}")]
63    Other(String),
64}
65
66impl VisionError {
67    /// Create a model load error.
68    pub fn model_load(msg: impl Into<String>) -> Self {
69        VisionError::ModelLoad(msg.into())
70    }
71
72    /// Create an image processing error.
73    pub fn image_processing(msg: impl Into<String>) -> Self {
74        VisionError::ImageProcessing(msg.into())
75    }
76
77    /// Create an OCR engine error.
78    pub fn ocr_engine(msg: impl Into<String>) -> Self {
79        VisionError::OcrEngine(msg.into())
80    }
81
82    /// Create an ONNX runtime error.
83    pub fn onnx_runtime(msg: impl Into<String>) -> Self {
84        VisionError::OnnxRuntime(msg.into())
85    }
86
87    /// Create a tesseract error.
88    pub fn tesseract(msg: impl Into<String>) -> Self {
89        VisionError::Tesseract(msg.into())
90    }
91
92    /// Create a configuration error.
93    pub fn config(msg: impl Into<String>) -> Self {
94        VisionError::Config(msg.into())
95    }
96
97    /// Create an unsupported provider error.
98    pub fn unsupported_provider(provider: impl Into<String>) -> Self {
99        VisionError::UnsupportedProvider(provider.into())
100    }
101
102    /// Get diagnostic information for this error.
103    ///
104    /// Returns helpful suggestions, documentation links, and system diagnostics
105    /// to help troubleshoot the error.
106    pub fn diagnostic(&self) -> ErrorDiagnostic {
107        match self {
108            VisionError::ModelLoad(msg) => {
109                // Extract path from error message if possible
110                let path = msg.split(':').next_back().unwrap_or(msg).trim();
111                ErrorDiagnostic::model_load(path)
112            }
113            VisionError::ModelNotLoaded => ErrorDiagnostic::model_not_loaded(),
114            VisionError::ImageProcessing(msg) | VisionError::ImageDecode(msg) => {
115                ErrorDiagnostic::image_format(msg)
116            }
117            VisionError::InvalidFormat(msg) => ErrorDiagnostic::image_format(msg),
118            VisionError::OnnxRuntime(msg) => ErrorDiagnostic::onnx_runtime(msg),
119            VisionError::Tesseract(msg) => ErrorDiagnostic::tesseract(msg),
120            VisionError::Config(msg) => ErrorDiagnostic::configuration(msg),
121            VisionError::ResourceExhaustion(msg) => ErrorDiagnostic::resource_exhaustion(msg),
122            VisionError::OcrEngine(msg) => {
123                // Determine which provider based on message content
124                if msg.contains("Tesseract") || msg.contains("tesseract") {
125                    ErrorDiagnostic::tesseract(msg)
126                } else if msg.contains("ONNX") || msg.contains("onnx") {
127                    ErrorDiagnostic::onnx_runtime(msg)
128                } else {
129                    ErrorDiagnostic::configuration(msg)
130                }
131            }
132            _ => ErrorDiagnostic::configuration(&self.to_string()),
133        }
134    }
135
136    /// Get a user-friendly error message with diagnostic information.
137    ///
138    /// This includes the error message plus suggestions and system diagnostics.
139    pub fn with_diagnostics(&self) -> String {
140        format!("{}\n{}", self, self.diagnostic().format())
141    }
142}
143
144/// Result type for vision operations.
145pub type Result<T> = std::result::Result<T, VisionError>;
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_error_display() {
153        let err = VisionError::model_load("Model file not found");
154        assert_eq!(
155            err.to_string(),
156            "Failed to load model: Model file not found"
157        );
158    }
159
160    #[test]
161    fn test_error_variants() {
162        let errors = vec![
163            VisionError::ModelNotLoaded,
164            VisionError::image_processing("test"),
165            VisionError::ocr_engine("test"),
166            VisionError::config("test"),
167            VisionError::unsupported_provider("test"),
168            VisionError::Timeout(1000),
169        ];
170
171        for err in errors {
172            // Just ensure Display works
173            let _ = err.to_string();
174        }
175    }
176
177    #[test]
178    fn test_error_diagnostics() {
179        let err = VisionError::ModelNotLoaded;
180        let diag = err.diagnostic();
181        assert!(!diag.suggestions.is_empty());
182
183        let err = VisionError::model_load("/nonexistent/model.onnx");
184        let diag = err.diagnostic();
185        assert!(!diag.suggestions.is_empty());
186        assert!(diag.system_info.is_some());
187    }
188
189    #[test]
190    fn test_error_with_diagnostics() {
191        let err = VisionError::ModelNotLoaded;
192        let msg = err.with_diagnostics();
193        assert!(msg.contains("Call provider.load_model()"));
194        assert!(msg.contains("Suggestions"));
195    }
196
197    #[test]
198    fn test_onnx_runtime_diagnostics() {
199        let err = VisionError::onnx_runtime("CUDA memory allocation failed");
200        let diag = err.diagnostic();
201        assert!(!diag.suggestions.is_empty());
202        assert!(diag.system_info.is_some());
203    }
204
205    #[test]
206    fn test_tesseract_diagnostics() {
207        let err = VisionError::tesseract("Language data not found");
208        let diag = err.diagnostic();
209        assert!(!diag.suggestions.is_empty());
210    }
211
212    #[test]
213    fn test_image_format_diagnostics() {
214        let err = VisionError::InvalidFormat("decode error".to_string());
215        let diag = err.diagnostic();
216        assert!(!diag.suggestions.is_empty());
217    }
218}