Skip to main content

torsh_ffi/
error.rs

1//! Enhanced error types for FFI operations
2//!
3//! This module provides a comprehensive error handling system for FFI operations
4//! with structured error codes, context, severity levels, and recovery suggestions.
5//!
6//! # Features
7//!
8//! - **Structured Error Codes**: Machine-readable error codes for automated handling
9//! - **Error Context**: File, line, column, and operation context
10//! - **Severity Levels**: Critical, Error, Warning, Info
11//! - **Recovery Suggestions**: Actionable suggestions for error recovery
12//! - **Error Categories**: Organized by domain (Tensor, Memory, Type, etc.)
13//! - **Serialization**: JSON serialization for logging and debugging
14//!
15//! # Example
16//!
17//! ```rust,ignore
18//! use torsh_ffi::error::{FfiError, ErrorBuilder, ErrorCode, Severity};
19//!
20//! // Create a detailed error with context
21//! let error = ErrorBuilder::new(ErrorCode::ShapeMismatch)
22//!     .message("Incompatible tensor shapes")
23//!     .context("operation", "matmul")
24//!     .context("expected_shape", "[2, 3]")
25//!     .context("actual_shape", "[3, 2]")
26//!     .source_location(file!(), line!(), column!())
27//!     .severity(Severity::Error)
28//!     .suggestion("Transpose one of the tensors or use broadcasting")
29//!     .build();
30//! ```
31
32// Framework infrastructure - components designed for future use
33#![allow(dead_code)]
34use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36use std::fmt;
37use thiserror::Error;
38
39/// Structured error codes for machine-readable error handling
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
41#[repr(u32)]
42pub enum ErrorCode {
43    // Tensor errors (1000-1999)
44    TensorCreationFailed = 1000,
45    TensorOperationFailed = 1001,
46    ShapeMismatch = 1002,
47    DTypeMismatch = 1003,
48    DeviceMismatch = 1004,
49    TensorNotFound = 1005,
50
51    // Memory errors (2000-2999)
52    AllocationFailed = 2000,
53    MemoryPoolExhausted = 2001,
54    OutOfMemory = 2002,
55    MemoryLeakDetected = 2003,
56    InvalidPointer = 2004,
57    DanglingPointer = 2005,
58
59    // Type conversion errors (3000-3999)
60    InvalidConversion = 3000,
61    TypeNotSupported = 3001,
62    PrecisionLoss = 3002,
63    OverflowDetected = 3003,
64    UnderflowDetected = 3004,
65
66    // Parameter validation errors (4000-4999)
67    InvalidParameter = 4000,
68    ParameterOutOfRange = 4001,
69    NullPointer = 4002,
70    InvalidShape = 4003,
71    InvalidDType = 4004,
72    InvalidDevice = 4005,
73
74    // Operation errors (5000-5999)
75    OperationNotSupported = 5000,
76    OperationFailed = 5001,
77    BroadcastingFailed = 5002,
78    MatrixNotInvertible = 5003,
79    DivisionByZero = 5004,
80
81    // Language binding errors (6000-6999)
82    PythonError = 6000,
83    JavaError = 6001,
84    CSharpError = 6002,
85    GoError = 6003,
86    SwiftError = 6004,
87    WasmError = 6005,
88
89    // I/O errors (7000-7999)
90    FileNotFound = 7000,
91    FileReadError = 7001,
92    FileWriteError = 7002,
93    SerializationFailed = 7003,
94    DeserializationFailed = 7004,
95
96    // Module errors (8000-8999)
97    ModuleNotFound = 8000,
98    ModuleInitFailed = 8001,
99    ModuleLoadError = 8002,
100
101    // Cross-language errors (9000-9999)
102    OwnershipConflict = 9000,
103    DataRace = 9001,
104    DeadlockDetected = 9002,
105
106    // Unknown/Other errors (10000+)
107    Unknown = 10000,
108    Internal = 10001,
109}
110
111impl ErrorCode {
112    /// Get error category
113    pub fn category(&self) -> ErrorCategory {
114        match *self as u32 {
115            1000..=1999 => ErrorCategory::Tensor,
116            2000..=2999 => ErrorCategory::Memory,
117            3000..=3999 => ErrorCategory::TypeConversion,
118            4000..=4999 => ErrorCategory::Validation,
119            5000..=5999 => ErrorCategory::Operation,
120            6000..=6999 => ErrorCategory::LanguageBinding,
121            7000..=7999 => ErrorCategory::IO,
122            8000..=8999 => ErrorCategory::Module,
123            9000..=9999 => ErrorCategory::CrossLanguage,
124            _ => ErrorCategory::Unknown,
125        }
126    }
127
128    /// Get default severity for this error code
129    pub fn default_severity(&self) -> Severity {
130        match self {
131            Self::AllocationFailed
132            | Self::OutOfMemory
133            | Self::DeadlockDetected
134            | Self::DataRace => Severity::Critical,
135
136            Self::ShapeMismatch
137            | Self::DTypeMismatch
138            | Self::InvalidParameter
139            | Self::OperationFailed => Severity::Error,
140
141            Self::PrecisionLoss | Self::MemoryLeakDetected => Severity::Warning,
142
143            _ => Severity::Error,
144        }
145    }
146}
147
148impl fmt::Display for ErrorCode {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        write!(f, "{:?} ({})", self, *self as u32)
151    }
152}
153
154/// Error category for grouping related errors
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
156pub enum ErrorCategory {
157    Tensor,
158    Memory,
159    TypeConversion,
160    Validation,
161    Operation,
162    LanguageBinding,
163    IO,
164    Module,
165    CrossLanguage,
166    Unknown,
167}
168
169impl fmt::Display for ErrorCategory {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        match self {
172            Self::Tensor => write!(f, "Tensor"),
173            Self::Memory => write!(f, "Memory"),
174            Self::TypeConversion => write!(f, "Type Conversion"),
175            Self::Validation => write!(f, "Validation"),
176            Self::Operation => write!(f, "Operation"),
177            Self::LanguageBinding => write!(f, "Language Binding"),
178            Self::IO => write!(f, "I/O"),
179            Self::Module => write!(f, "Module"),
180            Self::CrossLanguage => write!(f, "Cross-Language"),
181            Self::Unknown => write!(f, "Unknown"),
182        }
183    }
184}
185
186/// Error severity level (ordered from lowest to highest severity)
187#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
188pub enum Severity {
189    /// Info - informational message
190    Info,
191    /// Warning - potential issue detected
192    Warning,
193    /// Error - operation failed but system can continue
194    Error,
195    /// Critical error - system cannot continue
196    Critical,
197}
198
199impl fmt::Display for Severity {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        match self {
202            Self::Critical => write!(f, "CRITICAL"),
203            Self::Error => write!(f, "ERROR"),
204            Self::Warning => write!(f, "WARNING"),
205            Self::Info => write!(f, "INFO"),
206        }
207    }
208}
209
210/// Source code location for error tracking
211#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
212pub struct SourceLocation {
213    pub file: String,
214    pub line: u32,
215    pub column: u32,
216}
217
218impl fmt::Display for SourceLocation {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        write!(f, "{}:{}:{}", self.file, self.line, self.column)
221    }
222}
223
224/// Enhanced error with structured information
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct EnhancedError {
227    /// Error code for machine-readable identification
228    pub code: ErrorCode,
229
230    /// Human-readable error message
231    pub message: String,
232
233    /// Error severity level
234    pub severity: Severity,
235
236    /// Error category
237    pub category: ErrorCategory,
238
239    /// Source code location where error occurred
240    pub location: Option<SourceLocation>,
241
242    /// Additional context as key-value pairs
243    pub context: HashMap<String, String>,
244
245    /// Recovery suggestions
246    pub suggestions: Vec<String>,
247
248    /// Timestamp when error occurred
249    pub timestamp: chrono::DateTime<chrono::Utc>,
250
251    /// Chain of underlying errors
252    pub causes: Vec<String>,
253}
254
255impl EnhancedError {
256    /// Create a new enhanced error
257    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
258        Self {
259            code,
260            message: message.into(),
261            severity: code.default_severity(),
262            category: code.category(),
263            location: None,
264            context: HashMap::new(),
265            suggestions: Vec::new(),
266            timestamp: chrono::Utc::now(),
267            causes: Vec::new(),
268        }
269    }
270
271    /// Convert to legacy FfiError for backward compatibility
272    pub fn to_ffi_error(&self) -> FfiError {
273        FfiError::Enhanced(self.clone())
274    }
275
276    /// Serialize to JSON string
277    pub fn to_json(&self) -> Result<String, serde_json::Error> {
278        serde_json::to_string_pretty(self)
279    }
280
281    /// Check if error is recoverable (not critical)
282    pub fn is_recoverable(&self) -> bool {
283        self.severity < Severity::Critical
284    }
285}
286
287impl fmt::Display for EnhancedError {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        writeln!(f, "[{}] {} - {}", self.severity, self.code, self.message)?;
290
291        if let Some(ref loc) = self.location {
292            writeln!(f, "  at {}", loc)?;
293        }
294
295        if !self.context.is_empty() {
296            writeln!(f, "  Context:")?;
297            for (key, value) in &self.context {
298                writeln!(f, "    {}: {}", key, value)?;
299            }
300        }
301
302        if !self.suggestions.is_empty() {
303            writeln!(f, "  Suggestions:")?;
304            for suggestion in &self.suggestions {
305                writeln!(f, "    - {}", suggestion)?;
306            }
307        }
308
309        if !self.causes.is_empty() {
310            writeln!(f, "  Caused by:")?;
311            for cause in &self.causes {
312                writeln!(f, "    {}", cause)?;
313            }
314        }
315
316        Ok(())
317    }
318}
319
320impl std::error::Error for EnhancedError {}
321
322/// Builder for creating enhanced errors with fluent API
323pub struct ErrorBuilder {
324    error: EnhancedError,
325}
326
327impl ErrorBuilder {
328    /// Create a new error builder with error code
329    pub fn new(code: ErrorCode) -> Self {
330        Self {
331            error: EnhancedError::new(code, ""),
332        }
333    }
334
335    /// Set error message
336    pub fn message(mut self, message: impl Into<String>) -> Self {
337        self.error.message = message.into();
338        self
339    }
340
341    /// Set severity level
342    pub fn severity(mut self, severity: Severity) -> Self {
343        self.error.severity = severity;
344        self
345    }
346
347    /// Add source code location
348    pub fn source_location(mut self, file: &str, line: u32, column: u32) -> Self {
349        self.error.location = Some(SourceLocation {
350            file: file.to_string(),
351            line,
352            column,
353        });
354        self
355    }
356
357    /// Add context key-value pair
358    pub fn context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
359        self.error.context.insert(key.into(), value.into());
360        self
361    }
362
363    /// Add multiple context entries
364    pub fn contexts(mut self, contexts: HashMap<String, String>) -> Self {
365        self.error.context.extend(contexts);
366        self
367    }
368
369    /// Add recovery suggestion
370    pub fn suggestion(mut self, suggestion: impl Into<String>) -> Self {
371        self.error.suggestions.push(suggestion.into());
372        self
373    }
374
375    /// Add multiple suggestions
376    pub fn suggestions(mut self, suggestions: Vec<String>) -> Self {
377        self.error.suggestions.extend(suggestions);
378        self
379    }
380
381    /// Add underlying cause
382    pub fn cause(mut self, cause: impl std::fmt::Display) -> Self {
383        self.error.causes.push(cause.to_string());
384        self
385    }
386
387    /// Build the enhanced error
388    pub fn build(self) -> EnhancedError {
389        self.error
390    }
391
392    /// Build and convert to FfiError
393    pub fn build_ffi(self) -> FfiError {
394        FfiError::Enhanced(self.error)
395    }
396}
397
398/// FFI-specific error types (backward compatible)
399#[derive(Error, Debug, Clone)]
400pub enum FfiError {
401    /// Enhanced error with full context and structure
402    #[error("{0}")]
403    Enhanced(EnhancedError),
404
405    #[error("Tensor error: {message}")]
406    Tensor { message: String },
407
408    #[error("Shape mismatch: expected {expected:?}, got {actual:?}")]
409    ShapeMismatch {
410        expected: Vec<usize>,
411        actual: Vec<usize>,
412    },
413
414    #[error("Data type mismatch: expected {expected}, got {actual}")]
415    DTypeMismatch { expected: String, actual: String },
416
417    #[error("Invalid conversion: {message}")]
418    InvalidConversion { message: String },
419
420    #[error("Python error: {message}")]
421    Python { message: String },
422
423    #[error("NumPy error: {message}")]
424    NumPy { message: String },
425
426    #[error("Memory allocation failed: {message}")]
427    AllocationFailed { message: String },
428
429    #[error("Invalid parameter: {parameter} = {value}")]
430    InvalidParameter { parameter: String, value: String },
431
432    #[error("Operation not supported: {operation}")]
433    UnsupportedOperation { operation: String },
434
435    #[error("Module error: {message}")]
436    Module { message: String },
437
438    #[error("Memory pool error: {message}")]
439    MemoryPool { message: String },
440
441    #[error("Cross-language ownership conflict: {message}")]
442    OwnershipConflict { message: String },
443
444    #[error("Device transfer error: {message}")]
445    DeviceTransfer { message: String },
446}
447
448#[cfg(feature = "python")]
449impl From<FfiError> for pyo3::PyErr {
450    fn from(err: FfiError) -> Self {
451        match err {
452            FfiError::Enhanced(enhanced) => {
453                // Use the appropriate Python exception based on error category
454                use pyo3::exceptions::*;
455                let msg = enhanced.to_string();
456                match enhanced.category {
457                    ErrorCategory::Memory => PyMemoryError::new_err(msg),
458                    ErrorCategory::TypeConversion => PyTypeError::new_err(msg),
459                    ErrorCategory::Validation => PyValueError::new_err(msg),
460                    ErrorCategory::IO => PyIOError::new_err(msg),
461                    ErrorCategory::Module => PyModuleNotFoundError::new_err(msg),
462                    _ => PyRuntimeError::new_err(msg),
463                }
464            }
465            FfiError::Tensor { message } => pyo3::exceptions::PyRuntimeError::new_err(message),
466            FfiError::ShapeMismatch { expected, actual } => {
467                pyo3::exceptions::PyValueError::new_err(format!(
468                    "Shape mismatch: expected {:?}, got {:?}",
469                    expected, actual
470                ))
471            }
472            FfiError::DTypeMismatch { expected, actual } => pyo3::exceptions::PyTypeError::new_err(
473                format!("Data type mismatch: expected {}, got {}", expected, actual),
474            ),
475            FfiError::InvalidConversion { message } => {
476                pyo3::exceptions::PyValueError::new_err(message)
477            }
478            FfiError::Python { message } => pyo3::exceptions::PyRuntimeError::new_err(message),
479            FfiError::NumPy { message } => {
480                pyo3::exceptions::PyRuntimeError::new_err(format!("NumPy error: {}", message))
481            }
482            FfiError::AllocationFailed { message } => {
483                pyo3::exceptions::PyMemoryError::new_err(message)
484            }
485            FfiError::InvalidParameter { parameter, value } => {
486                pyo3::exceptions::PyValueError::new_err(format!(
487                    "Invalid parameter: {} = {}",
488                    parameter, value
489                ))
490            }
491            FfiError::UnsupportedOperation { operation } => {
492                pyo3::exceptions::PyNotImplementedError::new_err(format!(
493                    "Operation not supported: {}",
494                    operation
495                ))
496            }
497            FfiError::Module { message } => {
498                pyo3::exceptions::PyModuleNotFoundError::new_err(message)
499            }
500            FfiError::MemoryPool { message } => {
501                pyo3::exceptions::PyMemoryError::new_err(format!("Memory pool error: {}", message))
502            }
503            FfiError::OwnershipConflict { message } => pyo3::exceptions::PyRuntimeError::new_err(
504                format!("Ownership conflict: {}", message),
505            ),
506            FfiError::DeviceTransfer { message } => pyo3::exceptions::PyRuntimeError::new_err(
507                format!("Device transfer error: {}", message),
508            ),
509        }
510    }
511}
512
513// Error conversions
514impl From<std::fmt::Error> for FfiError {
515    fn from(err: std::fmt::Error) -> Self {
516        FfiError::InvalidConversion {
517            message: format!("Formatting error: {}", err),
518        }
519    }
520}
521
522impl From<std::io::Error> for FfiError {
523    fn from(err: std::io::Error) -> Self {
524        FfiError::InvalidConversion {
525            message: format!("IO error: {}", err),
526        }
527    }
528}
529
530impl From<torsh_core::error::TorshError> for FfiError {
531    fn from(err: torsh_core::error::TorshError) -> Self {
532        FfiError::Tensor {
533            message: format!("{}", err),
534        }
535    }
536}
537
538#[cfg(feature = "python")]
539pub fn fmt_error_to_pyerr(err: std::fmt::Error) -> pyo3::PyErr {
540    pyo3::exceptions::PyRuntimeError::new_err(format!("Formatting error: {}", err))
541}
542
543#[cfg(feature = "python")]
544pub fn torsh_error_to_pyerr(err: torsh_core::error::TorshError) -> pyo3::PyErr {
545    pyo3::exceptions::PyRuntimeError::new_err(format!("Tensor error: {}", err))
546}
547
548/// Result type for FFI operations
549pub type FfiResult<T> = Result<T, FfiError>;
550
551#[cfg(feature = "python")]
552pub mod python_exceptions {
553    //! Custom Python exception classes for better error handling
554
555    use pyo3::exceptions::PyException;
556    use pyo3::prelude::*;
557    use pyo3::types::PyAny;
558    use pyo3::{create_exception, Py, PyErr, Python};
559
560    // Custom exception types for ToRSh-specific errors
561    create_exception!(
562        torsh,
563        TorshError,
564        PyException,
565        "Base exception for ToRSh operations"
566    );
567    create_exception!(torsh, TensorError, TorshError, "Tensor operation error");
568    create_exception!(torsh, ShapeError, TorshError, "Tensor shape related error");
569    create_exception!(torsh, DeviceError, TorshError, "Device operation error");
570    create_exception!(
571        torsh,
572        NumericalError,
573        TorshError,
574        "Numerical computation error"
575    );
576    create_exception!(torsh, MemoryError, TorshError, "Memory management error");
577
578    /// Enhanced error context for Python exceptions
579    #[derive(Debug, Clone)]
580    pub struct ErrorContext {
581        pub operation: String,
582        pub file: Option<String>,
583        pub line: Option<u32>,
584        pub suggestion: Option<String>,
585        pub error_code: Option<i32>,
586        pub recoverable: bool,
587    }
588
589    impl ErrorContext {
590        pub fn new(operation: &str) -> Self {
591            Self {
592                operation: operation.to_string(),
593                file: None,
594                line: None,
595                suggestion: None,
596                error_code: None,
597                recoverable: false,
598            }
599        }
600
601        pub fn with_location(mut self, file: &str, line: u32) -> Self {
602            self.file = Some(file.to_string());
603            self.line = Some(line);
604            self
605        }
606
607        pub fn with_suggestion(mut self, suggestion: &str) -> Self {
608            self.suggestion = Some(suggestion.to_string());
609            self
610        }
611
612        pub fn with_error_code(mut self, code: i32) -> Self {
613            self.error_code = Some(code);
614            self
615        }
616
617        pub fn recoverable(mut self) -> Self {
618            self.recoverable = true;
619            self
620        }
621    }
622
623    /// Create enhanced Python exception with context
624    pub fn create_enhanced_exception(
625        py: Python<'_>,
626        exc_type: &Py<PyAny>,
627        message: &str,
628        context: ErrorContext,
629    ) -> PyErr {
630        let exc = PyErr::new::<PyException, _>((message.to_string(),));
631
632        // Add context attributes to the exception
633        if let Ok(exception_obj) = exc_type.call1(py, (message.to_string(),)) {
634            let _ = exception_obj.setattr(py, "operation", &context.operation);
635            let _ = exception_obj.setattr(py, "recoverable", context.recoverable);
636
637            if let Some(file) = &context.file {
638                let _ = exception_obj.setattr(py, "source_file", file);
639            }
640            if let Some(line) = context.line {
641                let _ = exception_obj.setattr(py, "source_line", line);
642            }
643            if let Some(suggestion) = &context.suggestion {
644                let _ = exception_obj.setattr(py, "suggestion", suggestion);
645            }
646            if let Some(code) = context.error_code {
647                let _ = exception_obj.setattr(py, "error_code", code);
648            }
649        }
650
651        exc
652    }
653
654    /// Register exception types with Python module
655    pub fn register_exceptions(m: &Bound<'_, PyModule>) -> PyResult<()> {
656        m.add("TorshError", m.py().get_type::<TorshError>())?;
657        m.add("TensorError", m.py().get_type::<TensorError>())?;
658        m.add("ShapeError", m.py().get_type::<ShapeError>())?;
659        m.add("DeviceError", m.py().get_type::<DeviceError>())?;
660        m.add("NumericalError", m.py().get_type::<NumericalError>())?;
661        m.add("MemoryError", m.py().get_type::<MemoryError>())?;
662        Ok(())
663    }
664
665    /// Utility function to create tensor shape errors with helpful suggestions
666    pub fn create_shape_error(expected: &[usize], actual: &[usize], operation: &str) -> PyErr {
667        let message = format!(
668            "Shape mismatch in {}: expected {:?}, got {:?}",
669            operation, expected, actual
670        );
671
672        let suggestion = if expected.len() != actual.len() {
673            Some(format!(
674                "Expected tensor with {} dimensions, but got {} dimensions. Consider using reshape() or unsqueeze()/squeeze() operations.",
675                expected.len(), actual.len()
676            ))
677        } else {
678            let mismatched_dims: Vec<_> = expected
679                .iter()
680                .zip(actual.iter())
681                .enumerate()
682                .filter(|(_, (e, a))| e != a)
683                .collect();
684
685            if mismatched_dims.len() == 1 {
686                let (dim, (exp, act)) = mismatched_dims[0];
687                Some(format!(
688                    "Dimension {} mismatch: expected {}, got {}. Consider using reshape(), transpose(), or broadcasting operations.",
689                    dim, exp, act
690                ))
691            } else {
692                Some("Multiple dimensions don't match. Verify tensor shapes and consider using broadcasting or reshape operations.".to_string())
693            }
694        };
695
696        let context = ErrorContext::new(operation)
697            .with_suggestion(&suggestion.unwrap_or_default())
698            .recoverable();
699
700        Python::attach(|py| {
701            create_enhanced_exception(py, &py.get_type::<ShapeError>().into(), &message, context)
702        })
703    }
704
705    /// Utility function to create numerical errors with recovery suggestions
706    pub fn create_numerical_error(message: &str, operation: &str) -> PyErr {
707        let suggestion = if message.contains("NaN") {
708            Some("Check for division by zero, invalid operations, or uninitialized values. Consider using torch.isnan() to detect NaN values.".to_string())
709        } else if message.contains("inf") || message.contains("infinity") {
710            Some("Values became infinite. Consider gradient clipping, smaller learning rates, or numerical stability improvements.".to_string())
711        } else if message.contains("overflow") {
712            Some("Numerical overflow detected. Try using smaller values, different data types, or scaling your data.".to_string())
713        } else {
714            Some(
715                "Numerical computation failed. Check input values and operation parameters."
716                    .to_string(),
717            )
718        };
719
720        let context = ErrorContext::new(operation)
721            .with_suggestion(&suggestion.unwrap_or_default())
722            .recoverable();
723
724        Python::attach(|py| {
725            create_enhanced_exception(
726                py,
727                &py.get_type::<NumericalError>().into(),
728                message,
729                context,
730            )
731        })
732    }
733}
734
735#[cfg(test)]
736mod tests {
737    use super::*;
738
739    #[test]
740    fn test_error_code_category() {
741        assert_eq!(ErrorCode::ShapeMismatch.category(), ErrorCategory::Tensor);
742        assert_eq!(
743            ErrorCode::AllocationFailed.category(),
744            ErrorCategory::Memory
745        );
746        assert_eq!(
747            ErrorCode::InvalidConversion.category(),
748            ErrorCategory::TypeConversion
749        );
750        assert_eq!(
751            ErrorCode::InvalidParameter.category(),
752            ErrorCategory::Validation
753        );
754    }
755
756    #[test]
757    fn test_error_code_severity() {
758        assert_eq!(
759            ErrorCode::OutOfMemory.default_severity(),
760            Severity::Critical
761        );
762        assert_eq!(ErrorCode::ShapeMismatch.default_severity(), Severity::Error);
763        assert_eq!(
764            ErrorCode::PrecisionLoss.default_severity(),
765            Severity::Warning
766        );
767    }
768
769    #[test]
770    fn test_severity_ordering() {
771        assert!(Severity::Critical > Severity::Error);
772        assert!(Severity::Error > Severity::Warning);
773        assert!(Severity::Warning > Severity::Info);
774    }
775
776    #[test]
777    fn test_enhanced_error_creation() {
778        let error = EnhancedError::new(ErrorCode::ShapeMismatch, "Test error");
779        assert_eq!(error.code, ErrorCode::ShapeMismatch);
780        assert_eq!(error.message, "Test error");
781        assert_eq!(error.severity, Severity::Error);
782        assert_eq!(error.category, ErrorCategory::Tensor);
783        assert!(error.is_recoverable());
784    }
785
786    #[test]
787    fn test_error_builder() {
788        let error = ErrorBuilder::new(ErrorCode::ShapeMismatch)
789            .message("Incompatible shapes")
790            .context("operation", "matmul")
791            .context("expected", "[2, 3]")
792            .context("actual", "[3, 2]")
793            .suggestion("Transpose one of the tensors")
794            .severity(Severity::Error)
795            .build();
796
797        assert_eq!(error.message, "Incompatible shapes");
798        assert_eq!(error.context.get("operation"), Some(&"matmul".to_string()));
799        assert_eq!(error.suggestions.len(), 1);
800        assert!(error.is_recoverable());
801    }
802
803    #[test]
804    fn test_error_with_location() {
805        let error = ErrorBuilder::new(ErrorCode::InvalidParameter)
806            .message("Invalid value")
807            .source_location("test.rs", 42, 10)
808            .build();
809
810        assert!(error.location.is_some());
811        let loc = error.location.unwrap();
812        assert_eq!(loc.file, "test.rs");
813        assert_eq!(loc.line, 42);
814        assert_eq!(loc.column, 10);
815    }
816
817    #[test]
818    fn test_error_with_causes() {
819        let error = ErrorBuilder::new(ErrorCode::OperationFailed)
820            .message("Operation failed")
821            .cause("Underlying IO error")
822            .cause("File not found")
823            .build();
824
825        assert_eq!(error.causes.len(), 2);
826        assert!(error.causes.contains(&"Underlying IO error".to_string()));
827    }
828
829    #[test]
830    fn test_error_display() {
831        let error = ErrorBuilder::new(ErrorCode::ShapeMismatch)
832            .message("Test error")
833            .context("op", "test")
834            .suggestion("Fix it")
835            .build();
836
837        let display = error.to_string();
838        assert!(display.contains("ShapeMismatch"));
839        assert!(display.contains("Test error"));
840        assert!(display.contains("op: test"));
841        assert!(display.contains("Fix it"));
842    }
843
844    #[test]
845    fn test_error_json_serialization() {
846        let error = EnhancedError::new(ErrorCode::InvalidConversion, "Test");
847        let json = error.to_json();
848        assert!(json.is_ok());
849        let json_str = json.unwrap();
850        assert!(json_str.contains("InvalidConversion"));
851        assert!(json_str.contains("Test"));
852    }
853
854    #[test]
855    fn test_error_recoverability() {
856        let critical = EnhancedError::new(ErrorCode::OutOfMemory, "OOM");
857        assert!(!critical.is_recoverable());
858
859        let warning = EnhancedError::new(ErrorCode::PrecisionLoss, "Precision");
860        assert!(warning.is_recoverable());
861    }
862
863    #[test]
864    fn test_ffi_error_enhanced_variant() {
865        let enhanced = EnhancedError::new(ErrorCode::TensorCreationFailed, "Failed");
866        let ffi_error = FfiError::Enhanced(enhanced);
867
868        match ffi_error {
869            FfiError::Enhanced(e) => {
870                assert_eq!(e.code, ErrorCode::TensorCreationFailed);
871            }
872            _ => panic!("Expected Enhanced variant"),
873        }
874    }
875
876    #[test]
877    fn test_error_builder_fluent_api() {
878        let error = ErrorBuilder::new(ErrorCode::MatrixNotInvertible)
879            .message("Matrix is singular")
880            .context("determinant", "0.0")
881            .context("condition_number", "inf")
882            .suggestion("Check matrix values")
883            .suggestion("Try adding regularization")
884            .source_location(file!(), line!(), column!())
885            .build();
886
887        assert_eq!(error.suggestions.len(), 2);
888        assert_eq!(error.context.len(), 2);
889        assert!(error.location.is_some());
890    }
891}