Skip to main content

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