use axum::http::{HeaderMap, StatusCode};
use chrono::TimeDelta;
use mime::Mime;
use std::collections::BTreeMap;
#[derive(Clone)]
pub enum RenderedBody {
Static(&'static [u8]),
Bytes(Vec<u8>),
String(String),
Empty,
}
trait RenderedBodyTypeSealed {}
impl RenderedBodyTypeSealed for () {}
impl RenderedBodyTypeSealed for RenderedBody {}
#[expect(private_bounds)]
pub trait RenderedBodyType: RenderedBodyTypeSealed {}
impl<T: RenderedBodyTypeSealed> RenderedBodyType for T {}
#[derive(Clone)]
pub struct Rendered<T: RenderedBodyType> {
pub code: StatusCode,
pub headers: HeaderMap,
pub body: T,
pub mime: Option<Mime>,
pub ttl: Option<TimeDelta>,
pub private: bool,
}
impl Rendered<()> {
pub fn with_body(self, body: RenderedBody) -> Rendered<RenderedBody> {
Rendered {
code: self.code,
headers: self.headers,
body,
mime: self.mime,
ttl: self.ttl,
private: self.private,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RenderContext {
pub client_info: ClientInfo,
pub route: String,
pub query: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum DeviceType {
Mobile,
#[default]
Desktop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClientInfo {
pub device_type: DeviceType,
}
impl ClientInfo {
pub(crate) fn from_headers(headers: &HeaderMap) -> Self {
let ua = headers
.get("user-agent")
.and_then(|x| x.to_str().ok())
.unwrap_or("");
let ch_mobile = headers
.get("Sec-CH-UA-Mobile")
.and_then(|x| x.to_str().ok())
.unwrap_or("");
let mut device_type = None;
if device_type.is_none() && ch_mobile.contains("1") {
device_type = Some(DeviceType::Mobile);
}
if device_type.is_none() && ua.contains("Mobile") {
device_type = Some(DeviceType::Mobile);
}
Self {
device_type: device_type.unwrap_or_default(),
}
}
}