apex_solver/
error.rs

1//! Error types for the apex-solver library
2//!
3//! This module provides the main error and result types used throughout the library.
4//! All errors use the `thiserror` crate for automatic trait implementations.
5//!
6//! # Error Hierarchy
7//!
8//! The library uses a hierarchical error system where:
9//! - **`ApexSolverError`** is the top-level error exposed to users via public APIs
10//! - **Module errors** (`CoreError`, `OptimizerError`, etc.) are wrapped inside ApexSolverError
11//! - **Error sources** are preserved, allowing full error chain inspection
12//!
13//! Example error chain:
14//! ```text
15//! ApexSolverError::Core(
16//!     CoreError::SymbolicStructure {
17//!         message: "Duplicate variable index",
18//!         context: "Variable 'x42' at position 15"
19//!     }
20//! )
21//! ```
22
23use crate::{
24    core::CoreError, io::IoError, linalg::LinAlgError, manifold::ManifoldError,
25    observers::ObserverError, optimizer::OptimizerError,
26};
27use std::error::Error as StdError;
28use thiserror::Error;
29
30/// Main result type used throughout the apex-solver library
31pub type ApexSolverResult<T> = Result<T, ApexSolverError>;
32
33/// Main error type for the apex-solver library
34///
35/// This is the top-level error type exposed by public APIs. It wraps module-specific
36/// errors while preserving the full error chain for debugging.
37///
38/// # Error Chain Access
39///
40/// You can access the full error chain using the `chain()` method:
41///
42/// ```rust,ignore
43/// if let Err(e) = solver.optimize(&problem, &initial_values) {
44///     warn!("Error: {}", e);
45///     warn!("Full chain: {}", e.chain());
46/// }
47/// ```
48#[derive(Debug, Error)]
49pub enum ApexSolverError {
50    /// Core module errors (problem construction, factors, variables)
51    #[error(transparent)]
52    Core(#[from] CoreError),
53
54    /// Optimization algorithm errors
55    #[error(transparent)]
56    Optimizer(#[from] OptimizerError),
57
58    /// Linear algebra errors
59    #[error(transparent)]
60    LinearAlgebra(#[from] LinAlgError),
61
62    /// Manifold operation errors
63    #[error(transparent)]
64    Manifold(#[from] ManifoldError),
65
66    /// I/O and file parsing errors
67    #[error(transparent)]
68    Io(#[from] IoError),
69
70    /// Observer/visualization errors
71    #[error(transparent)]
72    Observer(#[from] ObserverError),
73}
74
75// Module-specific errors are automatically converted via #[from] attributes above
76// No manual From implementations needed - thiserror handles it!
77
78impl ApexSolverError {
79    /// Get the full error chain as a string for logging and debugging.
80    ///
81    /// This method traverses the error source chain and returns a formatted string
82    /// showing the hierarchy of errors from the top-level ApexSolverError down to the
83    /// root cause.
84    ///
85    /// # Example
86    ///
87    /// ```rust,ignore
88    /// match solver.optimize(&problem, &initial_values) {
89    ///     Ok(result) => { /* ... */ }
90    ///     Err(e) => {
91    ///         warn!("Optimization failed!");
92    ///         warn!("Error chain: {}", e.chain());
93    ///         // Output: "Optimizer error: Linear system solve failed →
94    ///         //          Linear algebra error: Singular matrix detected"
95    ///     }
96    /// }
97    /// ```
98    pub fn chain(&self) -> String {
99        let mut chain = vec![self.to_string()];
100        let mut source = self.source();
101
102        while let Some(err) = source {
103            chain.push(format!("  → {}", err));
104            source = err.source();
105        }
106
107        chain.join("\n")
108    }
109
110    /// Get a compact single-line error chain for logging
111    ///
112    /// Similar to `chain()` but formats as a single line with arrow separators.
113    ///
114    /// # Example
115    ///
116    /// ```rust,ignore
117    /// error!("Operation failed: {}", apex_err.chain_compact());
118    /// // Output: "Optimizer error → Linear algebra error → Singular matrix"
119    /// ```
120    pub fn chain_compact(&self) -> String {
121        let mut chain = vec![self.to_string()];
122        let mut source = self.source();
123
124        while let Some(err) = source {
125            chain.push(err.to_string());
126            source = err.source();
127        }
128
129        chain.join(" → ")
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_apex_solver_error_display() {
139        let linalg_error = LinAlgError::SingularMatrix;
140        let error = ApexSolverError::from(linalg_error);
141        assert!(error.to_string().contains("Singular matrix"));
142    }
143
144    #[test]
145    fn test_apex_solver_error_chain() {
146        let linalg_error =
147            LinAlgError::FactorizationFailed("Cholesky factorization failed".to_string());
148        let error = ApexSolverError::from(linalg_error);
149
150        let chain = error.chain();
151        assert!(chain.contains("factorization"));
152        assert!(chain.contains("Cholesky"));
153    }
154
155    #[test]
156    fn test_apex_solver_error_chain_compact() {
157        let core_error = CoreError::Variable("Invalid variable index".to_string());
158        let error = ApexSolverError::from(core_error);
159
160        let chain_compact = error.chain_compact();
161        assert!(chain_compact.contains("Invalid variable index"));
162    }
163
164    #[test]
165    fn test_apex_solver_result_ok() {
166        let result: ApexSolverResult<i32> = Ok(42);
167        assert!(result.is_ok());
168        if let Ok(value) = result {
169            assert_eq!(value, 42);
170        }
171    }
172
173    #[test]
174    fn test_apex_solver_result_err() {
175        let core_error = CoreError::ResidualBlock("Test error".to_string());
176        let result: ApexSolverResult<i32> = Err(ApexSolverError::from(core_error));
177        assert!(result.is_err());
178    }
179
180    #[test]
181    fn test_transparent_error_conversion() {
182        // Test automatic conversion via #[from]
183        let manifold_error = ManifoldError::DimensionMismatch {
184            expected: 3,
185            actual: 2,
186        };
187
188        let apex_error: ApexSolverError = manifold_error.into();
189        match apex_error {
190            ApexSolverError::Manifold(_) => { /* Expected */ }
191            _ => panic!("Expected Manifold variant"),
192        }
193    }
194}