#![forbid(unsafe_code)]
#![deny(
clippy::dbg_macro,
missing_copy_implementations,
rustdoc::missing_crate_level_docs,
missing_debug_implementations,
missing_docs,
nonstandard_style,
unused_qualifications
)]
use trillium::{
async_trait, Conn, Handler,
KnownHeaderName::{ContentType, LastModified},
};
use trillium_include_dir::DirEntry;
pub use trillium_include_dir::{include_dir, try_include_dir, Dir, File};
#[derive(Debug, Clone, Copy)]
pub struct StaticCompiledHandler {
dir: Dir<'static>,
index_file: Option<&'static str>,
}
impl StaticCompiledHandler {
pub fn new(dir: Dir<'static>) -> Self {
Self {
dir,
index_file: None,
}
}
pub fn with_index_file(mut self, file: &'static str) -> Self {
self.index_file = Some(file);
self
}
fn serve_file(&self, mut conn: Conn, file: File<'static>) -> Conn {
let mime = mime_guess::from_path(file.path()).first_or_text_plain();
let is_ascii = file.contents().is_ascii();
let is_text = matches!(
(mime.type_(), mime.subtype()),
(mime::APPLICATION, mime::JAVASCRIPT) | (mime::TEXT, _) | (_, mime::HTML)
);
conn.headers_mut().try_insert(
ContentType,
if is_text && !is_ascii {
format!("{}; charset=utf-8", mime)
} else {
mime.to_string()
},
);
if let Some(modified) = file.modified() {
conn.headers_mut()
.try_insert(LastModified, httpdate::fmt_http_date(modified));
}
conn.ok(file.contents())
}
fn get_item(&self, path: &str) -> Option<DirEntry<'static>> {
if path.is_empty() {
Some(DirEntry::Dir(self.dir))
} else {
self.dir
.get_dir(path)
.map(DirEntry::Dir)
.or_else(|| self.dir.get_file(path).map(DirEntry::File))
}
}
}
#[async_trait]
impl Handler for StaticCompiledHandler {
async fn run(&self, conn: Conn) -> Conn {
match (
self.get_item(conn.path().trim_start_matches('/')),
self.index_file,
) {
(None, _) => conn,
(Some(DirEntry::File(file)), _) => self.serve_file(conn, file),
(Some(DirEntry::Dir(_)), None) => conn,
(Some(DirEntry::Dir(dir)), Some(index_file)) => {
if let Some(file) = dir.get_file(dir.path().join(index_file)) {
self.serve_file(conn, file)
} else {
conn
}
}
}
}
}
pub fn files(dir: Dir<'static>) -> StaticCompiledHandler {
StaticCompiledHandler::new(dir)
}
#[macro_export]
macro_rules! static_compiled {
($path:literal) => {
$crate::StaticCompiledHandler::new($crate::include_dir!($path))
};
}