add error middleware

This commit is contained in:
Hamcha 2023-11-18 16:58:28 +01:00
parent 074c1c3980
commit b90e6ae744
3 changed files with 63 additions and 29 deletions

View file

@ -1,19 +1,21 @@
use askama::Template; use askama::Template;
use axum::{ use axum::{
extract::rejection::JsonRejection, extract::rejection::JsonRejection,
http::StatusCode, http::{header::ACCEPT, Request, StatusCode},
middleware::Next,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
Json, Json,
}; };
use serde_json::json;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum AppError<'a> { pub enum AppError {
#[error("client error: <{code}> {message}")] #[error("client error: <{code}> {message}")]
Client { Client {
status: StatusCode, status: StatusCode,
code: &'a str, code: &'static str,
message: &'a str, message: &'static str,
}, },
#[error("unexpected internal error: {0}")] #[error("unexpected internal error: {0}")]
@ -23,34 +25,39 @@ pub enum AppError<'a> {
JSONFormat(#[from] JsonRejection), JSONFormat(#[from] JsonRejection),
} }
#[derive(Template)] struct ErrorInfo {
#[template(path = "error.html")] code: String,
struct ErrorTemplate<'a> { message: String,
code: &'a str,
message: &'a str,
} }
impl IntoResponse for AppError<'_> { #[derive(Template)]
#[template(path = "error.html")]
struct ErrorTemplate {
code: String,
message: String,
}
impl IntoResponse for AppError {
fn into_response(self) -> Response { fn into_response(self) -> Response {
match self { let (status, info) = match self {
AppError::Internal(err) => ( AppError::Internal(err) => (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
ErrorTemplate { ErrorInfo {
code: "server-error", code: "server-error".to_string(),
message: &err.to_string(), message: err.to_string(),
}, },
// Json(json!({"code":"server-error", "message": err.to_string()})), ),
)
.into_response(),
AppError::Client { AppError::Client {
status, status,
code, code,
message, message,
} => ( } => (
status, status,
ErrorTemplate { code, message }, // Json(json!({"code":code, "message": message})) ErrorInfo {
) code: code.to_string(),
.into_response(), message: message.to_string(),
},
),
AppError::JSONFormat(rejection) => { AppError::JSONFormat(rejection) => {
let status = match rejection { let status = match rejection {
JsonRejection::JsonDataError(_) => StatusCode::UNPROCESSABLE_ENTITY, JsonRejection::JsonDataError(_) => StatusCode::UNPROCESSABLE_ENTITY,
@ -60,13 +67,36 @@ impl IntoResponse for AppError<'_> {
}; };
( (
status, status,
ErrorTemplate { ErrorInfo {
code: "invalid-body", code: "invalid-body".to_string(),
message: &rejection.body_text(), message: rejection.body_text().clone(),
}, // Json(json!({"code":"invalid-body", "message": rejection.body_text()})), },
) )
.into_response()
}
} }
};
let mut response = status.into_response();
response.extensions_mut().insert(info);
response
} }
} }
pub async fn error_interceptor<B>(request: Request<B>, next: Next<B>) -> Response {
let accept_header = request
.headers()
.get(&ACCEPT)
.map(|value| value.as_ref().to_owned());
let mut response = next.run(request).await;
if let Some(ErrorInfo { code, message }) = response.extensions_mut().remove::<ErrorInfo>() {
match accept_header.as_deref() {
Some(b"application/json") => {
return Json(json!({"code": code, "message": message})).into_response()
}
_ => return ErrorTemplate { code, message }.into_response(),
}
}
response
}

View file

@ -1,9 +1,11 @@
use anyhow::Result; use anyhow::Result;
use axum::{routing::get, Router}; use axum::middleware::from_fn;
use bollard::Docker; use bollard::Docker;
use clap::Parser; use clap::Parser;
use std::net::SocketAddr; use std::net::SocketAddr;
use crate::http::error::error_interceptor;
mod http; mod http;
mod route; mod route;
mod stack; mod stack;
@ -48,7 +50,9 @@ async fn main() -> Result<()> {
docker, docker,
}; };
let app = route::router().with_state(state); let app = route::router()
.with_state(state)
.layer(from_fn(error_interceptor));
axum::Server::bind(&args.bind) axum::Server::bind(&args.bind)
.serve(app.into_make_service()) .serve(app.into_make_service())

View file

@ -9,7 +9,7 @@ struct HomeTemplate {
stacks: Vec<String>, stacks: Vec<String>,
} }
async fn home(State(state): State<AppState>) -> Result<HomeTemplate, AppError<'static>> { async fn home(State(state): State<AppState>) -> Result<HomeTemplate, AppError> {
let list = stack::list(&state.stack_dir) let list = stack::list(&state.stack_dir)
.await .await
.map_err(AppError::from)?; .map_err(AppError::from)?;