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}