scirs2_core/
error.rs

1//! Error types for the SciRS2 core module
2//!
3//! This module provides common error types used throughout the SciRS2 ecosystem.
4
5use std::fmt;
6use thiserror::Error;
7
8/// Location information for error context
9#[derive(Debug, Clone)]
10pub struct ErrorLocation {
11    /// File where the error occurred
12    pub file: &'static str,
13    /// Line number where the error occurred
14    pub line: u32,
15    /// Column number where the error occurred
16    pub column: Option<u32>,
17    /// Function where the error occurred
18    pub function: Option<&'static str>,
19}
20
21impl ErrorLocation {
22    /// Create a new error location
23    #[inline]
24    pub fn new(file: &'static str, line: u32) -> Self {
25        Self {
26            file,
27            line,
28            column: None,
29            function: None,
30        }
31    }
32
33    /// Create a new error location with function information
34    #[inline]
35    pub fn with_function(file: &'static str, line: u32, function: &'static str) -> Self {
36        Self {
37            file,
38            line,
39            column: None,
40            function: Some(function),
41        }
42    }
43
44    /// Create a new error location with column information
45    #[inline]
46    pub fn with_column(file: &'static str, line: u32, column: u32) -> Self {
47        Self {
48            file,
49            line,
50            column: Some(column),
51            function: None,
52        }
53    }
54
55    /// Create a new error location with function and column information
56    #[inline]
57    pub fn full(file: &'static str, line: u32, column: u32, function: &'static str) -> Self {
58        Self {
59            file,
60            line,
61            column: Some(column),
62            function: Some(function),
63        }
64    }
65}
66
67impl fmt::Display for ErrorLocation {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        write!(f, "{}:{}", self.file, self.line)?;
70        if let Some(column) = self.column {
71            write!(f, ":{}", column)?;
72        }
73        if let Some(function) = self.function {
74            write!(f, " in {}", function)?;
75        }
76        Ok(())
77    }
78}
79
80/// Error context containing additional information about an error
81#[derive(Debug)]
82pub struct ErrorContext {
83    /// Error message
84    pub message: String,
85    /// Location where the error occurred
86    pub location: Option<ErrorLocation>,
87    /// Cause of the error
88    pub cause: Option<Box<CoreError>>,
89}
90
91impl ErrorContext {
92    /// Create a new error context
93    pub fn new<S: Into<String>>(message: S) -> Self {
94        Self {
95            message: message.into(),
96            location: None,
97            cause: None,
98        }
99    }
100
101    /// Add location information to the error context
102    pub fn with_location(mut self, location: ErrorLocation) -> Self {
103        self.location = Some(location);
104        self
105    }
106
107    /// Add a cause to the error context
108    pub fn with_cause(mut self, cause: CoreError) -> Self {
109        self.cause = Some(Box::new(cause));
110        self
111    }
112}
113
114impl fmt::Display for ErrorContext {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(f, "{}", self.message)?;
117        if let Some(location) = &self.location {
118            write!(f, " at {}", location)?;
119        }
120        if let Some(cause) = &self.cause {
121            write!(f, "\nCaused by: {}", cause)?;
122        }
123        Ok(())
124    }
125}
126
127/// Core error type for SciRS2
128#[derive(Error, Debug)]
129pub enum CoreError {
130    /// Computation error (generic error)
131    #[error("{0}")]
132    ComputationError(ErrorContext),
133
134    /// Domain error (input outside valid domain)
135    #[error("{0}")]
136    DomainError(ErrorContext),
137
138    /// Dispatch error (array protocol dispatch failed)
139    #[error("{0}")]
140    DispatchError(ErrorContext),
141
142    /// Convergence error (algorithm did not converge)
143    #[error("{0}")]
144    ConvergenceError(ErrorContext),
145
146    /// Dimension mismatch error
147    #[error("{0}")]
148    DimensionError(ErrorContext),
149
150    /// Shape error (matrices/arrays have incompatible shapes)
151    #[error("{0}")]
152    ShapeError(ErrorContext),
153
154    /// Out of bounds error
155    #[error("{0}")]
156    IndexError(ErrorContext),
157
158    /// Value error (invalid value)
159    #[error("{0}")]
160    ValueError(ErrorContext),
161
162    /// Type error (invalid type)
163    #[error("{0}")]
164    TypeError(ErrorContext),
165
166    /// Not implemented error
167    #[error("{0}")]
168    NotImplementedError(ErrorContext),
169
170    /// Implementation error (method exists but not fully implemented yet)
171    #[error("{0}")]
172    ImplementationError(ErrorContext),
173
174    /// Memory error (could not allocate memory)
175    #[error("{0}")]
176    MemoryError(ErrorContext),
177
178    /// Configuration error (invalid configuration)
179    #[error("{0}")]
180    ConfigError(ErrorContext),
181
182    /// Invalid argument error
183    #[error("{0}")]
184    InvalidArgument(ErrorContext),
185
186    /// Permission error (insufficient permissions)
187    #[error("{0}")]
188    PermissionError(ErrorContext),
189
190    /// Validation error (input failed validation)
191    #[error("{0}")]
192    ValidationError(ErrorContext),
193
194    /// JIT compilation error (error during JIT compilation)
195    #[error("{0}")]
196    JITError(ErrorContext),
197
198    /// JSON error
199    #[error("JSON error: {0}")]
200    JSONError(ErrorContext),
201
202    /// IO error
203    #[error("IO error: {0}")]
204    IoError(#[from] std::io::Error),
205}
206
207/// Result type alias for core operations
208pub type CoreResult<T> = Result<T, CoreError>;
209
210/// Convert from serde_json::Error to CoreError
211#[cfg(feature = "serialization")]
212impl From<serde_json::Error> for CoreError {
213    fn from(err: serde_json::Error) -> Self {
214        CoreError::JSONError(ErrorContext::new(format!("JSON error: {}", err)))
215    }
216}
217
218/// Convert from OperationError to CoreError
219impl From<crate::array_protocol::OperationError> for CoreError {
220    fn from(err: crate::array_protocol::OperationError) -> Self {
221        use crate::array_protocol::OperationError;
222        match err {
223            // Preserving NotImplemented for compatibility with older code,
224            // but it will eventually be replaced with NotImplementedError
225            OperationError::NotImplemented(msg) => {
226                CoreError::NotImplementedError(ErrorContext::new(msg))
227            }
228            OperationError::ShapeMismatch(msg) => CoreError::ShapeError(ErrorContext::new(msg)),
229            OperationError::TypeMismatch(msg) => CoreError::TypeError(ErrorContext::new(msg)),
230            OperationError::Other(msg) => CoreError::ComputationError(ErrorContext::new(msg)),
231        }
232    }
233}
234
235/// Macro to create a new error context with location information
236///
237/// # Example
238///
239/// ```ignore
240/// // This is a placeholder example
241/// use scirs2_core::error_context;
242///
243/// fn example() -> scirs2_core::error::CoreResult<()> {
244///     if false {
245///         return Err(error_context!("An error occurred"));
246///     }
247///     Ok(())
248/// }
249/// ```
250#[macro_export]
251macro_rules! error_context {
252    ($message:expr) => {
253        $crate::error::ErrorContext::new($message)
254            .with_location($crate::error::ErrorLocation::new(file!(), line!()))
255    };
256    ($message:expr, $function:expr) => {
257        $crate::error::ErrorContext::new($message).with_location(
258            $crate::error::ErrorLocation::with_function(file!(), line!(), $function),
259        )
260    };
261}
262
263/// Macro to create a domain error with location information
264#[macro_export]
265macro_rules! domain_error {
266    ($message:expr) => {
267        $crate::error::CoreError::DomainError(error_context!($message))
268    };
269    ($message:expr, $function:expr) => {
270        $crate::error::CoreError::DomainError(error_context!($message, $function))
271    };
272}
273
274/// Macro to create a dimension error with location information
275#[macro_export]
276macro_rules! dimension_error {
277    ($message:expr) => {
278        $crate::error::CoreError::DimensionError(error_context!($message))
279    };
280    ($message:expr, $function:expr) => {
281        $crate::error::CoreError::DimensionError(error_context!($message, $function))
282    };
283}
284
285/// Macro to create a value error with location information
286#[macro_export]
287macro_rules! value_error {
288    ($message:expr) => {
289        $crate::error::CoreError::ValueError(error_context!($message))
290    };
291    ($message:expr, $function:expr) => {
292        $crate::error::CoreError::ValueError(error_context!($message, $function))
293    };
294}
295
296/// Macro to create a computation error with location information
297#[macro_export]
298macro_rules! computation_error {
299    ($message:expr) => {
300        $crate::error::CoreError::ComputationError(error_context!($message))
301    };
302    ($message:expr, $function:expr) => {
303        $crate::error::CoreError::ComputationError(error_context!($message, $function))
304    };
305}
306
307/// Checks if a condition is true, otherwise returns a domain error
308///
309/// # Arguments
310///
311/// * `condition` - The condition to check
312/// * `message` - The error message if the condition is false
313///
314/// # Returns
315///
316/// * `Ok(())` if the condition is true
317/// * `Err(CoreError::DomainError)` if the condition is false
318pub fn check_domain<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
319    if condition {
320        Ok(())
321    } else {
322        Err(CoreError::DomainError(
323            ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
324        ))
325    }
326}
327
328/// Checks dimensions
329///
330/// # Arguments
331///
332/// * `condition` - The condition to check
333/// * `message` - The error message if the condition is false
334///
335/// # Returns
336///
337/// * `Ok(())` if the condition is true
338/// * `Err(CoreError::DimensionError)` if the condition is false
339pub fn check_dimensions<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
340    if condition {
341        Ok(())
342    } else {
343        Err(CoreError::DimensionError(
344            ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
345        ))
346    }
347}
348
349/// Checks if a value is valid
350///
351/// # Arguments
352///
353/// * `condition` - The condition to check
354/// * `message` - The error message if the condition is false
355///
356/// # Returns
357///
358/// * `Ok(())` if the condition is true
359/// * `Err(CoreError::ValueError)` if the condition is false
360pub fn check_value<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
361    if condition {
362        Ok(())
363    } else {
364        Err(CoreError::ValueError(
365            ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
366        ))
367    }
368}
369
370/// Checks if a value is valid according to a validator function
371///
372/// # Arguments
373///
374/// * `value` - The value to validate
375/// * `validator` - A function that returns true if the value is valid
376/// * `message` - The error message if the value is invalid
377///
378/// # Returns
379///
380/// * `Ok(value)` if the value is valid
381/// * `Err(CoreError::ValidationError)` if the value is invalid
382pub fn validate<T, F, S>(value: T, validator: F, message: S) -> CoreResult<T>
383where
384    F: FnOnce(&T) -> bool,
385    S: Into<String>,
386{
387    if validator(&value) {
388        Ok(value)
389    } else {
390        Err(CoreError::ValidationError(
391            ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
392        ))
393    }
394}
395
396/// Convert an error from one type to a CoreError
397///
398/// # Arguments
399///
400/// * `error` - The error to convert
401/// * `message` - A message describing the context of the error
402///
403/// # Returns
404///
405/// * A CoreError with the original error as its cause
406pub fn convert_error<E, S>(error: E, message: S) -> CoreError
407where
408    E: std::error::Error + 'static,
409    S: Into<String>,
410{
411    // Create a computation error that contains the original error
412    // We combine the provided message with the error's own message for extra context
413    let message_str = message.into();
414    let error_message = format!("{} | Original error: {}", message_str, error);
415
416    // For I/O errors we have direct conversion via From trait implementation
417    // but we can't use it directly due to the generic bounds.
418    // In a real implementation, you would use a match or if statement with
419    // type_id or another approach to distinguish error types.
420
421    // For simplicity, we'll just use ComputationError as a general case
422    CoreError::ComputationError(
423        ErrorContext::new(error_message).with_location(ErrorLocation::new(file!(), line!())),
424    )
425}
426
427/// Create an error chain by adding a new error context
428///
429/// # Arguments
430///
431/// * `error` - The error to chain
432/// * `message` - A message describing the context of the error
433///
434/// # Returns
435///
436/// * A CoreError with the original error as its cause
437pub fn chain_error<S>(error: CoreError, message: S) -> CoreError
438where
439    S: Into<String>,
440{
441    CoreError::ComputationError(
442        ErrorContext::new(message)
443            .with_location(ErrorLocation::new(file!(), line!()))
444            .with_cause(error),
445    )
446}