2023-11-11 16:27:40 +01:00
|
|
|
use std::error::Error;
|
|
|
|
|
use std::fmt::{Debug, Display, Formatter};
|
2024-02-01 09:18:00 +01:00
|
|
|
use chrono::ParseError;
|
2026-10-12 07:51:29 +02:00
|
|
|
use leptos::prelude::ServerFnError;
|
2024-02-23 14:00:31 +01:00
|
|
|
use log::error;
|
2023-11-11 16:27:40 +01:00
|
|
|
|
2024-02-01 09:18:00 +01:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
2023-11-11 16:27:40 +01:00
|
|
|
pub enum AppError {
|
2023-12-08 17:02:00 +01:00
|
|
|
HourParseError,
|
|
|
|
|
ServerError(String),
|
2024-02-01 09:18:00 +01:00
|
|
|
FatalError(String),
|
2024-02-12 17:17:26 +01:00
|
|
|
SlotParseError,
|
|
|
|
|
MailAddrParseErr(String),
|
2024-02-22 21:54:00 +01:00
|
|
|
MailSendError(String),
|
|
|
|
|
FileIOError(String)
|
2023-11-11 16:27:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AppError {
|
|
|
|
|
fn as_string(&self) -> String {
|
2023-12-08 17:02:00 +01:00
|
|
|
match self {
|
|
|
|
|
AppError::HourParseError => {"Hour parse error".to_string()},
|
|
|
|
|
AppError::ServerError(e) => {format!("Server error: {}", e)},
|
2024-02-01 09:18:00 +01:00
|
|
|
AppError::FatalError(e) => {format!("Fatal error: {}", e)},
|
2024-02-12 17:17:26 +01:00
|
|
|
AppError::SlotParseError => {"Book slot parse error".to_string()},
|
|
|
|
|
AppError::MailAddrParseErr(e) => {format!("Cannot parse email address: {}", e)},
|
2024-02-22 21:54:00 +01:00
|
|
|
AppError::MailSendError(e) => {format!("Cannot send email: {}", e)},
|
|
|
|
|
AppError::FileIOError(e) => {format!("IO error: {}", e)}
|
2023-12-08 17:02:00 +01:00
|
|
|
}
|
2023-11-11 16:27:40 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-23 14:00:31 +01:00
|
|
|
fn log_error(error: &dyn ToString) {
|
|
|
|
|
error!("Application error: {}", error.to_string());
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-11 16:27:40 +01:00
|
|
|
impl Display for AppError {
|
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
write!(f, "{}", self.as_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-08 17:02:00 +01:00
|
|
|
impl Error for AppError {}
|
|
|
|
|
|
|
|
|
|
impl From<ServerFnError> for AppError {
|
|
|
|
|
fn from(value: ServerFnError) -> Self {
|
2024-02-23 14:00:31 +01:00
|
|
|
log_error(&value);
|
2023-12-08 17:02:00 +01:00
|
|
|
AppError::ServerError(value.to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "ssr")]
|
|
|
|
|
impl From<sqlx::Error> for AppError {
|
|
|
|
|
fn from(value: sqlx::Error) -> Self {
|
2024-02-23 14:00:31 +01:00
|
|
|
log_error(&value);
|
2023-12-08 17:02:00 +01:00
|
|
|
AppError::FatalError(value.to_string())
|
|
|
|
|
}
|
2024-02-01 09:18:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<ParseError> for AppError {
|
2024-02-23 14:00:31 +01:00
|
|
|
fn from(value: ParseError) -> Self {
|
|
|
|
|
log_error(&value);
|
2024-02-01 09:18:00 +01:00
|
|
|
AppError::HourParseError
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-12 17:17:26 +01:00
|
|
|
|
|
|
|
|
#[cfg(feature = "ssr")]
|
|
|
|
|
impl From<lettre::address::AddressError> for AppError {
|
|
|
|
|
fn from(value: lettre::address::AddressError) -> Self {
|
2024-02-23 14:00:31 +01:00
|
|
|
log_error(&value);
|
2024-02-12 17:17:26 +01:00
|
|
|
AppError::MailAddrParseErr(value.to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "ssr")]
|
|
|
|
|
impl From<lettre::error::Error> for AppError {
|
|
|
|
|
fn from(value: lettre::error::Error) -> Self {
|
2024-02-23 14:00:31 +01:00
|
|
|
log_error(&value);
|
2024-02-12 17:17:26 +01:00
|
|
|
AppError::MailSendError(value.to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "ssr")]
|
|
|
|
|
impl From<lettre::transport::smtp::Error> for AppError {
|
|
|
|
|
fn from(value: lettre::transport::smtp::Error) -> Self {
|
2024-02-23 14:00:31 +01:00
|
|
|
log_error(&value);
|
2024-02-12 17:17:26 +01:00
|
|
|
AppError::MailSendError(value.to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "ssr")]
|
|
|
|
|
impl From<lettre::transport::file::Error> for AppError {
|
|
|
|
|
fn from(value: lettre::transport::file::Error) -> Self {
|
2024-02-23 14:00:31 +01:00
|
|
|
log_error(&value);
|
2024-02-12 17:17:26 +01:00
|
|
|
AppError::MailSendError(value.to_string())
|
|
|
|
|
}
|
2024-02-22 21:54:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "ssr")]
|
|
|
|
|
impl From<std::io::Error> for AppError {
|
|
|
|
|
fn from(value: std::io::Error) -> Self {
|
2024-02-23 14:00:31 +01:00
|
|
|
log_error(&value);
|
2024-02-22 21:54:00 +01:00
|
|
|
AppError::FileIOError(value.to_string())
|
|
|
|
|
}
|
2026-10-10 17:01:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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"));
|
|
|
|
|
}
|
|
|
|
|
}
|