hehe
This commit is contained in:
parent
b90e6ae744
commit
c961c2ae23
6 changed files with 96 additions and 41 deletions
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod response;
|
||||||
|
|
65
src/http/response.rs
Normal file
65
src/http/response.rs
Normal 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
|
||||||
|
}
|
|
@ -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())
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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 %}
|
Reference in a new issue