From 0d0627ba8f3ed203f125c9ac7ba43ee8325f7bbc Mon Sep 17 00:00:00 2001 From: jutty Date: Thu, 11 Dec 2025 02:21:56 -0300 Subject: [PATCH] Add and resolve an obscene amount of lints --- Cargo.toml | 189 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/notes.md | 22 ++++++ src/formats.rs | 46 ++++++------ src/main.rs | 55 +++++++------- src/types.rs | 8 +-- 5 files changed, 264 insertions(+), 56 deletions(-) create mode 100644 docs/notes.md diff --git a/Cargo.toml b/Cargo.toml index b666217..ad3ce5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,15 @@ repository = "https://codeberg.org/jutty/en" edition = "2024" rust-version= "1.91.1" +[lints.rust] +# levels: allow, expect, warn, force-warn, deny, forbid +unsafe_code = { level = "forbid", priority = 99 } +unused = { level = "warn", priority = 10 } +let_underscore= { level = "warn", priority = 10 } +nonstandard-style = "warn" +future-incompatible = "warn" +keyword-idents = "warn" + [dependencies] axum = "0.8.7" tokio = { version = "1.48.0", features = ["rt-multi-thread"] } @@ -16,3 +25,183 @@ tera = { version = "1.20.1", features = ["builtins"] } serde_json = "1.0.145" serde = { version = "1.0.228", features = ["derive"] } toml = "0.9.8" + +[lints.clippy] +# levels: allow, warn, deny, forbid + +# pedantic +assigning_clones = "warn" +borrow_as_ptr = "warn" +cast_lossless = "warn" +cast_possible_wrap = "warn" +cast_ptr_alignment = "warn" +cast_sign_loss = "warn" +checked_conversions = "warn" +cloned_instead_of_copied = "warn" +comparison_chain = "warn" +copy_iterator = "warn" +default_trait_access = "warn" +doc_broken_link = "warn" +doc_comment_double_space_linebreaks = "warn" +doc_link_with_quotes = "warn" +doc_markdown = "warn" +empty_enum = "warn" +enum_glob_use = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +explicit_iter_loop = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp = "warn" +fn_params_excessive_bools = "warn" +format_push_string = "warn" +from_iter_instead_of_collect = "warn" +if_not_else = "warn" +ignore_without_reason = "warn" +ignored_unit_patterns = "warn" +implicit_clone = "warn" +implicit_hasher = "warn" +inconsistent_struct_constructor = "warn" +index_refutable_slice = "warn" +inefficient_to_string = "warn" +invalid_upcast_comparisons = "warn" +ip_constant = "warn" +iter_filter_is_ok = "warn" +iter_filter_is_some = "warn" +large_digit_groups = "warn" +large_futures = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +linkedlist = "warn" +manual_assert = "warn" +manual_instant_elapsed = "warn" +manual_is_power_of_two = "warn" +manual_is_variant_and = "warn" +manual_let_else = "warn" +manual_midpoint = "warn" +manual_string_new = "warn" +many_single_char_names = "warn" +map_unwrap_or = "warn" +match_bool = "warn" +match_same_arms = "warn" +match_wild_err_arm = "warn" +match_wildcard_for_single_variants = "warn" +maybe_infinite_iter = "warn" +mismatching_type_param_order = "warn" +missing_errors_doc = "warn" +missing_fields_in_debug = "warn" +missing_panics_doc = "warn" +must_use_candidate = "warn" +mut_mut = "warn" +naive_bytecount = "warn" +needless_continue = "warn" +needless_for_each = "warn" +needless_pass_by_value = "warn" +needless_raw_string_hashes = "warn" +no_effect_underscore_binding = "warn" +option_as_ref_cloned = "warn" +option_option = "warn" +ptr_as_ptr = "warn" +ptr_cast_constness = "warn" +pub_underscore_fields = "warn" +range_minus_one = "warn" +range_plus_one = "warn" +redundant_closure_for_method_calls = "warn" +ref_as_ptr = "warn" +ref_binding_to_reference = "warn" +ref_option = "warn" +ref_option_ref = "warn" +return_self_not_must_use = "warn" +same_functions_in_if_condition = "warn" +semicolon_if_nothing_returned = "warn" +should_panic_without_expect = "warn" +similar_names = "warn" +str_split_at_newline = "warn" +struct_excessive_bools = "warn" +struct_field_names = "warn" +trivially_copy_pass_by_ref = "warn" +unchecked_duration_subtraction = "warn" +unicode_not_nfc = "warn" +uninlined_format_args = "warn" +unnecessary_box_returns = "warn" +unnecessary_debug_formatting = "warn" +unnecessary_join = "warn" +unnecessary_literal_bound = "warn" +unnecessary_semicolon = "warn" +unnecessary_wraps = "warn" +unnested_or_patterns = "warn" +unreadable_literal = "warn" +unsafe_derive_deserialize = "warn" +unused_async = "warn" +unused_self = "warn" +used_underscore_binding = "warn" +wildcard_imports = "warn" +zero_sized_map_values = "warn" + +# restrictive +allow_attributes = "forbid" +arithmetic_side_effects = "warn" +as_conversions = "warn" +as_pointer_underscore = "warn" +as_underscore = "warn" +default_numeric_fallback = "warn" +deref_by_slicing = "warn" +else_if_without_else = "warn" +empty_drop = "warn" +empty_enum_variants_with_brackets = "warn" +error_impl_error = "warn" +exit = "warn" +expect_used = "warn" +filetype_is_file = "warn" +float_cmp_const = "warn" +fn_to_numeric_cast_any = "warn" +get_unwrap = "warn" +if_then_some_else_none = "warn" +indexing_slicing = "warn" +infinite_loop = "warn" +integer_division = "warn" +integer_division_remainder_used = "warn" +let_underscore_must_use = "warn" +let_underscore_untyped = "warn" +lossy_float_literal = "warn" +map_err_ignore = "warn" +map_with_unused_argument_over_ranges = "warn" +missing_assert_message = "warn" +missing_asserts_for_indexing = "warn" +mixed_read_write_in_expression = "warn" +module_name_repetitions = "warn" +multiple_inherent_impl = "warn" +needless_raw_strings = "warn" +non_zero_suggestions = "warn" +panic_in_result_fn = "warn" +pathbuf_init_then_push = "warn" +pattern_type_mismatch = "warn" +pub_without_shorthand = "warn" +redundant_test_prefix = "warn" +redundant_type_annotations = "warn" +ref_patterns = "warn" +renamed_function_params = "warn" +rest_pat_in_fully_bound_structs = "warn" +return_and_then = "warn" +same_name_method = "warn" +semicolon_inside_block = "warn" +shadow_reuse = "warn" +shadow_same = "warn" +shadow_unrelated = "warn" +single_char_lifetime_names = "warn" +string_add = "warn" +string_lit_chars_any = "warn" +tests_outside_test_module = "warn" +unnecessary_self_imports = "warn" +unneeded_field_pattern = "warn" +unseparated_literal_suffix = "warn" +unused_result_ok = "warn" +unused_trait_names = "warn" +unwrap_used = "warn" +verbose_file_reads = "warn" +wildcard_enum_match_arm = "warn" + +# cargo +negative_feature_names = "warn" +redundant_feature_names = "warn" diff --git a/docs/notes.md b/docs/notes.md new file mode 100644 index 0000000..d27acc4 --- /dev/null +++ b/docs/notes.md @@ -0,0 +1,22 @@ +# Notes + +## CI + +When adding CI jobs, consider the following lints: + +- `clippy::dbg_macro` +- `clippy::print_stderr` +- `clippy::print_stdout` +- `clippy::todo` +- `clippy::unimplemented` +- `clippy::unreachable` +- `clippy::use_debug` + +## BTreeMap + +Consider replacing HashMap with BTreeMap to stop nodes from shifting position constantly on every page load. + +See also: + - + - `clippy::iter_over_hash_type` + diff --git a/src/formats.rs b/src/formats.rs index 7a288d4..c8b9b4f 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::types::*; +use crate::types::{Graph, Node, Edge}; pub fn populate_graph() -> Graph { @@ -8,22 +8,22 @@ pub fn populate_graph() -> Graph { Ok(s) => s, Err(e) => format!("Error: {e}"), }; - let graph = deserialize_graph(Format::Toml, &toml_source); + let graph = deserialize_graph(&Format::Toml, &toml_source); - let nodes = modulate_nodes(graph.nodes.clone()); + let nodes = modulate_nodes(&graph.nodes); Graph { nodes: nodes.clone(), - incoming: make_incoming(nodes.clone()), + incoming: make_incoming(&nodes), ..graph } } -fn modulate_nodes(old_nodes: HashMap) -> HashMap { +fn modulate_nodes(old_nodes: &HashMap) -> HashMap { let mut nodes: HashMap = HashMap::new(); - for (key, node) in old_nodes.iter() { + for (key, node) in old_nodes { let connections = node.connections.clone().unwrap_or_default(); let mut new_edges = connections.clone(); @@ -33,8 +33,8 @@ fn modulate_nodes(old_nodes: HashMap) -> HashMap { let mut new_edge = edge.clone(); // Populate empty "from" IDs in edges with node's ID - if edge.from == "" { - new_edge.from = key.to_string(); + if edge.from.is_empty() { + new_edge.from.clone_from(key); } // Flag detached edges @@ -42,17 +42,17 @@ fn modulate_nodes(old_nodes: HashMap) -> HashMap { new_edge.detached = true; } - new_edges[i] = new_edge; + if let Some(e) = new_edges.get_mut(i) { *e = new_edge; } } // Create connections for each link - for link in node.links.iter() { + for link in &node.links { new_edges.push(Edge { - from: key.to_string(), - to: link.to_string(), + from: key.clone(), + to: link.clone(), anchor: String::new(), detached: !old_nodes.contains_key(link), - }) + }); } // Populate empty titles with IDs @@ -69,22 +69,22 @@ fn modulate_nodes(old_nodes: HashMap) -> HashMap { ..node.clone() }; - nodes.insert(key.to_string(), new_node); + nodes.insert(key.clone(), new_node); } nodes } // Construct a HashMap with incoming connections (reversed edges) -fn make_incoming(nodes: HashMap) -> HashMap> { +fn make_incoming(nodes: &HashMap) -> HashMap> { let mut incoming: HashMap> = HashMap::new(); for node in nodes.clone().into_values() { let empty_vec: Vec = vec![]; - for edge in node.connections.clone().unwrap_or_default().iter() { + for edge in &node.connections.clone().unwrap_or_default() { let mut edges = incoming.get(&edge.to.clone()).unwrap_or(&empty_vec).clone(); - edges.extend_from_slice(&[edge.clone()]); + edges.extend_from_slice(std::slice::from_ref(edge)); incoming.insert(edge.to.clone(), edges.clone()); } } @@ -97,9 +97,9 @@ pub enum Format { Json } -pub fn serialize_graph(out_format: Format, graph: &Graph) -> String { +pub fn serialize_graph(out_format: &Format, graph: &Graph) -> String { - match out_format { + match *out_format { Format::Toml => { match toml::to_string(graph) { Ok(s) => s, @@ -115,14 +115,14 @@ pub fn serialize_graph(out_format: Format, graph: &Graph) -> String { } } -pub fn deserialize_graph(in_format: Format, serial: &String) -> Graph { +pub fn deserialize_graph(in_format: &Format, serial: &str) -> Graph { - match in_format { - Format::Toml => { match toml::from_str(&serial) { + match *in_format { + 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) { + Format::Json => { match serde_json::from_str(serial) { Ok(g) => g, Err(error) => Graph::new(Some(error.to_string())) }} diff --git a/src/main.rs b/src/main.rs index 36b2470..9e236dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,11 +7,12 @@ use axum::{ Router, }; -mod types; -mod formats; +use formats::{populate_graph, serialize_graph, Format}; +use types::Node; + +mod formats; +mod types; -use formats::*; -use types::*; static ONSET: std::sync::LazyLock = std::sync::LazyLock::new(std::time::Instant::now); @@ -59,18 +60,17 @@ async fn main() { fn make_body( name: &str, - context: tera::Context, - error_code: u16, - error_message: &str, -) -> String { + context: &tera::Context, + error_message: Option<&str>, +) -> (String, u16) { let tera = match tera::Tera::new( concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"), ) { Ok(t) => t, Err(e) => { - println!("Tera parsing error: {}", e); - ::std::process::exit(1); + println!("Tera parsing error: {e:#?}"); + panic!("{e}") } }; @@ -139,7 +139,7 @@ async fn node_view(Path(id): Path) -> impl IntoResponse { let graph = populate_graph(); let nodes = graph.nodes; let empty_node = Node::new( - Some(format!("Could not find node with ID {}.", id)), + Some(format!("Could not find node with ID {id}.")), ); let node: &Node = nodes.get(&id).unwrap_or(&empty_node); @@ -205,14 +205,14 @@ async fn query(Form(query): Form) -> Redirect { async fn json_graph() -> impl IntoResponse { let graph = populate_graph(); - let body = serialize_graph(Format::Json, &graph); + let body = serialize_graph(&Format::Json, &graph); ([(header::CONTENT_TYPE, "application/json")], body) } async fn toml_graph() -> impl IntoResponse { let graph = populate_graph(); - let body = serialize_graph(Format::Toml, &graph); + let body = serialize_graph(&Format::Toml, &graph); ([(header::CONTENT_TYPE, "text/plain")], body) } @@ -242,19 +242,17 @@ fn make_error_body( let mut context = tera::Context::new(); - let code = code.unwrap_or(501); - let message = &message.unwrap_or("Unknown error"); + let out_code = code.unwrap_or(500); + let out_message = &message.unwrap_or("Unknown error"); - context.insert("title", &StatusCode::from_u16(code) + context.insert("title", &StatusCode::from_u16(out_code) .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR).to_string()); - context.insert("message", message); - context.insert("status_code", &code.to_string()); + context.insert("message", out_message); + context.insert("status_code", &out_code.to_string()); - make_body("error.html", context, 500, &format!( - "Failed to render template for Error {}: {}", - code, - message, - )) + make_body("error.html", &context, Some(&format!( + "Failed to render template for Error {out_code}: {out_message}" + ))).0 } fn make_error_response( @@ -262,15 +260,16 @@ fn make_error_response( message: Option<&str>, ) -> impl IntoResponse { - let code = code.unwrap_or(501); - let message = &message.unwrap_or("Unknown error"); + let out_code = code.unwrap_or(500); + let out_message = &message.unwrap_or("Unknown error"); - let body = make_error_body(Some(code), Some(message)); + let body = make_error_body(Some(out_code), Some(out_message)); ( - StatusCode::from_u16(code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), + StatusCode::from_u16(out_code) + .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), [(header::CONTENT_TYPE, "text/html")], - body.to_string(), + body.clone(), ) } diff --git a/src/types.rs b/src/types.rs index 69b7509..bef30aa 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,7 @@ -use serde::{Serialize, Deserialize}; use std::collections::HashMap; +use serde::{Serialize, Deserialize}; + #[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)] pub struct Graph { pub nodes: HashMap, @@ -40,10 +41,7 @@ impl Graph { } pub fn get_root(&self) -> Option { - match self.nodes.get(&self.root_node) { - Some(n) => Some(n.clone()), - None => None, - } + self.nodes.get(&self.root_node).cloned() } }