use std::convert::Infallible;
use askama::Template;
use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use axum::http::{HeaderMap, HeaderName, HeaderValue};
use axum::response::Html;
use crate::error::AppError;
pub fn html<T: Template>(t: &T) -> Result<Html<String>, AppError> {
Ok(Html(t.render()?))
}
pub fn is_htmx(headers: &HeaderMap) -> bool {
headers.get("hx-request").is_some_and(|v| v == "true")
}
#[derive(Debug, Clone, Default)]
pub struct Hx {
pub request: bool,
pub boosted: bool,
pub target: Option<String>,
}
impl Hx {
pub fn partial(&self) -> bool {
self.request && !self.boosted
}
}
impl<S: Send + Sync> FromRequestParts<S> for Hx {
type Rejection = Infallible;
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Hx, Infallible> {
let h = &parts.headers;
Ok(Hx {
request: is_htmx(h),
boosted: h.get("hx-boosted").is_some_and(|v| v == "true"),
target: h
.get("hx-target")
.and_then(|v| v.to_str().ok())
.map(str::to_owned),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToastKind {
Ok,
Info,
Error,
}
impl ToastKind {
fn as_str(self) -> &'static str {
match self {
ToastKind::Ok => "ok",
ToastKind::Info => "info",
ToastKind::Error => "error",
}
}
}
pub fn toast(kind: ToastKind, message: &str) -> (HeaderName, HeaderValue) {
let payload = serde_json::json!({ "toast": { "kind": kind.as_str(), "message": message } });
(
HeaderName::from_static("hx-trigger"),
HeaderValue::from_str(&payload.to_string())
.unwrap_or_else(|_| HeaderValue::from_static("{}")),
)
}
pub fn hx_redirect(to: &str) -> (HeaderName, HeaderValue) {
(
HeaderName::from_static("hx-redirect"),
HeaderValue::from_str(to).unwrap_or_else(|_| HeaderValue::from_static("/")),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn toast_header_is_json() {
let (name, value) = toast(ToastKind::Ok, "Crawl \"queued\"");
assert_eq!(name, "hx-trigger");
let v: serde_json::Value = serde_json::from_str(value.to_str().unwrap()).unwrap();
assert_eq!(v["toast"]["kind"], "ok");
assert_eq!(v["toast"]["message"], "Crawl \"queued\"");
}
}