use askama::Template;
use axum::{
response::{Html, IntoResponse},
http::StatusCode,
};
use askama_axum::Response;
use std::{sync::OnceLock, cell::RefCell, thread_local};
static GLOBAL_METADATA: OnceLock<Metadata> = OnceLock::new();
thread_local! {
static LOCAL_METADATA: RefCell<Option<Metadata>> = RefCell::new(None);
}
#[derive(Clone, Debug)]
pub struct Metadata {
pub title: String,
pub description: Option<String>,
pub keywords: Option<String>,
pub author: Option<String>,
pub og_title: Option<String>,
pub og_description: Option<String>,
pub og_image: Option<String>,
}
impl Default for Metadata {
fn default() -> Self {
Self {
title: "Ruskit".to_string(),
description: None,
keywords: None,
author: None,
og_title: None,
og_description: None,
og_image: None,
}
}
}
impl Metadata {
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
..Default::default()
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_keywords(mut self, keywords: impl Into<String>) -> Self {
self.keywords = Some(keywords.into());
self
}
pub fn with_author(mut self, author: impl Into<String>) -> Self {
self.author = Some(author.into());
self
}
pub fn with_og_title(mut self, title: impl Into<String>) -> Self {
self.og_title = Some(title.into());
self
}
pub fn with_og_description(mut self, description: impl Into<String>) -> Self {
self.og_description = Some(description.into());
self
}
pub fn with_og_image(mut self, image: impl Into<String>) -> Self {
self.og_image = Some(image.into());
self
}
}
pub fn set_global_metadata(metadata: Metadata) {
let _ = GLOBAL_METADATA.set(metadata);
}
pub fn get_global_metadata() -> &'static Metadata {
GLOBAL_METADATA.get_or_init(Metadata::default)
}
pub trait HasMetadata: Default {
fn metadata(&self) -> &'static Metadata {
LOCAL_METADATA.with(|local| {
if let Some(metadata) = &*local.borrow() {
unsafe { std::mem::transmute(metadata) }
} else {
get_global_metadata()
}
})
}
fn with_metadata(metadata: Metadata) -> Self where Self: Sized {
LOCAL_METADATA.with(|local| {
*local.borrow_mut() = Some(metadata);
});
Self::default()
}
}
impl<T: Template> HasMetadata for T where T: Default {}
pub trait TemplateExt: Template + HasMetadata + Sized {
fn into_response(self) -> Response {
let response = match self.render() {
Ok(html) => Html(html).into_response().into(),
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response().into(),
};
LOCAL_METADATA.with(|local| {
*local.borrow_mut() = None;
});
response
}
}
impl<T: Template + HasMetadata> TemplateExt for T {}