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 axum::{
extract::rejection::JsonRejection,
http::StatusCode,
http::{header::ACCEPT, Request, StatusCode},
middleware::Next,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError<'a> {
pub enum AppError {
#[error("client error: <{code}> {message}")]
Client {
status: StatusCode,
code: &'a str,
message: &'a str,
code: &'static str,
message: &'static str,
},
#[error("unexpected internal error: {0}")]
@ -23,34 +25,39 @@ pub enum AppError<'a> {
JSONFormat(#[from] JsonRejection),
}
#[derive(Template)]
#[template(path = "error.html")]
struct ErrorTemplate<'a> {
code: &'a str,
message: &'a str,
struct ErrorInfo {
code: String,
message: String,
}
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 {
match self {
let (status, info) = match self {
AppError::Internal(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
ErrorTemplate {
code: "server-error",
message: &err.to_string(),
ErrorInfo {
code: "server-error".to_string(),
message: err.to_string(),
},
// Json(json!({"code":"server-error", "message": err.to_string()})),
)
.into_response(),
),
AppError::Client {
status,
code,
message,
} => (
status,
ErrorTemplate { code, message }, // Json(json!({"code":code, "message": message}))
)
.into_response(),
ErrorInfo {
code: code.to_string(),
message: message.to_string(),
},
),
AppError::JSONFormat(rejection) => {
let status = match rejection {
JsonRejection::JsonDataError(_) => StatusCode::UNPROCESSABLE_ENTITY,
@ -60,13 +67,36 @@ impl IntoResponse for AppError<'_> {
};
(
status,
ErrorTemplate {
code: "invalid-body",
message: &rejection.body_text(),
}, // Json(json!({"code":"invalid-body", "message": rejection.body_text()})),
ErrorInfo {
code: "invalid-body".to_string(),
message: rejection.body_text().clone(),
},
)
.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 axum::{routing::get, Router};
use axum::middleware::from_fn;
use bollard::Docker;
use clap::Parser;
use std::net::SocketAddr;
use crate::http::error::error_interceptor;
mod http;
mod route;
mod stack;
@ -48,7 +50,9 @@ async fn main() -> Result<()> {
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)
.serve(app.into_make_service())

View file

@ -9,7 +9,7 @@ struct HomeTemplate {
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)
.await
.map_err(AppError::from)?;