use thiserror::Error;
#[derive(Error, Debug)]
pub enum PositiveError {
#[error("Invalid positive value {value}: {reason}")]
InvalidValue {
value: f64,
reason: String,
},
#[error("Arithmetic error during {operation}: {reason}")]
ArithmeticError {
operation: String,
reason: String,
},
#[error("Failed to convert from {from_type} to {to_type}: {reason}")]
ConversionError {
from_type: String,
to_type: String,
reason: String,
},
#[error("Value {value} is out of bounds (min: {min}, max: {max})")]
OutOfBounds {
value: f64,
min: f64,
max: f64,
},
#[error("Invalid precision {precision}: {reason}")]
InvalidPrecision {
precision: i32,
reason: String,
},
#[error("Positive error: {0}")]
Other(String),
}
pub type PositiveResult<T> = Result<T, PositiveError>;
impl PositiveError {
#[must_use]
pub fn invalid_value(value: f64, reason: &str) -> Self {
PositiveError::InvalidValue {
value,
reason: reason.to_string(),
}
}
#[must_use]
pub fn arithmetic_error(operation: &str, reason: &str) -> Self {
PositiveError::ArithmeticError {
operation: operation.to_string(),
reason: reason.to_string(),
}
}
#[must_use]
pub fn conversion_error(from_type: &str, to_type: &str, reason: &str) -> Self {
PositiveError::ConversionError {
from_type: from_type.to_string(),
to_type: to_type.to_string(),
reason: reason.to_string(),
}
}
#[must_use]
pub fn out_of_bounds(value: f64, min: f64, max: f64) -> Self {
PositiveError::OutOfBounds { value, min, max }
}
#[must_use]
pub fn invalid_precision(precision: i32, reason: &str) -> Self {
PositiveError::InvalidPrecision {
precision,
reason: reason.to_string(),
}
}
}
impl From<&str> for PositiveError {
fn from(s: &str) -> Self {
PositiveError::Other(s.to_string())
}
}
impl From<String> for PositiveError {
fn from(s: String) -> Self {
PositiveError::Other(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_value_error() {
let error = PositiveError::invalid_value(-1.0, "Value cannot be negative");
assert!(matches!(error, PositiveError::InvalidValue { .. }));
assert!(error.to_string().contains("cannot be negative"));
}
#[test]
fn test_arithmetic_error() {
let error = PositiveError::arithmetic_error("subtraction", "Result would be negative");
assert!(matches!(error, PositiveError::ArithmeticError { .. }));
assert!(error.to_string().contains("would be negative"));
}
#[test]
fn test_conversion_error() {
let error = PositiveError::conversion_error("f64", "Positive", "Value out of range");
assert!(matches!(error, PositiveError::ConversionError { .. }));
assert!(error.to_string().contains("out of range"));
}
#[test]
fn test_out_of_bounds_error() {
let error = PositiveError::out_of_bounds(-5.0, 0.0, 100.0);
assert!(matches!(error, PositiveError::OutOfBounds { .. }));
assert!(error.to_string().contains("-5"));
}
#[test]
fn test_invalid_precision_error() {
let error = PositiveError::invalid_precision(-1, "Precision must be non-negative");
assert!(matches!(error, PositiveError::InvalidPrecision { .. }));
assert!(error.to_string().contains("non-negative"));
}
#[test]
fn test_from_str() {
let error: PositiveError = "Custom error message".into();
assert!(matches!(error, PositiveError::Other(_)));
assert!(error.to_string().contains("Custom error message"));
}
#[test]
fn test_from_string() {
let error: PositiveError = String::from("Another error").into();
assert!(matches!(error, PositiveError::Other(_)));
assert!(error.to_string().contains("Another error"));
}
}