From b6dd286cccbfe2dca074791da630c2a281657fe9 Mon Sep 17 00:00:00 2001 From: Cathal Mullan Date: Sun, 5 Jan 2025 00:03:07 +0000 Subject: [PATCH] Lower MSRV to 1.63 --- Cargo.lock | 2 +- Cargo.toml | 84 ++++++++------------ README.md | 4 +- benches/matchit_criterion.rs | 4 +- benches/matchit_divan.rs | 3 +- benches/path_tree_criterion.rs | 4 +- benches/path_tree_divan.rs | 3 +- examples/oci/Cargo.toml | 49 +++++++++--- flake.nix | 4 +- src/delete.rs | 4 +- src/insert.rs | 136 ++++++++++++++++----------------- src/router.rs | 31 +++++--- src/search.rs | 54 ++++++++----- 13 files changed, 208 insertions(+), 174 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5c0282..ed4e511 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "actix-router" diff --git a/Cargo.toml b/Cargo.toml index c0fd82b..4da4f33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,18 +3,44 @@ resolver = "2" members = [".", "examples/*", "fuzz"] -[workspace.package] +[profile.dev.package] +insta.opt-level = 3 +similar.opt-level = 3 + +[profile.release] +lto = "fat" +codegen-units = 1 + +# https://doc.rust-lang.org/cargo/reference/manifest.html +[package] +name = "wayfind" +description = "A speedy, flexible router." +include = [ + "/benches", + "/examples", + "/fuzz", + "/src", + "/tests", + "/ARCHITECTURE.md", + "/BENCHMARKING.md", + "/CHANGELOG.md", + "/LICENSE-APACHE", + "/LICENSE-MIT", + "/README.md", +] + version = "0.7.0" authors = ["Cathal Mullan "] edition = "2021" -rust-version = "1.83" +# https://packages.debian.org/stable/rust/rustc +rust-version = "1.63" repository = "https://github.com/DuskSystems/wayfind" license = "MIT OR Apache-2.0" keywords = ["router"] categories = ["network-programming", "web-programming"] # https://doc.rust-lang.org/rustc/lints/groups.html -[workspace.lints.rust] +[lints.rust] unsafe_code = "forbid" unused = { level = "deny", priority = -2 } @@ -28,7 +54,7 @@ rust-2018-idioms = { level = "deny", priority = -1 } rust-2021-compatibility = { level = "deny", priority = -1 } # https://rust-lang.github.io/rust-clippy/master/index.html -[workspace.lints.clippy] +[lints.clippy] cargo = { level = "deny", priority = -1 } complexity = { level = "deny", priority = -1 } correctness = { level = "deny", priority = -1 } @@ -46,62 +72,16 @@ new_without_default = "allow" str_to_string = "deny" too_many_lines = "allow" -[profile.dev.package] -insta.opt-level = 3 -similar.opt-level = 3 - -[profile.release] -lto = "fat" -codegen-units = 1 - -[workspace.dependencies] +[dependencies] # Data Structures smallvec = { version = "1.13", features = ["const_generics", "union"] } +[dev-dependencies] # Testing # NOTE: Keep in sync with `cargo-insta` Nix package. insta = "=1.41.1" similar-asserts = "1.6" -# https://doc.rust-lang.org/cargo/reference/manifest.html -[package] -name = "wayfind" -description = "A speedy, flexible router." -include = [ - "/benches", - "/crates", - "/examples", - "/fuzz", - "/src", - "/tests", - "/ARCHITECTURE.md", - "/CHANGELOG.md", - "/LICENSE-APACHE", - "/LICENSE-MIT", - "/README.md", -] - -version.workspace = true -authors.workspace = true -edition.workspace = true -rust-version.workspace = true -repository.workspace = true -license.workspace = true -keywords.workspace = true -categories.workspace = true - -[lints] -workspace = true - -[dependencies] -# Data Structures -smallvec = { workspace = true } - -[dev-dependencies] -# Testing -insta = { workspace = true } -similar-asserts = { workspace = true } - # Benchmarking divan = "0.1" criterion = { version = "0.5", features = ["html_reports"] } diff --git a/README.md b/README.md index 628d3c4..368e8b2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![crates.io](https://img.shields.io/crates/v/wayfind)](https://crates.io/crates/wayfind) [![documentation](https://docs.rs/wayfind/badge.svg)](https://docs.rs/wayfind) -![rust: 1.83+](https://img.shields.io/badge/rust-1.83+-orange.svg) +![rust: 1.63+](https://img.shields.io/badge/rust-1.63+-orange.svg) ![`unsafe`: forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg) ![`wasm`: compatible](https://img.shields.io/badge/wasm-compatible-success.svg) @@ -305,6 +305,8 @@ fn main() -> Result<(), Box> { Routers can print their routes. +`[*]` denotes nodes within the tree that can be matched against. + Currenty, this doesn't handle split multi-byte characters well. #### Example diff --git a/benches/matchit_criterion.rs b/benches/matchit_criterion.rs index 4177627..42ab59e 100644 --- a/benches/matchit_criterion.rs +++ b/benches/matchit_criterion.rs @@ -1,9 +1,7 @@ //! Benches sourced from `matchit` (MIT AND BSD-3-Clause) //! -use std::hint::black_box; - -use codspeed_criterion_compat::{criterion_group, criterion_main, Criterion}; +use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Criterion}; use matchit_routes::paths; pub mod matchit_routes; diff --git a/benches/matchit_divan.rs b/benches/matchit_divan.rs index b6488f8..3bdb433 100644 --- a/benches/matchit_divan.rs +++ b/benches/matchit_divan.rs @@ -1,8 +1,7 @@ //! Benches sourced from `matchit` (MIT AND BSD-3-Clause) //! -use std::hint::black_box; - +use codspeed_criterion_compat::black_box; use divan::AllocProfiler; use matchit_routes::paths; diff --git a/benches/path_tree_criterion.rs b/benches/path_tree_criterion.rs index 481befb..832f234 100644 --- a/benches/path_tree_criterion.rs +++ b/benches/path_tree_criterion.rs @@ -1,9 +1,7 @@ //! Benches sourced from `path-tree` (MIT OR Apache-2.0) //! -use std::hint::black_box; - -use codspeed_criterion_compat::{criterion_group, criterion_main, Criterion}; +use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Criterion}; use path_tree_routes::paths; pub mod path_tree_routes; diff --git a/benches/path_tree_divan.rs b/benches/path_tree_divan.rs index 598b364..0cb6052 100644 --- a/benches/path_tree_divan.rs +++ b/benches/path_tree_divan.rs @@ -1,8 +1,7 @@ //! Benches sourced from `path-tree` (MIT OR Apache-2.0) //! -use std::hint::black_box; - +use codspeed_criterion_compat::black_box; use divan::AllocProfiler; use path_tree_routes::paths; diff --git a/examples/oci/Cargo.toml b/examples/oci/Cargo.toml index 26273ce..7444e82 100644 --- a/examples/oci/Cargo.toml +++ b/examples/oci/Cargo.toml @@ -2,18 +2,49 @@ [package] name = "wayfind-oci-example" description = "Example of using `wayfind` as an OCI registry." +rust-version = "1.83" publish = false -version.workspace = true -authors.workspace = true -edition.workspace = true -repository.workspace = true -license.workspace = true -keywords.workspace = true -categories.workspace = true +version = "0.7.0" +authors = ["Cathal Mullan "] +edition = "2021" +repository = "https://github.com/DuskSystems/wayfind" +license = "MIT OR Apache-2.0" +keywords = ["router"] +categories = ["network-programming", "web-programming"] -[lints] -workspace = true +# https://doc.rust-lang.org/rustc/lints/groups.html +[lints.rust] +unsafe_code = "forbid" + +unused = { level = "deny", priority = -2 } +future-incompatible = { level = "deny", priority = -1 } +keyword-idents = { level = "deny", priority = -1 } +let-underscore = { level = "deny", priority = -1 } +nonstandard-style = { level = "deny", priority = -1 } +refining-impl-trait = { level = "deny", priority = -1 } +rust-2018-compatibility = { level = "deny", priority = -1 } +rust-2018-idioms = { level = "deny", priority = -1 } +rust-2021-compatibility = { level = "deny", priority = -1 } + +# https://rust-lang.github.io/rust-clippy/master/index.html +[lints.clippy] +cargo = { level = "deny", priority = -1 } +complexity = { level = "deny", priority = -1 } +correctness = { level = "deny", priority = -1 } +nursery = { level = "deny", priority = -1 } +pedantic = { level = "deny", priority = -1 } +perf = { level = "deny", priority = -1 } +style = { level = "deny", priority = -1 } +suspicious = { level = "deny", priority = -1 } + +# Personal Preferences +clone_on_ref_ptr = "deny" +cognitive_complexity = "allow" +module_name_repetitions = "allow" +new_without_default = "allow" +str_to_string = "deny" +too_many_lines = "allow" [dependencies] wayfind = { path = "../.." } diff --git a/flake.nix b/flake.nix index 76126ca..ca1a309 100644 --- a/flake.nix +++ b/flake.nix @@ -143,7 +143,7 @@ buildInputs = with pkgs; [ # Rust - (rust-bin.stable."1.83.0".minimal) + (rust-bin.stable."1.63.0".minimal) sccache ]; }; @@ -164,7 +164,7 @@ buildInputs = with pkgs; [ # Rust - (rust-bin.stable."1.83.0".minimal.override { + (rust-bin.stable."1.63.0".minimal.override { targets = [ "wasm32-unknown-unknown" ]; extensions = [ "clippy" diff --git a/src/delete.rs b/src/delete.rs index bb78a63..7822bf8 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -35,7 +35,7 @@ impl Node<'_, T, S> { match data { NodeData::Inline { data, .. } => Some(data), - NodeData::Shared { data, .. } => Arc::into_inner(data), + NodeData::Shared { data, .. } => Arc::try_unwrap(data).ok(), } } } @@ -131,7 +131,7 @@ impl Node<'_, T, S> { match data { NodeData::Inline { data, .. } => Some(data), - NodeData::Shared { data, .. } => Arc::into_inner(data), + NodeData::Shared { data, .. } => Arc::try_unwrap(data).ok(), } } diff --git a/src/insert.rs b/src/insert.rs index 5289d76..51189ff 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -36,94 +36,94 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { fn insert_static(&mut self, template: &mut Template, data: NodeData<'r, T>, prefix: &[u8]) { // Check if the first byte is already a child here. - let Some(child) = self + if let Some(child) = self .static_children .iter_mut() .find(|child| child.state.prefix[0] == prefix[0]) - else { - self.static_children.push({ - let mut new_child = Node { - state: StaticState::new(prefix.to_vec()), - data: None, + { + let common_prefix = prefix + .iter() + .zip::<&[u8]>(child.state.prefix.as_ref()) + .take_while(|&(x, y)| x == y) + .count(); + + // If the new prefix matches or extends the existing prefix, we can just insert it directly. + if common_prefix >= child.state.prefix.len() { + if common_prefix >= prefix.len() { + child.insert(template, data); + } else { + child.insert_static(template, data, &prefix[common_prefix..]); + } - static_children: SortedNode::default(), - dynamic_children: SortedNode::default(), - dynamic_children_shortcut: false, - wildcard_children: SortedNode::default(), - wildcard_children_shortcut: false, - end_wildcard_children: SortedNode::default(), + self.needs_optimization = true; + return; + } - priority: 0, - needs_optimization: false, - }; + // Not a clean insert, need to split the existing child node. + let new_child_a = Node { + state: StaticState::new(child.state.prefix[common_prefix..].to_vec()), + data: child.data.take(), - new_child.insert(template, data); - new_child - }); + static_children: std::mem::take(&mut child.static_children), + dynamic_children: std::mem::take(&mut child.dynamic_children), + dynamic_children_shortcut: child.dynamic_children_shortcut, + wildcard_children: std::mem::take(&mut child.wildcard_children), + wildcard_children_shortcut: child.wildcard_children_shortcut, + end_wildcard_children: std::mem::take(&mut child.end_wildcard_children), - self.needs_optimization = true; - return; - }; + priority: child.priority, + needs_optimization: child.needs_optimization, + }; - let common_prefix = prefix - .iter() - .zip::<&[u8]>(child.state.prefix.as_ref()) - .take_while(|&(x, y)| x == y) - .count(); + let new_child_b = Node { + state: StaticState::new(prefix[common_prefix..].to_vec()), + data: None, - // If the new prefix matches or extends the existing prefix, we can just insert it directly. - if common_prefix >= child.state.prefix.len() { - if common_prefix >= prefix.len() { + static_children: SortedNode::default(), + dynamic_children: SortedNode::default(), + dynamic_children_shortcut: false, + wildcard_children: SortedNode::default(), + wildcard_children_shortcut: false, + end_wildcard_children: SortedNode::default(), + + priority: 0, + needs_optimization: false, + }; + + child.state = StaticState::new(child.state.prefix[..common_prefix].to_vec()); + child.needs_optimization = true; + + if prefix[common_prefix..].is_empty() { + child.static_children = SortedNode::new(vec![new_child_a]); child.insert(template, data); } else { - child.insert_static(template, data, &prefix[common_prefix..]); + child.static_children = SortedNode::new(vec![new_child_a, new_child_b]); + child.static_children[1].insert(template, data); } self.needs_optimization = true; return; - } - - // Not a clean insert, need to split the existing child node. - let new_child_a = Node { - state: StaticState::new(child.state.prefix[common_prefix..].to_vec()), - data: child.data.take(), - - static_children: std::mem::take(&mut child.static_children), - dynamic_children: std::mem::take(&mut child.dynamic_children), - dynamic_children_shortcut: child.dynamic_children_shortcut, - wildcard_children: std::mem::take(&mut child.wildcard_children), - wildcard_children_shortcut: child.wildcard_children_shortcut, - end_wildcard_children: std::mem::take(&mut child.end_wildcard_children), - - priority: child.priority, - needs_optimization: child.needs_optimization, }; - let new_child_b = Node { - state: StaticState::new(prefix[common_prefix..].to_vec()), - data: None, + self.static_children.push({ + let mut new_child = Node { + state: StaticState::new(prefix.to_vec()), + data: None, - static_children: SortedNode::default(), - dynamic_children: SortedNode::default(), - dynamic_children_shortcut: false, - wildcard_children: SortedNode::default(), - wildcard_children_shortcut: false, - end_wildcard_children: SortedNode::default(), - - priority: 0, - needs_optimization: false, - }; + static_children: SortedNode::default(), + dynamic_children: SortedNode::default(), + dynamic_children_shortcut: false, + wildcard_children: SortedNode::default(), + wildcard_children_shortcut: false, + end_wildcard_children: SortedNode::default(), - child.state = StaticState::new(child.state.prefix[..common_prefix].to_vec()); - child.needs_optimization = true; + priority: 0, + needs_optimization: false, + }; - if prefix[common_prefix..].is_empty() { - child.static_children = SortedNode::new(vec![new_child_a]); - child.insert(template, data); - } else { - child.static_children = SortedNode::new(vec![new_child_a, new_child_b]); - child.static_children[1].insert(template, data); - } + new_child.insert(template, data); + new_child + }); self.needs_optimization = true; } diff --git a/src/router.rs b/src/router.rs index fe1bd7d..623e024 100644 --- a/src/router.rs +++ b/src/router.rs @@ -158,8 +158,11 @@ impl<'r, T> Router<'r, T> { // Check for any conflicts or mismatches. for parsed_template in &parsed.templates { - let Some(found) = self.root.find(&mut parsed_template.clone()) else { - continue; + let found = match self.root.find(&mut parsed_template.clone()) { + Some(found) => found, + _ => { + continue; + } }; if found.template() == template { @@ -187,10 +190,13 @@ impl<'r, T> Router<'r, T> { } } - let Some(data) = output else { - return Err(DeleteError::NotFound { - template: template.to_owned(), - }); + let data = match output { + Some(data) => data, + _ => { + return Err(DeleteError::NotFound { + template: template.to_owned(), + }); + } }; self.root.optimize(); @@ -199,11 +205,14 @@ impl<'r, T> Router<'r, T> { pub fn search<'p>(&'r self, path: &'p str) -> Result>, SearchError> { let mut parameters = smallvec![]; - let Some((data, _)) = - self.root - .search(path.as_bytes(), &mut parameters, &self.constraints)? - else { - return Ok(None); + let data = match self + .root + .search(path.as_bytes(), &mut parameters, &self.constraints)? + { + Some((data, _)) => data, + _ => { + return Ok(None); + } }; let (data, template, expanded) = match data { diff --git a/src/search.rs b/src/search.rs index ce88779..24cb369 100644 --- a/src/search.rs +++ b/src/search.rs @@ -111,13 +111,19 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { })?, )); - let Some((data, priority)) = - child.search(&path[consumed..], &mut current_parameters, constraints)? - else { - continue; - }; - - if best_match.is_none_or(|(_, best_priority)| priority >= best_priority) { + let (data, priority) = + match child.search(&path[consumed..], &mut current_parameters, constraints)? { + Some((data, priority)) => (data, priority), + _ => { + continue; + } + }; + + if best_match.is_none() + || best_match + .as_ref() + .map_or(false, |(_, best_priority)| priority >= *best_priority) + { best_match = Some((data, priority)); best_match_parameters = current_parameters; } @@ -206,13 +212,19 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { })?, )); - let Some((data, priority)) = - child.search(&path[consumed..], &mut current_parameters, constraints)? - else { - continue; - }; - - if best_match.is_none_or(|(_, best_priority)| priority >= best_priority) { + let (data, priority) = + match child.search(&path[consumed..], &mut current_parameters, constraints)? { + Some((data, priority)) => (data, priority), + _ => { + continue; + } + }; + + if best_match.is_none() + || best_match + .as_ref() + .map_or(false, |(_, best_priority)| priority >= *best_priority) + { best_match = Some((data, priority)); best_match_parameters = current_parameters; } @@ -322,13 +334,19 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { segment: &[u8], constraints: &HashMap<&'r str, StoredConstraint>, ) -> bool { - let Some(constraint) = constraint else { - return true; + let constraint = match constraint { + Some(constraint) => constraint, + _ => { + return true; + } }; let constraint = constraints.get(constraint.as_str()).unwrap(); - let Ok(segment) = std::str::from_utf8(segment) else { - return false; + let segment = match std::str::from_utf8(segment) { + Ok(segment) => segment, + _ => { + return false; + } }; (constraint.check)(segment)