From 7db868ca6a37a9580ecdf96a730d3204f9f5b534 Mon Sep 17 00:00:00 2001 From: Cathal Mullan Date: Wed, 1 Jan 2025 14:55:42 +0000 Subject: [PATCH] Ensure routers can be run in any order --- Cargo.lock | 35 +- Cargo.toml | 5 +- README.md | 58 +- TODO.md | 2 +- benches/matchit_criterion.rs | 2 +- benches/matchit_divan.rs | 2 +- crates/authority/Cargo.toml | 2 + crates/authority/src/constraints.rs | 2 + crates/authority/src/delete.rs | 142 - crates/authority/src/errors.rs | 22 +- crates/authority/src/errors/constraint.rs | 8 +- crates/authority/src/errors/delete.rs | 33 +- crates/authority/src/errors/encoding.rs | 53 - crates/authority/src/errors/insert.rs | 20 +- crates/authority/src/errors/search.rs | 25 - crates/authority/src/errors/template.rs | 55 +- crates/authority/src/find.rs | 111 - crates/authority/src/insert.rs | 239 - crates/authority/src/lib.rs | 179 +- crates/authority/src/parser.rs | 202 +- crates/authority/src/search.rs | 333 - crates/method/Cargo.toml | 5 +- crates/method/src/display.rs | 6 +- crates/method/src/errors.rs | 12 +- crates/method/src/errors/delete.rs | 6 +- crates/method/src/errors/insert.rs | 6 +- crates/method/src/errors/search.rs | 6 +- crates/method/src/lib.rs | 52 +- crates/path/Cargo.toml | 2 + crates/path/src/display.rs | 103 - crates/path/src/errors.rs | 22 +- crates/path/src/errors/constraint.rs | 41 +- crates/path/src/errors/delete.rs | 101 +- crates/path/src/errors/encoding.rs | 53 - crates/path/src/errors/insert.rs | 53 +- crates/path/src/errors/search.rs | 25 - crates/path/src/errors/template.rs | 65 +- crates/path/src/find.rs | 111 - crates/path/src/id.rs | 10 +- crates/path/src/lib.rs | 199 +- crates/path/src/node.rs | 44 - crates/path/src/optimize.rs | 115 - crates/path/src/parser.rs | 323 +- crates/path/src/state.rs | 284 - crates/path/src/vec.rs | 82 - crates/percent/src/errors.rs | 10 +- crates/percent/src/lib.rs | 18 +- crates/punycode/src/errors.rs | 26 +- crates/punycode/src/lib.rs | 65 +- crates/storage/Cargo.toml | 19 + crates/storage/src/lib.rs | 81 + crates/tree/Cargo.toml | 20 + crates/{path => tree}/src/delete.rs | 94 +- crates/{authority => tree}/src/display.rs | 40 +- crates/tree/src/find.rs | 127 + crates/{path => tree}/src/insert.rs | 150 +- crates/tree/src/lib.rs | 12 + crates/{authority => tree}/src/node.rs | 34 +- crates/{authority => tree}/src/optimize.rs | 24 +- crates/tree/src/parser.rs | 25 + crates/{path => tree}/src/search.rs | 226 +- crates/{authority => tree}/src/state.rs | 18 +- crates/{authority => tree}/src/vec.rs | 0 examples/oci/src/router.rs | 10 +- flake.lock | 12 +- src/errors.rs | 21 +- src/errors/delete.rs | 21 +- src/errors/encoding.rs | 53 +- src/errors/insert.rs | 21 +- src/errors/route.rs | 20 +- src/errors/search.rs | 28 +- src/route.rs | 21 +- src/router.rs | 58 +- tests/authority.rs | 198 +- tests/constraint.rs | 26 +- tests/delete.rs | 36 +- tests/display.rs | 22 +- tests/dynamic.rs | 50 +- tests/escape.rs | 26 +- tests/gitlab.rs | 6768 ++++++++++---------- tests/insert.rs | 77 +- tests/method.rs | 68 +- tests/optimize.rs | 32 +- tests/optional.rs | 108 +- tests/static.rs | 60 +- tests/wildcard.rs | 50 +- 86 files changed, 5295 insertions(+), 6736 deletions(-) delete mode 100644 crates/authority/src/delete.rs delete mode 100644 crates/authority/src/errors/encoding.rs delete mode 100644 crates/authority/src/errors/search.rs delete mode 100644 crates/authority/src/find.rs delete mode 100644 crates/authority/src/insert.rs delete mode 100644 crates/authority/src/search.rs delete mode 100644 crates/path/src/display.rs delete mode 100644 crates/path/src/errors/encoding.rs delete mode 100644 crates/path/src/errors/search.rs delete mode 100644 crates/path/src/find.rs delete mode 100644 crates/path/src/node.rs delete mode 100644 crates/path/src/optimize.rs delete mode 100644 crates/path/src/state.rs delete mode 100644 crates/path/src/vec.rs create mode 100644 crates/storage/Cargo.toml create mode 100644 crates/storage/src/lib.rs create mode 100644 crates/tree/Cargo.toml rename crates/{path => tree}/src/delete.rs (59%) rename crates/{authority => tree}/src/display.rs (75%) create mode 100644 crates/tree/src/find.rs rename crates/{path => tree}/src/insert.rs (61%) create mode 100644 crates/tree/src/lib.rs rename crates/{authority => tree}/src/node.rs (55%) rename crates/{authority => tree}/src/optimize.rs (84%) create mode 100644 crates/tree/src/parser.rs rename crates/{path => tree}/src/search.rs (53%) rename crates/{authority => tree}/src/state.rs (92%) rename crates/{authority => tree}/src/vec.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 8c2f02df..35a291f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,9 +124,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "regex-automata", @@ -785,9 +785,9 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "matchit" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0aa4b8ca861b08d68afc8702af3250776898c1508b278e1da9d01e01d4b45c" +checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9" [[package]] name = "memchr" @@ -1223,9 +1223,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" -version = "2.0.93" +version = "2.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" dependencies = [ "proc-macro2", "quote", @@ -1515,6 +1515,7 @@ dependencies = [ "wayfind-percent", "wayfind-punycode", "wayfind-rails-macro", + "wayfind-tree", "xitca-router", ] @@ -1525,6 +1526,8 @@ dependencies = [ "insta", "similar-asserts", "smallvec", + "wayfind-storage", + "wayfind-tree", ] [[package]] @@ -1539,9 +1542,8 @@ dependencies = [ name = "wayfind-method" version = "0.7.0" dependencies = [ - "insta", - "similar-asserts", "smallvec", + "wayfind-storage", ] [[package]] @@ -1574,6 +1576,8 @@ dependencies = [ "insta", "similar-asserts", "smallvec", + "wayfind-storage", + "wayfind-tree", ] [[package]] @@ -1612,6 +1616,21 @@ dependencies = [ "wayfind", ] +[[package]] +name = "wayfind-storage" +version = "0.7.0" +dependencies = [ + "smallvec", +] + +[[package]] +name = "wayfind-tree" +version = "0.7.0" +dependencies = [ + "smallvec", + "wayfind-storage", +] + [[package]] name = "web-sys" version = "0.3.76" diff --git a/Cargo.toml b/Cargo.toml index 9e2824ab..84e968ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,9 @@ wayfind-authority = { path = "crates/authority" } wayfind-path = { path = "crates/path" } wayfind-method = { path = "crates/method" } +# FIXME +wayfind-tree = { path = "crates/tree" } + # Decoding wayfind-percent = { path = "crates/percent" } wayfind-punycode = { path = "crates/punycode" } @@ -131,7 +134,7 @@ codspeed-criterion-compat = "=2.7.2" # Routers actix-router = "=0.5.3" -matchit = "=0.8.5" +matchit = "=0.8.6" ntex-router = "=0.5.3" path-tree = "=0.8.1" route-recognizer = "=0.3.1" diff --git a/README.md b/README.md index 2d009f7a..7fae5081 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ fn main() -> Result<(), Box> { .build()?; let search = router.search(&request)?.unwrap(); assert_eq!(*search.data, 1); - assert_eq!(search.path.route, "/users/{id}"); + assert_eq!(search.path.route, "/users/{id}".into()); assert_eq!(search.path.parameters[0], ("id", "123")); let request = RequestBuilder::new() @@ -70,7 +70,7 @@ fn main() -> Result<(), Box> { .build()?; let search = router.search(&request)?.unwrap(); assert_eq!(*search.data, 2); - assert_eq!(search.path.route, "/users/{id}/files/{filename}.{extension}"); + assert_eq!(search.path.route, "/users/{id}/files/{filename}.{extension}".into()); assert_eq!(search.path.parameters[0], ("id", "123")); assert_eq!(search.path.parameters[1], ("filename", "my.document")); assert_eq!(search.path.parameters[2], ("extension", "pdf")); @@ -114,7 +114,7 @@ fn main() -> Result<(), Box> { .build()?; let search = router.search(&request)?.unwrap(); assert_eq!(*search.data, 1); - assert_eq!(search.path.route, "/files/{*slug}/delete"); + assert_eq!(search.path.route, "/files/{*slug}/delete".into()); assert_eq!(search.path.parameters[0], ("slug", "documents/reports/annual.pdf")); let request = RequestBuilder::new() @@ -122,7 +122,7 @@ fn main() -> Result<(), Box> { .build()?; let search = router.search(&request)?.unwrap(); assert_eq!(*search.data, 2); - assert_eq!(search.path.route, "/{*catch_all}"); + assert_eq!(search.path.route, "/{*catch_all}".into()); assert_eq!(search.path.parameters[0], ("catch_all", "any/other/path")); Ok(()) @@ -171,16 +171,16 @@ fn main() -> Result<(), Box> { .build()?; let search = router.search(&request)?.unwrap(); assert_eq!(*search.data, 1); - assert_eq!(search.path.route, "/users(/{id})"); - assert_eq!(search.path.expanded, Some("/users")); + assert_eq!(search.path.route, "/users(/{id})".into()); + assert_eq!(search.path.expanded, Some("/users".into())); let request = RequestBuilder::new() .path("/users/123") .build()?; let search = router.search(&request)?.unwrap(); assert_eq!(*search.data, 1); - assert_eq!(search.path.route, "/users(/{id})"); - assert_eq!(search.path.expanded, Some("/users/{id}")); + assert_eq!(search.path.route, "/users(/{id})".into()); + assert_eq!(search.path.expanded, Some("/users/{id}".into())); assert_eq!(search.path.parameters[0], ("id", "123")); let request = RequestBuilder::new() @@ -188,8 +188,8 @@ fn main() -> Result<(), Box> { .build()?; let search = router.search(&request)?.unwrap(); assert_eq!(*search.data, 2); - assert_eq!(search.path.route, "/files/{*slug}/{file}(.{extension})"); - assert_eq!(search.path.expanded, Some("/files/{*slug}/{file}.{extension}")); + assert_eq!(search.path.route, "/files/{*slug}/{file}(.{extension})".into()); + assert_eq!(search.path.expanded, Some("/files/{*slug}/{file}.{extension}".into())); assert_eq!(search.path.parameters[0], ("slug", "documents/folder")); assert_eq!(search.path.parameters[1], ("file", "report")); assert_eq!(search.path.parameters[2], ("extension", "pdf")); @@ -199,8 +199,8 @@ fn main() -> Result<(), Box> { .build()?; let search = router.search(&request)?.unwrap(); assert_eq!(*search.data, 2); - assert_eq!(search.path.route, "/files/{*slug}/{file}(.{extension})"); - assert_eq!(search.path.expanded, Some("/files/{*slug}/{file}")); + assert_eq!(search.path.route, "/files/{*slug}/{file}(.{extension})".into()); + assert_eq!(search.path.expanded, Some("/files/{*slug}/{file}".into())); assert_eq!(search.path.parameters[0], ("slug", "documents/folder")); assert_eq!(search.path.parameters[1], ("file", "readme")); @@ -293,14 +293,14 @@ fn main() -> Result<(), Box> { .build()?; let search = router.search(&request)?.unwrap(); assert_eq!(*search.data, 1); - assert_eq!(search.path.route, "/v2"); + assert_eq!(search.path.route, "/v2".into()); let request = RequestBuilder::new() .path("/v2/my-org/my-repo/blobs/sha256:1234567890") .build()?; let search = router.search(&request)?.unwrap(); assert_eq!(*search.data, 2); - assert_eq!(search.path.route, "/v2/{*name:namespace}/blobs/{type}:{digest}"); + assert_eq!(search.path.route, "/v2/{*name:namespace}/blobs/{type}:{digest}".into()); assert_eq!(search.path.parameters[0], ("name", "my-org/my-repo")); assert_eq!(search.path.parameters[1], ("type", "sha256")); assert_eq!(search.path.parameters[2], ("digest", "1234567890")); @@ -348,7 +348,7 @@ fn main() -> Result<(), Box> { let error = router.path.constraint::().unwrap_err(); insta::assert_snapshot!(error, @r" - duplicate constraint name + duplicate path constraint name The constraint name 'my_constraint' is already in use: - existing constraint type: 'rust_out::ConstraintA' @@ -504,26 +504,26 @@ fn main() -> Result<(), Box> { Empty === Path / - ├─ user [9] + ├─ user [*:9] │ ╰─ / - │ ├─ createWithList [10] + │ ├─ createWithList [*:10] │ ├─ log - │ │ ├─ out [12] - │ │ ╰─ in [11] - │ ╰─ {username} [13] - ├─ pet [1] + │ │ ├─ out [*:12] + │ │ ╰─ in [*:11] + │ ╰─ {username} [*:13] + ├─ pet [*:1] │ ╰─ / │ ├─ findBy - │ │ ├─ Status [2] - │ │ ╰─ Tags [3] - │ ╰─ {petId} [4] - │ ╰─ /uploadImage [5] + │ │ ├─ Status [*:2] + │ │ ╰─ Tags [*:3] + │ ╰─ {petId} [*:4] + │ ╰─ /uploadImage [*:5] ├─ store/ - │ ├─ inventory [6] - │ ╰─ order [7] + │ ├─ inventory [*:6] + │ ╰─ order [*:7] │ ╰─ / - │ ╰─ {orderId} [8] - ╰─ {*catch_all} [14] + │ ╰─ {orderId} [*:8] + ╰─ {*catch_all} [*:14] === Method [1] ├─ POST [1] diff --git a/TODO.md b/TODO.md index fc417b40..a7cc9344 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,7 @@ - [x] Authority router. - [ ] Stop using the term 'route' to mean 'template'. - [x] Split routers into seperate crates. -- [ ] Dedupe the 2 tree routers? (Auth vs Patha) +- [x] Dedupe the 2 tree routers? (Auth vs Patha) - [ ] Consider removing expanded routes, and accepting routes as a vec? (complexity/performance issues) - would need to revamp gitlab logic too - [ ] Improve our errors. - [ ] Documentation refresh. diff --git a/benches/matchit_criterion.rs b/benches/matchit_criterion.rs index 3f7d60e4..69f85683 100644 --- a/benches/matchit_criterion.rs +++ b/benches/matchit_criterion.rs @@ -1,5 +1,5 @@ //! Benches sourced from `matchit` (MIT AND BSD-3-Clause) -//! +//! use codspeed_criterion_compat::{criterion_group, criterion_main, Criterion}; use matchit_routes::paths; diff --git a/benches/matchit_divan.rs b/benches/matchit_divan.rs index e4cf07e2..a2e41794 100644 --- a/benches/matchit_divan.rs +++ b/benches/matchit_divan.rs @@ -1,5 +1,5 @@ //! Benches sourced from `matchit` (MIT AND BSD-3-Clause) -//! +//! use divan::AllocProfiler; use matchit_routes::paths; diff --git a/crates/authority/Cargo.toml b/crates/authority/Cargo.toml index 18a74f3f..6fa1fba7 100644 --- a/crates/authority/Cargo.toml +++ b/crates/authority/Cargo.toml @@ -16,6 +16,8 @@ categories.workspace = true workspace = true [dependencies] +wayfind-tree = { path = "../tree" } +wayfind-storage = { path = "../storage" } smallvec = { workspace = true } [dev-dependencies] diff --git a/crates/authority/src/constraints.rs b/crates/authority/src/constraints.rs index 2022185b..af99c0ea 100644 --- a/crates/authority/src/constraints.rs +++ b/crates/authority/src/constraints.rs @@ -3,3 +3,5 @@ pub trait AuthorityConstraint: Send + Sync { fn check(segment: &str) -> bool; } + +// TODO diff --git a/crates/authority/src/delete.rs b/crates/authority/src/delete.rs deleted file mode 100644 index 1a18f680..00000000 --- a/crates/authority/src/delete.rs +++ /dev/null @@ -1,142 +0,0 @@ -use super::{ - node::Node, - parser::{ParsedTemplate, Part}, - state::{State, StaticState}, -}; - -impl Node<'_, S> { - /// Deletes an authority route from the node tree. - /// - /// This method recursively traverses the tree to find and remove the specified authority. - /// Logic should match that used by the insert method. - /// - /// If the authority is found and deleted, we re-optimize the tree structure. - pub fn delete(&mut self, authority: &mut ParsedTemplate) { - if let Some(part) = authority.parts.pop() { - match part { - Part::Static { prefix } => self.delete_static(authority, &prefix), - Part::Dynamic { - name, constraint, .. - } => self.delete_dynamic(authority, &name, constraint.as_ref()), - Part::Wildcard { - name, constraint, .. - } if authority.parts.is_empty() => { - self.delete_end_wildcard(&name, constraint.as_ref()); - } - Part::Wildcard { - name, constraint, .. - } => self.delete_wildcard(authority, &name, constraint.as_ref()), - } - } else { - self.data.take(); - self.needs_optimization = true; - } - } - - fn delete_static(&mut self, authority: &mut ParsedTemplate, prefix: &[u8]) { - let Some(index) = self.static_children.iter().position(|child| { - prefix.len() >= child.state.prefix.len() - && child.state.prefix.iter().zip(prefix).all(|(a, b)| a == b) - }) else { - return; - }; - - let child = &mut self.static_children[index]; - child.needs_optimization = true; - - let remaining_prefix = &prefix[child.state.prefix.len()..]; - if remaining_prefix.is_empty() { - child.delete(authority); - } else { - child.delete_static(authority, remaining_prefix); - }; - - if child.is_empty() { - // Delete empty nodes. - self.static_children.remove(index); - self.needs_optimization = true; - } else if child.is_compressible() { - // Compress redundant nodes. - let merge = child.static_children.remove(0); - - let mut prefix = std::mem::take(&mut child.state.prefix); - prefix.extend(&merge.state.prefix); - - *child = Node { - state: StaticState::new(prefix), - needs_optimization: true, - ..merge - }; - } - } - - fn delete_dynamic( - &mut self, - authority: &mut ParsedTemplate, - name: &str, - constraint: Option<&String>, - ) { - let Some(index) = self.dynamic_children.iter().position(|child| { - child.state.name == name && child.state.constraint.as_ref() == constraint - }) else { - return; - }; - - let child = &mut self.dynamic_children[index]; - child.delete(authority); - - if child.is_empty() { - self.dynamic_children.remove(index); - self.needs_optimization = true; - } - } - - fn delete_wildcard( - &mut self, - authority: &mut ParsedTemplate, - name: &str, - constraint: Option<&String>, - ) { - let Some(index) = self.wildcard_children.iter().position(|child| { - child.state.name == name && child.state.constraint.as_ref() == constraint - }) else { - return; - }; - - let child = &mut self.wildcard_children[index]; - child.delete(authority); - - if child.is_empty() { - self.wildcard_children.remove(index); - self.needs_optimization = true; - } - } - - fn delete_end_wildcard(&mut self, name: &str, constraint: Option<&String>) { - let Some(index) = self.end_wildcard_children.iter().position(|child| { - child.state.name == name && child.state.constraint.as_ref() == constraint - }) else { - return; - }; - - let mut child = self.end_wildcard_children.remove(index); - child.data.take(); - self.needs_optimization = true; - } - - fn is_empty(&self) -> bool { - self.data.is_none() - && self.static_children.is_empty() - && self.dynamic_children.is_empty() - && self.wildcard_children.is_empty() - && self.end_wildcard_children.is_empty() - } - - fn is_compressible(&self) -> bool { - self.data.is_none() - && self.static_children.len() == 1 - && self.dynamic_children.is_empty() - && self.wildcard_children.is_empty() - && self.end_wildcard_children.is_empty() - } -} diff --git a/crates/authority/src/errors.rs b/crates/authority/src/errors.rs index 0a8bcada..a7ecd575 100644 --- a/crates/authority/src/errors.rs +++ b/crates/authority/src/errors.rs @@ -1,17 +1,11 @@ -pub mod constraint; -pub use constraint::ConstraintError; +mod constraint; +pub use constraint::AuthorityConstraintError; -pub mod delete; -pub use delete::DeleteError; +mod delete; +pub use delete::AuthorityDeleteError; -pub mod encoding; -pub use encoding::EncodingError; +mod insert; +pub use insert::AuthorityInsertError; -pub mod insert; -pub use insert::InsertError; - -pub mod search; -pub use search::SearchError; - -pub mod template; -pub use template::TemplateError; +mod template; +pub use template::AuthorityTemplateError; diff --git a/crates/authority/src/errors/constraint.rs b/crates/authority/src/errors/constraint.rs index 13746a54..a1e74dd2 100644 --- a/crates/authority/src/errors/constraint.rs +++ b/crates/authority/src/errors/constraint.rs @@ -1,7 +1,7 @@ use std::{error::Error, fmt::Display}; #[derive(Debug, PartialEq, Eq)] -pub enum ConstraintError { +pub enum AuthorityConstraintError { DuplicateName { name: &'static str, existing_type: &'static str, @@ -9,9 +9,9 @@ pub enum ConstraintError { }, } -impl Error for ConstraintError {} +impl Error for AuthorityConstraintError {} -impl Display for ConstraintError { +impl Display for AuthorityConstraintError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::DuplicateName { @@ -20,7 +20,7 @@ impl Display for ConstraintError { new_type, } => write!( f, - "duplicate constraint name + "duplicate authority constraint name The constraint name '{name}' is already in use: - existing constraint type: '{existing_type}' diff --git a/crates/authority/src/errors/delete.rs b/crates/authority/src/errors/delete.rs index 4f29a61e..89f38a6e 100644 --- a/crates/authority/src/errors/delete.rs +++ b/crates/authority/src/errors/delete.rs @@ -1,30 +1,15 @@ -use super::TemplateError; -use crate::errors::EncodingError; use std::{error::Error, fmt::Display}; #[derive(Debug, PartialEq, Eq)] -pub enum DeleteError { - Encoding(EncodingError), - Template(TemplateError), - NotFound { authority: String }, +pub enum AuthorityDeleteError { Mismatch { authority: String, inserted: String }, } -impl Error for DeleteError {} +impl Error for AuthorityDeleteError {} -impl Display for DeleteError { +impl Display for AuthorityDeleteError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Encoding(error) => error.fmt(f), - Self::Template(error) => error.fmt(f), - Self::NotFound { authority } => write!( - f, - r"not found - - Authority: {authority} - -The specified authority does not exist in the router" - ), Self::Mismatch { authority, inserted, @@ -40,15 +25,3 @@ The authority must be deleted using the same format as was inserted" } } } - -impl From for DeleteError { - fn from(error: EncodingError) -> Self { - Self::Encoding(error) - } -} - -impl From for DeleteError { - fn from(error: TemplateError) -> Self { - Self::Template(error) - } -} diff --git a/crates/authority/src/errors/encoding.rs b/crates/authority/src/errors/encoding.rs deleted file mode 100644 index 58f4ed12..00000000 --- a/crates/authority/src/errors/encoding.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::{error::Error, fmt::Display}; - -/// Errors relating to attempting to decode strings. -#[derive(Debug, PartialEq, Eq)] -pub enum EncodingError { - /// Invalid UTF-8 sequence encountered. - /// - /// # Examples - /// - /// ```rust - /// use wayfind_authority::errors::EncodingError; - /// - /// let error = EncodingError::Utf8Error { - /// input: "hello�world".to_string(), - /// }; - /// - /// let display = " - /// invalid UTF-8 sequence - /// - /// Input: hello�world - /// - /// Expected: valid UTF-8 characters - /// Found: invalid byte sequence - /// "; - /// - /// assert_eq!(error.to_string(), display.trim()); - /// ``` - Utf8Error { - /// The invalid input. - /// This will contain UTF-8 replacement symbols. - input: String, - }, -} - -impl Error for EncodingError {} - -impl Display for EncodingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Utf8Error { input } => { - write!( - f, - "invalid UTF-8 sequence - - Input: {input} - -Expected: valid UTF-8 characters - Found: invalid byte sequence", - ) - } - } - } -} diff --git a/crates/authority/src/errors/insert.rs b/crates/authority/src/errors/insert.rs index d8e4e176..3e0e17dd 100644 --- a/crates/authority/src/errors/insert.rs +++ b/crates/authority/src/errors/insert.rs @@ -1,24 +1,20 @@ -use super::TemplateError; -use crate::AuthorityId; use std::{error::Error, fmt::Display}; #[derive(Debug, PartialEq, Eq)] -pub enum InsertError { - TemplateError(TemplateError), - Overlapping { ids: Vec }, +pub enum AuthorityInsertError { + Overlapping { ids: Vec }, UnknownConstraint { constraint: String }, } -impl Error for InsertError {} +impl Error for AuthorityInsertError {} -impl Display for InsertError { +impl Display for AuthorityInsertError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::TemplateError(error) => error.fmt(f), Self::Overlapping { ids } => write!(f, r"overlapping authorities {ids:?}"), Self::UnknownConstraint { constraint } => write!( f, - r"unknown constraint + r"unknown authority constraint Constraint: {constraint} @@ -27,9 +23,3 @@ The router doesn't recognize this constraint" } } } - -impl From for InsertError { - fn from(error: TemplateError) -> Self { - Self::TemplateError(error) - } -} diff --git a/crates/authority/src/errors/search.rs b/crates/authority/src/errors/search.rs deleted file mode 100644 index 5c6f3117..00000000 --- a/crates/authority/src/errors/search.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::errors::EncodingError; -use std::{error::Error, fmt::Display}; - -/// Errors relating to attempting to search for a match in a [`Router`](crate::Router). -#[derive(Debug, PartialEq, Eq)] -pub enum SearchError { - /// A [`EncodingError`] that occurred during the search. - Encoding(EncodingError), -} - -impl Error for SearchError {} - -impl Display for SearchError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Encoding(error) => error.fmt(f), - } - } -} - -impl From for SearchError { - fn from(error: EncodingError) -> Self { - Self::Encoding(error) - } -} diff --git a/crates/authority/src/errors/template.rs b/crates/authority/src/errors/template.rs index db0a0ab6..929b9c4e 100644 --- a/crates/authority/src/errors/template.rs +++ b/crates/authority/src/errors/template.rs @@ -1,11 +1,10 @@ -use crate::errors::EncodingError; -use std::{error::Error, fmt::Display}; +use std::{error::Error, fmt::Display, str::Utf8Error}; /// Errors relating to malformed authorities. #[derive(Debug, PartialEq, Eq)] -pub enum TemplateError { - /// A [`EncodingError`] that occurred during the decoding. - Encoding(EncodingError), +pub enum AuthorityTemplateError { + /// A [`Utf8Error`] that occurred during the decoding. + Encoding(Utf8Error), /// The authority is empty. Empty, @@ -15,9 +14,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_authority::errors::TemplateError; + /// use wayfind_authority::errors::AuthorityTemplateError; /// - /// let error = TemplateError::EmptyBraces { + /// let error = AuthorityTemplateError::EmptyBraces { /// authority: "{}".to_string(), /// position: 0, /// }; @@ -43,9 +42,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_authority::errors::TemplateError; + /// use wayfind_authority::errors::AuthorityTemplateError; /// - /// let error = TemplateError::UnbalancedBrace { + /// let error = AuthorityTemplateError::UnbalancedBrace { /// authority: "{".to_string(), /// position: 0, /// }; @@ -73,9 +72,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_authority::errors::TemplateError; + /// use wayfind_authority::errors::AuthorityTemplateError; /// - /// let error = TemplateError::EmptyParameter { + /// let error = AuthorityTemplateError::EmptyParameter { /// authority: "{:}".to_string(), /// start: 0, /// length: 3, @@ -104,9 +103,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_authority::errors::TemplateError; + /// use wayfind_authority::errors::AuthorityTemplateError; /// - /// let error = TemplateError::InvalidParameter { + /// let error = AuthorityTemplateError::InvalidParameter { /// authority: "{a.b}".to_string(), /// name: "a.b".to_string(), /// start: 0, @@ -140,9 +139,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_authority::errors::TemplateError; + /// use wayfind_authority::errors::AuthorityTemplateError; /// - /// let error = TemplateError::DuplicateParameter { + /// let error = AuthorityTemplateError::DuplicateParameter { /// authority: "{id}.{id}".to_string(), /// name: "id".to_string(), /// first: 0, @@ -182,9 +181,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_authority::errors::TemplateError; + /// use wayfind_authority::errors::AuthorityTemplateError; /// - /// let error = TemplateError::EmptyWildcard { + /// let error = AuthorityTemplateError::EmptyWildcard { /// authority: "{*}".to_string(), /// start: 0, /// length: 3, @@ -213,9 +212,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_authority::errors::TemplateError; + /// use wayfind_authority::errors::AuthorityTemplateError; /// - /// let error = TemplateError::EmptyConstraint { + /// let error = AuthorityTemplateError::EmptyConstraint { /// authority: "{a:}".to_string(), /// start: 0, /// length: 4, @@ -244,9 +243,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_authority::errors::TemplateError; + /// use wayfind_authority::errors::AuthorityTemplateError; /// - /// let error = TemplateError::InvalidConstraint { + /// let error = AuthorityTemplateError::InvalidConstraint { /// authority: "{a:b/c}".to_string(), /// name: "b/c".to_string(), /// start: 0, @@ -280,9 +279,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_authority::errors::TemplateError; + /// use wayfind_authority::errors::AuthorityTemplateError; /// - /// let error = TemplateError::TouchingParameters { + /// let error = AuthorityTemplateError::TouchingParameters { /// authority: "{a}{b}".to_string(), /// start: 0, /// length: 6, @@ -309,9 +308,9 @@ pub enum TemplateError { }, } -impl Error for TemplateError {} +impl Error for AuthorityTemplateError {} -impl Display for TemplateError { +impl Display for AuthorityTemplateError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Encoding(error) => error.fmt(f), @@ -389,9 +388,7 @@ tip: Parameter names must not contain the characters: ':', '*', '{{', '}}', '.'" second_length, } => { let mut arrow = " ".repeat(authority.len()); - arrow.replace_range(*first..(*first + *first_length), &"^".repeat(*first_length)); - arrow.replace_range( *second..(*second + *second_length), &"^".repeat(*second_length), @@ -476,8 +473,8 @@ tip: Touching parameters are not supported" } } -impl From for TemplateError { - fn from(error: EncodingError) -> Self { +impl From for AuthorityTemplateError { + fn from(error: Utf8Error) -> Self { Self::Encoding(error) } } diff --git a/crates/authority/src/find.rs b/crates/authority/src/find.rs deleted file mode 100644 index 46895e3f..00000000 --- a/crates/authority/src/find.rs +++ /dev/null @@ -1,111 +0,0 @@ -use super::{ - node::Node, - parser::{ParsedTemplate, Part}, - state::State, - AuthorityData, -}; - -impl<'r, S: State> Node<'r, S> { - pub(crate) fn find(&'r self, authority: &mut ParsedTemplate) -> Option<&'r AuthorityData<'r>> { - if authority.parts.is_empty() { - return self.data.as_ref(); - } - - if let Some(part) = authority.parts.pop() { - return match part { - Part::Static { prefix } => self.find_static(authority, &prefix), - Part::Dynamic { name, constraint } => { - self.find_dynamic(authority, &name, constraint.as_deref()) - } - Part::Wildcard { name, constraint } if authority.parts.is_empty() => { - self.find_end_wildcard(authority, &name, constraint.as_deref()) - } - Part::Wildcard { name, constraint } => { - self.find_wildcard(authority, &name, constraint.as_deref()) - } - }; - } - - None - } - - fn find_static( - &'r self, - authority: &mut ParsedTemplate, - prefix: &[u8], - ) -> Option<&'r AuthorityData<'r>> { - for child in self.static_children.iter() { - if !child.state.prefix.is_empty() && child.state.prefix[0] == prefix[0] { - let common_prefix = prefix - .iter() - .zip(&child.state.prefix) - .take_while(|&(x, y)| x == y) - .count(); - - if common_prefix >= child.state.prefix.len() { - if common_prefix >= prefix.len() { - return child.find(authority); - } - - let remaining = prefix[common_prefix..].to_vec(); - if !remaining.is_empty() { - let mut new_authority = ParsedTemplate { - parts: authority.parts.clone(), - ..authority.clone() - }; - - new_authority.parts.push(Part::Static { prefix: remaining }); - return child.find(&mut new_authority); - } - } - } - } - - None - } - - fn find_dynamic( - &'r self, - authority: &mut ParsedTemplate, - name: &str, - constraint: Option<&str>, - ) -> Option<&'r AuthorityData<'r>> { - for child in self.dynamic_children.iter() { - if child.state.name == name && child.state.constraint.as_deref() == constraint { - return child.find(authority); - } - } - - None - } - - fn find_end_wildcard( - &'r self, - authority: &mut ParsedTemplate, - name: &str, - constraint: Option<&str>, - ) -> Option<&'r AuthorityData<'r>> { - for child in self.end_wildcard_children.iter() { - if child.state.name == name && child.state.constraint.as_deref() == constraint { - return child.find(authority); - } - } - - None - } - - fn find_wildcard( - &'r self, - authority: &mut ParsedTemplate, - name: &str, - constraint: Option<&str>, - ) -> Option<&'r AuthorityData<'r>> { - for child in self.wildcard_children.iter() { - if child.state.name == name && child.state.constraint.as_deref() == constraint { - return child.find(authority); - } - } - - None - } -} diff --git a/crates/authority/src/insert.rs b/crates/authority/src/insert.rs deleted file mode 100644 index 0589808e..00000000 --- a/crates/authority/src/insert.rs +++ /dev/null @@ -1,239 +0,0 @@ -use super::{ - node::Node, - parser::{ParsedTemplate, Part}, - state::{DynamicState, EndWildcardState, State, StaticState, WildcardState}, - AuthorityData, -}; -use crate::vec::SortedVec; - -impl<'r, S: State> Node<'r, S> { - /// Inserts a new authority route into the node tree with associated data. - /// Recursively traverses the node tree, creating new nodes as necessary. - pub fn insert(&mut self, authority: &mut ParsedTemplate, data: AuthorityData<'r>) { - if let Some(part) = authority.parts.pop() { - match part { - Part::Static { prefix } => self.insert_static(authority, data, &prefix), - Part::Dynamic { - name, constraint, .. - } => { - self.insert_dynamic(authority, data, name, constraint); - } - Part::Wildcard { - name, constraint, .. - } if authority.parts.is_empty() => { - self.insert_end_wildcard(data, name, constraint); - } - Part::Wildcard { - name, constraint, .. - } => { - self.insert_wildcard(authority, data, name, constraint); - } - }; - } else { - self.data = Some(data); - self.needs_optimization = true; - } - } - - fn insert_static( - &mut self, - authority: &mut ParsedTemplate, - data: AuthorityData<'r>, - prefix: &[u8], - ) { - 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, - - static_children: SortedVec::default(), - dynamic_children: SortedVec::default(), - dynamic_children_shortcut: false, - wildcard_children: SortedVec::default(), - wildcard_children_shortcut: false, - end_wildcard_children: SortedVec::default(), - - priority: 0, - needs_optimization: false, - }; - - new_child.insert(authority, data); - new_child - }); - - self.needs_optimization = true; - return; - }; - - let common_prefix = prefix - .iter() - .zip::<&[u8]>(child.state.prefix.as_ref()) - .take_while(|&(x, y)| x == y) - .count(); - - if common_prefix >= child.state.prefix.len() { - if common_prefix >= prefix.len() { - child.insert(authority, data); - } else { - child.insert_static(authority, data, &prefix[common_prefix..]); - } - - self.needs_optimization = true; - return; - } - - 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, - - static_children: SortedVec::default(), - dynamic_children: SortedVec::default(), - dynamic_children_shortcut: false, - wildcard_children: SortedVec::default(), - wildcard_children_shortcut: false, - end_wildcard_children: SortedVec::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 = SortedVec::new(vec![new_child_a]); - child.insert(authority, data); - } else { - child.static_children = SortedVec::new(vec![new_child_a, new_child_b]); - child.static_children[1].insert(authority, data); - } - - self.needs_optimization = true; - } - - fn insert_dynamic( - &mut self, - authority: &mut ParsedTemplate, - data: AuthorityData<'r>, - name: String, - constraint: Option, - ) { - if let Some(child) = self - .dynamic_children - .find_mut(|child| child.state.name == name && child.state.constraint == constraint) - { - child.insert(authority, data); - } else { - self.dynamic_children.push({ - let mut new_child = Node { - state: DynamicState::new(name, constraint), - data: None, - - static_children: SortedVec::default(), - dynamic_children: SortedVec::default(), - dynamic_children_shortcut: false, - wildcard_children: SortedVec::default(), - wildcard_children_shortcut: false, - end_wildcard_children: SortedVec::default(), - - priority: 0, - needs_optimization: false, - }; - - new_child.insert(authority, data); - new_child - }); - } - - self.needs_optimization = true; - } - - fn insert_wildcard( - &mut self, - authority: &mut ParsedTemplate, - data: AuthorityData<'r>, - name: String, - constraint: Option, - ) { - if let Some(child) = self - .wildcard_children - .find_mut(|child| child.state.name == name && child.state.constraint == constraint) - { - child.insert(authority, data); - } else { - self.wildcard_children.push({ - let mut new_child = Node { - state: WildcardState::new(name, constraint), - data: None, - - static_children: SortedVec::default(), - dynamic_children: SortedVec::default(), - dynamic_children_shortcut: false, - wildcard_children: SortedVec::default(), - wildcard_children_shortcut: false, - end_wildcard_children: SortedVec::default(), - - priority: 0, - needs_optimization: false, - }; - - new_child.insert(authority, data); - new_child - }); - } - - self.needs_optimization = true; - } - - fn insert_end_wildcard( - &mut self, - data: AuthorityData<'r>, - name: String, - constraint: Option, - ) { - if self - .end_wildcard_children - .iter() - .any(|child| child.state.name == name && child.state.constraint == constraint) - { - return; - } - - self.end_wildcard_children.push(Node { - state: EndWildcardState::new(name, constraint), - data: Some(data), - - static_children: SortedVec::default(), - dynamic_children: SortedVec::default(), - dynamic_children_shortcut: false, - wildcard_children: SortedVec::default(), - wildcard_children_shortcut: false, - end_wildcard_children: SortedVec::default(), - - priority: 0, - needs_optimization: false, - }); - - self.needs_optimization = true; - } -} diff --git a/crates/authority/src/lib.rs b/crates/authority/src/lib.rs index ca0997cd..c321f2fb 100644 --- a/crates/authority/src/lib.rs +++ b/crates/authority/src/lib.rs @@ -1,67 +1,76 @@ #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)] -use crate::vec::SortedVec; -use errors::{ConstraintError, DeleteError, InsertError, SearchError}; +use constraints::AuthorityConstraint; +use errors::{AuthorityConstraintError, AuthorityDeleteError, AuthorityInsertError}; use id::AuthorityIdGenerator; -use node::Node; -use parser::{Parser, Part}; +use parser::ParsedAuthority; use smallvec::{smallvec, SmallVec}; -use state::RootState; -use std::{collections::HashMap, fmt::Display}; +use std::{collections::HashMap, fmt::Display, str::Utf8Error, sync::Arc}; +use wayfind_storage::Storage; +use wayfind_tree::{ + node::{Config, Data, Node}, + parser::Part, + search::StoredConstraint, + state::RootState, + vec::SortedVec, +}; pub mod constraints; -pub mod delete; -pub mod display; pub mod errors; -pub mod find; pub mod id; -pub mod insert; -pub mod node; -pub mod optimize; pub mod parser; -pub mod search; -pub mod state; -pub mod vec; -pub use constraints::AuthorityConstraint; pub use id::AuthorityId; +pub type AuthorityParameters<'r, 'p> = SmallVec<[(&'r str, &'p str); 4]>; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AuthorityConfig; + +impl Config for AuthorityConfig { + type Data = AuthorityData; + + const DELIMITER: u8 = b'.'; +} + #[derive(Clone, Debug, Eq, PartialEq)] -pub struct AuthorityData<'r> { +pub struct AuthorityData { pub id: AuthorityId, - pub authority: &'r str, + pub authority: Arc, +} + +impl Data for AuthorityData { + fn id(&self) -> Option { + self.id.0 + } + + fn priority(&self) -> usize { + self.authority.len() + (self.authority.bytes().filter(|&b| b == b'.').count() * 100) + } } #[derive(Debug, Eq, PartialEq)] pub struct AuthorityMatch<'r, 'p> { pub id: AuthorityId, - pub authority: &'r str, + pub authority: Arc, pub parameters: AuthorityParameters<'r, 'p>, } -pub type AuthorityParameters<'r, 'p> = SmallVec<[(&'r str, &'p str); 4]>; - -#[derive(Clone)] -pub struct StoredConstraint { - pub type_name: &'static str, - pub check: fn(&str) -> bool, -} - #[derive(Clone)] -pub struct AuthorityRouter<'r> { - pub root: Node<'r, RootState>, - pub constraints: HashMap<&'r str, StoredConstraint>, +pub struct AuthorityRouter { + pub root: Node, + pub constraints: HashMap, StoredConstraint>, pub id: AuthorityIdGenerator, } -impl<'r> AuthorityRouter<'r> { +impl AuthorityRouter { #[must_use] pub fn new() -> Self { #[allow(unused_mut)] let mut router = Self { root: Node { state: RootState::new(), - data: None, + data: Storage::default(), static_children: SortedVec::default(), dynamic_children: SortedVec::default(), @@ -78,30 +87,13 @@ impl<'r> AuthorityRouter<'r> { }; // TODO - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); - // router.constraint::().unwrap(); router } - pub fn constraint(&mut self) -> Result<(), ConstraintError> { + pub fn constraint(&mut self) -> Result<(), AuthorityConstraintError> { if let Some(existing) = self.constraints.get(C::NAME) { - return Err(ConstraintError::DuplicateName { + return Err(AuthorityConstraintError::DuplicateName { name: C::NAME, existing_type: existing.type_name, new_type: std::any::type_name::(), @@ -109,7 +101,7 @@ impl<'r> AuthorityRouter<'r> { } self.constraints.insert( - C::NAME, + C::NAME.into(), StoredConstraint { type_name: std::any::type_name::(), check: C::check, @@ -119,21 +111,25 @@ impl<'r> AuthorityRouter<'r> { Ok(()) } - pub fn conflicts(&self, authority: &str) -> Result, DeleteError> { - let mut parsed = Parser::new(authority.as_bytes())?; - - if let Some(data) = self.root.find(&mut parsed.route) { + pub fn conflicts( + &self, + key: Option, + authority: &ParsedAuthority, + ) -> Result, AuthorityDeleteError> { + if let Some(data) = self.root.find(key, &authority.template) { return Ok(Some(data.id)); } Ok(None) } - pub fn insert(&mut self, authority: &'r str) -> Result { - let mut parsed = Parser::new(authority.as_bytes())?; - + pub fn insert( + &mut self, + key: Option, + authority: &ParsedAuthority, + ) -> Result { // Check for invalid constraints - for part in &parsed.route.parts { + for part in &authority.template.parts { if let Part::Dynamic { constraint: Some(name), .. @@ -144,7 +140,7 @@ impl<'r> AuthorityRouter<'r> { } = part { if !self.constraints.contains_key(name.as_str()) { - return Err(InsertError::UnknownConstraint { + return Err(AuthorityInsertError::UnknownConstraint { constraint: name.to_string(), }); } @@ -152,28 +148,36 @@ impl<'r> AuthorityRouter<'r> { } // Check for conflicts - if let Ok(Some(id)) = self.conflicts(authority) { + if let Ok(Some(id)) = self.conflicts(key, authority) { return Ok(id); } // No conflicts, proceed with new insert let id = self.id.generate(); - self.root - .insert(&mut parsed.route, AuthorityData { id, authority }); + self.root.insert( + key, + &authority.template, + AuthorityData { + id, + authority: Arc::clone(&authority.input), + }, + ); self.root.optimize(); Ok(id) } - pub fn find(&self, authority: &str) -> Result, DeleteError> { - let mut parsed = Parser::new(authority.as_bytes())?; - - if let Some(data) = self.root.find(&mut parsed.route) { - if data.authority != authority { - return Err(DeleteError::Mismatch { - authority: authority.to_owned(), - inserted: data.authority.to_owned(), + pub fn find( + &self, + key: Option, + authority: &ParsedAuthority, + ) -> Result, AuthorityDeleteError> { + if let Some(data) = self.root.find(key, &authority.template) { + if data.authority != authority.input { + return Err(AuthorityDeleteError::Mismatch { + authority: authority.input.to_string(), + inserted: data.authority.to_string(), }); } Ok(Some(data.id)) @@ -182,40 +186,45 @@ impl<'r> AuthorityRouter<'r> { } } - pub fn delete(&mut self, authority: &str) { - let Ok(mut parsed) = Parser::new(authority.as_bytes()) else { - return; - }; - - let Ok(Some(_)) = self.find(authority) else { + pub fn delete(&mut self, key: Option, authority: &ParsedAuthority) { + let Ok(Some(_)) = self.find(key, authority) else { return; }; - self.root.delete(&mut parsed.route); + self.root.delete(key, &authority.template); self.root.optimize(); } - pub fn search<'p>( + pub fn search<'r, 'p>( &'r self, + key: Option, authority: &'p [u8], - ) -> Result>, SearchError> { - let mut parameters = smallvec![]; + ) -> Result>, Utf8Error> + where + 'p: 'r, + { + let mut parameters: SmallVec<[(&str, &[u8]); 4]> = smallvec![]; let Some((data, _)) = self .root - .search(authority, &mut parameters, &self.constraints)? + .search(key, authority, &mut parameters, &self.constraints) else { return Ok(None); }; + let parameters = parameters + .into_iter() + .map(|(name, value)| std::str::from_utf8(value).map(|value| (name, value))) + .collect::>()?; + Ok(Some(AuthorityMatch { id: data.id, - authority: data.authority, + authority: Arc::clone(&data.authority), parameters, })) } } -impl Display for AuthorityRouter<'_> { +impl Display for AuthorityRouter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.root) } diff --git a/crates/authority/src/parser.rs b/crates/authority/src/parser.rs index 4ace3263..e291ce68 100644 --- a/crates/authority/src/parser.rs +++ b/crates/authority/src/parser.rs @@ -1,69 +1,48 @@ -use super::errors::TemplateError; -use crate::errors::EncodingError; +use crate::errors::AuthorityTemplateError; use smallvec::{smallvec, SmallVec}; +use std::sync::Arc; +use wayfind_tree::parser::{Part, Template}; /// Characters that are not allowed in parameter names or constraints. const INVALID_PARAM_CHARS: [u8; 5] = [b':', b'*', b'{', b'}', b'.']; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParsedTemplate { - pub input: Vec, - pub raw: Vec, - pub parts: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Part { - Static { - prefix: Vec, - }, - Dynamic { - name: String, - constraint: Option, - }, - Wildcard { - name: String, - constraint: Option, - }, -} - #[derive(Debug, PartialEq, Eq)] -pub struct Parser { - pub input: Vec, - pub route: ParsedTemplate, +pub struct ParsedAuthority { + pub input: Arc, + pub template: Template, } -impl Parser { - pub fn new(input: &[u8]) -> Result { +impl ParsedAuthority { + pub fn new(input: &str) -> Result { if input.is_empty() { - return Err(TemplateError::Empty); + return Err(AuthorityTemplateError::Empty); } - let route = Self::parse_route(input)?; - Ok(Self { - input: input.to_vec(), - route, + input: input.into(), + template: Self::parse_authority(input)?, }) } - fn parse_route(input: &[u8]) -> Result { + fn parse_authority(input: &str) -> Result { + let bytes = input.as_bytes(); + let mut parts = vec![]; let mut cursor = 0; // Parameters in order (name, start, length) let mut seen_parameters: SmallVec<[(String, usize, usize); 4]> = smallvec![]; - while cursor < input.len() { - match input[cursor] { + while cursor < bytes.len() { + match bytes[cursor] { b'{' => { let (part, next_cursor) = Self::parse_parameter_part(input, cursor)?; // Check for touching parameters. if let Some((_, start, length)) = seen_parameters.last() { if cursor == start + length { - return Err(TemplateError::TouchingParameters { - authority: String::from_utf8_lossy(input).to_string(), + return Err(AuthorityTemplateError::TouchingParameters { + authority: input.to_owned(), start: *start, length: next_cursor - start, }); @@ -76,8 +55,8 @@ impl Parser { .iter() .find(|(existing, _, _)| existing == name) { - return Err(TemplateError::DuplicateParameter { - authority: String::from_utf8_lossy(input).to_string(), + return Err(AuthorityTemplateError::DuplicateParameter { + authority: input.to_owned(), name: name.to_string(), first: *start, first_length: *length, @@ -93,8 +72,8 @@ impl Parser { cursor = next_cursor; } b'}' => { - return Err(TemplateError::UnbalancedBrace { - authority: String::from_utf8_lossy(input).to_string(), + return Err(AuthorityTemplateError::UnbalancedBrace { + authority: input.to_owned(), position: cursor, }) } @@ -108,19 +87,20 @@ impl Parser { parts.reverse(); - Ok(ParsedTemplate { - input: input.to_vec(), - raw: input.to_vec(), + Ok(Template { + input: input.into(), + raw: input.into(), parts, }) } - fn parse_static_part(input: &[u8], cursor: usize) -> (Part, usize) { + fn parse_static_part(input: &str, cursor: usize) -> (Part, usize) { + let bytes = input.as_bytes(); let mut prefix = vec![]; let mut end = cursor; - while end < input.len() { - match (input[end], input.get(end + 1)) { + while end < bytes.len() { + match (bytes[end], bytes.get(end + 1)) { (b'\\', Some(&next_char)) => { prefix.push(next_char); end += 2; @@ -140,13 +120,18 @@ impl Parser { (Part::Static { prefix }, end) } - fn parse_parameter_part(input: &[u8], cursor: usize) -> Result<(Part, usize), TemplateError> { + fn parse_parameter_part( + input: &str, + cursor: usize, + ) -> Result<(Part, usize), AuthorityTemplateError> { + let bytes = input.as_bytes(); + let start = cursor + 1; let mut end = start; let mut brace_count = 1; - while end < input.len() { - match input[end] { + while end < bytes.len() { + match bytes[end] { b'{' => brace_count += 1, b'}' => { brace_count -= 1; @@ -161,16 +146,16 @@ impl Parser { } if brace_count != 0 { - return Err(TemplateError::UnbalancedBrace { - authority: String::from_utf8_lossy(input).to_string(), + return Err(AuthorityTemplateError::UnbalancedBrace { + authority: input.to_owned(), position: cursor, }); } - let content = &input[start..end]; + let content = &bytes[start..end]; if content.is_empty() { - return Err(TemplateError::EmptyBraces { - authority: String::from_utf8_lossy(input).to_string(), + return Err(AuthorityTemplateError::EmptyBraces { + authority: input.to_owned(), position: cursor, }); } @@ -183,8 +168,8 @@ impl Parser { }); if name.is_empty() { - return Err(TemplateError::EmptyParameter { - authority: String::from_utf8_lossy(input).to_string(), + return Err(AuthorityTemplateError::EmptyParameter { + authority: input.to_owned(), start: cursor, length: end - cursor + 1, }); @@ -194,16 +179,16 @@ impl Parser { let name = if is_wildcard { &name[1..] } else { name }; if is_wildcard && name.is_empty() { - return Err(TemplateError::EmptyWildcard { - authority: String::from_utf8_lossy(input).to_string(), + return Err(AuthorityTemplateError::EmptyWildcard { + authority: input.to_owned(), start: cursor, length: end - cursor + 1, }); } if name.iter().any(|&c| INVALID_PARAM_CHARS.contains(&c)) { - return Err(TemplateError::InvalidParameter { - authority: String::from_utf8_lossy(input).to_string(), + return Err(AuthorityTemplateError::InvalidParameter { + authority: input.to_owned(), name: String::from_utf8_lossy(name).to_string(), start: cursor, length: end - cursor + 1, @@ -212,16 +197,16 @@ impl Parser { if let Some(constraint) = constraint { if constraint.is_empty() { - return Err(TemplateError::EmptyConstraint { - authority: String::from_utf8_lossy(input).to_string(), + return Err(AuthorityTemplateError::EmptyConstraint { + authority: input.to_owned(), start: cursor, length: end - cursor + 1, }); } if constraint.iter().any(|&c| INVALID_PARAM_CHARS.contains(&c)) { - return Err(TemplateError::InvalidConstraint { - authority: String::from_utf8_lossy(input).to_string(), + return Err(AuthorityTemplateError::InvalidConstraint { + authority: input.to_owned(), name: String::from_utf8_lossy(name).to_string(), start: cursor, length: end - cursor + 1, @@ -229,16 +214,9 @@ impl Parser { } } - let name = String::from_utf8(name.to_vec()).map_err(|_| EncodingError::Utf8Error { - input: String::from_utf8_lossy(name).to_string(), - })?; - + let name = std::str::from_utf8(name)?.to_owned(); let constraint = if let Some(constraint) = constraint { - Some( - String::from_utf8(constraint.to_vec()).map_err(|_| EncodingError::Utf8Error { - input: String::from_utf8_lossy(constraint).to_string(), - })?, - ) + Some(std::str::from_utf8(constraint)?.to_owned()) } else { None }; @@ -261,12 +239,12 @@ mod tests { #[test] fn test_parser_static_authority() { assert_eq!( - Parser::new(b"example.com"), - Ok(Parser { - input: b"example.com".to_vec(), - route: ParsedTemplate { - input: b"example.com".to_vec(), - raw: b"example.com".to_vec(), + ParsedAuthority::new("example.com"), + Ok(ParsedAuthority { + input: "example.com".into(), + template: Template { + input: "example.com".into(), + raw: "example.com".into(), parts: vec![Part::Static { prefix: b"example.com".to_vec() }], @@ -278,12 +256,12 @@ mod tests { #[test] fn test_parser_dynamic_authority() { assert_eq!( - Parser::new(b"{subdomain}.example.com"), - Ok(Parser { - input: b"{subdomain}.example.com".to_vec(), - route: ParsedTemplate { - input: b"{subdomain}.example.com".to_vec(), - raw: b"{subdomain}.example.com".to_vec(), + ParsedAuthority::new("{subdomain}.example.com"), + Ok(ParsedAuthority { + input: "{subdomain}.example.com".into(), + template: Template { + input: "{subdomain}.example.com".into(), + raw: "{subdomain}.example.com".into(), parts: vec![ Part::Static { prefix: b".example.com".to_vec() @@ -301,12 +279,12 @@ mod tests { #[test] fn test_parser_wildcard_authority() { assert_eq!( - Parser::new(b"{*subdomains}.example.com"), - Ok(Parser { - input: b"{*subdomains}.example.com".to_vec(), - route: ParsedTemplate { - input: b"{*subdomains}.example.com".to_vec(), - raw: b"{*subdomains}.example.com".to_vec(), + ParsedAuthority::new("{*subdomains}.example.com"), + Ok(ParsedAuthority { + input: "{*subdomains}.example.com".into(), + template: Template { + input: "{*subdomains}.example.com".into(), + raw: "{*subdomains}.example.com".into(), parts: vec![ Part::Static { prefix: b".example.com".to_vec() @@ -324,12 +302,12 @@ mod tests { #[test] fn test_parser_complex_authority() { assert_eq!( - Parser::new(b"{*wildcard}.{param:alpha}.example.com"), - Ok(Parser { - input: b"{*wildcard}.{param:alpha}.example.com".to_vec(), - route: ParsedTemplate { - input: b"{*wildcard}.{param:alpha}.example.com".to_vec(), - raw: b"{*wildcard}.{param:alpha}.example.com".to_vec(), + ParsedAuthority::new("{*wildcard}.{param:alpha}.example.com"), + Ok(ParsedAuthority { + input: "{*wildcard}.{param:alpha}.example.com".into(), + template: Template { + input: "{*wildcard}.{param:alpha}.example.com".into(), + raw: "{*wildcard}.{param:alpha}.example.com".into(), parts: vec![ Part::Static { prefix: b".example.com".to_vec() @@ -353,18 +331,18 @@ mod tests { #[test] fn test_parser_error_empty() { - let error = Parser::new(b"").unwrap_err(); - assert_eq!(error, TemplateError::Empty); + let error = ParsedAuthority::new("").unwrap_err(); + assert_eq!(error, AuthorityTemplateError::Empty); insta::assert_snapshot!(error, @"empty authority"); } #[test] fn test_parser_error_empty_braces() { - let error = Parser::new(b"test.{}.com").unwrap_err(); + let error = ParsedAuthority::new("test.{}.com").unwrap_err(); assert_eq!( error, - TemplateError::EmptyBraces { + AuthorityTemplateError::EmptyBraces { authority: "test.{}.com".to_owned(), position: 5, } @@ -380,10 +358,10 @@ mod tests { #[test] fn test_parser_error_unbalanced_brace_opening() { - let error = Parser::new(b"test.{param.com").unwrap_err(); + let error = ParsedAuthority::new("test.{param.com").unwrap_err(); assert_eq!( error, - TemplateError::UnbalancedBrace { + AuthorityTemplateError::UnbalancedBrace { authority: "test.{param.com".to_owned(), position: 5, } @@ -401,10 +379,10 @@ mod tests { #[test] fn test_parser_error_empty_parameter() { - let error = Parser::new(b"test.{:constraint}.com").unwrap_err(); + let error = ParsedAuthority::new("test.{:constraint}.com").unwrap_err(); assert_eq!( error, - TemplateError::EmptyParameter { + AuthorityTemplateError::EmptyParameter { authority: "test.{:constraint}.com".to_owned(), start: 5, length: 13, @@ -421,10 +399,10 @@ mod tests { #[test] fn test_parser_error_duplicate_parameter() { - let error = Parser::new(b"{param}.test.{param:alpha}.com").unwrap_err(); + let error = ParsedAuthority::new("{param}.test.{param:alpha}.com").unwrap_err(); assert_eq!( error, - TemplateError::DuplicateParameter { + AuthorityTemplateError::DuplicateParameter { authority: "{param}.test.{param:alpha}.com".to_owned(), name: "param".to_owned(), first: 0, @@ -446,10 +424,10 @@ mod tests { #[test] fn test_parser_error_touching_parameters() { - let error = Parser::new(b"{param1}{param2}.com").unwrap_err(); + let error = ParsedAuthority::new("{param1}{param2}.com").unwrap_err(); assert_eq!( error, - TemplateError::TouchingParameters { + AuthorityTemplateError::TouchingParameters { authority: "{param1}{param2}.com".to_owned(), start: 0, length: 16, diff --git a/crates/authority/src/search.rs b/crates/authority/src/search.rs deleted file mode 100644 index fd80734e..00000000 --- a/crates/authority/src/search.rs +++ /dev/null @@ -1,333 +0,0 @@ -use super::{node::Node, state::State, AuthorityData}; -use crate::{ - errors::{EncodingError, SearchError}, - AuthorityParameters, StoredConstraint, -}; -use smallvec::smallvec; -use std::collections::HashMap; - -impl<'r, S: State> Node<'r, S> { - pub fn search<'p>( - &'r self, - authority: &'p [u8], - parameters: &mut AuthorityParameters<'r, 'p>, - constraints: &HashMap<&'r str, StoredConstraint>, - ) -> Result, usize)>, SearchError> { - if authority.is_empty() { - return Ok(self.data.as_ref().map(|data| (data, self.priority))); - } - - if let Some(search) = self.search_static(authority, parameters, constraints)? { - return Ok(Some(search)); - } - - if let Some(search) = self.search_dynamic(authority, parameters, constraints)? { - return Ok(Some(search)); - } - - if let Some(search) = self.search_wildcard(authority, parameters, constraints)? { - return Ok(Some(search)); - } - - if let Some(search) = self.search_end_wildcard(authority, parameters, constraints)? { - return Ok(Some(search)); - } - - Ok(None) - } - - fn search_static<'p>( - &'r self, - authority: &'p [u8], - parameters: &mut AuthorityParameters<'r, 'p>, - constraints: &HashMap<&'r str, StoredConstraint>, - ) -> Result, usize)>, SearchError> { - for child in self.static_children.iter() { - if authority.len() >= child.state.prefix.len() - && child - .state - .prefix - .iter() - .zip(authority) - .all(|(a, b)| a == b) - { - let remaining = &authority[child.state.prefix.len()..]; - if let Some((data, priority)) = child.search(remaining, parameters, constraints)? { - return Ok(Some((data, priority))); - } - } - } - - Ok(None) - } - - fn search_dynamic<'p>( - &'r self, - authority: &'p [u8], - parameters: &mut AuthorityParameters<'r, 'p>, - constraints: &HashMap<&'r str, StoredConstraint>, - ) -> Result, usize)>, SearchError> { - if self.dynamic_children_shortcut { - self.search_dynamic_segment(authority, parameters, constraints) - } else { - self.search_dynamic_inline(authority, parameters, constraints) - } - } - - fn search_dynamic_inline<'p>( - &'r self, - authority: &'p [u8], - parameters: &mut AuthorityParameters<'r, 'p>, - constraints: &HashMap<&'r str, StoredConstraint>, - ) -> Result, usize)>, SearchError> { - for child in self.dynamic_children.iter() { - let mut consumed = 0; - - let mut best_match: Option<(&'r AuthorityData<'r>, usize)> = None; - let mut best_match_parameters = smallvec![]; - - while consumed < authority.len() { - if authority[consumed] == b'.' { - break; - } - - consumed += 1; - - let segment = &authority[..consumed]; - if !Self::check_constraint(child.state.constraint.as_ref(), segment, constraints) { - continue; - } - - let mut current_parameters = parameters.clone(); - current_parameters.push(( - &child.state.name, - std::str::from_utf8(segment).map_err(|_| EncodingError::Utf8Error { - input: String::from_utf8_lossy(segment).to_string(), - })?, - )); - - let Some((data, priority)) = - child.search(&authority[consumed..], &mut current_parameters, constraints)? - else { - continue; - }; - - if best_match.is_none_or(|(_, best_priority)| priority >= best_priority) { - best_match = Some((data, priority)); - best_match_parameters = current_parameters; - } - } - - if let Some(result) = best_match { - *parameters = best_match_parameters; - return Ok(Some(result)); - } - } - - Ok(None) - } - - fn search_dynamic_segment<'p>( - &'r self, - authority: &'p [u8], - parameters: &mut AuthorityParameters<'r, 'p>, - constraints: &HashMap<&'r str, StoredConstraint>, - ) -> Result, usize)>, SearchError> { - for child in self.dynamic_children.iter() { - let segment_end = authority - .iter() - .position(|&b| b == b'.') - .unwrap_or(authority.len()); - - let segment = &authority[..segment_end]; - if !Self::check_constraint(child.state.constraint.as_ref(), segment, constraints) { - continue; - } - - parameters.push(( - &child.state.name, - std::str::from_utf8(segment).map_err(|_| EncodingError::Utf8Error { - input: String::from_utf8_lossy(segment).to_string(), - })?, - )); - - if let Some(result) = - child.search(&authority[segment_end..], parameters, constraints)? - { - return Ok(Some(result)); - } - - parameters.pop(); - } - - Ok(None) - } - - fn search_wildcard<'p>( - &'r self, - authority: &'p [u8], - parameters: &mut AuthorityParameters<'r, 'p>, - constraints: &HashMap<&'r str, StoredConstraint>, - ) -> Result, usize)>, SearchError> { - if self.wildcard_children_shortcut { - self.search_wildcard_segment(authority, parameters, constraints) - } else { - self.search_wildcard_inline(authority, parameters, constraints) - } - } - - fn search_wildcard_inline<'p>( - &'r self, - authority: &'p [u8], - parameters: &mut AuthorityParameters<'r, 'p>, - constraints: &HashMap<&'r str, StoredConstraint>, - ) -> Result, usize)>, SearchError> { - for child in self.wildcard_children.iter() { - let mut consumed = 0; - - let mut best_match: Option<(&'r AuthorityData<'r>, usize)> = None; - let mut best_match_parameters = smallvec![]; - - while consumed < authority.len() { - consumed += 1; - - let segment = &authority[..consumed]; - if !Self::check_constraint(child.state.constraint.as_ref(), segment, constraints) { - continue; - } - - let mut current_parameters = parameters.clone(); - current_parameters.push(( - &child.state.name, - std::str::from_utf8(segment).map_err(|_| EncodingError::Utf8Error { - input: String::from_utf8_lossy(segment).to_string(), - })?, - )); - - let Some((data, priority)) = - child.search(&authority[consumed..], &mut current_parameters, constraints)? - else { - continue; - }; - - if best_match.is_none_or(|(_, best_priority)| priority >= best_priority) { - best_match = Some((data, priority)); - best_match_parameters = current_parameters; - } - } - - if let Some(result) = best_match { - *parameters = best_match_parameters; - return Ok(Some(result)); - } - } - - Ok(None) - } - - fn search_wildcard_segment<'p>( - &'r self, - authority: &'p [u8], - parameters: &mut AuthorityParameters<'r, 'p>, - constraints: &HashMap<&'r str, StoredConstraint>, - ) -> Result, usize)>, SearchError> { - for child in self.wildcard_children.iter() { - let mut consumed = 0; - let mut remaining = authority; - let mut section_end = false; - - while !remaining.is_empty() { - if section_end { - consumed += 1; - } - - let segment_end = remaining - .iter() - .position(|&b| b == b'.') - .unwrap_or(remaining.len()); - - if segment_end == 0 { - consumed += 1; - section_end = false; - } else { - consumed += segment_end; - section_end = true; - } - - let segment = if authority[..consumed].ends_with(b".") { - &authority[..consumed - 1] - } else { - &authority[..consumed] - }; - - if !Self::check_constraint(child.state.constraint.as_ref(), segment, constraints) { - break; - } - - parameters.push(( - &child.state.name, - std::str::from_utf8(segment).map_err(|_| EncodingError::Utf8Error { - input: String::from_utf8_lossy(segment).to_string(), - })?, - )); - - if let Some(result) = - child.search(&remaining[segment_end..], parameters, constraints)? - { - return Ok(Some(result)); - } - - parameters.pop(); - - if segment_end == remaining.len() { - break; - } - - remaining = &remaining[segment_end + 1..]; - } - } - - Ok(None) - } - - fn search_end_wildcard<'p>( - &'r self, - authority: &'p [u8], - parameters: &mut AuthorityParameters<'r, 'p>, - constraints: &HashMap<&'r str, StoredConstraint>, - ) -> Result, usize)>, SearchError> { - for child in self.end_wildcard_children.iter() { - if !Self::check_constraint(child.state.constraint.as_ref(), authority, constraints) { - continue; - } - - parameters.push(( - &child.state.name, - std::str::from_utf8(authority).map_err(|_| EncodingError::Utf8Error { - input: String::from_utf8_lossy(authority).to_string(), - })?, - )); - - return Ok(child.data.as_ref().map(|data| (data, child.priority))); - } - - Ok(None) - } - - fn check_constraint( - constraint: Option<&String>, - segment: &[u8], - constraints: &HashMap<&'r str, StoredConstraint>, - ) -> bool { - let Some(constraint) = constraint else { - return true; - }; - - let constraint = constraints.get(constraint.as_str()).unwrap(); - let Ok(segment) = std::str::from_utf8(segment) else { - return false; - }; - - (constraint.check)(segment) - } -} diff --git a/crates/method/Cargo.toml b/crates/method/Cargo.toml index b4e5a041..d98754cc 100644 --- a/crates/method/Cargo.toml +++ b/crates/method/Cargo.toml @@ -16,8 +16,5 @@ categories.workspace = true workspace = true [dependencies] +wayfind-storage = { path = "../storage" } smallvec = { workspace = true } - -[dev-dependencies] -insta = { workspace = true } -similar-asserts = { workspace = true } diff --git a/crates/method/src/display.rs b/crates/method/src/display.rs index 5f0ff7a4..1b97df45 100644 --- a/crates/method/src/display.rs +++ b/crates/method/src/display.rs @@ -7,7 +7,11 @@ impl Display for MethodRouter { let last_key = self.map.keys().last(); for key in self.map.keys() { - writeln!(output, "[{key}]")?; + writeln!( + output, + "[{}]", + key.map(|k| k.to_string()).unwrap_or_else(|| "*".to_owned()) + )?; let method_map = &self.map[key]; for (i, (method, id)) in method_map.iter().enumerate() { diff --git a/crates/method/src/errors.rs b/crates/method/src/errors.rs index b070fff6..e841aeec 100644 --- a/crates/method/src/errors.rs +++ b/crates/method/src/errors.rs @@ -1,8 +1,8 @@ -pub mod delete; -pub use delete::DeleteError; +mod delete; +pub use delete::MethodDeleteError; -pub mod insert; -pub use insert::InsertError; +mod insert; +pub use insert::MethodInsertError; -pub mod search; -pub use search::SearchError; +mod search; +pub use search::MethodSearchError; diff --git a/crates/method/src/errors/delete.rs b/crates/method/src/errors/delete.rs index 6c94b8df..a2744b7d 100644 --- a/crates/method/src/errors/delete.rs +++ b/crates/method/src/errors/delete.rs @@ -1,14 +1,14 @@ use std::{error::Error, fmt::Display}; #[derive(Debug, PartialEq, Eq)] -pub enum DeleteError { +pub enum MethodDeleteError { NotFound, Mismatch, } -impl Error for DeleteError {} +impl Error for MethodDeleteError {} -impl Display for DeleteError { +impl Display for MethodDeleteError { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } diff --git a/crates/method/src/errors/insert.rs b/crates/method/src/errors/insert.rs index 6d1a7b3e..23be352a 100644 --- a/crates/method/src/errors/insert.rs +++ b/crates/method/src/errors/insert.rs @@ -1,14 +1,14 @@ use std::{error::Error, fmt::Display}; #[derive(Debug, PartialEq, Eq)] -pub enum InsertError { +pub enum MethodInsertError { Empty, Conflict, } -impl Error for InsertError {} +impl Error for MethodInsertError {} -impl Display for InsertError { +impl Display for MethodInsertError { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } diff --git a/crates/method/src/errors/search.rs b/crates/method/src/errors/search.rs index 29925a37..a9e00e7b 100644 --- a/crates/method/src/errors/search.rs +++ b/crates/method/src/errors/search.rs @@ -1,13 +1,13 @@ use std::{error::Error, fmt::Display}; #[derive(Debug, PartialEq, Eq)] -pub enum SearchError { +pub enum MethodSearchError { NotAllowed, } -impl Error for SearchError {} +impl Error for MethodSearchError {} -impl Display for SearchError { +impl Display for MethodSearchError { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } diff --git a/crates/method/src/lib.rs b/crates/method/src/lib.rs index 41fd0f29..a1c71462 100644 --- a/crates/method/src/lib.rs +++ b/crates/method/src/lib.rs @@ -1,8 +1,9 @@ #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)] -use errors::{DeleteError, InsertError, SearchError}; +use errors::{MethodDeleteError, MethodInsertError, MethodSearchError}; use id::MethodIdGenerator; use std::collections::BTreeMap; +use wayfind_storage::Storage; pub mod display; pub mod errors; @@ -10,8 +11,6 @@ pub mod id; pub use id::MethodId; -// FIXME: Make the key here impl based, so we can make custom keys. - #[derive(Clone, Debug, Eq, PartialEq)] pub struct MethodMatch<'r> { pub id: MethodId, @@ -20,7 +19,7 @@ pub struct MethodMatch<'r> { #[derive(Clone)] pub struct MethodRouter { - pub map: BTreeMap>, + pub map: Storage>, pub id: MethodIdGenerator, } @@ -28,21 +27,28 @@ impl MethodRouter { #[must_use] pub fn new() -> Self { Self { - map: BTreeMap::default(), + map: Storage::default(), id: MethodIdGenerator::default(), } } - pub fn insert(&mut self, path_id: usize, methods: &[&str]) -> Result { + pub fn insert( + &mut self, + key: Option, + methods: &[&str], + ) -> Result { if methods.is_empty() { - return Err(InsertError::Empty); + return Err(MethodInsertError::Empty); } - let map = self.map.entry(path_id).or_default(); + if !self.map.contains(&key) { + self.map.insert(key, BTreeMap::default()); + } + let map = &mut self.map[&key]; for method in methods { if map.contains_key(*method) { - return Err(InsertError::Conflict); + return Err(MethodInsertError::Conflict); } } @@ -56,10 +62,10 @@ impl MethodRouter { pub fn search<'r>( &self, - path_id: usize, + key: Option, method: &'r str, - ) -> Result>, SearchError> { - if let Some(method_map) = self.map.get(&path_id) { + ) -> Result>, MethodSearchError> { + if let Some(method_map) = self.map.get(&key) { if let Some(id) = method_map.get(method) { return Ok(Some(MethodMatch { id: *id, @@ -67,21 +73,25 @@ impl MethodRouter { })); } - return Err(SearchError::NotAllowed); + return Err(MethodSearchError::NotAllowed); } Ok(None) } - pub fn find(&self, path_id: usize, methods: &[&str]) -> Result { + pub fn find( + &self, + key: Option, + methods: &[&str], + ) -> Result { if methods.is_empty() { return Ok(MethodId(None)); } - let map = self.map.get(&path_id).ok_or(DeleteError::NotFound)?; + let map = self.map.get(&key).ok_or(MethodDeleteError::NotFound)?; for method in methods { if !map.contains_key(*method) { - return Err(DeleteError::Mismatch); + return Err(MethodDeleteError::Mismatch); } } @@ -90,7 +100,7 @@ impl MethodRouter { match map.get(*method) { Some(id) if id == first_id => continue, _ => { - return Err(DeleteError::Mismatch); + return Err(MethodDeleteError::Mismatch); } } } @@ -99,17 +109,17 @@ impl MethodRouter { .iter() .any(|(method, id)| id == first_id && !methods.contains(&method.as_str())) { - return Err(DeleteError::Mismatch); + return Err(MethodDeleteError::Mismatch); } Ok(*first_id) } - pub fn delete(&mut self, path_id: usize, method_id: MethodId) { - if let Some(map) = self.map.get_mut(&path_id) { + pub fn delete(&mut self, key: Option, method_id: MethodId) { + if let Some(map) = self.map.get_mut(&key) { map.retain(|_, id| id != &method_id); if map.is_empty() { - self.map.remove(&path_id); + self.map.remove(&key); } } } diff --git a/crates/path/Cargo.toml b/crates/path/Cargo.toml index 59299695..e16ec2f9 100644 --- a/crates/path/Cargo.toml +++ b/crates/path/Cargo.toml @@ -16,6 +16,8 @@ categories.workspace = true workspace = true [dependencies] +wayfind-tree = { path = "../tree" } +wayfind-storage = { path = "../storage" } smallvec = { workspace = true } [dev-dependencies] diff --git a/crates/path/src/display.rs b/crates/path/src/display.rs deleted file mode 100644 index c3197882..00000000 --- a/crates/path/src/display.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::{node::Node, state::State}; -use std::fmt::{Display, Write}; - -impl Display for Node<'_, S> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fn debug_node( - output: &mut String, - node: &Node<'_, S>, - padding: &str, - is_top: bool, - is_last: bool, - ) -> std::fmt::Result { - let key = node.state.key(); - - if is_top { - if let Some(data) = node.data.as_ref() { - writeln!(output, "{key} [{}]", data.id)?; - } else { - writeln!(output, "{key}")?; - } - } else { - let branch = if is_last { "╰─" } else { "├─" }; - if let Some(data) = node.data.as_ref() { - writeln!(output, "{padding}{branch} {key} [{}]", data.id)?; - } else { - writeln!(output, "{padding}{branch} {key}")?; - } - } - - let new_prefix = if is_top { - padding.to_owned() - } else if is_last { - format!("{padding} ") - } else { - format!("{padding}│ ") - }; - - let mut total_children = node.static_children.len() - + node.dynamic_children.len() - + node.wildcard_children.len() - + node.end_wildcard_children.len(); - - for child in node.static_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - for child in node.dynamic_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - for child in node.wildcard_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - for child in node.end_wildcard_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - Ok(()) - } - - let mut output = String::new(); - let padding = " ".repeat(self.state.padding()); - - // Handle root node manually - if self.state.key().is_empty() { - let total_children = self.static_children.len() - + self.dynamic_children.len() - + self.wildcard_children.len() - + self.end_wildcard_children.len(); - - let mut remaining = total_children; - - for child in self.static_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - - for child in self.dynamic_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - - for child in self.wildcard_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - - for child in self.end_wildcard_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - } else { - debug_node(&mut output, self, &padding, true, true)?; - } - - write!(f, "{}", output.trim_end()) - } -} diff --git a/crates/path/src/errors.rs b/crates/path/src/errors.rs index 0a8bcada..a8719d9f 100644 --- a/crates/path/src/errors.rs +++ b/crates/path/src/errors.rs @@ -1,17 +1,11 @@ -pub mod constraint; -pub use constraint::ConstraintError; +mod constraint; +pub use constraint::PathConstraintError; -pub mod delete; -pub use delete::DeleteError; +mod delete; +pub use delete::PathDeleteError; -pub mod encoding; -pub use encoding::EncodingError; +mod insert; +pub use insert::PathInsertError; -pub mod insert; -pub use insert::InsertError; - -pub mod search; -pub use search::SearchError; - -pub mod template; -pub use template::TemplateError; +mod template; +pub use template::PathTemplateError; diff --git a/crates/path/src/errors/constraint.rs b/crates/path/src/errors/constraint.rs index b0a5a268..dd11adcb 100644 --- a/crates/path/src/errors/constraint.rs +++ b/crates/path/src/errors/constraint.rs @@ -1,50 +1,17 @@ use std::{error::Error, fmt::Display}; -/// Errors relating to constraints. #[derive(Debug, PartialEq, Eq)] -pub enum ConstraintError { - /// Constraint name is already in use. - /// - /// # Examples - /// - /// ```rust - /// use wayfind_path::errors::ConstraintError; - /// - /// let error = ConstraintError::DuplicateName { - /// name: "my_constraint", - /// existing_type: "my_crate::constraints::A", - /// new_type: "my_crate::constraints::B", - /// }; - /// - /// let display = " - /// duplicate constraint name - /// - /// The constraint name 'my_constraint' is already in use: - /// - existing constraint type: 'my_crate::constraints::A' - /// - new constraint type: 'my_crate::constraints::B' - /// - /// help: each constraint must have a unique name - /// - /// try: - /// - Check if you have accidentally added the same constraint twice - /// - Ensure different constraints have different names - /// "; - /// - /// assert_eq!(error.to_string(), display.trim()); - /// ``` +pub enum PathConstraintError { DuplicateName { - /// The name of the constraint. name: &'static str, - /// The [`type_name`](std::any::type_name) of the already existing constraint. existing_type: &'static str, - /// The [`type_name`](std::any::type_name) of the attempted new constraint. new_type: &'static str, }, } -impl Error for ConstraintError {} +impl Error for PathConstraintError {} -impl Display for ConstraintError { +impl Display for PathConstraintError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::DuplicateName { @@ -53,7 +20,7 @@ impl Display for ConstraintError { new_type, } => write!( f, - "duplicate constraint name + "duplicate path constraint name The constraint name '{name}' is already in use: - existing constraint type: '{existing_type}' diff --git a/crates/path/src/errors/delete.rs b/crates/path/src/errors/delete.rs index 0798973e..189e92b4 100644 --- a/crates/path/src/errors/delete.rs +++ b/crates/path/src/errors/delete.rs @@ -1,109 +1,24 @@ -use super::TemplateError; -use crate::errors::EncodingError; use std::{error::Error, fmt::Display}; -/// Errors relating to attempting to delete a route from a [`Router`](crate::Router). #[derive(Debug, PartialEq, Eq)] -pub enum DeleteError { - /// A [`EncodingError`] that occurred during the decoding. - Encoding(EncodingError), - - /// A [`TemplateError`] that occurred during the delete. - Template(TemplateError), - - /// Route to be deleted was not found in the router. - /// - /// # Examples - /// - /// ```rust - /// use wayfind_path::errors::DeleteError; - /// - /// let error = DeleteError::NotFound { - /// route: "/not_found".to_string(), - /// }; - /// - /// let display = " - /// not found - /// - /// Route: /not_found - /// - /// The specified route does not exist in the router - /// "; - /// - /// assert_eq!(error.to_string(), display.trim()); - /// ``` - NotFound { - /// The route that was not found in the router. - route: String, - }, - - /// Tried to delete a route using a format that doesn't match how it was inserted. - /// - /// # Examples - /// - /// ```rust - /// use wayfind_path::errors::DeleteError; - /// - /// let error = DeleteError::Mismatch { - /// route: "/users/{id}/".to_string(), - /// inserted: "/users/{id}(/)".to_string(), - /// }; - /// - /// let display = " - /// delete mismatch - /// - /// Route: /users/{id}/ - /// Inserted: /users/{id}(/) - /// - /// The route must be deleted using the same format as was inserted - /// "; - /// - /// assert_eq!(error.to_string(), display.trim()); - /// ``` - Mismatch { - /// The route that was attempted to be deleted. - route: String, - /// The route stored as stored in the router. - inserted: String, - }, +pub enum PathDeleteError { + Mismatch { path: String, inserted: String }, } -impl Error for DeleteError {} +impl Error for PathDeleteError {} -impl Display for DeleteError { +impl Display for PathDeleteError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Encoding(error) => error.fmt(f), - Self::Template(error) => error.fmt(f), - Self::NotFound { route } => write!( - f, - r"not found - - Route: {route} - -The specified route does not exist in the router" - ), - Self::Mismatch { route, inserted } => write!( + Self::Mismatch { path, inserted } => write!( f, r"delete mismatch - Route: {route} - Inserted: {inserted} + Path: {path} + Inserted: {inserted} -The route must be deleted using the same format as was inserted" +The path must be deleted using the same format as was inserted" ), } } } - -impl From for DeleteError { - fn from(error: EncodingError) -> Self { - Self::Encoding(error) - } -} - -impl From for DeleteError { - fn from(error: TemplateError) -> Self { - Self::Template(error) - } -} diff --git a/crates/path/src/errors/encoding.rs b/crates/path/src/errors/encoding.rs deleted file mode 100644 index 6590acd8..00000000 --- a/crates/path/src/errors/encoding.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::{error::Error, fmt::Display}; - -/// Errors relating to attempting to decode strings. -#[derive(Debug, PartialEq, Eq)] -pub enum EncodingError { - /// Invalid UTF-8 sequence encountered. - /// - /// # Examples - /// - /// ```rust - /// use wayfind_path::errors::EncodingError; - /// - /// let error = EncodingError::Utf8Error { - /// input: "hello�world".to_string(), - /// }; - /// - /// let display = " - /// invalid UTF-8 sequence - /// - /// Input: hello�world - /// - /// Expected: valid UTF-8 characters - /// Found: invalid byte sequence - /// "; - /// - /// assert_eq!(error.to_string(), display.trim()); - /// ``` - Utf8Error { - /// The invalid input. - /// This will contain UTF-8 replacement symbols. - input: String, - }, -} - -impl Error for EncodingError {} - -impl Display for EncodingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Utf8Error { input } => { - write!( - f, - "invalid UTF-8 sequence - - Input: {input} - -Expected: valid UTF-8 characters - Found: invalid byte sequence", - ) - } - } - } -} diff --git a/crates/path/src/errors/insert.rs b/crates/path/src/errors/insert.rs index 1018bfe1..771a7b9b 100644 --- a/crates/path/src/errors/insert.rs +++ b/crates/path/src/errors/insert.rs @@ -1,53 +1,20 @@ -use super::TemplateError; -use crate::PathId; use std::{error::Error, fmt::Display}; -/// Errors relating to attempting to insert a route into a [`Router`](crate::Router). #[derive(Debug, PartialEq, Eq)] -pub enum InsertError { - /// A [`TemplateError`] that occurred during the insert operation. - Template(TemplateError), - - /// TODO - OverlappingRoutes { ids: Vec }, - - /// The constraint specified in the route is not recognized by the router. - /// - /// # Examples - /// - /// ```rust - /// use wayfind_path::errors::InsertError; - /// - /// let error = InsertError::UnknownConstraint { - /// constraint: "unknown_constraint".to_string(), - /// }; - /// - /// let display = " - /// unknown constraint - /// - /// Constraint: unknown_constraint - /// - /// The router doesn't recognize this constraint - /// "; - /// - /// assert_eq!(error.to_string(), display.trim()); - /// ``` - UnknownConstraint { - /// The name of the unrecognized constraint. - constraint: String, - }, +pub enum PathInsertError { + Overlapping { ids: Vec }, + UnknownConstraint { constraint: String }, } -impl Error for InsertError {} +impl Error for PathInsertError {} -impl Display for InsertError { +impl Display for PathInsertError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Template(error) => error.fmt(f), - Self::OverlappingRoutes { ids } => write!(f, r"overlapping routes {ids:?}"), + Self::Overlapping { ids } => write!(f, r"overlapping authorities {ids:?}"), Self::UnknownConstraint { constraint } => write!( f, - r"unknown constraint + r"unknown path constraint Constraint: {constraint} @@ -56,9 +23,3 @@ The router doesn't recognize this constraint" } } } - -impl From for InsertError { - fn from(error: TemplateError) -> Self { - Self::Template(error) - } -} diff --git a/crates/path/src/errors/search.rs b/crates/path/src/errors/search.rs deleted file mode 100644 index 5c6f3117..00000000 --- a/crates/path/src/errors/search.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::errors::EncodingError; -use std::{error::Error, fmt::Display}; - -/// Errors relating to attempting to search for a match in a [`Router`](crate::Router). -#[derive(Debug, PartialEq, Eq)] -pub enum SearchError { - /// A [`EncodingError`] that occurred during the search. - Encoding(EncodingError), -} - -impl Error for SearchError {} - -impl Display for SearchError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Encoding(error) => error.fmt(f), - } - } -} - -impl From for SearchError { - fn from(error: EncodingError) -> Self { - Self::Encoding(error) - } -} diff --git a/crates/path/src/errors/template.rs b/crates/path/src/errors/template.rs index 7b954552..c22b4eba 100644 --- a/crates/path/src/errors/template.rs +++ b/crates/path/src/errors/template.rs @@ -1,11 +1,10 @@ -use crate::errors::EncodingError; -use std::{error::Error, fmt::Display}; +use std::{error::Error, fmt::Display, str::Utf8Error}; /// Errors relating to malformed routes. #[derive(Debug, PartialEq, Eq)] -pub enum TemplateError { - /// A [`EncodingError`] that occurred during the decoding. - Encoding(EncodingError), +pub enum PathTemplateError { + /// A [`Utf8Error`] that occurred during the decoding. + Encoding(Utf8Error), /// The route is empty. Empty, @@ -15,9 +14,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::MissingLeadingSlash { + /// let error = PathTemplateError::MissingLeadingSlash { /// route: "abc".to_string(), /// }; /// @@ -41,9 +40,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::EmptyBraces { + /// let error = PathTemplateError::EmptyBraces { /// route: "/{}".to_string(), /// position: 1, /// }; @@ -69,9 +68,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::UnbalancedBrace { + /// let error = PathTemplateError::UnbalancedBrace { /// route: "/{".to_string(), /// position: 1, /// }; @@ -99,9 +98,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::EmptyParentheses { + /// let error = PathTemplateError::EmptyParentheses { /// route: "/()".to_string(), /// position: 1, /// }; @@ -127,9 +126,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::UnbalancedParenthesis { + /// let error = PathTemplateError::UnbalancedParenthesis { /// route: "/(".to_string(), /// position: 1, /// }; @@ -157,9 +156,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::EmptyParameter { + /// let error = PathTemplateError::EmptyParameter { /// route: "/{:}".to_string(), /// start: 1, /// length: 3, @@ -188,9 +187,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::InvalidParameter { + /// let error = PathTemplateError::InvalidParameter { /// route: "/{a/b}".to_string(), /// name: "a/b".to_string(), /// start: 1, @@ -224,9 +223,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::DuplicateParameter { + /// let error = PathTemplateError::DuplicateParameter { /// route: "/{id}/{id}".to_string(), /// name: "id".to_string(), /// first: 1, @@ -266,9 +265,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::EmptyWildcard { + /// let error = PathTemplateError::EmptyWildcard { /// route: "/{*}".to_string(), /// start: 1, /// length: 3, @@ -297,9 +296,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::EmptyConstraint { + /// let error = PathTemplateError::EmptyConstraint { /// route: "/{a:}".to_string(), /// start: 1, /// length: 4, @@ -328,9 +327,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::InvalidConstraint { + /// let error = PathTemplateError::InvalidConstraint { /// route: "/{a:b/c}".to_string(), /// name: "b/c".to_string(), /// start: 1, @@ -364,9 +363,9 @@ pub enum TemplateError { /// # Examples /// /// ```rust - /// use wayfind_path::errors::TemplateError; + /// use wayfind_path::errors::PathTemplateError; /// - /// let error = TemplateError::TouchingParameters { + /// let error = PathTemplateError::TouchingParameters { /// route: "/{a}{b}".to_string(), /// start: 1, /// length: 6, @@ -393,9 +392,9 @@ pub enum TemplateError { }, } -impl Error for TemplateError {} +impl Error for PathTemplateError {} -impl Display for TemplateError { +impl Display for PathTemplateError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Encoding(error) => error.fmt(f), @@ -589,8 +588,8 @@ tip: Touching parameters are not supported" } } -impl From for TemplateError { - fn from(error: EncodingError) -> Self { +impl From for PathTemplateError { + fn from(error: Utf8Error) -> Self { Self::Encoding(error) } } diff --git a/crates/path/src/find.rs b/crates/path/src/find.rs deleted file mode 100644 index 10d90593..00000000 --- a/crates/path/src/find.rs +++ /dev/null @@ -1,111 +0,0 @@ -use super::{ - node::Node, - parser::{ParsedTemplate, Part}, - state::State, - PathData, -}; - -impl<'r, S: State> Node<'r, S> { - pub(crate) fn find(&'r self, route: &mut ParsedTemplate) -> Option<&'r PathData<'r>> { - if route.parts.is_empty() { - return self.data.as_ref(); - } - - if let Some(part) = route.parts.pop() { - return match part { - Part::Static { prefix } => self.find_static(route, &prefix), - Part::Dynamic { name, constraint } => { - self.find_dynamic(route, &name, constraint.as_deref()) - } - Part::Wildcard { name, constraint } if route.parts.is_empty() => { - self.find_end_wildcard(route, &name, constraint.as_deref()) - } - Part::Wildcard { name, constraint } => { - self.find_wildcard(route, &name, constraint.as_deref()) - } - }; - } - - None - } - - fn find_static( - &'r self, - route: &mut ParsedTemplate, - prefix: &[u8], - ) -> Option<&'r PathData<'r>> { - for child in self.static_children.iter() { - if !child.state.prefix.is_empty() && child.state.prefix[0] == prefix[0] { - let common_prefix = prefix - .iter() - .zip(&child.state.prefix) - .take_while(|&(x, y)| x == y) - .count(); - - if common_prefix >= child.state.prefix.len() { - if common_prefix >= prefix.len() { - return child.find(route); - } - - let remaining = prefix[common_prefix..].to_vec(); - if !remaining.is_empty() { - let mut new_route = ParsedTemplate { - parts: route.parts.clone(), - ..route.clone() - }; - - new_route.parts.push(Part::Static { prefix: remaining }); - return child.find(&mut new_route); - } - } - } - } - - None - } - - fn find_dynamic( - &'r self, - route: &mut ParsedTemplate, - name: &str, - constraint: Option<&str>, - ) -> Option<&'r PathData<'r>> { - for child in self.dynamic_children.iter() { - if child.state.name == name && child.state.constraint.as_deref() == constraint { - return child.find(route); - } - } - - None - } - - fn find_end_wildcard( - &'r self, - route: &mut ParsedTemplate, - name: &str, - constraint: Option<&str>, - ) -> Option<&'r PathData<'r>> { - for child in self.end_wildcard_children.iter() { - if child.state.name == name && child.state.constraint.as_deref() == constraint { - return child.find(route); - } - } - - None - } - - fn find_wildcard( - &'r self, - route: &mut ParsedTemplate, - name: &str, - constraint: Option<&str>, - ) -> Option<&'r PathData<'r>> { - for child in self.wildcard_children.iter() { - if child.state.name == name && child.state.constraint.as_deref() == constraint { - return child.find(route); - } - } - - None - } -} diff --git a/crates/path/src/id.rs b/crates/path/src/id.rs index dd63b99d..e14f7f07 100644 --- a/crates/path/src/id.rs +++ b/crates/path/src/id.rs @@ -8,15 +8,19 @@ pub struct PathIdGenerator { impl PathIdGenerator { pub fn generate(&mut self) -> PathId { self.id += 1; - PathId(self.id) + PathId(Some(self.id)) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct PathId(pub usize); +pub struct PathId(pub Option); impl Display for PathId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) + if let Some(id) = self.0 { + write!(f, "{id}") + } else { + write!(f, "*") + } } } diff --git a/crates/path/src/lib.rs b/crates/path/src/lib.rs index abda9b80..9bb8efe1 100644 --- a/crates/path/src/lib.rs +++ b/crates/path/src/lib.rs @@ -1,75 +1,86 @@ #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)] -use crate::vec::SortedVec; -use errors::{constraint::ConstraintError, DeleteError, InsertError, SearchError}; +use errors::{PathConstraintError, PathDeleteError, PathInsertError}; use id::PathIdGenerator; -use node::Node; -use parser::{Parser, Part}; +use parser::ParsedPath; use smallvec::{smallvec, SmallVec}; -use state::RootState; use std::{ collections::HashMap, fmt::Display, net::{Ipv4Addr, Ipv6Addr}, + str::Utf8Error, sync::Arc, }; - -// FIXME: Actually make use of key, like in method router. +use wayfind_storage::Storage; +use wayfind_tree::{ + node::{Config, Data, Node}, + parser::Part, + search::StoredConstraint, + state::RootState, + vec::SortedVec, +}; pub mod constraints; -pub mod delete; -pub mod display; pub mod errors; -pub mod find; pub mod id; -pub mod insert; -pub mod node; -pub mod optimize; pub mod parser; -pub mod search; -pub mod state; -pub mod vec; pub use constraints::PathConstraint; pub use id::PathId; #[derive(Clone, Debug, Eq, PartialEq)] -pub struct PathData<'r> { +pub struct PathConfig; + +impl Config for PathConfig { + type Data = PathData; + + const DELIMITER: u8 = b'/'; +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PathData { pub id: PathId, - pub route: &'r str, + pub route: Arc, pub expanded: Option>, } +impl Data for PathData { + fn id(&self) -> Option { + self.id.0 + } + + fn priority(&self) -> usize { + self.expanded.as_ref().map_or_else( + || self.route.len() + (self.route.bytes().filter(|&b| b == b'/').count() * 100), + |expanded| expanded.len() + (expanded.bytes().filter(|&b| b == b'/').count() * 100), + ) + } +} + #[derive(Debug, Eq, PartialEq)] pub struct PathMatch<'r, 'p> { pub id: PathId, - pub route: &'r str, - pub expanded: Option<&'r str>, + pub route: Arc, + pub expanded: Option>, pub parameters: PathParameters<'r, 'p>, } pub type PathParameters<'r, 'p> = SmallVec<[(&'r str, &'p str); 4]>; #[derive(Clone)] -pub struct StoredConstraint { - pub type_name: &'static str, - pub check: fn(&str) -> bool, -} - -#[derive(Clone)] -pub struct PathRouter<'r> { - pub root: Node<'r, RootState>, - pub constraints: HashMap<&'r str, StoredConstraint>, +pub struct PathRouter { + pub root: Node, + pub constraints: HashMap, StoredConstraint>, pub id: PathIdGenerator, } -impl<'r> PathRouter<'r> { +impl PathRouter { #[must_use] pub fn new() -> Self { let mut router = Self { root: Node { state: RootState::new(), - data: None, + data: Storage::default(), static_children: SortedVec::default(), dynamic_children: SortedVec::default(), @@ -106,9 +117,9 @@ impl<'r> PathRouter<'r> { router } - pub fn constraint(&mut self) -> Result<(), ConstraintError> { + pub fn constraint(&mut self) -> Result<(), PathConstraintError> { if let Some(existing) = self.constraints.get(C::NAME) { - return Err(ConstraintError::DuplicateName { + return Err(PathConstraintError::DuplicateName { name: C::NAME, existing_type: existing.type_name, new_type: std::any::type_name::(), @@ -116,7 +127,7 @@ impl<'r> PathRouter<'r> { } self.constraints.insert( - C::NAME, + C::NAME.into(), StoredConstraint { type_name: std::any::type_name::(), check: C::check, @@ -126,12 +137,14 @@ impl<'r> PathRouter<'r> { Ok(()) } - pub fn conflicts(&self, route: &str) -> Result, DeleteError> { - let parsed = Parser::new(route.as_bytes())?; - + pub fn conflicts( + &self, + key: Option, + path: &ParsedPath, + ) -> Result, PathDeleteError> { // Check if any expansion conflicts - for mut route_variant in parsed.routes { - if let Some(data) = self.root.find(&mut route_variant) { + for route in &path.routes { + if let Some(data) = self.root.find(key, route) { return Ok(Some(data.id)); } } @@ -139,11 +152,13 @@ impl<'r> PathRouter<'r> { Ok(None) } - pub fn insert(&mut self, route: &'r str) -> Result { - let mut parsed = Parser::new(route.as_bytes())?; - + pub fn insert( + &mut self, + key: Option, + path: &ParsedPath, + ) -> Result { // Check for invalid constraints. - for route in &parsed.routes { + for route in &path.routes { for part in &route.parts { if let Part::Dynamic { constraint: Some(name), @@ -155,7 +170,7 @@ impl<'r> PathRouter<'r> { } = part { if !self.constraints.contains_key(name.as_str()) { - return Err(InsertError::UnknownConstraint { + return Err(PathInsertError::UnknownConstraint { constraint: name.to_string(), }); } @@ -165,10 +180,10 @@ impl<'r> PathRouter<'r> { // Check for conflicts. let mut ids = vec![]; - for route in &parsed.routes { - let raw = String::from_utf8_lossy(&route.raw); - - let Ok(Some(found)) = self.conflicts(&raw) else { + for route in &path.routes { + // FIXME + let raw = ParsedPath::new(&route.raw).unwrap(); + let Ok(Some(found)) = self.conflicts(key, &raw) else { continue; }; @@ -184,7 +199,9 @@ impl<'r> PathRouter<'r> { }; if ids.iter().any(|id| id != first) { - return Err(InsertError::OverlappingRoutes { ids }); + return Err(PathInsertError::Overlapping { + ids: ids.into_iter().filter_map(|id| id.0).collect(), + }); } return Ok(*first); @@ -193,24 +210,25 @@ impl<'r> PathRouter<'r> { // No conflicts, proceed with new insert. let id = self.id.generate(); - if parsed.routes.len() > 1 { - for mut parsed_route in parsed.routes { - let expanded = Arc::from(String::from_utf8_lossy(&parsed_route.raw)); + if path.routes.len() > 1 { + for parsed_route in &path.routes { self.root.insert( - &mut parsed_route, + key, + parsed_route, PathData { id, - route, - expanded: Some(expanded), + route: Arc::clone(&path.input), + expanded: Some(Arc::clone(&parsed_route.raw)), }, ); } - } else if let Some(parsed_route) = parsed.routes.first_mut() { + } else if let Some(parsed_route) = path.routes.first() { self.root.insert( + key, parsed_route, PathData { id, - route, + route: Arc::clone(&path.input), expanded: None, }, ); @@ -220,24 +238,26 @@ impl<'r> PathRouter<'r> { Ok(id) } - pub fn find(&self, route: &str) -> Result, DeleteError> { - let parsed = Parser::new(route.as_bytes())?; - + pub fn find( + &self, + key: Option, + path: &ParsedPath, + ) -> Result, PathDeleteError> { let mut id = None; let mut mismatch = None; let mut missing = false; - for mut route_variant in parsed.routes { - if let Some(data) = self.root.find(&mut route_variant) { - if data.route != route { - mismatch = Some(data.route.to_owned()); + for route in &path.routes { + if let Some(data) = self.root.find(key, route) { + if data.route != path.input { + mismatch = Some(Arc::clone(&data.route)); } if let Some(existing_id) = id { if existing_id != data.id { - return Err(DeleteError::Mismatch { - route: route.to_owned(), - inserted: data.route.to_owned(), + return Err(PathDeleteError::Mismatch { + path: path.input.to_string(), + inserted: data.route.to_string(), }); } } else { @@ -249,9 +269,9 @@ impl<'r> PathRouter<'r> { } if let Some(inserted) = mismatch { - return Err(DeleteError::Mismatch { - route: route.to_owned(), - inserted, + return Err(PathDeleteError::Mismatch { + path: path.input.to_string(), + inserted: inserted.to_string(), }); } @@ -262,12 +282,8 @@ impl<'r> PathRouter<'r> { Ok(id) } - pub fn delete(&mut self, route: &str) { - let Ok(parsed) = Parser::new(route.as_bytes()) else { - return; - }; - - let Ok(data) = self.find(route) else { + pub fn delete(&mut self, key: Option, path: &ParsedPath) { + let Ok(data) = self.find(key, path) else { return; }; @@ -275,29 +291,44 @@ impl<'r> PathRouter<'r> { return; } - for mut expanded_route in parsed.routes { - self.root.delete(&mut expanded_route); + for expanded in &path.routes { + self.root.delete(key, expanded); } self.root.optimize(); } - pub fn search<'p>(&'r self, path: &'p [u8]) -> Result>, SearchError> { - let mut parameters = smallvec![]; - let Some((data, _)) = self.root.search(path, &mut parameters, &self.constraints)? else { + pub fn search<'r, 'p>( + &'r self, + key: Option, + path: &'p [u8], + ) -> Result>, Utf8Error> + where + 'p: 'r, + { + let mut parameters: SmallVec<[(&str, &[u8]); 4]> = smallvec![]; + let Some((data, _)) = self + .root + .search(key, path, &mut parameters, &self.constraints) + else { return Ok(None); }; + let parameters = parameters + .into_iter() + .map(|(name, value)| std::str::from_utf8(value).map(|value| (name, value))) + .collect::>()?; + Ok(Some(PathMatch { id: data.id, - route: data.route, - expanded: data.expanded.as_deref(), + route: Arc::clone(&data.route), + expanded: data.expanded.as_ref().map(Arc::clone), parameters, })) } } -impl Display for PathRouter<'_> { +impl Display for PathRouter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.root) } diff --git a/crates/path/src/node.rs b/crates/path/src/node.rs deleted file mode 100644 index dbdf8500..00000000 --- a/crates/path/src/node.rs +++ /dev/null @@ -1,44 +0,0 @@ -use super::{ - state::{DynamicState, EndWildcardState, State, StaticState, WildcardState}, - PathData, -}; -use crate::vec::SortedVec; -use std::cmp::Ordering; - -/// Represents a node in the tree structure. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Node<'r, S: State> { - /// The type of Node, and associated structure data. - pub state: S, - - /// Optional data associated with this node. - /// The presence of this data is needed to successfully match a route. - pub data: Option>, - - pub static_children: SortedVec>, - pub dynamic_children: SortedVec>, - pub dynamic_children_shortcut: bool, - pub wildcard_children: SortedVec>, - pub wildcard_children_shortcut: bool, - pub end_wildcard_children: SortedVec>, - - /// Higher values indicate more specific matches. - pub priority: usize, - /// Flag indicating whether this node or its children need optimization. - pub needs_optimization: bool, -} - -impl PartialOrd for Node<'_, S> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Node<'_, S> { - fn cmp(&self, other: &Self) -> Ordering { - other - .priority - .cmp(&self.priority) - .then_with(|| self.state.cmp(&other.state)) - } -} diff --git a/crates/path/src/optimize.rs b/crates/path/src/optimize.rs deleted file mode 100644 index e3a5644a..00000000 --- a/crates/path/src/optimize.rs +++ /dev/null @@ -1,115 +0,0 @@ -use super::{node::Node, state::State, PathData}; - -impl Node<'_, S> { - pub(crate) fn optimize(&mut self) { - self.optimize_inner(0); - } - - fn optimize_inner(&mut self, priority: usize) { - self.priority = priority + self.calculate_priority(); - - if !self.needs_optimization { - return; - } - - for child in self.static_children.iter_mut() { - child.optimize_inner(self.priority); - } - - for child in self.dynamic_children.iter_mut() { - child.optimize_inner(self.priority); - } - - for child in self.wildcard_children.iter_mut() { - child.optimize_inner(self.priority); - } - - for child in self.end_wildcard_children.iter_mut() { - child.optimize_inner(self.priority); - } - - self.static_children.sort(); - self.dynamic_children.sort(); - self.wildcard_children.sort(); - self.end_wildcard_children.sort(); - - self.update_dynamic_children_shortcut(); - self.update_wildcard_children_shortcut(); - - self.needs_optimization = false; - } - - // TODO: I'd really like to make priority relative. - fn calculate_priority(&self) -> usize { - let mut priority = self.state.priority(); - if self.data.is_some() { - priority += 1_000; - priority += match &self.data { - Some(PathData { - route, expanded, .. - }) => expanded.as_ref().map_or_else( - || route.len() + (route.bytes().filter(|&b| b == b'/').count() * 100), - |expanded| { - expanded.len() + (expanded.bytes().filter(|&b| b == b'/').count() * 100) - }, - ), - None => 0, - }; - } - - priority - } - - fn update_dynamic_children_shortcut(&mut self) { - self.dynamic_children_shortcut = self.dynamic_children.iter().all(|child| { - // Leading slash? - if child.state.name.as_bytes().first() == Some(&b'/') { - return true; - } - - // No children? - if child.static_children.is_empty() - && child.dynamic_children.is_empty() - && child.wildcard_children.is_empty() - && child.end_wildcard_children.is_empty() - { - return true; - } - - // All static children start with a slash? - if child - .static_children - .iter() - .all(|child| child.state.prefix.first() == Some(&b'/')) - { - return true; - } - - false - }); - } - - fn update_wildcard_children_shortcut(&mut self) { - self.wildcard_children_shortcut = self.wildcard_children.iter().all(|child| { - // No children? - if child.static_children.is_empty() - && child.dynamic_children.is_empty() - && child.wildcard_children.is_empty() - && child.end_wildcard_children.is_empty() - { - return true; - } - - // All static children start with a slash? - if child - .static_children - .iter() - .all(|child| child.state.prefix.first() == Some(&b'/')) - { - return true; - } - - false - }); - } -} diff --git a/crates/path/src/parser.rs b/crates/path/src/parser.rs index f762c438..e754a63c 100644 --- a/crates/path/src/parser.rs +++ b/crates/path/src/parser.rs @@ -1,43 +1,22 @@ -use super::errors::TemplateError; -use crate::errors::EncodingError; +use super::errors::PathTemplateError; use smallvec::{smallvec, SmallVec}; +use std::sync::Arc; +use wayfind_tree::parser::{Part, Template}; /// Characters that are not allowed in parameter names or constraints. const INVALID_PARAM_CHARS: [u8; 7] = [b':', b'*', b'{', b'}', b'(', b')', b'/']; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParsedTemplate { - pub input: Vec, - pub raw: Vec, - pub parts: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Part { - Static { - prefix: Vec, - }, - Dynamic { - name: String, - constraint: Option, - }, - Wildcard { - name: String, - constraint: Option, - }, -} - #[derive(Debug, PartialEq, Eq)] -pub struct Parser { - pub input: Vec, - pub routes: Vec, +pub struct ParsedPath { + pub input: Arc, + pub routes: Vec