optionstratlib_math/error/surfaces.rs
1/******************************************************************************
2 Author: Joaquín Béjar García
3 Email: jb@taunais.com
4 Date: 20/1/25
5******************************************************************************/
6//! Owned by `optionstratlib-math` (ADR-0001 D6, roadmap M1-14): `SurfaceError`.
7
8use crate::error::InterpolationError;
9use optionstratlib_core::error::{OperationErrorKind, OptionsError, PositionError};
10use thiserror::Error;
11
12/// Error variants that can occur when working with surface-related operations.
13///
14/// This enum categorizes different types of errors that might occur when handling
15/// mathematical or geometrical surfaces, particularly in the context of pricing
16/// models, interpolation, or volatility surfaces.
17///
18/// ## Error Categories
19///
20/// - Geometry errors (points in 3D space)
21/// - Operation-specific errors
22/// - Standard library errors
23/// - Surface construction errors
24/// - Analysis-related failures
25///
26/// This error type is designed to provide detailed context about what went wrong
27/// when working with financial or mathematical surface calculations, which is useful
28/// for debugging and error handling in financial modeling applications.
29#[derive(Error, Debug)]
30pub enum SurfaceError {
31 /// Error related to 3D point calculations or validations.
32 ///
33 /// This typically occurs when coordinates are invalid, out of expected range,
34 /// or when mathematical operations on points fail.
35 #[error("Error: {reason}")]
36 Point3DError {
37 /// A reference to a static string that explains the reason for an error or a condition.
38 reason: &'static str,
39 },
40
41 /// Error indicating a specific operation failed.
42 ///
43 /// Contains detailed information about why the operation could not be completed,
44 /// including whether the operation isn't supported for the given surface type
45 /// or was provided with invalid parameters.
46 #[error("Operation error: {0}")]
47 OperationError(OperationErrorKind),
48
49 /// A caller-supplied parametric generator failed.
50 ///
51 /// `ConstructionMethod::Parametric` takes a closure written by the
52 /// caller, so its failure belongs to whichever layer wrote it, and the
53 /// math layer cannot name that type without depending on the layer
54 /// above. The cause therefore travels boxed: it stays reachable through
55 /// `std::error::Error::source` and downcastable to the original type,
56 /// instead of being flattened into a message at the construction
57 /// boundary (roadmap M1-10).
58 #[error("parametric generator failed: {0}")]
59 Generator(#[source] Box<dyn std::error::Error + Send + Sync>),
60
61 /// Error that occurred during the construction of a surface.
62 ///
63 /// This is typically used when input data is valid but inconsistent or insufficient
64 /// to construct a well-formed surface object.
65 #[error("Construction error: {0}")]
66 ConstructionError(String),
67
68 /// Error that occurred during the analysis of a surface.
69 ///
70 /// This is used when operations like finding extrema, calculating slopes,
71 /// or evaluating a surface at specific points fail due to mathematical
72 /// or algorithmic constraints.
73 #[error("Analysis error: {0}")]
74 AnalysisError(String),
75
76 /// Error from position operations
77 #[error(transparent)]
78 Position(#[from] PositionError),
79
80 /// Error from options operations
81 #[error(transparent)]
82 Options(#[from] OptionsError),
83}
84
85/// Provides helper methods for constructing specific variants of the `SurfaceError` type.
86///
87/// These methods encapsulate common patterns of error creation, making it easier
88/// to consistently generate errors with the necessary context.
89///
90///
91/// ## Integration
92/// - These methods simplify the process of creating meaningful error objects, improving readability
93/// and maintainability of the code using the `SurfaceError` type.
94/// - The constructed errors leverage the [`OperationErrorKind`]
95/// to ensure structured and detailed error categorization.
96impl SurfaceError {
97 /// ### `operation_not_supported`
98 /// Constructs a `SurfaceError::OperationError` with an [`OperationErrorKind::NotSupported`] variant.
99 /// - **Parameters:**
100 /// - `operation` (`&str`): The name of the operation that is not supported.
101 /// - `reason` (`&str`): A description of why the operation is not supported.
102 /// - **Returns:**
103 /// - A `SurfaceError` containing a `NotSupported` operation error.
104 /// - **Use Cases:**
105 /// - Invoked when a requested operation is not compatible with the current context.
106 /// - For example, attempting an unsupported computation method on a specific curve type.
107 ///
108 #[must_use]
109 #[cold]
110 #[inline(never)]
111 pub fn operation_not_supported(operation: &str, reason: &str) -> Self {
112 SurfaceError::OperationError(OperationErrorKind::NotSupported {
113 operation: operation.to_string(),
114 reason: reason.to_string(),
115 })
116 }
117
118 /// ### `invalid_parameters`
119 /// Constructs a `SurfaceError::OperationError` with an [`OperationErrorKind::InvalidParameters`] variant.
120 /// - **Parameters:**
121 /// - `operation` (`&str`): The name of the operation that encountered invalid parameters.
122 /// - `reason` (`&str`): A description of why the parameters are invalid.
123 /// - **Returns:**
124 /// - A `SurfaceError` containing an `InvalidParameters` operation error.
125 /// - **Use Cases:**
126 /// - Used when an operation fails due to issues with the provided input.
127 /// - For example, providing malformed or missing parameters for interpolation or curve construction.
128 ///
129 #[must_use]
130 #[cold]
131 #[inline(never)]
132 pub fn invalid_parameters(operation: &str, reason: &str) -> Self {
133 SurfaceError::OperationError(OperationErrorKind::InvalidParameters {
134 operation: operation.to_string(),
135 reason: reason.to_string(),
136 })
137 }
138}
139
140/// Converts a `PositionError` into a `SurfaceError` by mapping it to an
141/// `OperationError` with the `InvalidParameters` variant.
142///
143/// This implementation ensures a smooth transition between error types
144/// when a `PositionError` is encountered within a context that operates
145/// on the `curves` module. The `InvalidParameters` variant is used to
146/// provide detailed information about the failed operation and the reason
147/// for its failure.
148///
149/// ## Details:
150/// - The `operation` field is hardcoded as `"Position"` to indicate the
151/// context of the error (i.e., relating to position management).
152/// - The `reason` field is derived from the `to_string` representation of
153/// the `PositionError`, ensuring a human-readable explanation.
154///
155/// ## Example Integration:
156/// 1. If a `PositionError` is encountered during curve calculations, this
157/// implementation converts it into a `SurfaceError` for consistent error
158/// handling within the `curves` module.
159/// 2. The generated `SurfaceError` provides detailed diagnostic information
160/// about the reason for the failure, enabling effective debugging.
161///
162/// ## Implementation Notes:
163/// - This conversion leverages the `OperationErrorKind::InvalidParameters`
164/// variant to communicate that invalid parameters (or settings) were the
165/// root cause of failure.
166/// - Use this implementation to handle interoperability between error types
167/// in modular design contexts.
168///
169/// ## Example Use Case:
170/// This conversion is frequently used in scenarios where:
171/// - A position-related error (e.g., from validation or limits) occurs during a
172/// curve operation.
173/// - Such errors need to be mapped into the `SurfaceError` domain to maintain
174/// consistent error handling across the library.
175///
176/// ## Debugging:
177/// The resulting `SurfaceError` will include contextual details, making it
178/// straightforward to trace and debug the underlying issue.
179impl SurfaceError {
180 /// Wraps a caller-supplied parametric generator failure, keeping the
181 /// original error reachable as the variant's `source`.
182 #[cold]
183 #[must_use]
184 pub fn generator<E>(error: E) -> Self
185 where
186 E: std::error::Error + Send + Sync + 'static,
187 {
188 SurfaceError::Generator(Box::new(error))
189 }
190}
191
192impl From<InterpolationError> for SurfaceError {
193 fn from(err: InterpolationError) -> Self {
194 SurfaceError::AnalysisError(err.to_string())
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::error::CurveError;
202 use crate::error::CurvesResult;
203 use optionstratlib_core::error::position::PositionValidationErrorKind;
204
205 #[test]
206 fn test_operation_not_supported() {
207 let error = SurfaceError::operation_not_supported(
208 "test_operation",
209 "Operation cannot be performed",
210 );
211
212 match error {
213 SurfaceError::OperationError(OperationErrorKind::NotSupported {
214 operation,
215 reason,
216 }) => {
217 assert_eq!(operation, "test_operation");
218 assert_eq!(reason, "Operation cannot be performed");
219 }
220 _ => panic!("Wrong error variant"),
221 }
222 }
223
224 #[test]
225 fn test_invalid_parameters() {
226 let error = SurfaceError::invalid_parameters("test_params", "Invalid input parameters");
227
228 match error {
229 SurfaceError::OperationError(OperationErrorKind::InvalidParameters {
230 operation,
231 reason,
232 }) => {
233 assert_eq!(operation, "test_params");
234 assert_eq!(reason, "Invalid input parameters");
235 }
236 _ => panic!("Wrong error variant"),
237 }
238 }
239
240 #[test]
241 fn test_display_operation_error() {
242 let error = SurfaceError::operation_not_supported(
243 "test_operation",
244 "Operation cannot be performed",
245 );
246 assert!(error.to_string().contains("Operation error"));
247 assert!(error.to_string().contains("test_operation"));
248 assert!(error.to_string().contains("Operation cannot be performed"));
249 }
250
251 #[test]
252 fn test_display_point3d_error() {
253 let error = SurfaceError::Point3DError {
254 reason: "Point error test",
255 };
256 assert_eq!(error.to_string(), "Error: Point error test");
257 }
258
259 #[test]
260 fn test_display_construction_error() {
261 let error = SurfaceError::ConstructionError("Construction failed".to_string());
262 assert_eq!(error.to_string(), "Construction error: Construction failed");
263 }
264
265 #[test]
266 fn test_display_analysis_error() {
267 let error = SurfaceError::AnalysisError("Analysis failed".to_string());
268 assert_eq!(error.to_string(), "Analysis error: Analysis failed");
269 }
270
271 #[test]
272 fn test_from_position_error() {
273 let pos_error =
274 PositionError::ValidationError(PositionValidationErrorKind::InvalidPosition {
275 reason: "Test position error".to_string(),
276 });
277 let surface_error = SurfaceError::from(pos_error);
278
279 match surface_error {
280 SurfaceError::Position(_) => {
281 // Conversion successful
282 }
283 _ => panic!("Expected Position variant"),
284 }
285 }
286
287 #[test]
288 fn test_from_interpolation_error_surfaces() {
289 let interpolation_err = InterpolationError::EmptyData;
290 let surface_err = SurfaceError::from(interpolation_err);
291 assert!(matches!(surface_err, SurfaceError::AnalysisError(_)));
292 }
293
294 #[test]
295 fn test_curves_result_err() {
296 let result: CurvesResult<i32> = Err(CurveError::AnalysisError("Test error".to_string()));
297 assert!(result.is_err());
298 match result {
299 Err(err) => {
300 assert_eq!(err.to_string(), "Analysis error: Test error");
301 }
302 Ok(_) => panic!("Expected an error result"),
303 }
304 }
305
306 #[test]
307 fn test_curves_result_err_alternative() {
308 let err = CurveError::AnalysisError("Test error".to_string());
309 assert_eq!(err.to_string(), "Analysis error: Test error");
310 let result: CurvesResult<i32> = Err(err);
311 assert!(result.is_err());
312 match result {
313 Err(e) => {
314 assert_eq!(e.to_string(), "Analysis error: Test error");
315 }
316 Ok(_) => panic!("Expected an error result"),
317 }
318 }
319}