Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New route syntax #50

Merged
merged 3 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use matchit::Router;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut router = Router::new();
router.insert("/home", "Welcome!")?;
router.insert("/users/:id", "A User")?;
router.insert("/users/{id}", "A User")?;

let matched = router.at("/users/978")?;
assert_eq!(matched.params.get("id"), Some("978"));
Expand All @@ -28,11 +28,11 @@ Along with static routes, the router also supports dynamic route segments. These

### Named Parameters

Named parameters like `/:id` match anything until the next `/` or the end of the path:
Named parameters like `/{id}` match anything until the next `/` or the end of the path:

```rust,ignore
let mut m = Router::new();
m.insert("/users/:id", true)?;
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"));
Expand All @@ -45,7 +45,7 @@ Catch-all parameters start with `*` and match anything until the end of the path

```rust,ignore
let mut m = Router::new();
m.insert("/*p", true)?;
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"));
Expand All @@ -62,7 +62,7 @@ Static and dynamic route segments are allowed to overlap. If they do, static seg
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
m.insert("/{*filepath}", "...").unwrap(); // priority: 2
```

## How does it work?
Expand Down
2 changes: 1 addition & 1 deletion benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fn compare_routers(c: &mut Criterion) {
let mut group = c.benchmark_group("Compare Routers");

let mut matchit = matchit::Router::new();
for route in register!(colon) {
for route in register!(brackets) {
matchit.insert(route, true).unwrap();
}
group.bench_function("matchit", |b| {
Expand Down
10 changes: 5 additions & 5 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
/// Represents errors that can occur when inserting a new route.
#[non_exhaustive]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum InsertError {

Check warning on line 8 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

item name ends with its containing module's name

Check warning on line 8 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

item name ends with its containing module's name
/// Attempted to insert a path that conflicts with an existing route.
Conflict {
/// The existing route that the insertion is conflicting with.
with: String,
},
/// Only one parameter per route segment is allowed.
TooManyParams,
/// Parameters must be registered with a name.
UnnamedParam,
InvalidParam,
/// Parameters must be registered with a valid name.
InvalidParamName,
/// Catch-all parameters are only allowed at the end of a path.
InvalidCatchAll,
}
Expand All @@ -23,14 +23,14 @@
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Conflict { with } => {
write!(

Check warning on line 26 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

variables can be used directly in the `format!` string

Check warning on line 26 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

variables can be used directly in the `format!` string
f,
"insertion failed due to conflict with previously registered route: {}",
with
)
}
Self::TooManyParams => write!(f, "only one parameter is allowed per path segment"),
Self::UnnamedParam => write!(f, "parameters must be registered with a name"),
Self::InvalidParam => write!(f, "only one parameter is allowed per path segment"),
Self::InvalidParamName => write!(f, "parameters must be registered with a valid name"),
Self::InvalidCatchAll => write!(
f,
"catch-all parameters are only allowed at the end of a route"
Expand All @@ -47,7 +47,7 @@
if prefix == current.prefix {
let mut route = route.to_owned();
denormalize_params(&mut route, &current.param_remapping);
return InsertError::Conflict {

Check warning on line 50 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

unnecessary structure name repetition

Check warning on line 50 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

unnecessary structure name repetition
with: String::from_utf8(route).unwrap(),
};
}
Expand All @@ -71,7 +71,7 @@

denormalize_params(&mut route, &last.param_remapping);

InsertError::Conflict {

Check warning on line 74 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

unnecessary structure name repetition

Check warning on line 74 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

unnecessary structure name repetition
with: String::from_utf8(route).unwrap(),
}
}
Expand All @@ -93,7 +93,7 @@
/// # Ok(())
/// # }
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum MatchError {

Check warning on line 96 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

item name ends with its containing module's name

Check warning on line 96 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

item name ends with its containing module's name
/// No matching route was found.
NotFound,
}
Expand Down
10 changes: 5 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut router = Router::new();
//! router.insert("/home", "Welcome!")?;
//! router.insert("/users/:id", "A User")?;
//! router.insert("/users/{id}", "A User")?;
//!
//! let matched = router.at("/users/978")?;
//! assert_eq!(matched.params.get("id"), Some("978"));
Expand All @@ -27,13 +27,13 @@
//!
//! ### Named Parameters
//!
//! Named parameters like `/:id` match anything until the next `/` or the end of the path:
//! Named parameters like `/{id}` match anything until the next `/` or the end of the path:
//!
//! ```rust
//! # use matchit::Router;
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut m = Router::new();
//! m.insert("/users/:id", true)?;
//! 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"));
Expand All @@ -52,7 +52,7 @@
//! # use matchit::Router;
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut m = Router::new();
//! m.insert("/*p", true)?;
//! 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"));
Expand All @@ -74,7 +74,7 @@
//! 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
//! m.insert("/{*filepath}", "...").unwrap(); // priority: 2
//!
//! # Ok(())
//! # }
Expand Down
28 changes: 14 additions & 14 deletions src/params.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use std::iter;
use std::mem;
use std::slice;
use std::{fmt, iter, mem, slice};

