use thiserror::Error;
#[derive(Error, Debug)]
pub enum DecimalError {
#[error("Invalid decimal value {value}: {reason}")]
InvalidValue {
value: f64,
reason: String,
},
#[error("Decimal arithmetic error during {operation}: {reason}")]
ArithmeticError {
operation: String,
reason: String,
},
#[error("Failed to convert decimal from {from_type} to {to_type}: {reason}")]
ConversionError {
from_type: String,
to_type: String,
reason: String,
},
#[error("Decimal value {value} is out of bounds (min: {min}, max: {max})")]
OutOfBounds {
value: f64,
min: f64,
max: f64,
},
#[error("Invalid decimal precision {precision}: {reason}")]
InvalidPrecision {
precision: i32,
reason: String,
},
#[error("Other decimal error: {0}")]
Other(String),
}
pub type DecimalResult<T> = Result<T, DecimalError>;
impl DecimalError {
pub fn invalid_value(value: f64, reason: &str) -> Self {
DecimalError::InvalidValue {
value,
reason: reason.to_string(),
}
}
pub fn arithmetic_error(operation: &str, reason: &str) -> Self {
DecimalError::ArithmeticError {
operation: operation.to_string(),
reason: reason.to_string(),
}
}
pub fn conversion_error(from_type: &str, to_type: &str, reason: &str) -> Self {
DecimalError::ConversionError {
from_type: from_type.to_string(),
to_type: to_type.to_string(),
reason: reason.to_string(),
}
}
pub fn out_of_bounds(value: f64, min: f64, max: f64) -> Self {
DecimalError::OutOfBounds { value, min, max }
}
pub fn invalid_precision(precision: i32, reason: &str) -> Self {
DecimalError::InvalidPrecision {
precision,
reason: reason.to_string(),
}
}
}
impl From<&str> for DecimalError {
fn from(s: &str) -> Self {
DecimalError::Other(s.to_string())
}
}
impl From<expiration_date::error::ExpirationDateError> for DecimalError {
fn from(err: expiration_date::error::ExpirationDateError) -> Self {
DecimalError::Other(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_value_error() {
let error = DecimalError::invalid_value(-1.0, "Value cannot be negative");
assert!(matches!(error, DecimalError::InvalidValue { .. }));
assert!(error.to_string().contains("cannot be negative"));
}
#[test]
fn test_arithmetic_error() {
let error = DecimalError::arithmetic_error("division", "Division by zero");
assert!(matches!(error, DecimalError::ArithmeticError { .. }));
assert!(error.to_string().contains("Division by zero"));
}
#[test]
fn test_conversion_error() {
let error = DecimalError::conversion_error("f64", "Decimal", "Value out of range");
assert!(matches!(error, DecimalError::ConversionError { .. }));
assert!(error.to_string().contains("out of range"));
}
#[test]
fn test_out_of_bounds_error() {
let error = DecimalError::out_of_bounds(150.0, 0.0, 100.0);
assert!(matches!(error, DecimalError::OutOfBounds { .. }));
assert!(error.to_string().contains("150"));
}
#[test]
fn test_invalid_precision_error() {
let error = DecimalError::invalid_precision(-1, "Precision must be non-negative");
assert!(matches!(error, DecimalError::InvalidPrecision { .. }));
assert!(error.to_string().contains("non-negative"));
}
}