This commit is contained in:
Hamcha 2023-11-18 17:52:15 +01:00
parent b90e6ae744
commit c961c2ae23
6 changed files with 96 additions and 41 deletions

View file

@ -1,12 +1,8 @@
use askama::Template;
use axum::{ use axum::{
extract::rejection::JsonRejection, extract::rejection::JsonRejection,
http::{header::ACCEPT, Request, StatusCode}, http::StatusCode,
middleware::Next,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
Json,
}; };
use serde_json::json;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -23,18 +19,14 @@ pub enum AppError {
#[error("incoming JSON format error: {0}")] #[error("incoming JSON format error: {0}")]
JSONFormat(#[from] JsonRejection), JSONFormat(#[from] JsonRejection),
#[error("template error: {0}")]
Template(#[from] askama::Error),
} }
struct ErrorInfo { pub(super) struct ErrorInfo {
code: String, pub(super) code: String,
message: String, pub(super) message: String,
}
#[derive(Template)]
#[template(path = "error.html")]
struct ErrorTemplate {
code: String,
message: String,
} }
impl IntoResponse for AppError { impl IntoResponse for AppError {
@ -73,6 +65,13 @@ impl IntoResponse for AppError {
}, },
) )
} }
AppError::Template(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
ErrorInfo {
code: "template-error".to_string(),
message: err.to_string(),
},
),
}; };
let mut response = status.into_response(); let mut response = status.into_response();
@ -80,23 +79,3 @@ impl IntoResponse for AppError {
response 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 +1,2 @@
pub mod error; pub mod error;
pub mod response;

65
src/http/response.rs Normal file
View file

@ -0,0 +1,65 @@
use askama::Template;
use askama_axum::IntoResponse;
use axum::{
http::{header::ACCEPT, Request, StatusCode},
middleware::Next,
response::Html,
Json,
};
use serde_json::json;
use super::error::{AppError, ErrorInfo};
struct Response {
html: String,
json: serde_json::Value,
}
#[derive(Template)]
#[template(path = "error.html")]
struct ErrorTemplate {
code: String,
message: String,
}
pub fn reply<T: Template>(
html: T,
json: serde_json::Value,
) -> Result<axum::response::Response, AppError> {
let mut response = StatusCode::OK.into_response();
response.extensions_mut().insert(Response {
html: html.render()?,
json,
});
Ok(response)
}
pub async fn response_interceptor<B>(
request: Request<B>,
next: Next<B>,
) -> axum::response::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(),
}
}
if let Some(Response { html, json }) = response.extensions_mut().remove::<Response>() {
match accept_header.as_deref() {
Some(b"application/json") => return Json(json).into_response(),
_ => return Html(html).into_response(),
}
}
response
}

View file

@ -4,7 +4,7 @@ use bollard::Docker;
use clap::Parser; use clap::Parser;
use std::net::SocketAddr; use std::net::SocketAddr;
use crate::http::error::error_interceptor; use crate::http::response::response_interceptor;
mod http; mod http;
mod route; mod route;
@ -52,7 +52,7 @@ async fn main() -> Result<()> {
let app = route::router() let app = route::router()
.with_state(state) .with_state(state)
.layer(from_fn(error_interceptor)); .layer(from_fn(response_interceptor));
axum::Server::bind(&args.bind) axum::Server::bind(&args.bind)
.serve(app.into_make_service()) .serve(app.into_make_service())

View file

@ -1,7 +1,12 @@
use askama::Template; use askama::Template;
use askama_axum::IntoResponse;
use axum::{extract::State, routing::get, Router}; use axum::{extract::State, routing::get, Router};
use serde_json::json;
use crate::{http::error::AppError, stack, AppState}; use crate::{
http::{error::AppError, response::reply},
stack, AppState,
};
#[derive(Template)] #[derive(Template)]
#[template(path = "home.html")] #[template(path = "home.html")]
@ -9,11 +14,16 @@ struct HomeTemplate {
stacks: Vec<String>, stacks: Vec<String>,
} }
async fn home(State(state): State<AppState>) -> Result<HomeTemplate, AppError> { async fn home(State(state): State<AppState>) -> Result<impl IntoResponse, 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)?;
Ok(HomeTemplate { stacks: list }) reply(
HomeTemplate {
stacks: list.clone(),
},
json!({ "stacks": list}),
)
} }
pub(super) fn router() -> Router<AppState> { pub(super) fn router() -> Router<AppState> {

View file

@ -5,7 +5,7 @@
{% block content %} {% block content %}
<main> <main>
{% for stack in stacks %} {% for stack in stacks %}
<li>{{stack}}</li> <li><a href="/stack/{{stack}}">{{stack}}</a></li>
{% endfor %} {% endfor %}
</main> </main>
{% endblock %} {% endblock %}