/// A single URL parameter, consisting of a key and a value.
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Default, Copy, Clone)]
#[derive(PartialEq, Eq, Ord, PartialOrd, Default, Copy, Clone)]
struct Param<'k, 'v> {
key: &'k [u8],
value: &'v [u8],
Expand All @@ -25,7 +23,7 @@
/// ```rust
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let mut router = matchit::Router::new();
/// # router.insert("/users/:id", true).unwrap();
/// # router.insert("/users/{id}", true).unwrap();
/// let matched = router.at("/users/1")?;
///
/// // you can iterate through the keys and values
Expand All @@ -39,15 +37,15 @@
/// # Ok(())
/// # }
/// ```
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone)]
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone)]
pub struct Params<'k, 'v> {
kind: ParamsKind<'k, 'v>,
}

// most routes have 1-3 dynamic parameters, so we can avoid a heap allocation in common cases.
const SMALL: usize = 3;

#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone)]
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone)]
enum ParamsKind<'k, 'v> {
None,
Small([Param<'k, 'v>; SMALL], usize),
Expand All @@ -55,13 +53,14 @@
}

impl<'k, 'v> Params<'k, 'v> {
pub(crate) fn new() -> Self {

Check warning on line 56 in src/params.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

this could be a `const fn`

Check warning on line 56 in src/params.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

this could be a `const fn`
let kind = ParamsKind::None;
Self { kind }
Self {
kind: ParamsKind::None,
}
}

/// Returns the number of parameters.
pub fn len(&self) -> usize {

Check warning on line 63 in src/params.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

this method could have a `#[must_use]` attribute

Check warning on line 63 in src/params.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

this method could have a `#[must_use]` attribute
match &self.kind {
ParamsKind::None => 0,
ParamsKind::Small(_, len) => *len,
Expand Down Expand Up @@ -100,12 +99,12 @@
}

/// Returns an iterator over the parameters in the list.
pub fn iter(&self) -> ParamsIter<'_, 'k, 'v> {

Check warning on line 102 in src/params.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

this method could have a `#[must_use]` attribute

Check warning on line 102 in src/params.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

`iter` method without an `IntoIterator` impl for `&Params<'k, 'v>`

Check warning on line 102 in src/params.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

this method could have a `#[must_use]` attribute

Check warning on line 102 in src/params.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

`iter` method without an `IntoIterator` impl for `&Params<'k, 'v>`
ParamsIter::new(self)
}

/// Returns `true` if there are no parameters in the list.
pub fn is_empty(&self) -> bool {

Check warning on line 107 in src/params.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

this method could have a `#[must_use]` attribute

Check warning on line 107 in src/params.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

this method could have a `#[must_use]` attribute
match &self.kind {
ParamsKind::None => true,
ParamsKind::Small(_, len) => *len == 0,
Expand Down Expand Up @@ -159,6 +158,12 @@
}
}

impl fmt::Debug for Params<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}

/// An iterator over the keys and values of a route's [parameters](crate::Params).
pub struct ParamsIter<'ps, 'k, 'v> {
kind: ParamsIterKind<'ps, 'k, 'v>,
Expand Down Expand Up @@ -201,11 +206,6 @@
mod tests {
use super::*;

#[test]
fn no_alloc() {
assert_eq!(Params::new().kind, ParamsKind::None);
}

#[test]
fn heap_alloc() {
let vec = vec![
Expand Down
Loading
Loading