#![forbid(unsafe_code)]
#![deny(
clippy::dbg_macro,
missing_copy_implementations,
rustdoc::missing_crate_level_docs,
missing_debug_implementations,
missing_docs,
nonstandard_style,
unused_qualifications
)]
pub use include_dir::include_dir;
use include_dir::{Dir, DirEntry, File};
use trillium::{async_trait, Conn, Handler, KnownHeaderName::ContentType};
#[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 = match (mime.type_(), mime.subtype()) {
(mime::APPLICATION, mime::JAVASCRIPT) => true,
(mime::TEXT, _) => true,
(_, mime::HTML) => true,
_ => false,
};
conn.headers_mut().try_insert(
ContentType,
if is_text && !is_ascii {
format!("{}; charset=utf-8", mime)
} else {
mime.to_string()
},
);
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
}
}
}
}
}