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}