use thiserror::Error;
#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
pub enum TempsError {
#[error("Failed to parse time expression: {message}")]
ParseError {
message: String,
input: String,
position: Option<usize>,
},
#[error("Date calculation error: {message}")]
DateCalculationError {
message: String,
context: Option<String>,
},
#[error("Invalid date: year={year}, month={month}, day={day}")]
InvalidDate {
year: u16,
month: u8,
day: u8,
},
#[error("Invalid time: {hour:02}:{minute:02}:{second:02}")]
InvalidTime {
hour: u8,
minute: u8,
second: u8,
},
#[error("Invalid timezone offset: {hours:+03}:{minutes:02}")]
InvalidTimezoneOffset {
hours: i8,
minutes: u8,
},
#[error("Ambiguous local time: {message}")]
AmbiguousTime {
message: String,
},
#[error("Arithmetic overflow: {operation}")]
ArithmeticOverflow {
operation: String,
},
#[error("Unsupported operation: {operation}")]
UnsupportedOperation {
operation: String,
},
#[error("Backend error: {message}")]
BackendError {
message: String,
backend: String,
},
}
impl TempsError {
#[must_use]
pub fn parse_error(message: impl Into<String>, input: impl Into<String>) -> Self {
Self::ParseError {
message: message.into(),
input: input.into(),
position: None,
}
}
#[must_use]
pub fn parse_error_with_position(
message: impl Into<String>,
input: impl Into<String>,
position: usize,
) -> Self {
Self::ParseError {
message: message.into(),
input: input.into(),
position: Some(position),
}
}
#[must_use]
pub fn date_calculation(message: impl Into<String>) -> Self {
Self::DateCalculationError {
message: message.into(),
context: None,
}
}
#[must_use]
pub fn date_calculation_with_source(
message: impl Into<String>,
context: impl Into<String>,
) -> Self {
Self::DateCalculationError {
message: message.into(),
context: Some(context.into()),
}
}
#[must_use]
pub fn invalid_date(year: u16, month: u8, day: u8) -> Self {
Self::InvalidDate { year, month, day }
}
#[must_use]
pub fn invalid_time(hour: u8, minute: u8, second: u8) -> Self {
Self::InvalidTime {
hour,
minute,
second,
}
}
#[must_use]
pub fn invalid_timezone_offset(hours: i8, minutes: u8) -> Self {
Self::InvalidTimezoneOffset { hours, minutes }
}
#[must_use]
pub fn ambiguous_time(message: impl Into<String>) -> Self {
Self::AmbiguousTime {
message: message.into(),
}
}
#[must_use]
pub fn arithmetic_overflow(operation: impl Into<String>) -> Self {
Self::ArithmeticOverflow {
operation: operation.into(),
}
}
#[must_use]
pub fn unsupported_operation(operation: impl Into<String>) -> Self {
Self::UnsupportedOperation {
operation: operation.into(),
}
}
#[must_use]
pub fn backend_error(message: impl Into<String>, backend: impl Into<String>) -> Self {
Self::BackendError {
message: message.into(),
backend: backend.into(),
}
}
}
pub type Result<T> = std::result::Result<T, TempsError>;
pub trait ParseErrorExt {
fn to_temps_error(self, input: &str) -> TempsError;
}
impl ParseErrorExt for winnow::error::ParseError<&str, winnow::error::ContextError> {
fn to_temps_error(self, input: &str) -> TempsError {
let position = self.offset();
let message = format!("Parser error: {self}");
TempsError::parse_error_with_position(message, input, position)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = TempsError::invalid_date(2024, 13, 32);
assert_eq!(err.to_string(), "Invalid date: year=2024, month=13, day=32");
let err = TempsError::invalid_time(25, 61, 61);
assert_eq!(err.to_string(), "Invalid time: 25:61:61");
let err = TempsError::parse_error("unexpected token", "in 5 minuts");
assert_eq!(
err.to_string(),
"Failed to parse time expression: unexpected token"
);
}
#[test]
fn test_error_creation_helpers() {
let err = TempsError::date_calculation("month out of range");
match err {
TempsError::DateCalculationError { message, context } => {
assert_eq!(message, "month out of range");
assert!(context.is_none());
}
_ => panic!("Wrong error type"),
}
let err = TempsError::backend_error("conversion failed", "chrono");
match err {
TempsError::BackendError { message, backend } => {
assert_eq!(message, "conversion failed");
assert_eq!(backend, "chrono");
}
_ => panic!("Wrong error type"),
}
}
}