Skip to content
Open
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
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,23 @@ assert_eq!(matched.params.get("id"), Some("1"));
assert!(router.at("/images/img-1.jpg").is_err());
```

Catch-all parameters start with a `*` and match anything until the end of the path. They must always be at the *end* of the route.
Catch-all parameters start with a `*` and match anything until the next static segment, or the end of the path.

```rust,ignore
let mut router = Router::new();
router.insert("/{*rest}", true)?;
router.insert("/v2/{*name}/manifests/{reference}", true)?;

let matched = router.at("/foo.html")?;
assert_eq!(matched.params.get("rest"), Some("foo.html"));

let matched = router.at("/static/bar.css")?;
assert_eq!(matched.params.get("rest"), Some("static/bar.css"));

let matched = router.at("/v2/library/ubuntu/manifests/latest")?;
assert_eq!(matched.params.get("name"), Some("library/ubuntu"));
assert_eq!(matched.params.get("reference"), Some("latest"));

// Note that this would lead to an empty parameter value.
assert!(router.at("/").is_err());
```
Expand Down Expand Up @@ -107,7 +112,7 @@ Given set of routes, their overlapping segments may include, in order of priorit
- A single route parameter with both a prefix and a suffix (`/a{x}b`).
- *One* of the following;
- A single standalone parameter (`/{x}`).
- A single standalone catch-all parameter (`/{*rest}`). Note this only applies to the final route segment.
- A single standalone catch-all parameter (`/{*segments}/foo`, `/{*rest}`).

Any other combination of route segments is considered ambiguous, and attempting to insert such a route will result in an error.

Expand Down
45 changes: 44 additions & 1 deletion benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,50 @@ fn compare_routers(c: &mut Criterion) {
group.finish();
}

criterion_group!(benches, compare_routers);
fn catchall(c: &mut Criterion) {
let mut group = c.benchmark_group("Catch-all");

let mut terminal = matchit::Router::new();
terminal.insert("/src/{*path}", true).unwrap();
group.bench_function("terminal", |b| {
b.iter(|| {
let result = black_box(terminal.at(black_box("/src/some/nested/file.png")).unwrap());
assert!(*result.value);
assert_eq!(result.params.get("path"), Some("some/nested/file.png"));
});
});

let mut non_terminal = matchit::Router::new();
non_terminal
.insert("/v2/{*name}/blobs/{digest}", true)
.unwrap();
non_terminal
.insert("/v2/{*name}/manifests/{reference}", true)
.unwrap();
group.bench_function("non-terminal", |b| {
b.iter(|| {
let result = black_box(
non_terminal
.at(black_box("/v2/team/project/image/blobs/sha256:abc"))
.unwrap(),
);
assert!(*result.value);
assert_eq!(result.params.get("name"), Some("team/project/image"));
assert_eq!(result.params.get("digest"), Some("sha256:abc"));
});
});

group.bench_function("non-terminal repeated-boundary miss", |b| {
b.iter(|| {
let result = black_box(non_terminal.at(black_box("/v2/a/blobs/b/blobs/c/tags/list")));
assert!(result.is_err());
});
});

group.finish();
}

criterion_group!(benches, compare_routers, catchall);
criterion_main!(benches);

macro_rules! routes {
Expand Down
12 changes: 7 additions & 5 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub enum InsertError {
/// Note you can use `{{` or `}}` to escape literal brackets.
InvalidParam,

/// Catch-all parameters are only allowed at the end of a path.
/// Catch-all parameters cannot have a suffix in the same path segment.
InvalidCatchAll,
}

Expand All @@ -42,10 +42,12 @@ impl fmt::Display for InsertError {
write!(f, "Only one parameter is allowed per path segment")
}
Self::InvalidParam => 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"
),
Self::InvalidCatchAll => {
write!(
f,
"Catch-all parameters cannot have a suffix in the same path segment",
)
}
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,25 @@ assert!(router.at("/images/img-1.jpg").is_err());
# }
```

Catch-all parameters start with a `*` and match anything until the end of the path. They must always be at the *end* of the route.
Catch-all parameters start with a `*` and match anything until the next static segment, or the end of the path.

```rust
# use matchit::Router;
# fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut router = Router::new();
router.insert("/{*rest}", true)?;
router.insert("/v2/{*name}/manifests/{reference}", true)?;

let matched = router.at("/foo.html")?;
assert_eq!(matched.params.get("rest"), Some("foo.html"));

let matched = router.at("/static/bar.css")?;
assert_eq!(matched.params.get("rest"), Some("static/bar.css"));

let matched = router.at("/v2/library/ubuntu/manifests/latest")?;
assert_eq!(matched.params.get("name"), Some("library/ubuntu"));
assert_eq!(matched.params.get("reference"), Some("latest"));

// Note that this would lead to an empty parameter value.
assert!(router.at("/").is_err());
# Ok(())
Expand Down Expand Up @@ -121,7 +126,7 @@ Given set of routes, their overlapping segments may include, in order of priorit
- A single route parameter with both a prefix and a suffix (`/a{x}b`).
- *One* of the following;
- A single standalone parameter (`/{x}`).
- A single standalone catch-all parameter (`/{*rest}`). Note this only applies to the final route segment.
- A single standalone catch-all parameter (`/{*segments}/foo`, `/{*rest}`).

Any other combination of route segments is considered ambiguous, and attempting to insert such a route will result in an error.

Expand Down
6 changes: 3 additions & 3 deletions src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,10 @@ impl<'k, 'v> Params<'k, 'v> {

// Applies a transformation function to each key.
#[inline]
pub(crate) fn for_each_key_mut(&mut self, f: impl Fn((usize, &mut Param<'k, 'v>))) {
pub(crate) fn for_each_key_mut(&mut self, mut f: impl FnMut((usize, &mut Param<'k, 'v>))) {
match &mut self.kind {
ParamsKind::Small(arr, len) => arr.iter_mut().take(*len).enumerate().for_each(f),
ParamsKind::Large(vec) => vec.iter_mut().enumerate().for_each(f),
ParamsKind::Small(arr, len) => arr.iter_mut().take(*len).enumerate().for_each(&mut f),
ParamsKind::Large(vec) => vec.iter_mut().enumerate().for_each(&mut f),
}
}
}
Expand Down
Loading