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}