Skip to main content

oxirs_core/
error.rs

1//! Core error types for OxiRS
2//!
3//! This module provides the base error types that all OxiRS modules should use.
4//! Module-specific errors should include this as a variant.
5
6// Removed unused std::fmt import
7
8/// Core error type for OxiRS operations
9#[derive(Debug, Clone, thiserror::Error)]
10pub enum CoreError {
11    /// Invalid parameter provided
12    #[error("Invalid parameter '{name}': {message}")]
13    InvalidParameter { name: String, message: String },
14
15    /// Resource not found
16    #[error("Resource not found: {0}")]
17    NotFound(String),
18
19    /// Operation not supported
20    #[error("Operation not supported: {0}")]
21    NotSupported(String),
22
23    /// Dimension mismatch in vector operations
24    #[error("Dimension mismatch: expected {expected}, got {actual}")]
25    DimensionMismatch { expected: usize, actual: usize },
26
27    /// Platform capability not available
28    #[error("Platform capability not available: {0}")]
29    CapabilityNotAvailable(String),
30
31    /// Memory allocation failure
32    #[error("Memory allocation failed: {0}")]
33    MemoryError(String),
34
35    /// I/O error
36    #[error("I/O error: {0}")]
37    IoError(String),
38
39    /// Serialization error
40    #[error("Serialization error: {0}")]
41    SerializationError(String),
42
43    /// Configuration error
44    #[error("Configuration error: {0}")]
45    ConfigError(String),
46
47    /// Timeout error
48    #[error("Operation timed out: {0}")]
49    Timeout(String),
50
51    /// Generic internal error
52    #[error("Internal error: {0}")]
53    Internal(String),
54}
55
56impl From<std::io::Error> for CoreError {
57    fn from(err: std::io::Error) -> Self {
58        CoreError::IoError(err.to_string())
59    }
60}
61
62impl From<serde_json::Error> for CoreError {
63    fn from(err: serde_json::Error) -> Self {
64        CoreError::SerializationError(err.to_string())
65    }
66}
67
68/// Result type alias using CoreError
69pub type CoreResult<T> = Result<T, CoreError>;
70
71// Re-export the main OxiRS error types for compatibility
72pub use crate::{OxirsError, Result as OxirsResult};
73
74/// Validation functions for common parameter checks
75pub mod validation {
76    use super::{CoreError, CoreResult};
77    use std::fmt;
78
79    /// Check that a value is positive
80    pub fn check_positive<T>(value: T, name: &str) -> CoreResult<T>
81    where
82        T: PartialOrd + Default + fmt::Display + Copy,
83    {
84        if value <= T::default() {
85            Err(CoreError::InvalidParameter {
86                name: name.to_string(),
87                message: format!("Value must be positive, got {value}"),
88            })
89        } else {
90            Ok(value)
91        }
92    }
93
94    /// Check that a value is finite (for floating point)
95    pub fn check_finite_f32(value: f32, name: &str) -> CoreResult<f32> {
96        if !value.is_finite() {
97            Err(CoreError::InvalidParameter {
98                name: name.to_string(),
99                message: format!("Value must be finite, got {value}"),
100            })
101        } else {
102            Ok(value)
103        }
104    }
105
106    /// Check that a value is finite (for floating point)
107    pub fn check_finite_f64(value: f64, name: &str) -> CoreResult<f64> {
108        if !value.is_finite() {
109            Err(CoreError::InvalidParameter {
110                name: name.to_string(),
111                message: format!("Value must be finite, got {value}"),
112            })
113        } else {
114            Ok(value)
115        }
116    }
117
118    /// Check that an array contains only finite values
119    pub fn check_finite_array(values: &[f32]) -> CoreResult<()> {
120        for (i, &value) in values.iter().enumerate() {
121            if !value.is_finite() {
122                return Err(CoreError::InvalidParameter {
123                    name: format!("array[{i}]"),
124                    message: format!("Value must be finite, got {value}"),
125                });
126            }
127        }
128        Ok(())
129    }
130
131    /// Check that dimensions match
132    pub fn check_dimensions(expected: usize, actual: usize, _context: &str) -> CoreResult<()> {
133        if expected != actual {
134            Err(CoreError::DimensionMismatch { expected, actual })
135        } else {
136            Ok(())
137        }
138    }
139
140    /// Check that a slice is not empty
141    pub fn check_non_empty<T>(slice: &[T], name: &str) -> CoreResult<()> {
142        if slice.is_empty() {
143            Err(CoreError::InvalidParameter {
144                name: name.to_string(),
145                message: "Value must not be empty".to_string(),
146            })
147        } else {
148            Ok(())
149        }
150    }
151
152    /// Check that a string is not empty
153    pub fn check_non_empty_str(value: &str, name: &str) -> CoreResult<()> {
154        if value.is_empty() {
155            Err(CoreError::InvalidParameter {
156                name: name.to_string(),
157                message: "Value must not be empty".to_string(),
158            })
159        } else {
160            Ok(())
161        }
162    }
163
164    /// Check that a value is within a range
165    pub fn check_range<T>(value: T, min: T, max: T, name: &str) -> CoreResult<T>
166    where
167        T: PartialOrd + fmt::Display + Copy,
168    {
169        if value < min || value > max {
170            Err(CoreError::InvalidParameter {
171                name: name.to_string(),
172                message: format!("Value must be between {min} and {max}, got {value}"),
173            })
174        } else {
175            Ok(value)
176        }
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::validation::*;
183    use super::*;
184
185    #[test]
186    fn test_core_error_display() {
187        let error = CoreError::InvalidParameter {
188            name: "test".to_string(),
189            message: "test message".to_string(),
190        };
191        assert_eq!(error.to_string(), "Invalid parameter 'test': test message");
192
193        let error = CoreError::NotFound("resource".to_string());
194        assert_eq!(error.to_string(), "Resource not found: resource");
195
196        let error = CoreError::DimensionMismatch {
197            expected: 3,
198            actual: 5,
199        };
200        assert_eq!(error.to_string(), "Dimension mismatch: expected 3, got 5");
201    }
202
203    #[test]
204    fn test_from_io_error() {
205        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
206        let core_error = CoreError::from(io_error);
207        match core_error {
208            CoreError::IoError(msg) => assert!(msg.contains("file not found")),
209            _ => panic!("Expected IoError"),
210        }
211    }
212
213    #[test]
214    fn test_from_serde_error() {
215        let serde_error = serde_json::from_str::<i32>("invalid").unwrap_err();
216        let core_error = CoreError::from(serde_error);
217        match core_error {
218            CoreError::SerializationError(_) => {} // Expected
219            _ => panic!("Expected SerializationError"),
220        }
221    }
222
223    #[test]
224    fn test_check_positive() {
225        // Test positive values
226        assert_eq!(
227            check_positive(5, "test").expect("value should be positive"),
228            5
229        );
230        assert_eq!(
231            check_positive(1.5f32, "test").expect("value should be positive"),
232            1.5f32
233        );
234
235        // Test zero and negative values
236        assert!(check_positive(0, "test").is_err());
237        assert!(check_positive(-1, "test").is_err());
238        assert!(check_positive(-1.5f32, "test").is_err());
239
240        // Check error message
241        let err = check_positive(0, "test_param").unwrap_err();
242        match err {
243            CoreError::InvalidParameter { name, message } => {
244                assert_eq!(name, "test_param");
245                assert!(message.contains("must be positive"));
246            }
247            _ => panic!("Expected InvalidParameter"),
248        }
249    }
250
251    #[test]
252    fn test_check_finite_f32() {
253        // Test finite values
254        assert_eq!(
255            check_finite_f32(1.5, "test").expect("value should be finite f32"),
256            1.5
257        );
258        assert_eq!(
259            check_finite_f32(0.0, "test").expect("value should be finite f32"),
260            0.0
261        );
262        assert_eq!(
263            check_finite_f32(-1.5, "test").expect("value should be finite f32"),
264            -1.5
265        );
266
267        // Test non-finite values
268        assert!(check_finite_f32(f32::INFINITY, "test").is_err());
269        assert!(check_finite_f32(f32::NEG_INFINITY, "test").is_err());
270        assert!(check_finite_f32(f32::NAN, "test").is_err());
271
272        // Check error message
273        let err = check_finite_f32(f32::INFINITY, "test_param").unwrap_err();
274        match err {
275            CoreError::InvalidParameter { name, message } => {
276                assert_eq!(name, "test_param");
277                assert!(message.contains("must be finite"));
278            }
279            _ => panic!("Expected InvalidParameter"),
280        }
281    }
282
283    #[test]
284    fn test_check_finite_f64() {
285        // Test finite values
286        assert_eq!(
287            check_finite_f64(1.5, "test").expect("value should be finite f64"),
288            1.5
289        );
290        assert_eq!(
291            check_finite_f64(0.0, "test").expect("value should be finite f64"),
292            0.0
293        );
294        assert_eq!(
295            check_finite_f64(-1.5, "test").expect("value should be finite f64"),
296            -1.5
297        );
298
299        // Test non-finite values
300        assert!(check_finite_f64(f64::INFINITY, "test").is_err());
301        assert!(check_finite_f64(f64::NEG_INFINITY, "test").is_err());
302        assert!(check_finite_f64(f64::NAN, "test").is_err());
303    }
304
305    #[test]
306    fn test_check_finite_array() {
307        // Test array with all finite values
308        let finite_array = [1.0, 2.5, -3.0, 0.0];
309        assert!(check_finite_array(&finite_array).is_ok());
310
311        // Test empty array
312        assert!(check_finite_array(&[]).is_ok());
313
314        // Test array with infinity
315        let inf_array = [1.0, f32::INFINITY, 3.0];
316        let err = check_finite_array(&inf_array).unwrap_err();
317        match err {
318            CoreError::InvalidParameter { name, message } => {
319                assert_eq!(name, "array[1]");
320                assert!(message.contains("must be finite"));
321            }
322            _ => panic!("Expected InvalidParameter"),
323        }
324
325        // Test array with NaN
326        let nan_array = [1.0, f32::NAN, 3.0];
327        assert!(check_finite_array(&nan_array).is_err());
328    }
329
330    #[test]
331    fn test_check_dimensions() {
332        // Test matching dimensions
333        assert!(check_dimensions(5, 5, "test").is_ok());
334        assert!(check_dimensions(0, 0, "test").is_ok());
335
336        // Test mismatched dimensions
337        let err = check_dimensions(5, 3, "test").unwrap_err();
338        match err {
339            CoreError::DimensionMismatch { expected, actual } => {
340                assert_eq!(expected, 5);
341                assert_eq!(actual, 3);
342            }
343            _ => panic!("Expected DimensionMismatch"),
344        }
345    }
346
347    #[test]
348    fn test_check_non_empty() {
349        // Test non-empty slice
350        let data = [1, 2, 3];
351        assert!(check_non_empty(&data, "test").is_ok());
352
353        // Test empty slice
354        let empty: &[i32] = &[];
355        let err = check_non_empty(empty, "test_param").unwrap_err();
356        match err {
357            CoreError::InvalidParameter { name, message } => {
358                assert_eq!(name, "test_param");
359                assert!(message.contains("must not be empty"));
360            }
361            _ => panic!("Expected InvalidParameter"),
362        }
363    }
364
365    #[test]
366    fn test_check_non_empty_str() {
367        // Test non-empty string
368        assert!(check_non_empty_str("hello", "test").is_ok());
369
370        // Test empty string
371        let err = check_non_empty_str("", "test_param").unwrap_err();
372        match err {
373            CoreError::InvalidParameter { name, message } => {
374                assert_eq!(name, "test_param");
375                assert!(message.contains("must not be empty"));
376            }
377            _ => panic!("Expected InvalidParameter"),
378        }
379    }
380
381    #[test]
382    fn test_check_range() {
383        // Test value within range
384        assert_eq!(
385            check_range(5, 1, 10, "test").expect("value should be in range"),
386            5
387        );
388        assert_eq!(
389            check_range(1, 1, 10, "test").expect("value should be in range"),
390            1
391        );
392        assert_eq!(
393            check_range(10, 1, 10, "test").expect("value should be in range"),
394            10
395        );
396        assert_eq!(
397            check_range(2.5f32, 1.0, 5.0, "test").expect("value should be in range"),
398            2.5f32
399        );
400
401        // Test value below range
402        let err = check_range(0, 1, 10, "test_param").unwrap_err();
403        match err {
404            CoreError::InvalidParameter { name, message } => {
405                assert_eq!(name, "test_param");
406                assert!(message.contains("between 1 and 10"));
407                assert!(message.contains("got 0"));
408            }
409            _ => panic!("Expected InvalidParameter"),
410        }
411
412        // Test value above range
413        let err = check_range(11, 1, 10, "test_param").unwrap_err();
414        match err {
415            CoreError::InvalidParameter { name, message } => {
416                assert_eq!(name, "test_param");
417                assert!(message.contains("between 1 and 10"));
418                assert!(message.contains("got 11"));
419            }
420            _ => panic!("Expected InvalidParameter"),
421        }
422    }
423
424    #[test]
425    fn test_error_variants() {
426        let error = CoreError::NotSupported("test operation".to_string());
427        assert_eq!(error.to_string(), "Operation not supported: test operation");
428
429        let error = CoreError::CapabilityNotAvailable("SIMD".to_string());
430        assert_eq!(error.to_string(), "Platform capability not available: SIMD");
431
432        let error = CoreError::MemoryError("allocation failed".to_string());
433        assert_eq!(
434            error.to_string(),
435            "Memory allocation failed: allocation failed"
436        );
437
438        let error = CoreError::ConfigError("invalid setting".to_string());
439        assert_eq!(error.to_string(), "Configuration error: invalid setting");
440
441        let error = CoreError::Timeout("5 seconds".to_string());
442        assert_eq!(error.to_string(), "Operation timed out: 5 seconds");
443
444        let error = CoreError::Internal("unexpected state".to_string());
445        assert_eq!(error.to_string(), "Internal error: unexpected state");
446    }
447}