diff --git a/.clippy.toml b/.clippy.toml index adbd2c9..4bda813 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,3 +1,4 @@ allow-unwrap-in-tests = true allow-expect-in-tests = true single-char-binding-names-threshold = 2 +upper-case-acronyms-aggressive = true diff --git a/src/router.rs b/src/router.rs index 51182e7..4617e47 100644 --- a/src/router.rs +++ b/src/router.rs @@ -9,6 +9,7 @@ mod handlers { pub mod navigation; pub mod fixed; pub mod error; + pub mod mime; } #[derive(Clone)] @@ -32,36 +33,7 @@ pub fn new(graph: Graph) -> Router { .route("/graph/{format}", get(handlers::fixed::serial)) .route("/search", get(handlers::navigation::search)) .route("/redirect", get(handlers::navigation::redirect)) - .route( - "/static/style.css", - get(|| handlers::fixed::file("./static/style.css", "text/css")), - ) - .route( - "/static/fonts/sans", - get(|| handlers::fixed::file("./static/fonts/sans", "")), - ) - .route( - "/static/fonts/serifed", - get(|| handlers::fixed::file("./static/fonts/serifed", "")), - ) - .route( - "/static/fonts/mono", - get(|| handlers::fixed::file("./static/fonts/mono", "")), - ) - .route( - "/static/fonts/title", - get(|| handlers::fixed::file("./static/fonts/title", "")), - ) - .route( - "/static/fonts/prose", - get(|| handlers::fixed::file("./static/fonts/prose", "")), - ) - .route( - "/static/favicon.svg", - get(|| { - handlers::fixed::file("./static/favicon.svg", "image/svg+xml") - }), - ); + .route("/static/{*path}", get(handlers::fixed::file)); if state.graph.meta.config.tree { router = router.route("/tree", get(handlers::navigation::tree)); @@ -127,8 +99,8 @@ mod tests { "/tree", "/data", "/node/Syntax", - "/static/style.css", - "/static/favicon.svg", + "/static/assets/style.css", + "/static/assets/favicon.svg", "/graph/json", "/graph/toml", ]; diff --git a/src/router/handlers/fixed.rs b/src/router/handlers/fixed.rs index 3bcc8a2..3d4d4be 100644 --- a/src/router/handlers/fixed.rs +++ b/src/router/handlers/fixed.rs @@ -1,24 +1,44 @@ use axum::{ - body::Body, - extract::{Path, State}, http::{HeaderValue, Response, StatusCode, header}, + { + body::Body, + extract::{Path, State}, + }, }; use crate::prelude::*; use crate::{ graph::{Format, Graph, SerialErrorCause}, router::{GlobalState, handlers}, + router::handlers::mime::Mime, }; -/// # Panics -/// Will panic if file read fails. -#[expect(clippy::unused_async)] -pub async fn file(file_path: &str, content_type: &str) -> Response { +pub async fn file( + Path(path): Path, + State(state): State, +) -> Response { let instant = now(); - let content = match std::fs::read(file_path) { + let target = format!("static/public/{path}"); + let content = match std::fs::read(&target) { Ok(s) => s, Err(e) => { - panic!("Failed to read {file_path} contents: {e}") + let mut error_message = String::from( + "The requested file does not exist, the server does not have \ + permission to access it or a filesystem error ocurred.", + ); + if log::env_level() >= DEBUG { + error_message = format!( + "

{error_message}

\ +

Targeted path: {target}

\ +

Error message:

