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/// ```no_run
43/// # use apex_solver::error::ApexSolverError;
44/// # use tracing::warn;
45/// # fn solver_optimize() -> Result<(), ApexSolverError> { Ok(()) }
46/// # fn example() {
47/// if let Err(e) = solver_optimize() {
48///     warn!("Error: {}", e);
49///     warn!("Full chain: {}", e.chain());
50/// }
51/// # }
52/// ```
53#[derive(Debug, Error)]
54pub enum ApexSolverError {
55    /// Core module errors (problem construction, factors, variables)
56    #[error(transparent)]
57    Core(#[from] CoreError),
58
59    /// Optimization algorithm errors
60    #[error(transparent)]
61    Optimizer(#[from] OptimizerError),
62
63    /// Linear algebra errors
64    #[error(transparent)]
65    LinearAlgebra(#[from] LinAlgError),
66
67    /// Manifold operation errors
68    #[error(transparent)]
69    Manifold(#[from] ManifoldError),
70
71    /// I/O and file parsing errors
72    #[error(transparent)]
73    Io(#[from] IoError),
74
75    /// Observer/visualization errors
76    #[error(transparent)]
77    Observer(#[from] ObserverError),
78}
79
80// Module-specific errors are automatically converted via #[from] attributes above
81// No manual From implementations needed - thiserror handles it!
82
83impl ApexSolverError {
84    /// Get the full error chain as a string for logging and debugging.
85    ///
86    /// This method traverses the error source chain and returns a formatted string
87    /// showing the hierarchy of errors from the top-level ApexSolverError down to the
88    /// root cause.
89    ///
90    /// # Example
91    ///
92    /// ```no_run
93    /// # use apex_solver::error::ApexSolverError;
94    /// # use tracing::warn;
95    /// # fn solver_optimize() -> Result<(), ApexSolverError> { Ok(()) }
96    /// # fn example() {
97    /// match solver_optimize() {
98    ///     Ok(result) => { /* ... */ }
99    ///     Err(e) => {
100    ///         warn!("Optimization failed!");
101    ///         warn!("Error chain: {}", e.chain());
102    ///         // Output: "Optimizer error: Linear system solve failed →
103    ///         //          Linear algebra error: Singular matrix detected"
104    ///     }
105    /// }
106    /// # }
107    /// ```
108    pub fn chain(&self) -> String {
109        let mut chain = vec![self.to_string()];
110        let mut source = self.source();
111
112        while let Some(err) = source {
113            chain.push(format!("  → {}", err));
114            source = err.source();
115        }
116
117        chain.join("\n")
118    }
119
120    /// Get a compact single-line error chain for logging
121    ///
122    /// Similar to `chain()` but formats as a single line with arrow separators.
123    ///
124    /// # Example
125    ///
126    /// ```no_run
127    /// # use apex_solver::error::ApexSolverError;
128    /// # use apex_solver::core::CoreError;
129    /// # use tracing::error;
130    /// # fn example() {
131    /// # let apex_err = ApexSolverError::Core(CoreError::InvalidInput("test".to_string()));
132    /// error!("Operation failed: {}", apex_err.chain_compact());
133    /// // Output: "Optimizer error → Linear algebra error → Singular matrix"
134    /// # }
135    /// ```
136    pub fn chain_compact(&self) -> String {
137        let mut chain = vec![self.to_string()];
138        let mut source = self.source();
139
140        while let Some(err) = source {
141            chain.push(err.to_string());
142            source = err.source();
143        }
144
145        chain.join(" → ")
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_apex_solver_error_display() {
155        let linalg_error = LinAlgError::SingularMatrix("test singular matrix".to_string());
156        let error = ApexSolverError::from(linalg_error);
157        assert!(error.to_string().contains("Singular matrix"));
158    }
159
160    #[test]
161    fn test_apex_solver_error_chain() {
162        let linalg_error =
163            LinAlgError::FactorizationFailed("Cholesky factorization failed".to_string());
164        let error = ApexSolverError::from(linalg_error);
165
166        let chain = error.chain();
167        assert!(chain.contains("factorization"));
168        assert!(chain.contains("Cholesky"));
169    }
170
171    #[test]
172    fn test_apex_solver_error_chain_compact() {
173        let core_error = CoreError::Variable("Invalid variable index".to_string());
174        let error = ApexSolverError::from(core_error);
175
176        let chain_compact = error.chain_compact();
177        assert!(chain_compact.contains("Invalid variable index"));
178    }
179
180    #[test]
181    fn test_apex_solver_result_ok() {
182        let result: ApexSolverResult<i32> = Ok(42);
183        assert!(result.is_ok());
184        if let Ok(value) = result {
185            assert_eq!(value, 42);
186        }
187    }
188
189    #[test]
190    fn test_apex_solver_result_err() {
191        let core_error = CoreError::ResidualBlock("Test error".to_string());
192        let result: ApexSolverResult<i32> = Err(ApexSolverError::from(core_error));
193        assert!(result.is_err());
194    }
195
196    #[test]
197    fn test_transparent_error_conversion() {
198        // Test automatic conversion via #[from]
199        let manifold_error = ManifoldError::DimensionMismatch {
200            expected: 3,
201            actual: 2,
202        };
203
204        let apex_error: ApexSolverError = manifold_error.into();
205        match apex_error {
206            ApexSolverError::Manifold(_) => { /* Expected */ }
207            _ => panic!("Expected Manifold variant"),
208        }
209    }
210}