//! Handle high-level errors that should be converted into HTTP //! responses. //! //! The [Error] type defined in this module is used by route handlers //! to convert errors into HTTP responses. Some error types (such as //! [db::Error]) are automatcially converted into [Error]. [Error] //! also supplies some constructors, such as [Error::new_unexpected()] //! and [Error::new_unauthorized] that can be used directly. //! //! The intended use is that error types which have [From] defined //! might sometimes be handled at a higher level. If an error should //! always be handled here then other modules should call the //! appropriate [Error] constructor directly. use { crate::{authentication, db}, askama::Template, askama_axum::{IntoResponse, Response}, http::status::StatusCode, tracing::{debug, error, info}, }; #[derive(Debug)] enum ErrorType { InternalServerError, Unauthorized, JwtExpired(db::User), } impl std::fmt::Display for ErrorType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ErrorType::InternalServerError => write!(f, "InternalServerError"), ErrorType::Unauthorized => write!(f, "Unauthorized"), ErrorType::JwtExpired(user) => write!(f, "JWT Expired for {:?}", user), } } } /// Error which should be converted into a HTTP response #[derive(Debug)] pub struct Error { error_type: ErrorType, inner: Option>, } impl Error { /// An unexpected error has occurred which prevented the request /// from being handled properly.. pub fn new_unexpected(message: &str) -> Error { error!("Unexpected error: {}", message); Error { error_type: ErrorType::InternalServerError, inner: None, } } /// Either authorization failed or the current user is not /// authorized to perform the requested action pub fn new_unauthorized() -> Error { info!("Unauthorized user"); Error { error_type: ErrorType::Unauthorized, inner: None, } } /// The user's JWT has expired and the they must log in again pub fn new_jwt_expired(user: db::User) -> Error { debug!("Jwt Expired"); Error { error_type: ErrorType::JwtExpired(user), inner: None, } } } impl From for Error { fn from(value: db::Error) -> Self { Error { error_type: ErrorType::InternalServerError, inner: Some(Box::new(value)), } } } impl From for Error { fn from(value: authentication::JwtError) -> Self { error!(inner = value.to_string(), "Unhandled JWT error"); Error { error_type: ErrorType::InternalServerError, inner: Some(Box::new(value)), } } } impl From for Error { fn from(value: authentication::AuthenticationError) -> Self { error!(inner = value.to_string(), "Error during authentication"); Error { error_type: ErrorType::InternalServerError, inner: Some(Box::new(value)), } } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error { error_type, inner: Some(inner_err), } => write!(f, "{}: {}", error_type, inner_err), Error { error_type, inner: None, } => write!(f, "{}", error_type), } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self.inner { Some(inner) => Some(inner.as_ref()), None => None, } } } #[derive(Template)] #[template(path = "error.html")] struct ErrorTemplate {} impl IntoResponse for Error { fn into_response(self) -> Response { match self.error_type { ErrorType::InternalServerError => { error!(inner = self.inner, "Uncaught error producing HTTP 500."); (StatusCode::INTERNAL_SERVER_ERROR, ErrorTemplate {}).into_response() } ErrorType::Unauthorized => { (StatusCode::UNAUTHORIZED, "User not authorized.").into_response() } ErrorType::JwtExpired(_) => { todo!() } } } }