add error middleware
This commit is contained in:
parent
074c1c3980
commit
b90e6ae744
3 changed files with 63 additions and 29 deletions
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
Reference in a new issue