ntex_router/lib.rs
1#![deny(clippy::pedantic)]
2#![allow(
3 clippy::must_use_candidate,
4 clippy::missing_panics_doc,
5 clippy::missing_errors_doc,
6 clippy::cast_possible_wrap,
7 clippy::cast_sign_loss,
8 clippy::cast_possible_truncation,
9 clippy::too_many_lines
10)]
11
12//! Resource path matching library.
13//!
14//! A [`Router`] maps request paths to values. Resources are registered with
15//! a [`RouterBuilder`] from path patterns, see [`ResourceDef`]. Matching a
16//! [`Path`] returns the value of the first registered resource that matches,
17//! and stores the values of the pattern's dynamic segments in the `Path`.
18//!
19//! # Pattern syntax
20//!
21//! Patterns are split into segments by `/`.
22//!
23//! * `/users/list` — static segments match literally. A trailing `/` is
24//! significant, `/users/` does not match `/users`.
25//! * `/users/{id}` — a dynamic segment matches one or more characters up to
26//! the next `/` and stores them as `id`.
27//! * `/files/{name}.{ext}` — a segment can combine static text and several
28//! dynamic parts.
29//! * `/users/{id:[0-9]+}` — a dynamic segment with a custom regular
30//! expression.
31//! * `/static/{tail}*` — a tail segment matches the rest of the path,
32//! including `/`. Custom regular expressions are not supported for tails.
33//! * `/static/*` — a static tail matches the rest of the path without storing
34//! it.
35//!
36//! [`ResourceDef::prefix()`] creates a resource that matches paths starting
37//! with the pattern, at a segment boundary. After a match, [`Path::path()`]
38//! returns the rest of the path.
39//!
40//! # Example
41//!
42//! ```
43//! use ntex_router::{Path, Router};
44//!
45//! let mut builder = Router::<&str>::builder();
46//! builder.path("/users/{id}", "user");
47//! builder.path("/files/{tail}*", "files");
48//! let router = builder.build();
49//!
50//! let mut path = Path::new("/users/42");
51//! let (value, _) = router.recognize(&mut path).unwrap();
52//! assert_eq!(*value, "user");
53//! assert_eq!(path.get("id"), Some("42"));
54//!
55//! let mut path = Path::new("/files/css/site.css");
56//! let (value, _) = router.recognize(&mut path).unwrap();
57//! assert_eq!(*value, "files");
58//! assert_eq!(&path["tail"], "css/site.css");
59//!
60//! assert!(router.recognize(&mut Path::new("/unknown")).is_none());
61//! ```
62#![warn(missing_docs)]
63mod de;
64mod path;
65mod resource;
66mod router;
67mod tree;
68
69pub use self::de::PathDeserializer;
70pub use self::path::{Path, PathIter};
71pub use self::resource::ResourceDef;
72pub use self::router::{ResourceId, Router, RouterBuilder, RouterEntry};
73
74/// A value that can be matched by a [`Router`].
75///
76/// [`Path`] implements it, a request type can implement it to be matched
77/// directly.
78pub trait Resource<T: ResourcePath> {
79 /// Path to match.
80 fn path(&self) -> &str;
81
82 /// Path state that stores match results.
83 fn resource_path(&mut self) -> &mut Path<T>;
84}
85
86/// A path source, e.g. a string or an `urly::Url`.
87pub trait ResourcePath {
88 /// Full path.
89 fn path(&self) -> &str;
90
91 /// Decodes a path segment before it is matched.
92 ///
93 /// The path is split into segments on `/` before decoding, so a decoded
94 /// segment may contain `/`. The default implementation returns the
95 /// segment unchanged.
96 fn unquote(s: &str) -> std::borrow::Cow<'_, str> {
97 s.into()
98 }
99}
100
101impl ResourcePath for String {
102 fn path(&self) -> &str {
103 self.as_str()
104 }
105}
106
107impl ResourcePath for &str {
108 fn path(&self) -> &str {
109 self
110 }
111}
112
113impl ResourcePath for ntex_bytes::ByteString {
114 fn path(&self) -> &str {
115 self
116 }
117}
118
119impl<T: ResourcePath> ResourcePath for &T {
120 fn path(&self) -> &str {
121 (*self).path()
122 }
123}
124
125/// Helper trait for type that could be converted to path patterns.
126///
127/// Implemented for strings, and for vectors and arrays of strings for
128/// resources with several patterns.
129pub trait IntoPattern {
130 /// Path patterns.
131 fn patterns(&self) -> Vec<String>;
132}
133
134impl IntoPattern for String {
135 fn patterns(&self) -> Vec<String> {
136 vec![self.clone()]
137 }
138}
139
140impl IntoPattern for &String {
141 fn patterns(&self) -> Vec<String> {
142 vec![self.as_str().to_string()]
143 }
144}
145
146impl IntoPattern for &str {
147 fn patterns(&self) -> Vec<String> {
148 vec![(*self).to_string()]
149 }
150}
151
152impl<T: AsRef<str>> IntoPattern for Vec<T> {
153 fn patterns(&self) -> Vec<String> {
154 self.iter().map(|v| v.as_ref().to_string()).collect()
155 }
156}
157
158impl<T: AsRef<str>, const N: usize> IntoPattern for [T; N] {
159 fn patterns(&self) -> Vec<String> {
160 self.iter().map(|v| v.as_ref().to_string()).collect()
161 }
162}
163
164mod url_support {
165 use super::ResourcePath;
166 use urly::Url;
167
168 /// Path segments are percent-decoded, escapes that would not decode to
169 /// valid utf-8 are kept percent-encoded.
170 ///
171 /// Segments are decoded after the path is split on `/`, so parameter
172 /// values can contain any character, including `/` and `%`. For example
173 /// `/files/..%2F..%2Fetc` matches `/files/{name}` with `name` set to
174 /// `../../etc`. Such values must be validated before they are used, e.g.
175 /// as a file system path.
176 impl ResourcePath for Url {
177 fn path(&self) -> &str {
178 self.path().as_str()
179 }
180
181 fn unquote(s: &str) -> std::borrow::Cow<'_, str> {
182 urly::quoting::unquote(s, urly::quoting::Component::Path)
183 }
184 }
185}