scirs2_core/error/
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    #[must_use]
24    #[inline]
25    pub const fn new(file: &'static str, line: u32) -> Self {
26        Self {
27            file,
28            line,
29            column: None,
30            function: None,
31        }
32    }
33
34    /// Create a new error location with function information
35    #[must_use]
36    #[inline]
37    pub const fn new_with_function(file: &'static str, line: u32, function: &'static str) -> Self {
38        Self {
39            file,
40            line,
41            column: None,
42            function: Some(function),
43        }
44    }
45
46    /// Create a new error location with column information
47    #[must_use]
48    #[inline]
49    pub const fn new_with_column(file: &'static str, line: u32, column: u32) -> Self {
50        Self {
51            file,
52            line,
53            column: Some(column),
54            function: None,
55        }
56    }
57
58    /// Create a new error location with function and column information
59    #[must_use]
60    #[inline]
61    pub const fn new_full(
62        file: &'static str,
63        line: u32,
64        column: u32,
65        function: &'static str,
66    ) -> Self {
67        Self {
68            file,
69            line,
70            column: Some(column),
71            function: Some(function),
72        }
73    }
74
75    /// Create an error location for the current position (convenience method)
76    #[must_use]
77    #[inline]
78    pub fn here() -> Self {
79        Self::new(file!(), line!())
80    }
81}
82
83impl fmt::Display for ErrorLocation {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        write!(f, "{}:{}", self.file, self.line)?;
86        if let Some(column) = self.column {
87            write!(f, ":{column}")?;
88        }
89        if let Some(function) = self.function {
90            write!(f, " in {function}")?;
91        }
92        Ok(())
93    }
94}
95
96/// Error context containing additional information about an error
97#[derive(Debug, Clone)]
98pub struct ErrorContext {
99    /// Error message
100    pub message: String,
101    /// Location where the error occurred
102    pub location: Option<ErrorLocation>,
103    /// Cause of the error
104    pub cause: Option<Box<CoreError>>,
105}
106
107impl ErrorContext {
108    /// Create a new error context
109    #[must_use]
110    pub fn new<S: Into<String>>(message: S) -> Self {
111        Self {
112            message: message.into(),
113            location: None,
114            cause: None,
115        }
116    }
117
118    /// Add location information to the error context
119    #[must_use]
120    pub fn with_location(mut self, location: ErrorLocation) -> Self {
121        self.location = Some(location);
122        self
123    }
124
125    /// Add a cause to the error context
126    #[must_use]
127    pub fn with_cause(mut self, cause: CoreError) -> Self {
128        self.cause = Some(Box::new(cause));
129        self
130    }
131}
132
133impl fmt::Display for ErrorContext {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        write!(f, "{}", self.message)?;
136        if let Some(location) = &self.location {
137            write!(f, " at {location}")?;
138        }
139        if let Some(cause) = &self.cause {
140            write!(f, "\nCaused by: {cause}")?;
141        }
142        Ok(())
143    }
144}
145
146/// Core error type for ``SciRS2``
147#[derive(Error, Debug, Clone)]
148pub enum CoreError {
149    /// Computation error (generic error)
150    #[error("{0}")]
151    ComputationError(ErrorContext),
152
153    /// Domain error (input outside valid domain)
154    #[error("{0}")]
155    DomainError(ErrorContext),
156
157    /// Dispatch error (array protocol dispatch failed)
158    #[error("{0}")]
159    DispatchError(ErrorContext),
160
161    /// Convergence error (algorithm did not converge)
162    #[error("{0}")]
163    ConvergenceError(ErrorContext),
164
165    /// Dimension mismatch error
166    #[error("{0}")]
167    DimensionError(ErrorContext),
168
169    /// Shape error (matrices/arrays have incompatible shapes)
170    #[error("{0}")]
171    ShapeError(ErrorContext),
172
173    /// Out of bounds error
174    #[error("{0}")]
175    IndexError(ErrorContext),
176
177    /// Value error (invalid value)
178    #[error("{0}")]
179    ValueError(ErrorContext),
180
181    /// Type error (invalid type)
182    #[error("{0}")]
183    TypeError(ErrorContext),
184
185    /// Not implemented error
186    #[error("{0}")]
187    NotImplementedError(ErrorContext),
188
189    /// Implementation error (method exists but not fully implemented yet)
190    #[error("{0}")]
191    ImplementationError(ErrorContext),
192
193    /// Memory error (could not allocate memory)
194    #[error("{0}")]
195    MemoryError(ErrorContext),
196
197    /// Allocation error (memory allocation failed)
198    #[error("{0}")]
199    AllocationError(ErrorContext),
200
201    /// Configuration error (invalid configuration)
202    #[error("{0}")]
203    ConfigError(ErrorContext),
204
205    /// Invalid argument error
206    #[error("{0}")]
207    InvalidArgument(ErrorContext),
208
209    /// Invalid input error
210    #[error("{0}")]
211    InvalidInput(ErrorContext),
212
213    /// Permission error (insufficient permissions)
214    #[error("{0}")]
215    PermissionError(ErrorContext),
216
217    /// Validation error (input failed validation)
218    #[error("{0}")]
219    ValidationError(ErrorContext),
220
221    /// Invalid state error (object is in an invalid state)
222    #[error("{0}")]
223    InvalidState(ErrorContext),
224
225    /// JIT compilation error (error during JIT compilation)
226    #[error("{0}")]
227    JITError(ErrorContext),
228
229    /// JSON error
230    #[error("JSON error: {0}")]
231    JSONError(ErrorContext),
232
233    /// IO error
234    #[error("IO error: {0}")]
235    IoError(ErrorContext),
236
237    /// Scheduler error (error in work-stealing scheduler)
238    #[error("Scheduler error: {0}")]
239    SchedulerError(ErrorContext),
240
241    /// Timeout error (operation timed out)
242    #[error("Timeout error: {0}")]
243    TimeoutError(ErrorContext),
244
245    /// Compression error (error during compression/decompression)
246    #[error("Compression error: {0}")]
247    CompressionError(ErrorContext),
248
249    /// Invalid shape error (array shape is invalid)
250    #[error("Invalid shape: {0}")]
251    InvalidShape(ErrorContext),
252
253    /// Device error (GPU/hardware device error)
254    #[error("Device error: {0}")]
255    DeviceError(ErrorContext),
256
257    /// Mutex error (mutex poisoning or lock error)
258    #[error("Mutex error: {0}")]
259    MutexError(ErrorContext),
260
261    /// Thread error (threading error)
262    #[error("Thread error: {0}")]
263    ThreadError(ErrorContext),
264
265    /// Stream error (streaming operation error)
266    #[error("Stream error: {0}")]
267    StreamError(ErrorContext),
268
269    /// End of stream error (stream ended unexpectedly)
270    #[error("End of stream: {0}")]
271    EndOfStream(ErrorContext),
272
273    /// Resource error (insufficient or unavailable resources)
274    #[error("Resource error: {0}")]
275    ResourceError(ErrorContext),
276
277    /// Communication error (network or inter-process communication error)
278    #[error("Communication error: {0}")]
279    CommunicationError(ErrorContext),
280
281    /// Security error (authentication, authorization, or security-related error)
282    #[error("Security error: {0}")]
283    SecurityError(ErrorContext),
284}
285
286/// Result type alias for core operations
287pub type CoreResult<T> = Result<T, CoreError>;
288
289/// Convert from std::io::Error to CoreError
290impl From<std::io::Error> for CoreError {
291    fn from(err: std::io::Error) -> Self {
292        CoreError::IoError(ErrorContext::new(format!("IO error: {err}")))
293    }
294}
295
296/// Convert from serde_json::Error to CoreError
297#[cfg(feature = "serialization")]
298impl From<serde_json::Error> for CoreError {
299    fn from(err: serde_json::Error) -> Self {
300        CoreError::JSONError(ErrorContext::new(format!("JSON error: {err}")))
301    }
302}
303
304/// Convert from String to CoreError (for parsing errors)
305impl From<String> for CoreError {
306    fn from(err: String) -> Self {
307        CoreError::ValueError(ErrorContext::new(err))
308    }
309}
310
311/// Convert from OperationError to CoreError
312impl From<crate::array_protocol::OperationError> for CoreError {
313    fn from(err: crate::array_protocol::OperationError) -> Self {
314        use crate::array_protocol::OperationError;
315        match err {
316            // Preserving NotImplemented for compatibility with older code,
317            // but it will eventually be replaced with NotImplementedError
318            OperationError::NotImplemented(msg) => {
319                CoreError::NotImplementedError(ErrorContext::new(msg))
320            }
321            OperationError::ShapeMismatch(msg) => CoreError::ShapeError(ErrorContext::new(msg)),
322            OperationError::TypeMismatch(msg) => CoreError::TypeError(ErrorContext::new(msg)),
323            OperationError::Other(msg) => CoreError::ComputationError(ErrorContext::new(msg)),
324        }
325    }
326}
327
328/// Macro to create a new error context with location information
329///
330/// # Example
331///
332/// ```rust
333/// use scirs2_core::error_context;
334/// use scirs2_core::error::{CoreResult, CoreError};
335///
336/// fn example() -> CoreResult<()> {
337///     let condition = false;
338///     if condition {
339///         return Err(CoreError::ComputationError(error_context!("An error occurred")));
340///     }
341///     Ok(())
342/// }
343/// ```
344#[macro_export]
345macro_rules! error_context {
346    ($message:expr) => {
347        $crate::error::ErrorContext::new($message)
348            .with_location($crate::error::ErrorLocation::new(file!(), line!()))
349    };
350    ($message:expr, $function:expr) => {
351        $crate::error::ErrorContext::new($message).with_location(
352            $crate::error::ErrorLocation::new_with_function(file!(), line!(), $function),
353        )
354    };
355}
356
357/// Macro to create a domain error with location information
358#[macro_export]
359macro_rules! domainerror {
360    ($message:expr) => {
361        $crate::error::CoreError::DomainError(error_context!($message))
362    };
363    ($message:expr, $function:expr) => {
364        $crate::error::CoreError::DomainError(error_context!($message, $function))
365    };
366}
367
368/// Macro to create a dimension error with location information
369#[macro_export]
370macro_rules! dimensionerror {
371    ($message:expr) => {
372        $crate::error::CoreError::DimensionError(error_context!($message))
373    };
374    ($message:expr, $function:expr) => {
375        $crate::error::CoreError::DimensionError(error_context!($message, $function))
376    };
377}
378
379/// Macro to create a value error with location information
380#[macro_export]
381macro_rules! valueerror {
382    ($message:expr) => {
383        $crate::error::CoreError::ValueError(error_context!($message))
384    };
385    ($message:expr, $function:expr) => {
386        $crate::error::CoreError::ValueError(error_context!($message, $function))
387    };
388}
389
390/// Macro to create a computation error with location information
391#[macro_export]
392macro_rules! computationerror {
393    ($message:expr) => {
394        $crate::error::CoreError::ComputationError(error_context!($message))
395    };
396    ($message:expr, $function:expr) => {
397        $crate::error::CoreError::ComputationError(error_context!($message, $function))
398    };
399}
400
401/// Checks if a condition is true, otherwise returns a domain error
402///
403/// # Arguments
404///
405/// * `condition` - The condition to check
406/// * `message` - The error message if the condition is false
407///
408/// # Returns
409///
410/// * `Ok(())` if the condition is true
411/// * `Err(CoreError::DomainError)` if the condition is false
412///
413/// # Errors
414///
415/// Returns `CoreError::DomainError` if the condition is false.
416#[allow(dead_code)]
417pub fn check_domain<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
418    if condition {
419        Ok(())
420    } else {
421        Err(CoreError::DomainError(
422            ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
423        ))
424    }
425}
426
427/// Checks dimensions
428///
429/// # Arguments
430///
431/// * `condition` - The condition to check
432/// * `message` - The error message if the condition is false
433///
434/// # Returns
435///
436/// * `Ok(())` if the condition is true
437/// * `Err(CoreError::DimensionError)` if the condition is false
438///
439/// # Errors
440///
441/// Returns `CoreError::DimensionError` if the condition is false.
442#[allow(dead_code)]
443pub fn check_dimensions<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
444    if condition {
445        Ok(())
446    } else {
447        Err(CoreError::DimensionError(
448            ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
449        ))
450    }
451}
452
453/// Checks if a value is valid
454///
455/// # Arguments
456///
457/// * `condition` - The condition to check
458/// * `message` - The error message if the condition is false
459///
460/// # Returns
461///
462/// * `Ok(())` if the condition is true
463/// * `Err(CoreError::ValueError)` if the condition is false
464///
465/// # Errors
466///
467/// Returns `CoreError::ValueError` if the condition is false.
468#[allow(dead_code)]
469pub fn check_value<S: Into<String>>(condition: bool, message: S) -> CoreResult<()> {
470    if condition {
471        Ok(())
472    } else {
473        Err(CoreError::ValueError(
474            ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
475        ))
476    }
477}
478
479/// Checks if a value is valid according to a validator function
480///
481/// # Arguments
482///
483/// * `value` - The value to validate
484/// * `validator` - A function that returns true if the value is valid
485/// * `message` - The error message if the value is invalid
486///
487/// # Returns
488///
489/// * `Ok(value)` if the value is valid
490/// * `Err(CoreError::ValidationError)` if the value is invalid
491///
492/// # Errors
493///
494/// Returns `CoreError::ValidationError` if the validator function returns false.
495#[allow(dead_code)]
496pub fn validate<T, F, S>(value: T, validator: F, message: S) -> CoreResult<T>
497where
498    F: FnOnce(&T) -> bool,
499    S: Into<String>,
500{
501    if validator(&value) {
502        Ok(value)
503    } else {
504        Err(CoreError::ValidationError(
505            ErrorContext::new(message).with_location(ErrorLocation::new(file!(), line!())),
506        ))
507    }
508}
509
510/// Convert an error from one type to a CoreError
511///
512/// # Arguments
513///
514/// * `error` - The error to convert
515/// * `message` - A message describing the context of the error
516///
517/// # Returns
518///
519/// * A CoreError with the original error as its cause
520#[must_use]
521#[allow(dead_code)]
522pub fn converterror<E, S>(error: E, message: S) -> CoreError
523where
524    E: std::error::Error + 'static,
525    S: Into<String>,
526{
527    // Create a computation error that contains the original error
528    // We combine the provided message with the error's own message for extra context
529    let message_str = message.into();
530    let error_message = format!("{message_str} | Original error: {error}");
531
532    // For I/O errors we have direct conversion via From trait implementation
533    // but we can't use it directly due to the generic bounds.
534    // In a real implementation, you would use a match or if statement with
535    // type_id or another approach to distinguish error types.
536
537    // For simplicity, we'll just use ComputationError as a general case
538    CoreError::ComputationError(
539        ErrorContext::new(error_message).with_location(ErrorLocation::new(file!(), line!())),
540    )
541}
542
543/// Create an error chain by adding a new error context
544///
545/// # Arguments
546///
547/// * `error` - The error to chain
548/// * `message` - A message describing the context of the error
549///
550/// # Returns
551///
552/// * A CoreError with the original error as its cause
553#[must_use]
554#[allow(dead_code)]
555pub fn chainerror<S>(error: CoreError, message: S) -> CoreError
556where
557    S: Into<String>,
558{
559    CoreError::ComputationError(
560        ErrorContext::new(message)
561            .with_location(ErrorLocation::new(file!(), line!()))
562            .with_cause(error),
563    )
564}