diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..10fe2ff --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,25 @@ +match_block_trailing_comma = true +max_width = 80 +reorder_imports = false +reorder_modules = false +use_field_init_shorthand = true +use_try_shorthand = true + +# blank_lines_lower_bound = 1 +# not stabilized yet +# where_single_line = true +# overflow_delimited_expr = true +# normalize_doc_attributes = true +# normalize_comments = true +# inline_attribute_width = 40 +# imports_granularity = "Crate" +# hex_literal_case = "Lower" +# group_imports = "StdExternalCrate" +# format_strings = true +# force_multiline_blocks = true +# error_on_unformatted = true +# error_on_line_overflow = true +# condense_wildcard_suffixes = true +# doc_comment_code_block_width = 70 +# format_code_in_doc_comments = true +# wrap_comments = true diff --git a/Cargo.toml b/Cargo.toml index 11c7582..d5268ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,15 @@ repository = "https://codeberg.org/jutty/en" edition = "2024" rust-version= "1.91.1" +[package.metadata.bacon.jobs.fmt-check] +command = [ + "cargo", "fmt", + "--check", + "--", + "--color=always", +] +need_stdout = true + [lints.rust] # levels: allow, expect, warn, force-warn, deny, forbid unsafe_code = { level = "forbid", priority = 99 } diff --git a/src/formats.rs b/src/formats.rs index c8b9b4f..2054962 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use crate::types::{Graph, Node, Edge}; pub fn populate_graph() -> Graph { - let toml_source = match std::fs::read_to_string("./static/graph.toml") { Ok(s) => s, Err(e) => format!("Error: {e}"), @@ -20,16 +19,13 @@ pub fn populate_graph() -> Graph { } fn modulate_nodes(old_nodes: &HashMap) -> HashMap { - let mut nodes: HashMap = HashMap::new(); for (key, node) in old_nodes { - let connections = node.connections.clone().unwrap_or_default(); let mut new_edges = connections.clone(); for (i, edge) in connections.iter().enumerate() { - let mut new_edge = edge.clone(); // Populate empty "from" IDs in edges with node's ID @@ -38,11 +34,13 @@ fn modulate_nodes(old_nodes: &HashMap) -> HashMap { } // Flag detached edges - if ! old_nodes.contains_key(&edge.to) { + if !old_nodes.contains_key(&edge.to) { new_edge.detached = true; } - if let Some(e) = new_edges.get_mut(i) { *e = new_edge; } + if let Some(e) = new_edges.get_mut(i) { + *e = new_edge; + } } // Create connections for each link @@ -77,13 +75,13 @@ fn modulate_nodes(old_nodes: &HashMap) -> HashMap { // Construct a HashMap with incoming connections (reversed edges) fn make_incoming(nodes: &HashMap) -> HashMap> { - let mut incoming: HashMap> = HashMap::new(); - for node in nodes.clone().into_values() { + for node in nodes.clone().into_values() { let empty_vec: Vec = vec![]; for edge in &node.connections.clone().unwrap_or_default() { - let mut edges = incoming.get(&edge.to.clone()).unwrap_or(&empty_vec).clone(); + let mut edges = + incoming.get(&edge.to.clone()).unwrap_or(&empty_vec).clone(); edges.extend_from_slice(std::slice::from_ref(edge)); incoming.insert(edge.to.clone(), edges.clone()); } @@ -94,39 +92,31 @@ fn make_incoming(nodes: &HashMap) -> HashMap> { pub enum Format { Toml, - Json + Json, } pub fn serialize_graph(out_format: &Format, graph: &Graph) -> String { - match *out_format { - Format::Toml => { - match toml::to_string(graph) { - Ok(s) => s, - Err(e) => e.to_string(), - } + Format::Toml => match toml::to_string(graph) { + Ok(s) => s, + Err(e) => e.to_string(), }, - Format::Json => { - match serde_json::to_string(graph) { - Ok(s) => s, - Err(e) => e.to_string(), - } + Format::Json => match serde_json::to_string(graph) { + Ok(s) => s, + Err(e) => e.to_string(), }, } } pub fn deserialize_graph(in_format: &Format, serial: &str) -> Graph { - match *in_format { - Format::Toml => { match toml::from_str(serial) { + Format::Toml => match toml::from_str(serial) { Ok(g) => g, - Err(error) => Graph::new(Some(error.to_string())) - }}, - Format::Json => { match serde_json::from_str(serial) { + Err(error) => Graph::new(Some(error.to_string())), + }, + Format::Json => match serde_json::from_str(serial) { Ok(g) => g, - Err(error) => Graph::new(Some(error.to_string())) - }} + Err(error) => Graph::new(Some(error.to_string())), + }, } } - - diff --git a/src/main.rs b/src/main.rs index d0a0f35..9ac4e19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,10 @@ use axum::{ body::Body, extract::Path, - http::{ header, HeaderValue, Response, StatusCode }, - response::{ Redirect }, + http::{header, HeaderValue, Response, StatusCode}, + response::{Redirect}, routing::get, - Form, - Router, + Form, Router, }; use formats::{populate_graph, serialize_graph, Format}; @@ -19,44 +18,50 @@ static ONSET: std::sync::LazyLock = #[tokio::main] async fn main() { - std::panic::set_hook(Box::new(|info| { - - let payload = info.payload_as_str().unwrap_or( - "No string payload. Is edition > 2021?"); + let payload = info + .payload_as_str() + .unwrap_or("No string payload. Is edition > 2021?"); let location = info.location().map_or_else( - || "location unavailable".to_string(), - |s| format!("{}:{}:{}", s.file(), s.line(), s.column())); + || "location unavailable".to_string(), + |s| format!("{}:{}:{}", s.file(), s.line(), s.column()), + ); eprintln!(" P! [{:?}] {}: {}", ONSET.elapsed(), location, payload); - })); let app = Router::new() .route("/", get(index).post(query)) .route("/graph/toml", get(toml_graph)) .route("/graph/json", get(json_graph)) - .route("/static/style.css", get( - || { file_handler("./static/style.css", "text/css") })) - .route("/static/favicon.svg", get( - || { file_handler("./static/favicon.svg", "image/svg+xml") })) + .route( + "/static/style.css", + get(|| file_handler("./static/style.css", "text/css")), + ) + .route( + "/static/favicon.svg", + get(|| file_handler("./static/favicon.svg", "image/svg+xml")), + ) .route("/node/{node_id}", get(node_view).post(node_view)) .route("/tree", get(tree)) .route("/about", get(|| static_template_handler("about.html"))) - .route("/acknowledgments", get(|| { - static_template_handler("acknowledgments.html") - })) - .fallback(not_found) - ; - - if let Ok(listener) = tokio::net::TcpListener::bind("0.0.0.0:3000").await - .or(Err("Failed to instantiate Tokio listener")) { + .route( + "/acknowledgments", + get(|| static_template_handler("acknowledgments.html")), + ) + .fallback(not_found); + if let Ok(listener) = tokio::net::TcpListener::bind("0.0.0.0:3000") + .await + .or(Err("Failed to instantiate Tokio listener")) + { match axum::serve(listener, app).await { Ok(()) => (), Err(e) => { - eprintln!("Failed to serve application with axum::serve: {e:#?}"); + eprintln!( + "Failed to serve application with axum::serve: {e:#?}" + ); std::process::exit(1); }, } @@ -68,21 +73,20 @@ fn make_body( context: &tera::Context, error_message: Option, ) -> (String, u16) { - - let tera = match tera::Tera::new( - concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"), - ) { + let tera = match tera::Tera::new(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/**/*" + )) { Ok(t) => t, Err(e) => { println!("Tera parsing error: {e:#?}"); panic!("{e}") - } + }, }; match tera.render(name, context) { Ok(t) => (t, 200), Err(e) => { - let mut error_context = tera::Context::new(); let out_error_message = match error_message { @@ -92,47 +96,54 @@ fn make_body( Engine message:\n
{e:#?}
\n\ Context:\n
{context:#?}
" ), - None => { - &format!( - "Template render failed.\n\ - Engine message:\n
{e:#?}
\n\ - Context:\n
{context:#?}
" - ) - } + None => &format!( + "Template render failed.\n\ + Engine message:\n
{e:#?}
\n\ + Context:\n
{context:#?}
" + ), }; error_context.insert("message", out_error_message); - error_context.insert("title", - &StatusCode::INTERNAL_SERVER_ERROR.to_string()); + error_context.insert( + "title", + &StatusCode::INTERNAL_SERVER_ERROR.to_string(), + ); - (tera.render("error.html", &error_context) - .unwrap_or(out_error_message.clone()), 500) - } + ( + tera.render("error.html", &error_context) + .unwrap_or(out_error_message.clone()), + 500, + ) + }, } } fn make_response( body: &str, status_code: u16, - headers: &[(header::HeaderName, &str)] + headers: &[(header::HeaderName, &str)], ) -> Response { - let mut response = Response::new(Body::from(body.to_owned())); *response.status_mut() = StatusCode::from_u16(status_code) - .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); + .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); for header in headers { if let Ok(wrapped) = HeaderValue::from_str(header.1) { - if let Some(overwritten) = response.headers_mut().insert( - header.0.clone(), - wrapped, - ) { eprintln!("[make_response] Overwrote header {overwritten:?} \ - because another for key {} already existed", header.0); + if let Some(overwritten) = + response.headers_mut().insert(header.0.clone(), wrapped) + { + eprintln!( + "[make_response] Overwrote header {overwritten:?} \ + because another for key {} already existed", + header.0 + ); } } else { - eprintln!("[make_response] Failed to wrap header value {}", - header.1); + eprintln!( + "[make_response] Failed to wrap header value {}", + header.1 + ); } } @@ -146,25 +157,20 @@ fn template_handler( error_message: Option, is_error: bool, ) -> Response { - - let (body, render_status) = make_body( - name, context, error_message); + let (body, render_status) = make_body(name, context, error_message); let status_code = if is_error { error_code } else { render_status }; - make_response(&body, status_code, - &[(header::CONTENT_TYPE, "text/html")]) + make_response(&body, status_code, &[(header::CONTENT_TYPE, "text/html")]) } -async fn node_view(Path(id): Path) -> Response { - +async fn node_view(Path(id): Path) -> Response { let mut context = tera::Context::new(); let graph = populate_graph(); let nodes = graph.nodes; - let empty_node = Node::new( - Some(format!("Could not find node with ID {id}.")), - ); + let empty_node = + Node::new(Some(format!("Could not find node with ID {id}."))); let node: &Node = nodes.get(&id).unwrap_or(&empty_node); @@ -181,17 +187,19 @@ async fn node_view(Path(id): Path) -> Response { &template_name, &context, if not_found { 404 } else { 500 }, - Some(format!( - "Failed to generate page for node {} (ID {}).\n\ - Node struct:
{:#?}
", - node.title, id, node - ).to_owned()), + Some( + format!( + "Failed to generate page for node {} (ID {}).\n\ + Node struct:
{:#?}
", + node.title, id, node + ) + .to_owned(), + ), not_found, ) } async fn index() -> Response { - let mut context = tera::Context::new(); let graph = populate_graph(); let root_node = graph.get_root().unwrap_or_default(); @@ -204,7 +212,6 @@ async fn index() -> Response { } async fn tree() -> Response { - let mut context = tera::Context::new(); let graph = populate_graph(); let root_node = graph.get_root().unwrap_or_default(); @@ -222,14 +229,12 @@ async fn static_template_handler(name: &str) -> Response { } #[expect(clippy::unused_async)] -async fn file_handler( - file_path: &str, - content_type: &str, -) -> Response { - +async fn file_handler(file_path: &str, content_type: &str) -> Response { let content = match std::fs::read(file_path) { Ok(s) => s, - Err(e) => panic!("[static_file_handler] Failed to read file contents: {e}"), + Err(e) => { + panic!("[static_file_handler] Failed to read file contents: {e}") + }, }; let mut response = Response::new(Body::from(content)); @@ -238,17 +243,25 @@ async fn file_handler( if let Ok(header_value) = HeaderValue::from_str(content_type) { if let Some(h) = response.headers_mut().insert(header, header_value) { - eprintln!("[static_file_handler] Overwrote existing header {h:?} \ - because a header for the same key existed"); + eprintln!( + "[static_file_handler] Overwrote existing header {h:?} \ + because a header for the same key existed" + ); } - } else { eprintln!("[static_file_handler] Failed to create content type \ - header value from {content_type}"); } + } else { + eprintln!( + "[static_file_handler] Failed to create content type \ + header value from {content_type}" + ); + } response } #[derive(serde::Deserialize)] -struct Query { node: String } +struct Query { + node: String, +} async fn query(Form(query): Form) -> Redirect { Redirect::permanent(format!("/node/{}", query.node).as_str()) @@ -268,41 +281,43 @@ async fn toml_graph() -> Response { make_response(&body, 200, &[(header::CONTENT_TYPE, "text/plain")]) } -fn make_error_body( - code: Option, - message: Option<&str>, -) -> String { - +fn make_error_body(code: Option, message: Option<&str>) -> String { let mut context = tera::Context::new(); let out_code = code.unwrap_or(500); let out_message = &message.unwrap_or("Unknown error"); - context.insert("title", &StatusCode::from_u16(out_code) - .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR).to_string()); + context.insert( + "title", + &StatusCode::from_u16(out_code) + .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) + .to_string(), + ); + context.insert("message", out_message); context.insert("status_code", &out_code.to_string()); - make_body("error.html", &context, Some(&format!( - "Failed to render template for Error {out_code}: {out_message}" - )).cloned()).0 + make_body( + "error.html", + &context, + Some(&format!( + "Failed to render template for Error {out_code}: {out_message}" + )) + .cloned(), + ) + .0 } fn make_error_response( code: Option, message: Option<&str>, ) -> Response { - let out_code = code.unwrap_or(500); let out_message = &message.unwrap_or("Unknown error"); let body = make_error_body(Some(out_code), Some(out_message)); - make_response( - &body, - out_code, - &[(header::CONTENT_TYPE, "text/html")], - ) + make_response(&body, out_code, &[(header::CONTENT_TYPE, "text/html")]) } async fn not_found() -> Response { diff --git a/src/types.rs b/src/types.rs index bef30aa..4bbcfee 100644 --- a/src/types.rs +++ b/src/types.rs @@ -6,16 +6,21 @@ use serde::{Serialize, Deserialize}; pub struct Graph { pub nodes: HashMap, pub root_node: String, - #[serde(default)] pub messages: Vec, - #[serde(skip)] pub incoming: HashMap>, + #[serde(default)] + pub messages: Vec, + #[serde(skip)] + pub incoming: HashMap>, } #[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)] pub struct Node { pub text: String, - #[serde(default)] pub title: String, - #[serde(default)] pub links: Vec, - #[serde(default)] pub id: String, + #[serde(default)] + pub title: String, + #[serde(default)] + pub links: Vec, + #[serde(default)] + pub id: String, #[serde(skip_serializing_if = "Option::is_none")] pub connections: Option>, @@ -24,9 +29,12 @@ pub struct Node { #[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)] pub struct Edge { pub to: String, - #[serde(default)] pub anchor: String, - #[serde(default)] pub from: String, - #[serde(default)] pub detached: bool, + #[serde(default)] + pub anchor: String, + #[serde(default)] + pub from: String, + #[serde(default)] + pub detached: bool, } impl Graph { @@ -35,8 +43,10 @@ impl Graph { nodes: HashMap::new(), root_node: "VoidNode".to_string(), incoming: HashMap::new(), - messages: vec![message - .unwrap_or("This graph is empty or in error".to_string())], + messages: vec![ + message + .unwrap_or("This graph is empty or in error".to_string()), + ], } } @@ -52,7 +62,7 @@ impl Node { title: "Pure Void".to_string(), text: match message { Some(s) => s, - None => "Node is empty, missing or wasn't found.".to_string() + None => "Node is empty, missing or wasn't found.".to_string(), }, connections: None, links: vec![],