locality/src/error.rs

151 lines
4.4 KiB
Rust

//! 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<Box<dyn std::error::Error + Send + Sync>>,
}
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<db::Error> for Error {
fn from(value: db::Error) -> Self {
Error {
error_type: ErrorType::InternalServerError,
inner: Some(Box::new(value)),
}
}
}
impl From<authentication::JwtError> 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<authentication::AuthenticationError> 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!()
}
}
}
}