route_recognizer/
lib.rs

1//! Recognizes URL patterns with support for dynamic and wildcard segments
2//!
3//! # Examples
4//!
5//! ```
6//! use route_recognizer::{Router, Params};
7//!
8//! let mut router = Router::new();
9//!
10//! router.add("/thomas", "Thomas".to_string());
11//! router.add("/tom", "Tom".to_string());
12//! router.add("/wycats", "Yehuda".to_string());
13//!
14//! let m = router.recognize("/thomas").unwrap();
15//!
16//! assert_eq!(m.handler().as_str(), "Thomas");
17//! assert_eq!(m.params(), &Params::new());
18//! ```
19//!
20//! # Routing params
21//!
22//! The router supports four kinds of route segments:
23//! - __segments__: these are of the format `/a/b`.
24//! - __params__: these are of the format `/a/:b`.
25//! - __named wildcards__: these are of the format `/a/*b`.
26//! - __unnamed wildcards__: these are of the format `/a/*`.
27//!
28//! The difference between a "named wildcard" and a "param" is how the
29//! matching rules apply. Given the router `/a/:b`, passing in `/foo/bar/baz`
30//! will not match because `/baz` has no counterpart in the router.
31//!
32//! However if we define the route `/a/*b` and we pass `/foo/bar/baz` we end up
33//! with a named param `"b"` that contains the value `"bar/baz"`. Wildcard
34//! routing rules are useful when you don't know which routes may follow. The
35//! difference between "named" and "unnamed" wildcards is that the former will
36//! show up in `Params`, while the latter won't.
37
38#![cfg_attr(feature = "docs", feature(doc_cfg))]
39#![deny(unsafe_code)]
40#![deny(missing_debug_implementations, nonstandard_style)]
41#![warn(missing_docs, unreachable_pub, future_incompatible, rust_2018_idioms)]
42#![doc(test(attr(deny(warnings))))]
43#![doc(test(attr(allow(unused_extern_crates, unused_variables))))]
44#![doc(html_favicon_url = "https://yoshuawuyts.com/assets/http-rs/favicon.ico")]
45#![doc(html_logo_url = "https://yoshuawuyts.com/assets/http-rs/logo-rounded.png")]
46
47use std::cmp::Ordering;
48use std::collections::{btree_map, BTreeMap};
49use std::ops::Index;
50
51use crate::nfa::{CharacterClass, NFA};
52
53#[doc(hidden)]
54pub mod nfa;
55
56#[derive(Clone, Eq, Debug)]
57struct Metadata {
58    statics: u32,
59    dynamics: u32,
60    wildcards: u32,
61    param_names: Vec<String>,
62}
63
64impl Metadata {
65    pub(crate) fn new() -> Self {
66        Self {
67            statics: 0,
68            dynamics: 0,
69            wildcards: 0,
70            param_names: Vec::new(),
71        }
72    }
73}
74
75impl Ord for Metadata {
76    fn cmp(&self, other: &Self) -> Ordering {
77        if self.statics > other.statics {
78            Ordering::Greater
79        } else if self.statics < other.statics {
80            Ordering::Less
81        } else if self.dynamics > other.dynamics {
82            Ordering::Greater
83        } else if self.dynamics < other.dynamics {
84            Ordering::Less
85        } else if self.wildcards > other.wildcards {
86            Ordering::Greater
87        } else if self.wildcards < other.wildcards {
88            Ordering::Less
89        } else {
90            Ordering::Equal
91        }
92    }
93}
94
95impl PartialOrd for Metadata {
96    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
97        Some(self.cmp(other))
98    }
99}
100
101impl PartialEq for Metadata {
102    fn eq(&self, other: &Self) -> bool {
103        self.statics == other.statics
104            && self.dynamics == other.dynamics
105            && self.wildcards == other.wildcards
106    }
107}
108
109/// Router parameters.
110#[derive(PartialEq, Clone, Debug, Default)]
111pub struct Params {
112    map: BTreeMap<String, String>,
113}
114
115impl Params {
116    /// Create a new instance of `Params`.
117    pub fn new() -> Self {
118        Self {
119            map: BTreeMap::new(),
120        }
121    }
122
123    /// Insert a new param into `Params`.
124    pub fn insert(&mut self, key: String, value: String) {
125        self.map.insert(key, value);
126    }
127
128    /// Find a param by name in `Params`.
129    pub fn find(&self, key: &str) -> Option<&str> {
130        self.map.get(key).map(|s| &s[..])
131    }
132
133    /// Iterate over all named params.
134    ///
135    /// This will return all named params and named wildcards.
136    pub fn iter(&self) -> Iter<'_> {
137        Iter(self.map.iter())
138    }
139}
140
141impl Index<&str> for Params {
142    type Output = String;
143    fn index(&self, index: &str) -> &String {
144        match self.map.get(index) {
145            None => panic!("params[{}] did not exist", index),
146            Some(s) => s,
147        }
148    }
149}
150
151impl<'a> IntoIterator for &'a Params {
152    type IntoIter = Iter<'a>;
153    type Item = (&'a str, &'a str);
154
155    fn into_iter(self) -> Iter<'a> {
156        self.iter()
157    }
158}
159
160/// An iterator over `Params`.
161#[derive(Debug)]
162pub struct Iter<'a>(btree_map::Iter<'a, String, String>);
163
164impl<'a> Iterator for Iter<'a> {
165    type Item = (&'a str, &'a str);
166
167    #[inline]
168    fn next(&mut self) -> Option<(&'a str, &'a str)> {
169        self.0.next().map(|(k, v)| (&**k, &**v))
170    }
171
172    fn size_hint(&self) -> (usize, Option<usize>) {
173        self.0.size_hint()
174    }
175}
176
177/// The result of a successful match returned by `Router::recognize`.
178#[derive(Debug)]
179pub struct Match<T> {
180    /// Return the endpoint handler.
181    handler: T,
182    /// Return the params.
183    params: Params,
184}
185
186impl<T> Match<T> {
187    /// Create a new instance of `Match`.
188    pub fn new(handler: T, params: Params) -> Self {
189        Self { handler, params }
190    }
191
192    /// Get a handle to the handler.
193    pub fn handler(&self) -> &T {
194        &self.handler
195    }
196
197    /// Get a mutable handle to the handler.
198    pub fn handler_mut(&mut self) -> &mut T {
199        &mut self.handler
200    }
201
202    /// Get a handle to the params.
203    pub fn params(&self) -> &Params {
204        &self.params
205    }
206
207    /// Get a mutable handle to the params.
208    pub fn params_mut(&mut self) -> &mut Params {
209        &mut self.params
210    }
211}
212
213/// Recognizes URL patterns with support for dynamic and wildcard segments.
214#[derive(Clone, Debug)]
215pub struct Router<T> {
216    nfa: NFA<Metadata>,
217    handlers: BTreeMap<usize, T>,
218}
219
220fn segments(route: &str) -> Vec<(Option<char>, &str)> {
221    let predicate = |c| c == '.' || c == '/';
222
223    let mut segments = vec![];
224    let mut segment_start = 0;
225
226    while segment_start < route.len() {
227        let segment_end = route[segment_start + 1..]
228            .find(predicate)
229            .map(|i| i + segment_start + 1)
230            .unwrap_or_else(|| route.len());
231        let potential_sep = route.chars().nth(segment_start);
232        let sep_and_segment = match potential_sep {
233            Some(sep) if predicate(sep) => (Some(sep), &route[segment_start + 1..segment_end]),
234            _ => (None, &route[segment_start..segment_end]),
235        };
236
237        segments.push(sep_and_segment);
238        segment_start = segment_end;
239    }
240
241    segments
242}
243
244impl<T> Router<T> {
245    /// Create a new instance of `Router`.
246    pub fn new() -> Self {
247        Self {
248            nfa: NFA::new(),
249            handlers: BTreeMap::new(),
250        }
251    }
252
253    /// Add a route to the router.
254    pub fn add(&mut self, mut route: &str, dest: T) {
255        if !route.is_empty() && route.as_bytes()[0] == b'/' {
256            route = &route[1..];
257        }
258
259        let nfa = &mut self.nfa;
260        let mut state = 0;
261        let mut metadata = Metadata::new();
262
263        for (separator, segment) in segments(route) {
264            if let Some(separator) = separator {
265                state = nfa.put(state, CharacterClass::valid_char(separator));
266            }
267
268            if !segment.is_empty() && segment.as_bytes()[0] == b':' {
269                state = process_dynamic_segment(nfa, state);
270                metadata.dynamics += 1;
271                metadata.param_names.push(segment[1..].to_string());
272            } else if !segment.is_empty() && segment.as_bytes()[0] == b'*' {
273                state = process_star_state(nfa, state);
274                metadata.wildcards += 1;
275                metadata.param_names.push(segment[1..].to_string());
276            } else {
277                state = process_static_segment(segment, nfa, state);
278                metadata.statics += 1;
279            }
280        }
281
282        nfa.acceptance(state);
283        nfa.metadata(state, metadata);
284        self.handlers.insert(state, dest);
285    }
286
287    /// Match a route on the router.
288    pub fn recognize(&self, mut path: &str) -> Result<Match<&T>, String> {
289        if !path.is_empty() && path.as_bytes()[0] == b'/' {
290            path = &path[1..];
291        }
292
293        let nfa = &self.nfa;
294        let result = nfa.process(path, |index| nfa.get(index).metadata.as_ref().unwrap());
295
296        match result {
297            Ok(nfa_match) => {
298                let mut map = Params::new();
299                let state = &nfa.get(nfa_match.state);
300                let metadata = state.metadata.as_ref().unwrap();
301                let param_names = metadata.param_names.clone();
302
303                for (i, capture) in nfa_match.captures.iter().enumerate() {
304                    if !param_names[i].is_empty() {
305                        map.insert(param_names[i].to_string(), capture.to_string());
306                    }
307                }
308
309                let handler = self.handlers.get(&nfa_match.state).unwrap();
310                Ok(Match::new(handler, map))
311            }
312            Err(str) => Err(str),
313        }
314    }
315}
316
317impl<T> Default for Router<T> {
318    fn default() -> Self {
319        Self::new()
320    }
321}
322
323fn process_static_segment<T>(segment: &str, nfa: &mut NFA<T>, mut state: usize) -> usize {
324    for char in segment.chars() {
325        state = nfa.put(state, CharacterClass::valid_char(char));
326    }
327
328    state
329}
330
331fn process_dynamic_segment<T>(nfa: &mut NFA<T>, mut state: usize) -> usize {
332    state = nfa.put(state, CharacterClass::invalid_char('/'));
333    nfa.put_state(state, state);
334    nfa.start_capture(state);
335    nfa.end_capture(state);
336
337    state
338}
339
340fn process_star_state<T>(nfa: &mut NFA<T>, mut state: usize) -> usize {
341    state = nfa.put(state, CharacterClass::any());
342    nfa.put_state(state, state);
343    nfa.start_capture(state);
344    nfa.end_capture(state);
345
346    state
347}
348
349#[cfg(test)]
350mod tests {
351    use super::{Params, Router};
352
353    #[test]
354    fn basic_router() {
355        let mut router = Router::new();
356
357        router.add("/thomas", "Thomas".to_string());
358        router.add("/tom", "Tom".to_string());
359        router.add("/wycats", "Yehuda".to_string());
360
361        let m = router.recognize("/thomas").unwrap();
362
363        assert_eq!(*m.handler, "Thomas".to_string());
364        assert_eq!(m.params, Params::new());
365    }
366
367    #[test]
368    fn root_router() {
369        let mut router = Router::new();
370        router.add("/", 10);
371        assert_eq!(*router.recognize("/").unwrap().handler, 10)
372    }
373
374    #[test]
375    fn empty_path() {
376        let mut router = Router::new();
377        router.add("/", 12);
378        assert_eq!(*router.recognize("").unwrap().handler, 12)
379    }
380
381    #[test]
382    fn empty_route() {
383        let mut router = Router::new();
384        router.add("", 12);
385        assert_eq!(*router.recognize("/").unwrap().handler, 12)
386    }
387
388    #[test]
389    fn ambiguous_router() {
390        let mut router = Router::new();
391
392        router.add("/posts/new", "new".to_string());
393        router.add("/posts/:id", "id".to_string());
394
395        let id = router.recognize("/posts/1").unwrap();
396
397        assert_eq!(*id.handler, "id".to_string());
398        assert_eq!(id.params, params("id", "1"));
399
400        let new = router.recognize("/posts/new").unwrap();
401        assert_eq!(*new.handler, "new".to_string());
402        assert_eq!(new.params, Params::new());
403    }
404
405    #[test]
406    fn ambiguous_router_b() {
407        let mut router = Router::new();
408
409        router.add("/posts/:id", "id".to_string());
410        router.add("/posts/new", "new".to_string());
411
412        let id = router.recognize("/posts/1").unwrap();
413
414        assert_eq!(*id.handler, "id".to_string());
415        assert_eq!(id.params, params("id", "1"));
416
417        let new = router.recognize("/posts/new").unwrap();
418        assert_eq!(*new.handler, "new".to_string());
419        assert_eq!(new.params, Params::new());
420    }
421
422    #[test]
423    fn multiple_params() {
424        let mut router = Router::new();
425
426        router.add("/posts/:post_id/comments/:id", "comment".to_string());
427        router.add("/posts/:post_id/comments", "comments".to_string());
428
429        let com = router.recognize("/posts/12/comments/100").unwrap();
430        let coms = router.recognize("/posts/12/comments").unwrap();
431
432        assert_eq!(*com.handler, "comment".to_string());
433        assert_eq!(com.params, two_params("post_id", "12", "id", "100"));
434
435        assert_eq!(*coms.handler, "comments".to_string());
436        assert_eq!(coms.params, params("post_id", "12"));
437        assert_eq!(coms.params["post_id"], "12".to_string());
438    }
439
440    #[test]
441    fn wildcard() {
442        let mut router = Router::new();
443
444        router.add("*foo", "test".to_string());
445        router.add("/bar/*foo", "test2".to_string());
446
447        let m = router.recognize("/test").unwrap();
448        assert_eq!(*m.handler, "test".to_string());
449        assert_eq!(m.params, params("foo", "test"));
450
451        let m = router.recognize("/foo/bar").unwrap();
452        assert_eq!(*m.handler, "test".to_string());
453        assert_eq!(m.params, params("foo", "foo/bar"));
454
455        let m = router.recognize("/bar/foo").unwrap();
456        assert_eq!(*m.handler, "test2".to_string());
457        assert_eq!(m.params, params("foo", "foo"));
458    }
459
460    #[test]
461    fn wildcard_colon() {
462        let mut router = Router::new();
463
464        router.add("/a/*b", "ab".to_string());
465        router.add("/a/*b/c", "abc".to_string());
466        router.add("/a/*b/c/:d", "abcd".to_string());
467
468        let m = router.recognize("/a/foo").unwrap();
469        assert_eq!(*m.handler, "ab".to_string());
470        assert_eq!(m.params, params("b", "foo"));
471
472        let m = router.recognize("/a/foo/bar").unwrap();
473        assert_eq!(*m.handler, "ab".to_string());
474        assert_eq!(m.params, params("b", "foo/bar"));
475
476        let m = router.recognize("/a/foo/c").unwrap();
477        assert_eq!(*m.handler, "abc".to_string());
478        assert_eq!(m.params, params("b", "foo"));
479
480        let m = router.recognize("/a/foo/bar/c").unwrap();
481        assert_eq!(*m.handler, "abc".to_string());
482        assert_eq!(m.params, params("b", "foo/bar"));
483
484        let m = router.recognize("/a/foo/c/baz").unwrap();
485        assert_eq!(*m.handler, "abcd".to_string());
486        assert_eq!(m.params, two_params("b", "foo", "d", "baz"));
487
488        let m = router.recognize("/a/foo/bar/c/baz").unwrap();
489        assert_eq!(*m.handler, "abcd".to_string());
490        assert_eq!(m.params, two_params("b", "foo/bar", "d", "baz"));
491
492        let m = router.recognize("/a/foo/bar/c/baz/bay").unwrap();
493        assert_eq!(*m.handler, "ab".to_string());
494        assert_eq!(m.params, params("b", "foo/bar/c/baz/bay"));
495    }
496
497    #[test]
498    fn unnamed_parameters() {
499        let mut router = Router::new();
500
501        router.add("/foo/:/bar", "test".to_string());
502        router.add("/foo/:bar/*", "test2".to_string());
503        let m = router.recognize("/foo/test/bar").unwrap();
504        assert_eq!(*m.handler, "test");
505        assert_eq!(m.params, Params::new());
506
507        let m = router.recognize("/foo/test/blah").unwrap();
508        assert_eq!(*m.handler, "test2");
509        assert_eq!(m.params, params("bar", "test"));
510    }
511
512    fn params(key: &str, val: &str) -> Params {
513        let mut map = Params::new();
514        map.insert(key.to_string(), val.to_string());
515        map
516    }
517
518    fn two_params(k1: &str, v1: &str, k2: &str, v2: &str) -> Params {
519        let mut map = Params::new();
520        map.insert(k1.to_string(), v1.to_string());
521        map.insert(k2.to_string(), v2.to_string());
522        map
523    }
524
525    #[test]
526    fn dot() {
527        let mut router = Router::new();
528        router.add("/1/baz.:wibble", ());
529        router.add("/2/:bar.baz", ());
530        router.add("/3/:dynamic.:extension", ());
531        router.add("/4/static.static", ());
532
533        let m = router.recognize("/1/baz.jpg").unwrap();
534        assert_eq!(m.params, params("wibble", "jpg"));
535
536        let m = router.recognize("/2/test.baz").unwrap();
537        assert_eq!(m.params, params("bar", "test"));
538
539        let m = router.recognize("/3/any.thing").unwrap();
540        assert_eq!(m.params, two_params("dynamic", "any", "extension", "thing"));
541
542        let m = router.recognize("/3/this.performs.a.greedy.match").unwrap();
543        assert_eq!(
544            m.params,
545            two_params("dynamic", "this.performs.a.greedy", "extension", "match")
546        );
547
548        let m = router.recognize("/4/static.static").unwrap();
549        assert_eq!(m.params, Params::new());
550
551        let m = router.recognize("/4/static/static");
552        assert!(m.is_err());
553
554        let m = router.recognize("/4.static.static");
555        assert!(m.is_err());
556    }
557
558    #[test]
559    fn test_chinese() {
560        let mut router = Router::new();
561        router.add("/crates/:foo/:bar", "Hello".to_string());
562
563        let m = router.recognize("/crates/实打实打算/d's'd").unwrap();
564        assert_eq!(m.handler().as_str(), "Hello");
565        assert_eq!(m.params().find("foo"), Some("实打实打算"));
566        assert_eq!(m.params().find("bar"), Some("d's'd"));
567    }
568}