use crate::error::{DecimalError, GreeksError, PricingError};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum OptionsError {
#[error("Validation error for field '{field}': {reason}")]
ValidationError {
field: String,
reason: String,
},
#[error("Pricing error using method '{method}': {reason}")]
PricingError {
method: String,
reason: String,
},
#[error("Error calculating greek '{greek}': {reason}")]
GreeksCalculationError {
greek: String,
reason: String,
},
#[error("Time calculation error in '{operation}': {reason}")]
TimeError {
operation: String,
reason: String,
},
#[error("Payoff calculation error: {reason}")]
PayoffError {
reason: String,
},
#[error("Update error for field '{field}': {reason}")]
UpdateError {
field: String,
reason: String,
},
#[error("Other error: {reason}")]
OtherError {
reason: String,
},
#[error(transparent)]
Decimal(#[from] DecimalError),
#[error(transparent)]
Greeks(#[from] GreeksError),
}
pub type OptionsResult<T> = Result<T, OptionsError>;
impl OptionsError {
pub fn validation_error(field: &str, reason: &str) -> Self {
OptionsError::ValidationError {
field: field.to_string(),
reason: reason.to_string(),
}
}
pub fn pricing_error(method: &str, reason: &str) -> Self {
OptionsError::PricingError {
method: method.to_string(),
reason: reason.to_string(),
}
}
pub fn greeks_error(greek: &str, reason: &str) -> Self {
OptionsError::GreeksCalculationError {
greek: greek.to_string(),
reason: reason.to_string(),
}
}
pub fn time_error(operation: &str, reason: &str) -> Self {
OptionsError::TimeError {
operation: operation.to_string(),
reason: reason.to_string(),
}
}
pub fn payoff_error(reason: &str) -> Self {
OptionsError::PayoffError {
reason: reason.to_string(),
}
}
pub fn update_error(field: &str, reason: &str) -> Self {
OptionsError::UpdateError {
field: field.to_string(),
reason: reason.to_string(),
}
}
}
impl From<Box<dyn std::error::Error>> for OptionsError {
fn from(err: Box<dyn std::error::Error>) -> Self {
OptionsError::OtherError {
reason: err.to_string(),
}
}
}
impl From<&str> for OptionsError {
fn from(err: &str) -> Self {
OptionsError::ValidationError {
field: "unknown".to_string(),
reason: err.to_string(),
}
}
}
impl From<String> for OptionsError {
fn from(err: String) -> Self {
OptionsError::ValidationError {
field: "unknown".to_string(),
reason: err,
}
}
}
impl From<expiration_date::error::ExpirationDateError> for OptionsError {
fn from(err: expiration_date::error::ExpirationDateError) -> Self {
OptionsError::OtherError {
reason: err.to_string(),
}
}
}
impl From<PricingError> for OptionsError {
fn from(value: PricingError) -> Self {
Self::PricingError {
method: "unknown".to_string(),
reason: value.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validation_error_creation() {
let error = OptionsError::validation_error("price", "must be positive");
match error {
OptionsError::ValidationError { field, reason } => {
assert_eq!(field, "price");
assert_eq!(reason, "must be positive");
}
_ => panic!("Expected ValidationError"),
}
}
#[test]
fn test_pricing_error_creation() {
let error = OptionsError::pricing_error("black_scholes", "invalid parameters");
match error {
OptionsError::PricingError { method, reason } => {
assert_eq!(method, "black_scholes");
assert_eq!(reason, "invalid parameters");
}
_ => panic!("Expected PricingError"),
}
}
#[test]
fn test_greeks_error_creation() {
let error = OptionsError::greeks_error("delta", "calculation failed");
match error {
OptionsError::GreeksCalculationError { greek, reason } => {
assert_eq!(greek, "delta");
assert_eq!(reason, "calculation failed");
}
_ => panic!("Expected GreeksCalculationError"),
}
}
#[test]
fn test_time_error_creation() {
let error = OptionsError::time_error("expiry", "invalid date");
match error {
OptionsError::TimeError { operation, reason } => {
assert_eq!(operation, "expiry");
assert_eq!(reason, "invalid date");
}
_ => panic!("Expected TimeError"),
}
}
#[test]
fn test_payoff_error_creation() {
let error = OptionsError::payoff_error("invalid strike price");
match error {
OptionsError::PayoffError { reason } => {
assert_eq!(reason, "invalid strike price");
}
_ => panic!("Expected PayoffError"),
}
}
#[test]
fn test_update_error_creation() {
let error = OptionsError::update_error("volatility", "out of bounds");
match error {
OptionsError::UpdateError { field, reason } => {
assert_eq!(field, "volatility");
assert_eq!(reason, "out of bounds");
}
_ => panic!("Expected UpdateError"),
}
}
#[test]
fn test_error_display() {
let error = OptionsError::validation_error("price", "must be positive");
assert_eq!(
error.to_string(),
"Validation error for field 'price': must be positive"
);
let error = OptionsError::pricing_error("black_scholes", "invalid parameters");
assert_eq!(
error.to_string(),
"Pricing error using method 'black_scholes': invalid parameters"
);
}
#[test]
fn test_from_str_conversion() {
let error: OptionsError = "test error".into();
match error {
OptionsError::ValidationError { field, reason } => {
assert_eq!(field, "unknown");
assert_eq!(reason, "test error");
}
_ => panic!("Expected ValidationError"),
}
}
#[test]
fn test_from_string_conversion() {
let error: OptionsError = String::from("test error").into();
match error {
OptionsError::ValidationError { field, reason } => {
assert_eq!(field, "unknown");
assert_eq!(reason, "test error");
}
_ => panic!("Expected ValidationError"),
}
}
#[test]
fn test_from_box_dyn_error_conversion() {
struct TestError(String);
impl std::fmt::Display for TestError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::fmt::Debug for TestError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "TestError({})", self.0)
}
}
impl std::error::Error for TestError {}
let original_error: Box<dyn std::error::Error> =
Box::new(TestError("test error".to_string()));
let error: OptionsError = original_error.into();
match error {
OptionsError::OtherError { reason } => {
assert_eq!(reason, "test error");
}
_ => panic!("Expected OtherError"),
}
}
#[test]
fn test_to_box_dyn_error_conversion() {
let error = OptionsError::validation_error("price", "must be positive");
let boxed: Box<dyn std::error::Error> = error.into();
assert_eq!(
boxed.to_string(),
"Validation error for field 'price': must be positive"
);
}
#[test]
fn test_error_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<OptionsError>();
}
#[test]
fn test_options_result_type() {
let success: OptionsResult<i32> = Ok(42);
let failure: OptionsResult<i32> = Err(OptionsError::validation_error("test", "error"));
assert!(success.is_ok());
assert!(failure.is_err());
}
}
#[cfg(test)]
mod tests_extended {
use super::*;
#[test]
fn test_error_chaining() {
let error1 = OptionsError::validation_error("strike", "invalid value");
let error2: OptionsError = error1.to_string().into();
match error2 {
OptionsError::ValidationError { field, reason } => {
assert!(reason.contains("invalid value"));
assert_eq!(field, "unknown");
}
_ => panic!("Expected ValidationError"),
}
let error3 = OptionsError::validation_error("price", "must be positive");
let error4: OptionsError = error3.to_string().as_str().into();
match error4 {
OptionsError::ValidationError { field, reason } => {
assert!(reason.contains("must be positive"));
assert_eq!(field, "unknown");
}
_ => panic!("Expected ValidationError"),
}
}
#[test]
fn test_multiple_conversions() {
let io_error = std::io::Error::other("test error");
let boxed: Box<dyn std::error::Error> = Box::new(io_error);
let error: OptionsError = boxed.into();
assert!(matches!(error, OptionsError::OtherError { .. }));
}
#[test]
fn test_complex_error_scenario() {
fn nested_function() -> OptionsResult<()> {
Err(OptionsError::validation_error("nested", "inner error"))
}
fn outer_function() -> OptionsResult<()> {
nested_function().map_err(|e| OptionsError::time_error("outer", &e.to_string()))
}
let result = outer_function();
assert!(result.is_err());
let error = result.unwrap_err();
assert!(matches!(error, OptionsError::TimeError { .. }));
}
#[test]
fn test_validation_combinations() {
let errors = vec![
OptionsError::validation_error("price", "negative value"),
OptionsError::validation_error("strike", "too high"),
OptionsError::validation_error("expiry", "past date"),
];
for error in errors {
match error {
OptionsError::ValidationError { field, reason } => {
assert!(!field.is_empty());
assert!(!reason.is_empty());
}
_ => panic!("Expected ValidationError"),
}
}
}
#[test]
fn test_pricing_error_variants() {
let methods = ["black_scholes", "binomial", "monte_carlo"];
let reasons = ["invalid vol", "negative rate", "bad params"];
for (method, reason) in methods.iter().zip(reasons.iter()) {
let error = OptionsError::pricing_error(method, reason);
let error_str = error.to_string();
assert!(error_str.contains(method));
assert!(error_str.contains(reason));
}
}
#[test]
fn test_error_conversion_preservation() {
let original = "preserve this message";
let error1: OptionsError = original.into();
let error2: OptionsError = error1.to_string().into();
assert!(error2.to_string().contains(original));
}
#[test]
fn test_option_result_operations() {
let success: OptionsResult<i32> = Ok(42);
let failure: OptionsResult<i32> = Err(OptionsError::validation_error("test", "error"));
let mapped_success = success.map(|x| x * 2);
let mapped_failure = failure.map(|x| x * 2);
assert_eq!(mapped_success.unwrap(), 84);
assert!(mapped_failure.is_err());
}
#[test]
fn test_nested_error_handling() {
fn process_value(value: i32) -> OptionsResult<i32> {
if value < 0 {
Err(OptionsError::validation_error("value", "must be positive"))
} else {
Ok(value)
}
}
let results: Vec<OptionsResult<i32>> =
vec![-1, 0, 1].into_iter().map(process_value).collect();
assert_eq!(results.len(), 3);
assert!(results[0].is_err());
assert!(results[1].is_ok());
assert!(results[2].is_ok());
}
#[test]
fn test_options_error_greeks_calculation_error() {
let error = OptionsError::GreeksCalculationError {
greek: "Delta".to_string(),
reason: "Division by zero".to_string(),
};
assert_eq!(
format!("{error}"),
"Error calculating greek 'Delta': Division by zero"
);
}
#[test]
fn test_options_error_time_error() {
let error = OptionsError::TimeError {
operation: "Option maturity".to_string(),
reason: "Negative time value".to_string(),
};
assert_eq!(
format!("{error}"),
"Time calculation error in 'Option maturity': Negative time value"
);
}
#[test]
fn test_options_error_payoff_error() {
let error = OptionsError::PayoffError {
reason: "Payoff cannot be negative".to_string(),
};
assert_eq!(
format!("{error}"),
"Payoff calculation error: Payoff cannot be negative"
);
}
#[test]
fn test_options_error_update_error() {
let error = OptionsError::UpdateError {
field: "Volatility".to_string(),
reason: "Invalid update value".to_string(),
};
assert_eq!(
format!("{error}"),
"Update error for field 'Volatility': Invalid update value"
);
}
}