use std::convert::Infallible;
use axum::{
Extension,
extract::{Request, rejection::ExtensionRejection},
http::HeaderMap,
middleware::{self, Next},
response::{IntoResponse, Response},
};
use maud::DOCTYPE;
use tower::{Layer, Service, layer::util::Stack};
#[derive(Clone)]
pub struct AcceptHtml;
async fn axum_htmlify(headers: HeaderMap, mut request: Request, next: Next) -> Response {
if let Some(accept) = headers.get("accept")
&& let Ok(accept) = accept.to_str()
&& accept.split(',').any(|s| s == "text/html")
{
request.extensions_mut().insert(AcceptHtml);
}
next.run(request).await
}
#[derive(Clone)]
pub struct NoCats;
async fn axum_catify(
extension: Result<Extension<AcceptHtml>, ExtensionRejection>,
request: Request,
next: Next,
) -> Response {
let html = extension.is_ok();
let response = next.run(request).await;
if html
&& response
.headers()
.get("content-type")
.is_none_or(|ty| ty == "text/plain; charset=utf-8")
&& response.extensions().get::<NoCats>().is_none()
{
let code = response.status();
let number = code.as_u16();
let src = format!("https://http.cat/{number}");
let mut headers = response.headers().clone();
headers.remove("content-type");
let html = maud::html! {
(DOCTYPE)
html {
head {
title { (code) }
meta name="viewport" content="width=device-width, initial-scale=1";
meta charset="utf-8";
style {
r#"
html, body {
margin: 0;
background: black;
display: flex;
width: 100vw;
height: 100vh;
}
img {
margin: auto;
}
"#
}
}
body {
img src=(src);
}
}
};
let status = response.status();
(status, headers, html).into_response()
} else {
response
}
}
pub fn htmlify<
I: 'static
+ Send
+ Sync
+ Clone
+ Service<Request, Response: IntoResponse, Error = Infallible, Future: 'static + Send>,
>() -> impl 'static
+ Send
+ Sync
+ Clone
+ Layer<
I,
Service: 'static
+ Send
+ Sync
+ Clone
+ Service<Request, Response = Response, Error = Infallible, Future: 'static + Send>,
> {
middleware::from_fn(axum_htmlify)
}
pub fn catify<
I: 'static
+ Send
+ Sync
+ Clone
+ Service<Request, Response: IntoResponse, Error = Infallible, Future: 'static + Send>,
>() -> impl 'static
+ Send
+ Sync
+ Clone
+ Layer<
I,
Service: 'static
+ Send
+ Sync
+ Clone
+ Service<Request, Response = Response, Error = Infallible, Future: 'static + Send>,
> {
middleware::from_fn(axum_catify)
}
pub fn htmlify_catify<
I: 'static
+ Send
+ Sync
+ Clone
+ Service<Request, Response: IntoResponse, Error = Infallible, Future: 'static + Send>,
>() -> impl 'static
+ Send
+ Sync
+ Clone
+ Layer<
I,
Service: 'static
+ Send
+ Sync
+ Clone
+ Service<Request, Response = Response, Error = Infallible, Future: 'static + Send>,
> {
Stack::new(catify(), htmlify())
}