mod admin; mod health_check; mod home; mod login; mod posts; mod subscriptions; mod subscriptions_confirm; pub use admin::*; use askama::Template; use axum::{ http::HeaderMap, response::{Html, IntoResponse, Response}, }; pub use health_check::*; pub use home::*; pub use login::*; pub use posts::*; use reqwest::StatusCode; pub use subscriptions::*; pub use subscriptions_confirm::*; use crate::{ authentication::AuthError, templates::{InternalErrorTemplate, MessageTemplate, NotFoundTemplate}, }; #[derive(thiserror::Error)] pub enum AppError { #[error("An unexpected error was encountered.")] UnexpectedError { #[source] error: anyhow::Error, full_page: bool, }, #[error("A validation error happened.")] FormError(#[source] anyhow::Error), #[error("Authentication is required.")] NotAuthenticated, } impl From for AppError { fn from(value: anyhow::Error) -> Self { Self::UnexpectedError { error: value, full_page: false, } } } impl AppError { pub fn unexpected_page(error: anyhow::Error) -> Self { Self::UnexpectedError { error, full_page: true, } } pub fn unexpected_message(error: anyhow::Error) -> Self { Self::UnexpectedError { error, full_page: false, } } } impl std::fmt::Debug for AppError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { error_chain_fmt(self, f) } } impl IntoResponse for AppError { fn into_response(self) -> Response { tracing::error!("{:?}", self); match &self { AppError::UnexpectedError { error: _, full_page, } => { let html = if *full_page { Html(InternalErrorTemplate.render().unwrap()) } else { let template = MessageTemplate::Error { message: "An internal server error occured.".into(), }; Html(template.render().unwrap()) }; (StatusCode::INTERNAL_SERVER_ERROR, html).into_response() } AppError::FormError(error) => { let template = MessageTemplate::Error { message: error.to_string(), }; Html(template.render().unwrap()).into_response() } AppError::NotAuthenticated => { let mut headers = HeaderMap::new(); headers.insert("HX-Redirect", "/login".parse().unwrap()); (StatusCode::OK, headers).into_response() } } } } impl From for AppError { fn from(value: AdminError) -> Self { match value { AdminError::UnexpectedError(error) => AppError::unexpected_message(error), AdminError::NotAuthenticated => AppError::NotAuthenticated, AdminError::ChangePassword(s) => AppError::FormError(anyhow::anyhow!(s)), AdminError::Publish(e) => AppError::FormError(e), AdminError::Idempotency(s) => AppError::UnexpectedError { error: anyhow::anyhow!(s), full_page: false, }, } } } impl From for AppError { fn from(value: AuthError) -> Self { match value { AuthError::UnexpectedError(error) => AppError::unexpected_message(error), AuthError::InvalidCredentials(error) => { AppError::FormError(error.context("Invalid credentials.")) } } } } pub async fn not_found() -> Response { ( StatusCode::NOT_FOUND, Html(NotFoundTemplate.render().unwrap()), ) .into_response() }