{e}
" + ); + } + log!(ERROR, "{error_message}"); + return super::error::by_code( + Some(404), + Some(&error_message), + &state.graph, + ); }, }; @@ -26,19 +46,20 @@ pub async fn file(file_path: &str, content_type: &str) -> Response { *response.status_mut() = StatusCode::OK; let header = header::CONTENT_TYPE; - if let Ok(header_value) = HeaderValue::from_str(content_type) { + let content_type = Mime::guess(&path); + + if let Ok(header_value) = + HeaderValue::from_str(&String::from(content_type.clone())) + { response.headers_mut().append(header, header_value); } else { log!( WARN, - "Failed to create content type header value from {content_type}" + "Failed to create content type header value from {content_type:?}" ); } - tlog!( - &instant, - "Assembled response for {content_type} {file_path}" - ); + tlog!(&instant, "Assembled response for {content_type:?} {path}"); response } @@ -147,30 +168,4 @@ mod tests { == "application/json" ); } - - #[tokio::test] - async fn file_valid_header() { - let payload = "y1mgMhjeIMFsRNZ1tskP52DfWuvhvbRP"; - let response = file("./static/graph.toml", payload).await; - assert_eq!( - response.headers().get(header::CONTENT_TYPE).unwrap(), - payload - ); - } - - #[tokio::test] - async fn file_invalid_header() { - let response = file("./static/graph.toml", "\n").await; - println!("{response:#?}"); - assert!(response.headers().get(header::CONTENT_TYPE).is_none()); - } - - #[tokio::test] - #[should_panic( - expected = "Failed to read IvnhZhdHb1xDnUw4hYDDNIERoaOojkiu \ - contents: No such file or directory (os error 2)" - )] - async fn file_invalid_path() { - drop(file("IvnhZhdHb1xDnUw4hYDDNIERoaOojkiu", "text/plain").await); - } } diff --git a/src/router/handlers/mime.rs b/src/router/handlers/mime.rs new file mode 100644 index 0000000..4b82089 --- /dev/null +++ b/src/router/handlers/mime.rs @@ -0,0 +1,94 @@ +#[derive(Debug, Clone)] +pub enum Mime { + Txt, + Csv, + Css, + Ttf, + Otf, + Woff, + Woff2, + Svg, + Ico, + Jpeg, + Png, + Apng, + Gif, + Webp, + Avif, + Toml, + Xml, + Json, + Js, + Pdf, + Epub, + Unknown, +} + +impl From<&str> for Mime { + fn from(extension: &str) -> Mime { + match extension { + "txt" => Mime::Txt, + "csv" => Mime::Csv, + "css" => Mime::Css, + "ttf" => Mime::Ttf, + "otf" => Mime::Otf, + "woff" => Mime::Woff, + "woff2" => Mime::Woff2, + "svg" => Mime::Svg, + "ico" => Mime::Ico, + "jpeg" => Mime::Jpeg, + "png" => Mime::Png, + "apng" => Mime::Apng, + "gif" => Mime::Gif, + "webp" => Mime::Webp, + "avif" => Mime::Avif, + "toml" => Mime::Toml, + "xml" => Mime::Xml, + "json" => Mime::Json, + "js" => Mime::Js, + "pdf" => Mime::Pdf, + "epub" => Mime::Epub, + _ => Mime::Unknown, + } + } +} + +impl From for String { + fn from(mime: Mime) -> String { + let s = match mime { + Mime::Txt => "text/plain", + Mime::Csv => "text/csv", + Mime::Css => "text/css", + Mime::Ttf => "font/ttf", + Mime::Otf => "font/otf", + Mime::Woff => "font/woff", + Mime::Woff2 => "font/woff2", + Mime::Svg => "image/svg+xml", + Mime::Ico => "image/x-icon", + Mime::Jpeg => "image/jpeg", + Mime::Png => "image/png", + Mime::Apng => "image/apng", + Mime::Gif => "image/gif", + Mime::Webp => "image/webp", + Mime::Avif => "image/avif", + Mime::Toml => "application/toml", + Mime::Xml => "application/xml", + Mime::Json => "application/json", + Mime::Js => "text/javascript", + Mime::Pdf => "application/pdf", + Mime::Epub => "application/epub+zip", + Mime::Unknown => "application/octet-stream", + }; + String::from(s) + } +} + +impl Mime { + pub fn guess(path: &str) -> Mime { + if let Some(pair) = path.rsplit_once('.') { + Mime::from(pair.1) + } else { + Mime::Unknown + } + } +} diff --git a/static/fonts/mono b/static/fonts/mono deleted file mode 120000 index 274627c..0000000 --- a/static/fonts/mono +++ /dev/null @@ -1 +0,0 @@ -mononoki/mononoki-latin-400-normal.woff2 \ No newline at end of file diff --git a/static/fonts/prose b/static/fonts/prose deleted file mode 120000 index cd79e75..0000000 --- a/static/fonts/prose +++ /dev/null @@ -1 +0,0 @@ -reforma/Reforma1969-Blanca.woff2 \ No newline at end of file diff --git a/static/fonts/prose-bold b/static/fonts/prose-bold deleted file mode 120000 index 233b22c..0000000 --- a/static/fonts/prose-bold +++ /dev/null @@ -1 +0,0 @@ -reforma/Reforma1969-Gris.woff2 \ No newline at end of file diff --git a/static/fonts/prose-bold-italic b/static/fonts/prose-bold-italic deleted file mode 120000 index 383f1a9..0000000 --- a/static/fonts/prose-bold-italic +++ /dev/null @@ -1 +0,0 @@ -reforma/Reforma1969-GrisItalica.woff2 \ No newline at end of file diff --git a/static/fonts/prose-italic b/static/fonts/prose-italic deleted file mode 120000 index 59e30c7..0000000 --- a/static/fonts/prose-italic +++ /dev/null @@ -1 +0,0 @@ -reforma/Reforma1969-BlancaItalica.woff2 \ No newline at end of file diff --git a/static/fonts/rawengulk/RawengulkLight.otf b/static/fonts/rawengulk/RawengulkLight.otf deleted file mode 100644 index a187371..0000000 Binary files a/static/fonts/rawengulk/RawengulkLight.otf and /dev/null differ diff --git a/static/fonts/sans b/static/fonts/sans deleted file mode 120000 index a3df751..0000000 --- a/static/fonts/sans +++ /dev/null @@ -1 +0,0 @@ -maven/maven-pro-latin-400-normal.woff2 \ No newline at end of file diff --git a/static/fonts/serifed b/static/fonts/serifed deleted file mode 120000 index 4f488ec..0000000 --- a/static/fonts/serifed +++ /dev/null @@ -1 +0,0 @@ -cormorant/cormorant-infant-latin-300-normal.woff2 \ No newline at end of file diff --git a/static/fonts/serifed-italic b/static/fonts/serifed-italic deleted file mode 120000 index 719d1f3..0000000 --- a/static/fonts/serifed-italic +++ /dev/null @@ -1 +0,0 @@ -cormorant/cormorant-infant-latin-300-italic.woff2 \ No newline at end of file diff --git a/static/fonts/title b/static/fonts/title deleted file mode 120000 index 498110b..0000000 --- a/static/fonts/title +++ /dev/null @@ -1 +0,0 @@ -rawengulk/RawengulkLight.otf \ No newline at end of file diff --git a/static/graph.toml b/static/graph.toml index 631d6c4..54b060d 100644 --- a/static/graph.toml +++ b/static/graph.toml @@ -589,7 +589,7 @@ text = """ - [ ] Branch deeper - Customization - [ ] Custom assets (favicon, CSS) - - [ ] Drop all hardcoded assets endpoints + - [x] Drop all hardcoded assets endpoints - [ ] Custom header include - [ ] Custom templates - [ ] Themes diff --git a/static/favicon.svg b/static/public/assets/favicon.svg similarity index 100% rename from static/favicon.svg rename to static/public/assets/favicon.svg diff --git a/static/fonts/cormorant/LICENSE b/static/public/assets/fonts/cormorant/LICENSE similarity index 100% rename from static/fonts/cormorant/LICENSE rename to static/public/assets/fonts/cormorant/LICENSE diff --git a/static/fonts/cormorant/cormorant-infant-latin-300-italic.woff2 b/static/public/assets/fonts/cormorant/cormorant-infant-latin-300-italic.woff2 similarity index 100% rename from static/fonts/cormorant/cormorant-infant-latin-300-italic.woff2 rename to static/public/assets/fonts/cormorant/cormorant-infant-latin-300-italic.woff2 diff --git a/static/fonts/cormorant/cormorant-infant-latin-300-normal.woff2 b/static/public/assets/fonts/cormorant/cormorant-infant-latin-300-normal.woff2 similarity index 100% rename from static/fonts/cormorant/cormorant-infant-latin-300-normal.woff2 rename to static/public/assets/fonts/cormorant/cormorant-infant-latin-300-normal.woff2 diff --git a/static/public/assets/fonts/fonts.css b/static/public/assets/fonts/fonts.css new file mode 100644 index 0000000..65bfaea --- /dev/null +++ b/static/public/assets/fonts/fonts.css @@ -0,0 +1,65 @@ +@font-face { + src: url("maven/maven-pro-latin-400-normal.woff2") format("woff2"); + font-family: "sans"; + size-adjust: 110%; + display: swap; +} + +@font-face { + src: + url("cormorant/cormorant-infant-latin-300-normal.woff2") + format("woff2"); + font-family: "serifed"; + display: swap; +} + +@font-face { + src: + url("cormorant/cormorant-infant-latin-300-italic.woff2") + format("woff2"); + font-family: "serifed"; + font-style: italic; + display: swap; +} + +@font-face { + src: url("mononoki/mononoki-latin-400-normal.woff2") format("woff2"); + font-family: "mono"; + display: swap; +} + +@font-face { + src: url("rawengulk/RawengulkLight.woff2") format("woff2"); + font-family: "title"; + display: swap; +} + +@font-face { + src: url("reforma/Reforma1969-Blanca.woff2") format("woff2"); + font-family: "prose"; + size-adjust: 120%; + display: swap; +} + +@font-face { + src: url("reforma/Reforma1969-Gris.woff2") format("woff2"); + font-family: "prose"; + font-weight: bold; + display: swap; +} + +@font-face { + src: url("reforma/Reforma1969-BlancaItalica.woff2") format("woff2"); + src: url("prose-italic"); + font-family: "prose"; + font-style: italic; + display: swap; +} + +@font-face { + src: url("reforma/Reforma1969-GrisItalica.woff2") format("woff2"); + font-family: "prose"; + font-weight: bold; + font-style: italic; + display: swap; +} diff --git a/static/fonts/maven/LICENSE b/static/public/assets/fonts/maven/LICENSE similarity index 100% rename from static/fonts/maven/LICENSE rename to static/public/assets/fonts/maven/LICENSE diff --git a/static/fonts/maven/maven-pro-latin-400-normal.woff2 b/static/public/assets/fonts/maven/maven-pro-latin-400-normal.woff2 similarity index 100% rename from static/fonts/maven/maven-pro-latin-400-normal.woff2 rename to static/public/assets/fonts/maven/maven-pro-latin-400-normal.woff2 diff --git a/static/fonts/mononoki/LICENSE b/static/public/assets/fonts/mononoki/LICENSE similarity index 100% rename from static/fonts/mononoki/LICENSE rename to static/public/assets/fonts/mononoki/LICENSE diff --git a/static/fonts/mononoki/README.md b/static/public/assets/fonts/mononoki/README.md similarity index 100% rename from static/fonts/mononoki/README.md rename to static/public/assets/fonts/mononoki/README.md diff --git a/static/fonts/mononoki/mononoki-latin-400-normal.woff2 b/static/public/assets/fonts/mononoki/mononoki-latin-400-normal.woff2 similarity index 100% rename from static/fonts/mononoki/mononoki-latin-400-normal.woff2 rename to static/public/assets/fonts/mononoki/mononoki-latin-400-normal.woff2 diff --git a/static/public/assets/fonts/rawengulk/RawengulkLight.woff2 b/static/public/assets/fonts/rawengulk/RawengulkLight.woff2 new file mode 100644 index 0000000..d866f46 Binary files /dev/null and b/static/public/assets/fonts/rawengulk/RawengulkLight.woff2 differ diff --git a/static/fonts/rawengulk/SIL Open Font License.txt b/static/public/assets/fonts/rawengulk/SIL Open Font License.txt similarity index 100% rename from static/fonts/rawengulk/SIL Open Font License.txt rename to static/public/assets/fonts/rawengulk/SIL Open Font License.txt diff --git a/static/fonts/reforma/LICENSE.txt b/static/public/assets/fonts/reforma/LICENSE.txt similarity index 100% rename from static/fonts/reforma/LICENSE.txt rename to static/public/assets/fonts/reforma/LICENSE.txt diff --git a/static/fonts/reforma/README.txt b/static/public/assets/fonts/reforma/README.txt similarity index 100% rename from static/fonts/reforma/README.txt rename to static/public/assets/fonts/reforma/README.txt diff --git a/static/fonts/reforma/Reforma1969-Blanca.woff2 b/static/public/assets/fonts/reforma/Reforma1969-Blanca.woff2 similarity index 100% rename from static/fonts/reforma/Reforma1969-Blanca.woff2 rename to static/public/assets/fonts/reforma/Reforma1969-Blanca.woff2 diff --git a/static/fonts/reforma/Reforma1969-BlancaItalica.woff2 b/static/public/assets/fonts/reforma/Reforma1969-BlancaItalica.woff2 similarity index 100% rename from static/fonts/reforma/Reforma1969-BlancaItalica.woff2 rename to static/public/assets/fonts/reforma/Reforma1969-BlancaItalica.woff2 diff --git a/static/fonts/reforma/Reforma1969-Gris.woff2 b/static/public/assets/fonts/reforma/Reforma1969-Gris.woff2 similarity index 100% rename from static/fonts/reforma/Reforma1969-Gris.woff2 rename to static/public/assets/fonts/reforma/Reforma1969-Gris.woff2 diff --git a/static/fonts/reforma/Reforma1969-GrisItalica.woff2 b/static/public/assets/fonts/reforma/Reforma1969-GrisItalica.woff2 similarity index 100% rename from static/fonts/reforma/Reforma1969-GrisItalica.woff2 rename to static/public/assets/fonts/reforma/Reforma1969-GrisItalica.woff2 diff --git a/static/style.css b/static/public/assets/style.css similarity index 88% rename from static/style.css rename to static/public/assets/style.css index e7060f4..84d767e 100644 --- a/static/style.css +++ b/static/public/assets/style.css @@ -2,64 +2,6 @@ --base-font-size: 1em; } -@font-face { - src: url("fonts/sans"); - font-family: "sans"; - display: swap; - size-adjust: 110%; -} - -@font-face { - src: url("fonts/serifed"); - font-family: "serifed"; - display: swap; -} - -@font-face { - src: url("fonts/serifed-italic"); - font-family: "serifed"; - display: swap; - font-style: italic; -} - -@font-face { - src: url("fonts/mono"); - font-family: "mono"; - display: swap; -} - -@font-face { - src: url("fonts/title"); - font-family: "title"; - display: swap; -} - -@font-face { - src: url("fonts/prose"); - font-family: "prose"; - display: swap; - size-adjust: 120%; -} - -@font-face { - src: url("fonts/prose-bold"); - font-family: "prose"; - font-weight: bold; -} - -@font-face { - src: url("fonts/prose-italic"); - font-family: "prose"; - font-style: italic; -} - -@font-face { - src: url("fonts/prose-bold-italic"); - font-family: "prose"; - font-weight: bold; - font-style: italic; -} - html { height: 100%; } @@ -366,9 +308,9 @@ em#index-node-count { } table { - margin: auto; - border-collapse: collapse; - border: 0.5px dotted #666; + margin: auto; + border-collapse: collapse; + border: 0.5px dotted #666; } td, th { diff --git a/templates/base.html b/templates/base.html index 190a0bd..6e5dfc8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -12,8 +12,9 @@ {% endif %} - - + + + {% block head %} {% endblock head %}