use crate::model::types::{OptionType, Side};
use crate::model::{SettlementEvent, SettlementType};
use chrono::{DateTime, Utc};
use thiserror::Error;
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SettlementError {
#[error(
"unsupported option type for settlement: {option_type} (supported: European, American)"
)]
UnsupportedOptionType {
option_type: OptionType,
},
#[error("missing settlement price for a {settlement_type:?} {event:?} event")]
MissingPrice {
settlement_type: SettlementType,
event: SettlementEvent,
},
#[error("settlement price observed at {observed_at} is after the cutoff {cutoff}")]
PriceAfterCutoff {
observed_at: DateTime<Utc>,
cutoff: DateTime<Utc>,
},
#[error("settlement event {event:?} is incompatible with a {side} position")]
EventSideMismatch {
event: SettlementEvent,
side: Side,
},
#[error("settlement arithmetic overflow in {operation}: {reason}")]
ArithmeticOverflow {
operation: &'static str,
reason: String,
},
}
#[cfg(test)]
mod tests_settlement_error {
use super::*;
use chrono::TimeZone;
#[test]
fn test_settlement_error_unsupported_option_type_names_the_type() {
let error = SettlementError::UnsupportedOptionType {
option_type: OptionType::Cliquet {
reset_dates: Vec::new(),
},
};
assert_eq!(
error.to_string(),
"unsupported option type for settlement: Cliquet Option (Reset Dates: []) (supported: European, American)"
);
}
#[test]
fn test_settlement_error_missing_price_names_the_event() {
let error = SettlementError::MissingPrice {
settlement_type: SettlementType::Physical,
event: SettlementEvent::Expiry,
};
assert_eq!(
error.to_string(),
"missing settlement price for a Physical Expiry event"
);
}
#[test]
fn test_settlement_error_price_after_cutoff_names_both_instants() {
let cutoff = Utc.timestamp_opt(1_000, 0).single();
let observed_at = Utc.timestamp_opt(2_000, 0).single();
assert!(cutoff.is_some() && observed_at.is_some());
if let (Some(cutoff), Some(observed_at)) = (cutoff, observed_at) {
let text = SettlementError::PriceAfterCutoff {
observed_at,
cutoff,
}
.to_string();
assert!(text.contains(&observed_at.to_string()));
assert!(text.contains(&cutoff.to_string()));
}
}
#[test]
fn test_settlement_error_event_side_mismatch_names_event_and_side() {
let error = SettlementError::EventSideMismatch {
event: SettlementEvent::Assignment,
side: Side::Long,
};
assert_eq!(
error.to_string(),
"settlement event Assignment is incompatible with a Long position"
);
}
#[test]
fn test_settlement_error_overflow_names_the_operation() {
let error = SettlementError::ArithmeticOverflow {
operation: "physical cash",
reason: "out of range".to_string(),
};
assert_eq!(
error.to_string(),
"settlement arithmetic overflow in physical cash: out of range"
);
}
}