Skip to main content

scirs2_core/error/
error_kind.rs

1//! ErrorKind enum for programmatic error matching
2//!
3//! Provides a lightweight, copyable enum that mirrors `CoreError` variants
4//! without carrying payload, enabling efficient `match` / `if`-based dispatch.
5//!
6//! # Usage
7//!
8//! ```rust
9//! use scirs2_core::error::{CoreError, ErrorContext};
10//! use scirs2_core::error::error_kind::ErrorKind;
11//!
12//! let err = CoreError::DimensionError(ErrorContext::new("shape mismatch"));
13//! assert_eq!(err.kind(), ErrorKind::Dimension);
14//! assert!(err.kind().is_shape_related());
15//! ```
16
17use super::error::CoreError;
18
19/// Lightweight classification of `CoreError` variants.
20///
21/// Every `CoreError` variant maps to exactly one `ErrorKind`.
22/// This enum is `Copy` and suitable for use in `match` arms, hash maps, etc.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum ErrorKind {
25    /// Generic computation error
26    Computation,
27    /// Input outside valid mathematical domain
28    Domain,
29    /// Array protocol dispatch failure
30    Dispatch,
31    /// Algorithm did not converge
32    Convergence,
33    /// Array dimension mismatch
34    Dimension,
35    /// Array shape incompatibility
36    Shape,
37    /// Index out of bounds
38    Index,
39    /// Invalid value
40    Value,
41    /// Type mismatch
42    Type,
43    /// Feature not implemented
44    NotImplemented,
45    /// Partial implementation
46    Implementation,
47    /// Memory allocation failure
48    Memory,
49    /// Allocation-specific error
50    Allocation,
51    /// Configuration error
52    Config,
53    /// Invalid argument
54    InvalidArgument,
55    /// Invalid input
56    InvalidInput,
57    /// Permission error
58    Permission,
59    /// Validation failure
60    Validation,
61    /// Invalid object state
62    InvalidState,
63    /// JIT compilation error
64    Jit,
65    /// JSON error
66    Json,
67    /// I/O error
68    Io,
69    /// Scheduler error
70    Scheduler,
71    /// Timeout error
72    Timeout,
73    /// Compression error
74    Compression,
75    /// Invalid array shape
76    InvalidShape,
77    /// GPU/device error
78    Device,
79    /// Mutex/lock error
80    Mutex,
81    /// Thread error
82    Thread,
83    /// Stream error
84    Stream,
85    /// End-of-stream
86    EndOfStream,
87    /// Resource error
88    Resource,
89    /// Communication error
90    Communication,
91    /// Security error
92    Security,
93}
94
95impl ErrorKind {
96    /// Human-readable name of this error kind.
97    pub const fn as_str(&self) -> &'static str {
98        match self {
99            ErrorKind::Computation => "computation",
100            ErrorKind::Domain => "domain",
101            ErrorKind::Dispatch => "dispatch",
102            ErrorKind::Convergence => "convergence",
103            ErrorKind::Dimension => "dimension",
104            ErrorKind::Shape => "shape",
105            ErrorKind::Index => "index",
106            ErrorKind::Value => "value",
107            ErrorKind::Type => "type",
108            ErrorKind::NotImplemented => "not_implemented",
109            ErrorKind::Implementation => "implementation",
110            ErrorKind::Memory => "memory",
111            ErrorKind::Allocation => "allocation",
112            ErrorKind::Config => "config",
113            ErrorKind::InvalidArgument => "invalid_argument",
114            ErrorKind::InvalidInput => "invalid_input",
115            ErrorKind::Permission => "permission",
116            ErrorKind::Validation => "validation",
117            ErrorKind::InvalidState => "invalid_state",
118            ErrorKind::Jit => "jit",
119            ErrorKind::Json => "json",
120            ErrorKind::Io => "io",
121            ErrorKind::Scheduler => "scheduler",
122            ErrorKind::Timeout => "timeout",
123            ErrorKind::Compression => "compression",
124            ErrorKind::InvalidShape => "invalid_shape",
125            ErrorKind::Device => "device",
126            ErrorKind::Mutex => "mutex",
127            ErrorKind::Thread => "thread",
128            ErrorKind::Stream => "stream",
129            ErrorKind::EndOfStream => "end_of_stream",
130            ErrorKind::Resource => "resource",
131            ErrorKind::Communication => "communication",
132            ErrorKind::Security => "security",
133        }
134    }
135
136    /// Whether this error kind is related to array shapes or dimensions.
137    pub const fn is_shape_related(&self) -> bool {
138        matches!(
139            self,
140            ErrorKind::Dimension | ErrorKind::Shape | ErrorKind::InvalidShape
141        )
142    }
143
144    /// Whether this error kind is related to resource exhaustion.
145    pub const fn is_resource_related(&self) -> bool {
146        matches!(
147            self,
148            ErrorKind::Memory | ErrorKind::Allocation | ErrorKind::Resource | ErrorKind::Timeout
149        )
150    }
151
152    /// Whether this error kind is related to I/O or communication.
153    pub const fn is_io_related(&self) -> bool {
154        matches!(
155            self,
156            ErrorKind::Io | ErrorKind::Stream | ErrorKind::EndOfStream | ErrorKind::Communication
157        )
158    }
159
160    /// Whether this error kind is related to concurrency.
161    pub const fn is_concurrency_related(&self) -> bool {
162        matches!(
163            self,
164            ErrorKind::Mutex | ErrorKind::Thread | ErrorKind::Scheduler
165        )
166    }
167
168    /// Whether this error kind indicates a programming error (e.g., invalid argument).
169    pub const fn is_programming_error(&self) -> bool {
170        matches!(
171            self,
172            ErrorKind::InvalidArgument
173                | ErrorKind::InvalidInput
174                | ErrorKind::InvalidState
175                | ErrorKind::NotImplemented
176                | ErrorKind::Implementation
177        )
178    }
179
180    /// Whether this error is typically retryable.
181    pub const fn is_retryable(&self) -> bool {
182        matches!(
183            self,
184            ErrorKind::Timeout
185                | ErrorKind::Resource
186                | ErrorKind::Communication
187                | ErrorKind::Mutex
188                | ErrorKind::Device
189        )
190    }
191}
192
193impl std::fmt::Display for ErrorKind {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        write!(f, "{}", self.as_str())
196    }
197}
198
199// ---------------------------------------------------------------------------
200// CoreError::kind() method
201// ---------------------------------------------------------------------------
202
203impl CoreError {
204    /// Classify this error into an `ErrorKind` for programmatic matching.
205    pub fn kind(&self) -> ErrorKind {
206        match self {
207            CoreError::ComputationError(_) => ErrorKind::Computation,
208            CoreError::DomainError(_) => ErrorKind::Domain,
209            CoreError::DispatchError(_) => ErrorKind::Dispatch,
210            CoreError::ConvergenceError(_) => ErrorKind::Convergence,
211            CoreError::DimensionError(_) => ErrorKind::Dimension,
212            CoreError::ShapeError(_) => ErrorKind::Shape,
213            CoreError::IndexError(_) => ErrorKind::Index,
214            CoreError::ValueError(_) => ErrorKind::Value,
215            CoreError::TypeError(_) => ErrorKind::Type,
216            CoreError::NotImplementedError(_) => ErrorKind::NotImplemented,
217            CoreError::ImplementationError(_) => ErrorKind::Implementation,
218            CoreError::MemoryError(_) => ErrorKind::Memory,
219            CoreError::AllocationError(_) => ErrorKind::Allocation,
220            CoreError::ConfigError(_) => ErrorKind::Config,
221            CoreError::InvalidArgument(_) => ErrorKind::InvalidArgument,
222            CoreError::InvalidInput(_) => ErrorKind::InvalidInput,
223            CoreError::PermissionError(_) => ErrorKind::Permission,
224            CoreError::ValidationError(_) => ErrorKind::Validation,
225            CoreError::InvalidState(_) => ErrorKind::InvalidState,
226            CoreError::JITError(_) => ErrorKind::Jit,
227            CoreError::JSONError(_) => ErrorKind::Json,
228            CoreError::IoError(_) => ErrorKind::Io,
229            CoreError::SchedulerError(_) => ErrorKind::Scheduler,
230            CoreError::TimeoutError(_) => ErrorKind::Timeout,
231            CoreError::CompressionError(_) => ErrorKind::Compression,
232            CoreError::InvalidShape(_) => ErrorKind::InvalidShape,
233            CoreError::DeviceError(_) => ErrorKind::Device,
234            CoreError::MutexError(_) => ErrorKind::Mutex,
235            CoreError::ThreadError(_) => ErrorKind::Thread,
236            CoreError::StreamError(_) => ErrorKind::Stream,
237            CoreError::EndOfStream(_) => ErrorKind::EndOfStream,
238            CoreError::ResourceError(_) => ErrorKind::Resource,
239            CoreError::CommunicationError(_) => ErrorKind::Communication,
240            CoreError::SecurityError(_) => ErrorKind::Security,
241        }
242    }
243
244    /// Extract the error message string from the inner `ErrorContext`.
245    pub fn error_message(&self) -> &str {
246        match self {
247            CoreError::ComputationError(ctx)
248            | CoreError::DomainError(ctx)
249            | CoreError::DispatchError(ctx)
250            | CoreError::ConvergenceError(ctx)
251            | CoreError::DimensionError(ctx)
252            | CoreError::ShapeError(ctx)
253            | CoreError::IndexError(ctx)
254            | CoreError::ValueError(ctx)
255            | CoreError::TypeError(ctx)
256            | CoreError::NotImplementedError(ctx)
257            | CoreError::ImplementationError(ctx)
258            | CoreError::MemoryError(ctx)
259            | CoreError::AllocationError(ctx)
260            | CoreError::ConfigError(ctx)
261            | CoreError::InvalidArgument(ctx)
262            | CoreError::InvalidInput(ctx)
263            | CoreError::PermissionError(ctx)
264            | CoreError::ValidationError(ctx)
265            | CoreError::InvalidState(ctx)
266            | CoreError::JITError(ctx)
267            | CoreError::JSONError(ctx)
268            | CoreError::IoError(ctx)
269            | CoreError::SchedulerError(ctx)
270            | CoreError::TimeoutError(ctx)
271            | CoreError::CompressionError(ctx)
272            | CoreError::InvalidShape(ctx)
273            | CoreError::DeviceError(ctx)
274            | CoreError::MutexError(ctx)
275            | CoreError::ThreadError(ctx)
276            | CoreError::StreamError(ctx)
277            | CoreError::EndOfStream(ctx)
278            | CoreError::ResourceError(ctx)
279            | CoreError::CommunicationError(ctx)
280            | CoreError::SecurityError(ctx) => &ctx.message,
281        }
282    }
283
284    /// Get the inner `ErrorContext`.
285    pub fn context(&self) -> &super::error::ErrorContext {
286        match self {
287            CoreError::ComputationError(ctx)
288            | CoreError::DomainError(ctx)
289            | CoreError::DispatchError(ctx)
290            | CoreError::ConvergenceError(ctx)
291            | CoreError::DimensionError(ctx)
292            | CoreError::ShapeError(ctx)
293            | CoreError::IndexError(ctx)
294            | CoreError::ValueError(ctx)
295            | CoreError::TypeError(ctx)
296            | CoreError::NotImplementedError(ctx)
297            | CoreError::ImplementationError(ctx)
298            | CoreError::MemoryError(ctx)
299            | CoreError::AllocationError(ctx)
300            | CoreError::ConfigError(ctx)
301            | CoreError::InvalidArgument(ctx)
302            | CoreError::InvalidInput(ctx)
303            | CoreError::PermissionError(ctx)
304            | CoreError::ValidationError(ctx)
305            | CoreError::InvalidState(ctx)
306            | CoreError::JITError(ctx)
307            | CoreError::JSONError(ctx)
308            | CoreError::IoError(ctx)
309            | CoreError::SchedulerError(ctx)
310            | CoreError::TimeoutError(ctx)
311            | CoreError::CompressionError(ctx)
312            | CoreError::InvalidShape(ctx)
313            | CoreError::DeviceError(ctx)
314            | CoreError::MutexError(ctx)
315            | CoreError::ThreadError(ctx)
316            | CoreError::StreamError(ctx)
317            | CoreError::EndOfStream(ctx)
318            | CoreError::ResourceError(ctx)
319            | CoreError::CommunicationError(ctx)
320            | CoreError::SecurityError(ctx) => ctx,
321        }
322    }
323
324    /// Return the chained cause, if any.
325    pub fn cause(&self) -> Option<&CoreError> {
326        self.context().cause.as_deref()
327    }
328}
329
330// ---------------------------------------------------------------------------
331// Additional From implementations for cross-crate error conversion
332// ---------------------------------------------------------------------------
333
334impl From<std::num::ParseIntError> for CoreError {
335    fn from(err: std::num::ParseIntError) -> Self {
336        CoreError::ValueError(super::error::ErrorContext::new(format!(
337            "Integer parse error: {err}"
338        )))
339    }
340}
341
342impl From<std::num::ParseFloatError> for CoreError {
343    fn from(err: std::num::ParseFloatError) -> Self {
344        CoreError::ValueError(super::error::ErrorContext::new(format!(
345            "Float parse error: {err}"
346        )))
347    }
348}
349
350impl From<std::fmt::Error> for CoreError {
351    fn from(err: std::fmt::Error) -> Self {
352        CoreError::ComputationError(super::error::ErrorContext::new(format!(
353            "Formatting error: {err}"
354        )))
355    }
356}
357
358impl From<std::str::Utf8Error> for CoreError {
359    fn from(err: std::str::Utf8Error) -> Self {
360        CoreError::ValueError(super::error::ErrorContext::new(format!(
361            "UTF-8 decode error: {err}"
362        )))
363    }
364}
365
366impl From<std::string::FromUtf8Error> for CoreError {
367    fn from(err: std::string::FromUtf8Error) -> Self {
368        CoreError::ValueError(super::error::ErrorContext::new(format!(
369            "UTF-8 decode error: {err}"
370        )))
371    }
372}
373
374impl<T> From<std::sync::PoisonError<T>> for CoreError {
375    fn from(err: std::sync::PoisonError<T>) -> Self {
376        CoreError::MutexError(super::error::ErrorContext::new(format!(
377            "Lock poisoned: {err}"
378        )))
379    }
380}
381
382impl From<std::time::SystemTimeError> for CoreError {
383    fn from(err: std::time::SystemTimeError) -> Self {
384        CoreError::ComputationError(super::error::ErrorContext::new(format!(
385            "System time error: {err}"
386        )))
387    }
388}
389
390// ---------------------------------------------------------------------------
391// ErrorContext backtrace support
392// ---------------------------------------------------------------------------
393
394impl super::error::ErrorContext {
395    /// Capture a backtrace string and attach it to this context.
396    ///
397    /// Uses `std::backtrace::Backtrace` when available, otherwise
398    /// produces a placeholder.
399    pub fn with_backtrace(mut self) -> Self {
400        // std::backtrace is stable since Rust 1.65
401        let bt = std::backtrace::Backtrace::capture();
402        let bt_string = format!("{bt}");
403        if !bt_string.is_empty() && !bt_string.contains("disabled") {
404            self.message = format!("{}\nBacktrace:\n{bt_string}", self.message);
405        }
406        self
407    }
408}
409
410// ---------------------------------------------------------------------------
411// Tests
412// ---------------------------------------------------------------------------
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417    use crate::error::ErrorContext;
418
419    #[test]
420    fn test_kind_mapping_computation() {
421        let err = CoreError::ComputationError(ErrorContext::new("test"));
422        assert_eq!(err.kind(), ErrorKind::Computation);
423    }
424
425    #[test]
426    fn test_kind_mapping_dimension() {
427        let err = CoreError::DimensionError(ErrorContext::new("bad shape"));
428        assert_eq!(err.kind(), ErrorKind::Dimension);
429        assert!(err.kind().is_shape_related());
430    }
431
432    #[test]
433    fn test_kind_mapping_memory() {
434        let err = CoreError::MemoryError(ErrorContext::new("oom"));
435        assert_eq!(err.kind(), ErrorKind::Memory);
436        assert!(err.kind().is_resource_related());
437    }
438
439    #[test]
440    fn test_kind_mapping_io() {
441        let err = CoreError::IoError(ErrorContext::new("file not found"));
442        assert_eq!(err.kind(), ErrorKind::Io);
443        assert!(err.kind().is_io_related());
444    }
445
446    #[test]
447    fn test_kind_mapping_mutex() {
448        let err = CoreError::MutexError(ErrorContext::new("poisoned"));
449        assert_eq!(err.kind(), ErrorKind::Mutex);
450        assert!(err.kind().is_concurrency_related());
451        assert!(err.kind().is_retryable());
452    }
453
454    #[test]
455    fn test_kind_as_str() {
456        assert_eq!(ErrorKind::Computation.as_str(), "computation");
457        assert_eq!(ErrorKind::Io.as_str(), "io");
458        assert_eq!(ErrorKind::Security.as_str(), "security");
459    }
460
461    #[test]
462    fn test_kind_display() {
463        let kind = ErrorKind::Domain;
464        assert_eq!(format!("{kind}"), "domain");
465    }
466
467    #[test]
468    fn test_error_message_extraction() {
469        let err = CoreError::ValueError(ErrorContext::new("bad value 42"));
470        assert_eq!(err.error_message(), "bad value 42");
471    }
472
473    #[test]
474    fn test_error_cause_chain() {
475        let inner = CoreError::IoError(ErrorContext::new("disk full"));
476        let outer =
477            CoreError::ComputationError(ErrorContext::new("write failed").with_cause(inner));
478        let cause = outer.cause();
479        assert!(cause.is_some());
480        assert_eq!(cause.map(|e| e.kind()), Some(ErrorKind::Io));
481    }
482
483    #[test]
484    fn test_from_parse_int_error() {
485        let result: Result<i32, _> = "not_a_number".parse();
486        let err: CoreError = result.expect_err("should fail").into();
487        assert_eq!(err.kind(), ErrorKind::Value);
488    }
489
490    #[test]
491    fn test_from_parse_float_error() {
492        let result: Result<f64, _> = "not_a_float".parse();
493        let err: CoreError = result.expect_err("should fail").into();
494        assert_eq!(err.kind(), ErrorKind::Value);
495    }
496
497    #[test]
498    fn test_from_utf8_error() {
499        let bytes = vec![0xFF, 0xFE];
500        let result = String::from_utf8(bytes);
501        let err: CoreError = result.expect_err("should fail").into();
502        assert_eq!(err.kind(), ErrorKind::Value);
503    }
504
505    #[test]
506    fn test_is_programming_error() {
507        assert!(ErrorKind::InvalidArgument.is_programming_error());
508        assert!(ErrorKind::NotImplemented.is_programming_error());
509        assert!(!ErrorKind::Io.is_programming_error());
510    }
511
512    #[test]
513    fn test_all_error_kinds_have_unique_as_str() {
514        use std::collections::HashSet;
515        let kinds = [
516            ErrorKind::Computation,
517            ErrorKind::Domain,
518            ErrorKind::Dispatch,
519            ErrorKind::Convergence,
520            ErrorKind::Dimension,
521            ErrorKind::Shape,
522            ErrorKind::Index,
523            ErrorKind::Value,
524            ErrorKind::Type,
525            ErrorKind::NotImplemented,
526            ErrorKind::Implementation,
527            ErrorKind::Memory,
528            ErrorKind::Allocation,
529            ErrorKind::Config,
530            ErrorKind::InvalidArgument,
531            ErrorKind::InvalidInput,
532            ErrorKind::Permission,
533            ErrorKind::Validation,
534            ErrorKind::InvalidState,
535            ErrorKind::Jit,
536            ErrorKind::Json,
537            ErrorKind::Io,
538            ErrorKind::Scheduler,
539            ErrorKind::Timeout,
540            ErrorKind::Compression,
541            ErrorKind::InvalidShape,
542            ErrorKind::Device,
543            ErrorKind::Mutex,
544            ErrorKind::Thread,
545            ErrorKind::Stream,
546            ErrorKind::EndOfStream,
547            ErrorKind::Resource,
548            ErrorKind::Communication,
549            ErrorKind::Security,
550        ];
551        let names: HashSet<&str> = kinds.iter().map(|k| k.as_str()).collect();
552        assert_eq!(
553            names.len(),
554            kinds.len(),
555            "Each ErrorKind must have a unique as_str()"
556        );
557    }
558
559    #[test]
560    fn test_context_accessor() {
561        let err = CoreError::TimeoutError(
562            ErrorContext::new("timed out")
563                .with_location(super::super::error::ErrorLocation::new("test.rs", 42)),
564        );
565        let ctx = err.context();
566        assert_eq!(ctx.message, "timed out");
567        assert!(ctx.location.is_some());
568    }
569
570    #[test]
571    fn test_with_backtrace_does_not_panic() {
572        let ctx = ErrorContext::new("test").with_backtrace();
573        // Should not panic; message should contain at least original text
574        assert!(ctx.message.contains("test"));
575    }
576}