From 2ca3611662a77e18d0d46694b573ab444b5eee7a Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Thu, 11 Jul 2024 03:18:30 -0400 Subject: [PATCH] update documentation --- README.md | 2 +- src/lib.rs | 251 +++++++++++++++++++++++--------------------------- src/router.rs | 12 +-- 3 files changed, 124 insertions(+), 141 deletions(-) diff --git a/README.md b/README.md index 8a6be73..d54e7b6 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ assert_eq!(m.at("/foo.js")?.params.get("p"), Some("foo.js")); assert_eq!(m.at("/c/bar.css")?.params.get("p"), Some("c/bar.css")); // note that this will not match -assert_eq!(m.at("/").is_err()); +assert!(m.at("/").is_err()); ``` The literal characters `{` and `}` may be included in a static route by escaping them with the same character. For example, the `{` character is escaped with `{{` and the `}` character is escaped with `}}`. diff --git a/src/lib.rs b/src/lib.rs index 3bb7df3..cf12852 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,128 +1,116 @@ -//! # `matchit` -//! -//! [![Documentation](https://img.shields.io/badge/docs-0.8.3-4d76ae?style=for-the-badge)](https://docs.rs/matchit) -//! [![Version](https://img.shields.io/crates/v/matchit?style=for-the-badge)](https://crates.io/crates/matchit) -//! [![License](https://img.shields.io/crates/l/matchit?style=for-the-badge)](https://crates.io/crates/matchit) -//! -//! A blazing fast URL router. -//! -//! ```rust -//! use matchit::Router; -//! -//! # fn main() -> Result<(), Box> { -//! let mut router = Router::new(); -//! router.insert("/home", "Welcome!")?; -//! router.insert("/users/{id}", "A User")?; -//! -//! let matched = router.at("/users/978")?; -//! assert_eq!(matched.params.get("id"), Some("978")); -//! assert_eq!(*matched.value, "A User"); -//! # Ok(()) -//! # } -//! ``` -//! -//! ## Parameters -//! -//! Along with static routes, the router also supports dynamic route segments. These can either be named or catch-all parameters. -//! -//! ### Named Parameters -//! -//! Named parameters like `/{id}` match anything until the next `/` or the end of the path. -//! -//! ```rust -//! # use matchit::Router; -//! # fn main() -> Result<(), Box> { -//! let mut m = Router::new(); -//! m.insert("/users/{id}", true)?; -//! -//! assert_eq!(m.at("/users/1")?.params.get("id"), Some("1")); -//! assert_eq!(m.at("/users/23")?.params.get("id"), Some("23")); -//! assert!(m.at("/users").is_err()); -//! -//! # Ok(()) -//! # } -//! ``` -//! -//! Note that named parameters must be followed by a `/` or the end of the route. Dynamic suffixes are not currently supported. -//! -//! ### Catch-all Parameters -//! -//! Catch-all parameters start with `*` and match anything until the end of the path. -//! They must always be at the **end** of the route. -//! -//! ```rust -//! # use matchit::Router; -//! # fn main() -> Result<(), Box> { -//! let mut m = Router::new(); -//! m.insert("/{*p}", true)?; -//! -//! assert_eq!(m.at("/foo.js")?.params.get("p"), Some("foo.js")); -//! assert_eq!(m.at("/c/bar.css")?.params.get("p"), Some("c/bar.css")); -//! -//! // note that this will not match -//! assert!(m.at("/").is_err()); -//! -//! # Ok(()) -//! # } -//! ``` -//! -//! ### Escaping Parameters -//! -//! The literal characters `{` and `}` may be included in a static route by escaping them with the same character. -//! For example, the `{` character is escaped with `{{` and the `}` character is escaped with `}}`. -//! -//! ```rust -//! # use matchit::Router; -//! # fn main() -> Result<(), Box> { -//! let mut m = Router::new(); -//! m.insert("/{{hello}}", true)?; -//! m.insert("/{hello}", true)?; -//! -//! // match the static route -//! assert!(m.at("/{hello}")?.value); -//! -//! // match the dynamic route -//! assert_eq!(m.at("/hello")?.params.get("hello"), Some("hello")); -//! # Ok(()) -//! # } -//! ``` -//! -//! ## Routing Priority -//! -//! Static and dynamic route segments are allowed to overlap. If they do, static segments will be given higher priority: -//! -//! ```rust -//! # use matchit::Router; -//! # fn main() -> Result<(), Box> { -//! let mut m = Router::new(); -//! m.insert("/", "Welcome!").unwrap() ; // priority: 1 -//! m.insert("/about", "About Me").unwrap(); // priority: 1 -//! m.insert("/{*filepath}", "...").unwrap(); // priority: 2 -//! -//! # Ok(()) -//! # } -//! ``` -//! -//! ## How does it work? -//! -//! The router takes advantage of the fact that URL routes generally follow a hierarchical structure. Routes are stored them in a radix trie that makes heavy use of common prefixes: -//! -//! ```text -//! Priority Path Value -//! 9 \ 1 -//! 3 ├s None -//! 2 |├earch\ 2 -//! 1 |└upport\ 3 -//! 2 ├blog\ 4 -//! 1 | └{post} None -//! 1 | └\ 5 -//! 2 ├about-us\ 6 -//! 1 | └team\ 7 -//! 1 └contact\ 8 -//! ``` -//! -//! This allows us to reduce the route search to a small number of branches. Child nodes on the same level of the tree are also prioritized -//! by the number of children with registered values, increasing the chance of choosing the correct branch of the first try. +/*! +A high performance, zero-copy URL router. + +```rust +use matchit::Router; + +fn main() -> Result<(), Box> { + let mut router = Router::new(); + router.insert("/home", "Welcome!")?; + router.insert("/users/{id}", "A User")?; + + let matched = router.at("/users/978")?; + assert_eq!(matched.params.get("id"), Some("978")); + assert_eq!(*matched.value, "A User"); + + Ok(()) +} +``` + +# Parameters + +The router supports dynamic route segments. These can either be named or catch-all parameters. + +Named parameters like `/{id}` match anything until the next `/` or the end of the path. Note that named parameters must be followed +by a `/` or the end of the route. Dynamic suffixes are not currently supported. + +```rust +# use matchit::Router; +# fn main() -> Result<(), Box> { +let mut m = Router::new(); +m.insert("/users/{id}", true)?; + +assert_eq!(m.at("/users/1")?.params.get("id"), Some("1")); +assert_eq!(m.at("/users/23")?.params.get("id"), Some("23")); +assert!(m.at("/users").is_err()); +# Ok(()) +# } +``` + +Catch-all parameters start with `*` and match anything until the end of the path. They must always be at the **end** of the route. + +```rust +# use matchit::Router; +# fn main() -> Result<(), Box> { +let mut m = Router::new(); +m.insert("/{*p}", true)?; + +assert_eq!(m.at("/foo.js")?.params.get("p"), Some("foo.js")); +assert_eq!(m.at("/c/bar.css")?.params.get("p"), Some("c/bar.css")); + +// note that this will not match +assert!(m.at("/").is_err()); +# Ok(()) +# } +``` + +The literal characters `{` and `}` may be included in a static route by escaping them with the same character. For example, the `{` character is escaped with `{{` and the `}` character is escaped with `}}`. + +```rust +# use matchit::Router; +# fn main() -> Result<(), Box> { +let mut m = Router::new(); +m.insert("/{{hello}}", true)?; +m.insert("/{hello}", true)?; + +// match the static route +assert!(m.at("/{hello}")?.value); + +// match the dynamic route +assert_eq!(m.at("/hello")?.params.get("hello"), Some("hello")); +# Ok(()) +# } +``` + +# Routing Priority + +Static and dynamic route segments are allowed to overlap. If they do, static segments will be given higher priority: + +```rust +# use matchit::Router; +# fn main() -> Result<(), Box> { +let mut m = Router::new(); +m.insert("/", "Welcome!").unwrap(); // priority: 1 +m.insert("/about", "About Me").unwrap(); // priority: 1 +m.insert("/{*filepath}", "...").unwrap(); // priority: 2 +# Ok(()) +# } +``` + +# How does it work? + +The router takes advantage of the fact that URL routes generally follow a hierarchical structure. Routes are stored them in a radix trie that makes heavy use of common prefixes. + +```text +Priority Path Value +9 \ 1 +3 ├s None +2 |├earch\ 2 +1 |└upport\ 3 +2 ├blog\ 4 +1 | └{post} None +1 | └\ 5 +2 ├about-us\ 6 +1 | └team\ 7 +1 └contact\ 8 +``` + +This allows us to reduce the route search to a small number of branches. Child nodes on the same level of the tree are also prioritized +by the number of children with registered values, increasing the chance of choosing the correct branch of the first try. + +As it turns out, this method of routing is extremely fast. See the [benchmark results](https://github.com/ibraheemdev/matchit?tab=readme-ov-file#benchmarks) for details. +*/ + #![deny(rust_2018_idioms, clippy::all)] mod error; @@ -136,13 +124,8 @@ pub use params::{Params, ParamsIter}; pub use router::{Match, Router}; #[cfg(doctest)] -mod test_readme { - macro_rules! doc_comment { - ($x:expr) => { - #[doc = $x] - extern "C" {} - }; - } - - doc_comment!(include_str!("../README.md")); +mod readme { + #[allow(dead_code)] + #[doc = include_str!("../README.md")] + struct Readme; } diff --git a/src/router.rs b/src/router.rs index 529d20a..8256c4b 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,7 +1,7 @@ use crate::tree::Node; use crate::{InsertError, MatchError, Params}; -/// A URL router. +/// A zero-copy URL router. /// /// See [the crate documentation](crate) for details. #[derive(Clone, Debug)] @@ -55,7 +55,7 @@ impl Router { /// # Ok(()) /// # } /// ``` - pub fn at<'m, 'p>(&'m self, path: &'p str) -> Result, MatchError> { + pub fn at<'path>(&self, path: &'path str) -> Result, MatchError> { match self.root.at(path.as_bytes()) { Ok((value, params)) => Ok(Match { // Safety: We only expose `&mut T` through `&mut self` @@ -82,10 +82,10 @@ impl Router { /// # Ok(()) /// # } /// ``` - pub fn at_mut<'m, 'p>( - &'m mut self, - path: &'p str, - ) -> Result, MatchError> { + pub fn at_mut<'path>( + &mut self, + path: &'path str, + ) -> Result, MatchError> { match self.root.at(path.as_bytes()) { Ok((value, params)) => Ok(Match { // Safety: We have `&mut self`