use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use chrono::ParseError; use leptos::prelude::ServerFnError; use log::error; #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum AppError { HourParseError, ServerError(String), FatalError(String), SlotParseError, MailAddrParseErr(String), MailSendError(String), FileIOError(String) } impl AppError { fn as_string(&self) -> String { match self { AppError::HourParseError => {"Hour parse error".to_string()}, AppError::ServerError(e) => {format!("Server error: {}", e)}, AppError::FatalError(e) => {format!("Fatal error: {}", e)}, AppError::SlotParseError => {"Book slot parse error".to_string()}, AppError::MailAddrParseErr(e) => {format!("Cannot parse email address: {}", e)}, AppError::MailSendError(e) => {format!("Cannot send email: {}", e)}, AppError::FileIOError(e) => {format!("IO error: {}", e)} } } } fn log_error(error: &dyn ToString) { error!("Application error: {}", error.to_string()); } impl Display for AppError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.as_string()) } } impl Error for AppError {} impl From for AppError { fn from(value: ServerFnError) -> Self { log_error(&value); AppError::ServerError(value.to_string()) } } #[cfg(feature = "ssr")] impl From for AppError { fn from(value: sqlx::Error) -> Self { log_error(&value); AppError::FatalError(value.to_string()) } } impl From for AppError { fn from(value: ParseError) -> Self { log_error(&value); AppError::HourParseError } } #[cfg(feature = "ssr")] impl From for AppError { fn from(value: lettre::address::AddressError) -> Self { log_error(&value); AppError::MailAddrParseErr(value.to_string()) } } #[cfg(feature = "ssr")] impl From for AppError { fn from(value: lettre::error::Error) -> Self { log_error(&value); AppError::MailSendError(value.to_string()) } } #[cfg(feature = "ssr")] impl From for AppError { fn from(value: lettre::transport::smtp::Error) -> Self { log_error(&value); AppError::MailSendError(value.to_string()) } } #[cfg(feature = "ssr")] impl From for AppError { fn from(value: lettre::transport::file::Error) -> Self { log_error(&value); AppError::MailSendError(value.to_string()) } } #[cfg(feature = "ssr")] impl From for AppError { fn from(value: std::io::Error) -> Self { log_error(&value); AppError::FileIOError(value.to_string()) } } #[cfg(test)] mod tests { use super::*; use chrono::NaiveTime; #[test] fn error_messages() { assert_eq!(AppError::HourParseError.to_string(), "Hour parse error"); assert_eq!(AppError::SlotParseError.to_string(), "Book slot parse error"); assert_eq!(AppError::ServerError("boom".to_string()).to_string(), "Server error: boom"); assert_eq!(AppError::FatalError("boom".to_string()).to_string(), "Fatal error: boom"); assert_eq!(AppError::MailAddrParseErr("boom".to_string()).to_string(), "Cannot parse email address: boom"); assert_eq!(AppError::MailSendError("boom".to_string()).to_string(), "Cannot send email: boom"); assert_eq!(AppError::FileIOError("boom".to_string()).to_string(), "IO error: boom"); } #[test] fn time_parse_error_becomes_hour_parse_error() { let err = NaiveTime::parse_from_str("nonsense", "%H:%M").unwrap_err(); assert_eq!(AppError::from(err), AppError::HourParseError); } #[test] fn server_fn_error_keeps_message() { let err = AppError::from(ServerFnError::new("boom")); assert!(matches!(err, AppError::ServerError(ref m) if m.contains("boom"))); } #[cfg(feature = "ssr")] #[test] fn io_error_becomes_file_io_error() { let err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing"); assert!(matches!(AppError::from(err), AppError::FileIOError(ref m) if m.contains("missing"))); } #[test] fn app_error_converts_to_server_fn_error() { let err: ServerFnError = AppError::HourParseError.into(); assert!(err.to_string().contains("Hour parse error")); } }