Skip to main content

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}