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}