Skip to main content

rustyqlib/core/
errors.rs

1//! The library-wide error type.
2//!
3//! Every fallible public operation returns [`RustyQLibError`], usually
4//! through the [`Result`] alias. Variants are grouped by failure domain so
5//! callers can match on the class of failure without parsing message text:
6//! bad user input, an engine/product combination the library refuses to
7//! price, a calibration that did not converge, a numerical breakdown inside
8//! an otherwise valid computation, or malformed contract/market data.
9
10use thiserror::Error;
11
12/// Library-wide result alias.
13pub type Result<T> = std::result::Result<T, RustyQLibError>;
14
15/// The error type returned by all fallible RustyQLib operations.
16#[derive(Debug, Clone, PartialEq, Error)]
17pub enum RustyQLibError {
18    /// A user-supplied value is outside its valid domain (negative
19    /// volatility, maturity in the past, correlation outside [-1, 1], ...).
20    #[error("invalid input `{field}`: {reason}")]
21    InvalidInput {
22        /// The contract/market-data field or function argument at fault.
23        field: String,
24        /// Why the value was rejected.
25        reason: String,
26    },
27
28    /// The requested engine cannot price the requested product/model
29    /// combination (e.g. path-dependent payoffs on the binomial engine).
30    #[error("unsupported: {0}")]
31    UnsupportedEngine(String),
32
33    /// An iterative calibration or root search terminated without meeting
34    /// its convergence criterion.
35    #[error("calibration failed after {iterations} iterations (residual {residual:.6e}): {reason}")]
36    CalibrationFailed {
37        /// Iterations performed before giving up.
38        iterations: usize,
39        /// Final objective / residual value at termination.
40        residual: f64,
41        /// What was being calibrated and why it stopped.
42        reason: String,
43    },
44
45    /// A numerical method broke down on otherwise valid input (singular
46    /// matrix, bracketing failure, NaN in an intermediate, ...).
47    #[error("numerical error: {0}")]
48    NumericalError(String),
49
50    /// Contract or market data could not be parsed / deserialized.
51    #[error("parse error: {0}")]
52    ParseError(String),
53
54    /// A [`Market`](crate::core::market::Market) lookup found no datum at
55    /// the given key. `key` is the debug rendering of the typed key, e.g.
56    /// `Spot("ACME")`.
57    #[error("missing market data: {key}")]
58    MissingMarketData {
59        /// The key that had no entry.
60        key: String,
61    },
62}
63
64impl From<crate::core::vols::VolError> for RustyQLibError {
65    fn from(e: crate::core::vols::VolError) -> Self {
66        RustyQLibError::invalid_input("vol_surface", e.to_string())
67    }
68}
69
70impl From<crate::core::curves::CurveError> for RustyQLibError {
71    fn from(e: crate::core::curves::CurveError) -> Self {
72        RustyQLibError::invalid_input("discount_curve", e.to_string())
73    }
74}
75
76impl RustyQLibError {
77    /// Convenience constructor for [`RustyQLibError::InvalidInput`].
78    pub fn invalid_input(field: impl Into<String>, reason: impl Into<String>) -> Self {
79        RustyQLibError::InvalidInput { field: field.into(), reason: reason.into() }
80    }
81}