reset_recognizer/
lib.rs

1/*!
2A fast [`RegexSet`](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) based path router, in the style of
3[route-recognizer](https://docs.rs/route-recognizer).
4
5[reset-router](https://docs.rs/reset-router), a higher level path router for use with Hyper 0.13, uses this library internally.
6
7## Usage:
8
9```rust,no_run
10let router = reset_recognizer::Router::build()
11    .add(r"^/posts/(.+)/comments/(.+)$", "comment".to_string())
12    .add(r"^/posts/(.+)/comments$", "comments".to_string())
13    .add(r"^/posts/(.+)$", "post".to_string())
14    .add(r"^/posts$", "posts".to_string())
15    .add(r"^/comments$", "comments2".to_string())
16    .add(r"^/comments/(.+)$", "comment2".to_string())
17    .add_with_priority(r"^/(.+)$", 1, "not_found".to_string())
18    .finish()?;
19
20let matched = router.recognize("/posts/100/comments/200")?;
21
22let (post_id, comment_id) = matched.captures.parsed::<(i32, i32)>()?;
23
24println!("{:?}", (&matched.handler, &post_id, &comment_id));
25```
26*/
27
28use std::collections::HashMap;
29use std::str::FromStr;
30
31/// Error handling
32pub mod err {
33    /// The error enum
34    #[derive(Debug)]
35    pub enum Error {
36        Captures,
37        Unmatched(String),
38        Regex(regex::Error),
39    }
40
41    impl std::fmt::Display for Error {
42        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
43            use Error::*;
44            match self {
45                Captures => "Could not parse captures".fmt(f),
46                Unmatched(ref path) => write!(f, "Could not match path: {}", path),
47                Regex(ref inner) => inner.fmt(f),
48            }
49        }
50    }
51
52    impl std::error::Error for Error {
53        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
54            use Error::*;
55            match self {
56                Regex(ref inner) => Some(inner),
57                _ => None,
58            }
59        }
60    }
61
62    /// Result wrapper: `Result<T, Error>`
63    pub type Result<T> = std::result::Result<T, Error>;
64}
65
66/// Captures data for matched Regex
67pub struct Captures {
68    path: Box<str>,
69    locs: Option<regex::CaptureLocations>,
70    capture_names: Option<CaptureNames>,
71}
72
73impl Captures {
74    fn new(
75        path: Box<str>,
76        regex: &regex::Regex,
77        mut locs: regex::CaptureLocations,
78        capture_names: Option<CaptureNames>,
79    ) -> Self {
80        let locs = regex.captures_read(&mut locs, &path).map(|_| locs);
81
82        Self { path, locs, capture_names }
83    }
84
85    /// Get named capture match
86    pub fn name(&self, name: &str) -> Option<&str> {
87        self.capture_names.as_ref().and_then(|map| map.0.get(name)).and_then(|i| self.get(*i))
88    }
89
90    /// Get positional capture match
91    pub fn get(&self, i: usize) -> Option<&str> {
92        self.locs
93            .as_ref()
94            .and_then(|loc| loc.get(i))
95            .and_then(|(start, end)| self.path.get(start..end) )
96    }
97
98    /// Parse positional captures into tuple
99    pub fn parsed<C: FromCaptures>(&self) -> err::Result<C> {
100        Ok(C::from_captures(&self)?)
101    }
102
103    /// Iterate over all captures (including capture 0) as `Iterator<Item=&str>`
104    pub fn iter(&self) -> CapturesIter {
105        let iter = self
106            .locs
107            .as_ref()
108            .map(|locs| (0..locs.len()).into_iter().filter_map(move |i| locs.get(i)))
109            .into_iter()
110            .flatten();
111
112        CapturesIter(Box::new(iter), &self.path)
113    }
114}
115
116#[doc(hidden)]
117pub struct CapturesIter<'a>(Box<dyn Iterator<Item=(usize, usize)> + 'a>, &'a str);
118
119impl<'a> Iterator for CapturesIter<'a> {
120    type Item = &'a str;
121
122    fn next(&mut self) -> Option<&'a str> {
123        self.0.next().and_then(|(start, end)| self.1.get(start..end) )
124    }
125}
126
127/// Match data for a recognized route
128pub struct Match<'a, T> {
129    pub handler: &'a T,
130    pub captures: Captures,
131}
132
133#[derive(Debug)]
134struct RouteParts<T> {
135    regex: String,
136    priority: i8,
137    handler: T,
138}
139
140/// Builder for a `Router`
141#[derive(Default)]
142pub struct RouterBuilder<T> {
143    parts: Vec<RouteParts<T>>,
144}
145
146impl<T> RouterBuilder<T> {
147    fn new() -> Self {
148        RouterBuilder {
149            parts: Vec::new(),
150        }
151    }
152
153    /// Add a route with handler and default priority 0
154    pub fn add<I: Into<String>>(self, regex: I, handler: T) -> Self {
155        self.add_with_priority(regex, 0, handler)
156    }
157
158    /// Add a route with handler and priority (higher is better)
159    pub fn add_with_priority<I: Into<String>>(
160        mut self,
161        regex: I,
162        priority: i8,
163        handler: T,
164    ) -> Self {
165        self.parts.push(RouteParts { regex: regex.into(), priority, handler });
166
167        self
168    }
169
170    /// Build finished router
171    pub fn finish(self) -> err::Result<Router<T>> {
172        let (regex_strs, priorities, handlers) = self.parts.into_iter().fold(
173            (Vec::new(), Vec::new(), Vec::new()),
174            |(mut regex_strs, mut priorities, mut handlers),
175             RouteParts { regex, priority, handler }| {
176                regex_strs.push(regex);
177                priorities.push(priority);
178                handlers.push(handler);
179                (regex_strs, priorities, handlers)
180            },
181        );
182
183        let regex_set = regex::RegexSet::new(regex_strs.iter()).map_err(err::Error::Regex)?;
184
185        let mut regexes = Vec::new();
186        let mut capture_names = Vec::new();
187        let mut capture_locations = Vec::new();
188
189        for regex_str in regex_strs.iter() {
190            let regex = regex::Regex::new(regex_str).map_err(err::Error::Regex)?;
191            capture_names.push(CaptureNames::build(&regex));
192            capture_locations.push(regex.capture_locations());
193            regexes.push(regex);
194        }
195
196        Ok(Router { regex_set, regexes, capture_names, capture_locations, priorities, handlers })
197    }
198}
199
200#[derive(Clone)]
201struct CaptureNames(std::sync::Arc<HashMap<String, usize>>);
202
203impl CaptureNames {
204    fn build(regex: &regex::Regex) -> Option<Self> {
205        let map = regex
206            .capture_names()
207            .enumerate()
208            .filter_map(|(i, opt_name)| opt_name.map(|name| (String::from(name), i)))
209            .collect::<HashMap<_, _>>();
210        if map.is_empty() {
211            None
212        } else {
213            Some(Self(std::sync::Arc::new(map)))
214        }
215    }
216}
217
218/// The complete route matcher
219pub struct Router<T> {
220    regex_set: regex::RegexSet,
221    regexes: Vec<regex::Regex>,
222    capture_locations: Vec<regex::CaptureLocations>,
223    capture_names: Vec<Option<CaptureNames>>,
224    priorities: Vec<i8>,
225    handlers: Vec<T>,
226}
227
228impl<T> Router<T> {
229    /// Build a new router
230    pub fn build() -> RouterBuilder<T> {
231        RouterBuilder::new()
232    }
233
234    /// Match a route and return match data
235    pub fn recognize<'a>(&'a self, path: &str) -> err::Result<Match<'a, T>> {
236        if let Some(i) = self
237            .regex_set
238            .matches(path)
239            .iter()
240            .max_by(|x, y| self.priorities[*x].cmp(&self.priorities[*y]))
241        {
242            let handler = &self.handlers[i];
243            let regex = &self.regexes[i];
244            let capture_names = &self.capture_names[i];
245            let capture_locations = &self.capture_locations[i];
246            Ok(Match {
247                handler,
248                captures: Captures::new(
249                    path.into(),
250                    regex,
251                    capture_locations.clone(),
252                    capture_names.clone(),
253                ),
254            })
255        } else {
256            Err(err::Error::Unmatched(String::from(path)))
257        }
258    }
259}
260
261/// Parse captures data into tuples
262pub trait FromCaptures: Sized {
263    fn from_captures(caps: &Captures) -> err::Result<Self>;
264}
265
266impl<U: FromStr> FromCaptures for (U,) {
267    fn from_captures(caps: &Captures) -> err::Result<Self> {
268        let out_1 = caps.get(1).and_then(|x| x.parse().ok()).ok_or(err::Error::Captures)?;
269        Ok((out_1,))
270    }
271}
272
273impl<U1: FromStr, U2: FromStr> FromCaptures for (U1, U2) {
274    fn from_captures(caps: &Captures) -> err::Result<Self> {
275        let out_1 = caps.get(1).and_then(|x| x.parse().ok()).ok_or(err::Error::Captures)?;
276        let out_2 = caps.get(2).and_then(|x| x.parse().ok()).ok_or(err::Error::Captures)?;
277        Ok((out_1, out_2))
278    }
279}
280
281impl<U1: FromStr, U2: FromStr, U3: FromStr> FromCaptures for (U1, U2, U3) {
282    fn from_captures(caps: &Captures) -> err::Result<Self> {
283        let out_1 = caps.get(1).and_then(|x| x.parse().ok()).ok_or(err::Error::Captures)?;
284        let out_2 = caps.get(2).and_then(|x| x.parse().ok()).ok_or(err::Error::Captures)?;
285        let out_3 = caps.get(3).and_then(|x| x.parse().ok()).ok_or(err::Error::Captures)?;
286        Ok((out_1, out_2, out_3))
287    }
288}
289
290impl<U1: FromStr, U2: FromStr, U3: FromStr, U4: FromStr> FromCaptures for (U1, U2, U3, U4) {
291    fn from_captures(caps: &Captures) -> err::Result<Self> {
292        let out_1 = caps.get(1).and_then(|x| x.parse().ok()).ok_or(err::Error::Captures)?;
293        let out_2 = caps.get(2).and_then(|x| x.parse().ok()).ok_or(err::Error::Captures)?;
294        let out_3 = caps.get(3).and_then(|x| x.parse().ok()).ok_or(err::Error::Captures)?;
295        let out_4 = caps.get(4).and_then(|x| x.parse().ok()).ok_or(err::Error::Captures)?;
296        Ok((out_1, out_2, out_3, out_4))
297    }
298}