4 releases (breaking)

0.4.0 Dec 27, 2025
0.3.0 Jul 24, 2024
0.2.0 Jan 19, 2024
0.1.0 Dec 29, 2023

#787 in HTTP server

Download history 426/week @ 2025-10-01 647/week @ 2025-10-08 574/week @ 2025-10-15 685/week @ 2025-10-22 388/week @ 2025-10-29 563/week @ 2025-11-05 178/week @ 2025-11-12 184/week @ 2025-11-19 181/week @ 2025-11-26 181/week @ 2025-12-03 113/week @ 2025-12-10 304/week @ 2025-12-17 355/week @ 2025-12-24 78/week @ 2025-12-31 99/week @ 2026-01-07 185/week @ 2026-01-14

734 downloads per month
Used in 5 crates (2 directly)

MIT license

140KB
3K SLoC

A fork of matchit using small string type for params lifetime elision.

use xitca_router::Router;

fn main() -> Result<(), Box<dyn std::error::Error>> {
   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 static segment or the end of the path.

let mut router = Router::new();
router.insert("/users/{id}", 42)?;

let matched = router.at("/users/1")?;
assert_eq!(matched.params.get("id"), Some("1"));

let matched = router.at("/users/23")?;
assert_eq!(matched.params.get("id"), Some("23"));

assert!(router.at("/users").is_err());

Prefixes and suffixes within a segment are also supported. However, there may only be a single named parameter per route segment.

let mut router = Router::new();
router.insert("/images/img-{id}.png", true)?;

let matched = router.at("/images/img-1.png")?;
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.

let mut router = Router::new();
router.insert("/{*rest}", 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"));

// Note that this would lead to an empty parameter value.
assert!(router.at("/").is_err());

Relaxed Catch-all Parameters Relaxed Catch-all parameters with a single * and match everything after the /(Including / itself). They must always be at the end of the route:

let mut m = Router::new();
m.insert("/{*}", true)?;

assert!(m.at("/")?.value);
assert!(m.at("/foo")?.value);

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 }}.

let mut router = Router::new();
router.insert("/{{hello}}", true)?;
router.insert("/{hello}", true)?;

// Match the static route.
let matched = router.at("/{hello}")?;
assert!(matched.params.is_empty());

// Match the dynamic route.
let matched = router.at("/hello")?;
assert_eq!(matched.params.get("hello"), Some("hello"));

Conflict Rules

Static and dynamic route segments are allowed to overlap. If they do, static segments will be given higher priority:

let mut router = Router::new();
router.insert("/", "Welcome!").unwrap();       // Priority: 1
router.insert("/about", "About Me").unwrap();  // Priority: 1
router.insert("/{*filepath}", "...").unwrap();  // Priority: 2

Formally, a route consists of a list of segments separated by /, with an optional leading and trailing slash: (/)<segment_1>/.../<segment_n>(/).

Given set of routes, their overlapping segments may include, in order of priority:

  • Any number of static segments (/a, /b, ...).
  • One of the following:
  • Any number of route parameters with a suffix (/{x}a, /{x}b, ...), prioritizing the longest suffix.
  • Any number of route parameters with a prefix (/a{x}, /b{x}, ...), prioritizing the longest prefix.
  • 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.

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

The one exception to the above set of rules is that catch-all parameters are always considered to conflict with suffixed route parameters, i.e. that /{*rest} and /{x}suffix are overlapping. This is due to an implementation detail of the routing tree that may be relaxed in the future.


A fork of matchit

Compare to matchit

  • Pros
    • clean public types with no lifetime pollution. (easier to pass params around)
    • 100% safe Rust. (unsafe code still used through dependencies)
  • Cons
    • immutable router value.
    • potentially slower in micro benchmark.

Dependencies