scrappy_router/
resource.rs

1use std::cmp::min;
2use std::collections::HashMap;
3use std::hash::{Hash, Hasher};
4
5use regex::{escape, Regex, RegexSet};
6
7use crate::path::{Path, PathItem};
8use crate::{IntoPattern, Resource, ResourcePath};
9
10const MAX_DYNAMIC_SEGMENTS: usize = 16;
11
12/// ResourceDef describes an entry in resources table
13///
14/// Resource definition can contain only 16 dynamic segments
15#[derive(Clone, Debug)]
16pub struct ResourceDef {
17    id: u16,
18    tp: PatternType,
19    name: String,
20    pattern: String,
21    elements: Vec<PatternElement>,
22}
23
24#[derive(Debug, Clone, PartialEq)]
25enum PatternElement {
26    Str(String),
27    Var(String),
28}
29
30#[derive(Clone, Debug)]
31enum PatternType {
32    Static(String),
33    Prefix(String),
34    Dynamic(Regex, Vec<&'static str>, usize),
35    DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>, usize)>),
36}
37
38impl ResourceDef {
39    /// Parse path pattern and create new `Pattern` instance.
40    ///
41    /// Panics if path pattern is malformed.
42    pub fn new<T: IntoPattern>(path: T) -> Self {
43        if path.is_single() {
44            let patterns = path.patterns();
45            ResourceDef::with_prefix(&patterns[0], false)
46        } else {
47            let set = path.patterns();
48            let mut data = Vec::new();
49            let mut re_set = Vec::new();
50
51            for path in set {
52                let (pattern, _, _, len) = ResourceDef::parse(&path, false);
53
54                let re = match Regex::new(&pattern) {
55                    Ok(re) => re,
56                    Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
57                };
58                // scrappy creates one router per thread
59                let names: Vec<_> = re
60                    .capture_names()
61                    .filter_map(|name| {
62                        name.map(|name| Box::leak(Box::new(name.to_owned())).as_str())
63                    })
64                    .collect();
65                data.push((re, names, len));
66                re_set.push(pattern);
67            }
68
69            ResourceDef {
70                id: 0,
71                tp: PatternType::DynamicSet(RegexSet::new(re_set).unwrap(), data),
72                elements: Vec::new(),
73                name: String::new(),
74                pattern: "".to_owned(),
75            }
76        }
77    }
78
79    /// Parse path pattern and create new `Pattern` instance.
80    ///
81    /// Use `prefix` type instead of `static`.
82    ///
83    /// Panics if path regex pattern is malformed.
84    pub fn prefix(path: &str) -> Self {
85        ResourceDef::with_prefix(path, true)
86    }
87
88    /// Parse path pattern and create new `Pattern` instance.
89    /// Inserts `/` to begging of the pattern.
90    ///
91    ///
92    /// Use `prefix` type instead of `static`.
93    ///
94    /// Panics if path regex pattern is malformed.
95    pub fn root_prefix(path: &str) -> Self {
96        ResourceDef::with_prefix(&insert_slash(path), true)
97    }
98
99    /// Resource id
100    pub fn id(&self) -> u16 {
101        self.id
102    }
103
104    /// Set resource id
105    pub fn set_id(&mut self, id: u16) {
106        self.id = id;
107    }
108
109    /// Parse path pattern and create new `Pattern` instance with custom prefix
110    fn with_prefix(path: &str, for_prefix: bool) -> Self {
111        let path = path.to_owned();
112        let (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix);
113
114        let tp = if is_dynamic {
115            let re = match Regex::new(&pattern) {
116                Ok(re) => re,
117                Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
118            };
119            // scrappy creates one router per thread
120            let names = re
121                .capture_names()
122                .filter_map(|name| {
123                    name.map(|name| Box::leak(Box::new(name.to_owned())).as_str())
124                })
125                .collect();
126            PatternType::Dynamic(re, names, len)
127        } else if for_prefix {
128            PatternType::Prefix(pattern)
129        } else {
130            PatternType::Static(pattern)
131        };
132
133        ResourceDef {
134            tp,
135            elements,
136            id: 0,
137            name: String::new(),
138            pattern: path,
139        }
140    }
141
142    /// Resource pattern name
143    pub fn name(&self) -> &str {
144        &self.name
145    }
146
147    /// Mutable reference to a name of a resource definition.
148    pub fn name_mut(&mut self) -> &mut String {
149        &mut self.name
150    }
151
152    /// Path pattern of the resource
153    pub fn pattern(&self) -> &str {
154        &self.pattern
155    }
156
157    #[inline]
158    /// Check if path matchs this pattern?
159    pub fn is_match(&self, path: &str) -> bool {
160        match self.tp {
161            PatternType::Static(ref s) => s == path,
162            PatternType::Prefix(ref s) => path.starts_with(s),
163            PatternType::Dynamic(ref re, _, _) => re.is_match(path),
164            PatternType::DynamicSet(ref re, _) => re.is_match(path),
165        }
166    }
167
168    /// Is prefix path a match against this resource?
169    pub fn is_prefix_match(&self, path: &str) -> Option<usize> {
170        let plen = path.len();
171        let path = if path.is_empty() { "/" } else { path };
172
173        match self.tp {
174            PatternType::Static(ref s) => {
175                if s == path {
176                    Some(plen)
177                } else {
178                    None
179                }
180            }
181            PatternType::Dynamic(ref re, _, len) => {
182                if let Some(captures) = re.captures(path) {
183                    let mut pos = 0;
184                    let mut passed = false;
185                    for capture in captures.iter() {
186                        if let Some(ref m) = capture {
187                            if !passed {
188                                passed = true;
189                                continue;
190                            }
191
192                            pos = m.end();
193                        }
194                    }
195                    Some(pos + len)
196                } else {
197                    None
198                }
199            }
200            PatternType::Prefix(ref s) => {
201                let len = if path == s {
202                    s.len()
203                } else if path.starts_with(s)
204                    && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/'))
205                {
206                    if s.ends_with('/') {
207                        s.len() - 1
208                    } else {
209                        s.len()
210                    }
211                } else {
212                    return None;
213                };
214                Some(min(plen, len))
215            }
216            PatternType::DynamicSet(ref re, ref params) => {
217                if let Some(idx) = re.matches(path).into_iter().next() {
218                    let (ref pattern, _, len) = params[idx];
219                    if let Some(captures) = pattern.captures(path) {
220                        let mut pos = 0;
221                        let mut passed = false;
222                        for capture in captures.iter() {
223                            if let Some(ref m) = capture {
224                                if !passed {
225                                    passed = true;
226                                    continue;
227                                }
228
229                                pos = m.end();
230                            }
231                        }
232                        Some(pos + len)
233                    } else {
234                        None
235                    }
236                } else {
237                    None
238                }
239            }
240        }
241    }
242
243    /// Is the given path and parameters a match against this pattern?
244    pub fn match_path<T: ResourcePath>(&self, path: &mut Path<T>) -> bool {
245        match self.tp {
246            PatternType::Static(ref s) => {
247                if s == path.path() {
248                    path.skip(path.len() as u16);
249                    true
250                } else {
251                    false
252                }
253            }
254            PatternType::Prefix(ref s) => {
255                let rpath = path.path();
256                let len = if s == rpath {
257                    s.len()
258                } else if rpath.starts_with(s)
259                    && (s.ends_with('/') || rpath.split_at(s.len()).1.starts_with('/'))
260                {
261                    if s.ends_with('/') {
262                        s.len() - 1
263                    } else {
264                        s.len()
265                    }
266                } else {
267                    return false;
268                };
269                let rpath_len = rpath.len();
270                path.skip(min(rpath_len, len) as u16);
271                true
272            }
273            PatternType::Dynamic(ref re, ref names, len) => {
274                let mut idx = 0;
275                let mut pos = 0;
276                let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
277                    [PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
278
279                if let Some(captures) = re.captures(path.path()) {
280                    for (no, name) in names.iter().enumerate() {
281                        if let Some(m) = captures.name(&name) {
282                            idx += 1;
283                            pos = m.end();
284                            segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
285                        } else {
286                            log::error!(
287                                "Dynamic path match but not all segments found: {}",
288                                name
289                            );
290                            return false;
291                        }
292                    }
293                } else {
294                    return false;
295                }
296                for idx in 0..idx {
297                    path.add(names[idx].clone(), segments[idx]);
298                }
299                path.skip((pos + len) as u16);
300                true
301            }
302            PatternType::DynamicSet(ref re, ref params) => {
303                if let Some(idx) = re.matches(path.path()).into_iter().next() {
304                    let (ref pattern, ref names, len) = params[idx];
305                    let mut idx = 0;
306                    let mut pos = 0;
307                    let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
308                        [PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
309
310                    if let Some(captures) = pattern.captures(path.path()) {
311                        for (no, name) in names.iter().enumerate() {
312                            if let Some(m) = captures.name(&name) {
313                                idx += 1;
314                                pos = m.end();
315                                segments[no] =
316                                    PathItem::Segment(m.start() as u16, m.end() as u16);
317                            } else {
318                                log::error!(
319                                    "Dynamic path match but not all segments found: {}",
320                                    name
321                                );
322                                return false;
323                            }
324                        }
325                    } else {
326                        return false;
327                    }
328                    for idx in 0..idx {
329                        path.add(names[idx].clone(), segments[idx]);
330                    }
331                    path.skip((pos + len) as u16);
332                    true
333                } else {
334                    false
335                }
336            }
337        }
338    }
339
340    /// Is the given path and parameters a match against this pattern?
341    pub fn match_path_checked<R, T, F, U>(
342        &self,
343        res: &mut R,
344        check: &F,
345        user_data: &Option<U>,
346    ) -> bool
347    where
348        T: ResourcePath,
349        R: Resource<T>,
350        F: Fn(&R, &Option<U>) -> bool,
351    {
352        match self.tp {
353            PatternType::Static(ref s) => {
354                if s == res.resource_path().path() && check(res, user_data) {
355                    let path = res.resource_path();
356                    path.skip(path.len() as u16);
357                    true
358                } else {
359                    false
360                }
361            }
362            PatternType::Prefix(ref s) => {
363                let len = {
364                    let rpath = res.resource_path().path();
365                    if s == rpath {
366                        s.len()
367                    } else if rpath.starts_with(s)
368                        && (s.ends_with('/') || rpath.split_at(s.len()).1.starts_with('/'))
369                    {
370                        if s.ends_with('/') {
371                            s.len() - 1
372                        } else {
373                            s.len()
374                        }
375                    } else {
376                        return false;
377                    }
378                };
379                if !check(res, user_data) {
380                    return false;
381                }
382                let path = res.resource_path();
383                path.skip(min(path.path().len(), len) as u16);
384                true
385            }
386            PatternType::Dynamic(ref re, ref names, len) => {
387                let mut idx = 0;
388                let mut pos = 0;
389                let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
390                    [PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
391
392                if let Some(captures) = re.captures(res.resource_path().path()) {
393                    for (no, name) in names.iter().enumerate() {
394                        if let Some(m) = captures.name(&name) {
395                            idx += 1;
396                            pos = m.end();
397                            segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
398                        } else {
399                            log::error!(
400                                "Dynamic path match but not all segments found: {}",
401                                name
402                            );
403                            return false;
404                        }
405                    }
406                } else {
407                    return false;
408                }
409
410                if !check(res, user_data) {
411                    return false;
412                }
413
414                let path = res.resource_path();
415                for idx in 0..idx {
416                    path.add(names[idx].clone(), segments[idx]);
417                }
418                path.skip((pos + len) as u16);
419                true
420            }
421            PatternType::DynamicSet(ref re, ref params) => {
422                let path = res.resource_path().path();
423                if let Some(idx) = re.matches(path).into_iter().next() {
424                    let (ref pattern, ref names, len) = params[idx];
425                    let mut idx = 0;
426                    let mut pos = 0;
427                    let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] =
428                        [PathItem::Static(""); MAX_DYNAMIC_SEGMENTS];
429
430                    if let Some(captures) = pattern.captures(path) {
431                        for (no, name) in names.iter().enumerate() {
432                            if let Some(m) = captures.name(&name) {
433                                idx += 1;
434                                pos = m.end();
435                                segments[no] =
436                                    PathItem::Segment(m.start() as u16, m.end() as u16);
437                            } else {
438                                log::error!(
439                                    "Dynamic path match but not all segments found: {}",
440                                    name
441                                );
442                                return false;
443                            }
444                        }
445                    } else {
446                        return false;
447                    }
448
449                    if !check(res, user_data) {
450                        return false;
451                    }
452
453                    let path = res.resource_path();
454                    for idx in 0..idx {
455                        path.add(names[idx].clone(), segments[idx]);
456                    }
457                    path.skip((pos + len) as u16);
458                    true
459                } else {
460                    false
461                }
462            }
463        }
464    }
465
466    /// Build resource path from elements. Returns `true` on success.
467    pub fn resource_path<U, I>(&self, path: &mut String, elements: &mut U) -> bool
468    where
469        U: Iterator<Item = I>,
470        I: AsRef<str>,
471    {
472        match self.tp {
473            PatternType::Prefix(ref p) => path.push_str(p),
474            PatternType::Static(ref p) => path.push_str(p),
475            PatternType::Dynamic(..) => {
476                for el in &self.elements {
477                    match *el {
478                        PatternElement::Str(ref s) => path.push_str(s),
479                        PatternElement::Var(_) => {
480                            if let Some(val) = elements.next() {
481                                path.push_str(val.as_ref())
482                            } else {
483                                return false;
484                            }
485                        }
486                    }
487                }
488            }
489            PatternType::DynamicSet(..) => {
490                return false;
491            }
492        }
493        true
494    }
495
496    /// Build resource path from elements. Returns `true` on success.
497    pub fn resource_path_named<K, V, S>(
498        &self,
499        path: &mut String,
500        elements: &HashMap<K, V, S>,
501    ) -> bool
502    where
503        K: std::borrow::Borrow<str> + Eq + Hash,
504        V: AsRef<str>,
505        S: std::hash::BuildHasher,
506    {
507        match self.tp {
508            PatternType::Prefix(ref p) => path.push_str(p),
509            PatternType::Static(ref p) => path.push_str(p),
510            PatternType::Dynamic(..) => {
511                for el in &self.elements {
512                    match *el {
513                        PatternElement::Str(ref s) => path.push_str(s),
514                        PatternElement::Var(ref name) => {
515                            if let Some(val) = elements.get(name) {
516                                path.push_str(val.as_ref())
517                            } else {
518                                return false;
519                            }
520                        }
521                    }
522                }
523            }
524            PatternType::DynamicSet(..) => {
525                return false;
526            }
527        }
528        true
529    }
530
531    fn parse_param(pattern: &str) -> (PatternElement, String, &str, bool) {
532        const DEFAULT_PATTERN: &str = "[^/]+";
533        const DEFAULT_PATTERN_TAIL: &str = ".*";
534        let mut params_nesting = 0usize;
535        let close_idx = pattern
536            .find(|c| match c {
537                '{' => {
538                    params_nesting += 1;
539                    false
540                }
541                '}' => {
542                    params_nesting -= 1;
543                    params_nesting == 0
544                }
545                _ => false,
546            })
547            .expect("malformed dynamic segment");
548        let (mut param, mut rem) = pattern.split_at(close_idx + 1);
549        param = &param[1..param.len() - 1]; // Remove outer brackets
550        let tail = rem == "*";
551
552        let (name, pattern) = match param.find(':') {
553            Some(idx) => {
554                if tail {
555                    panic!("Custom regex is not supported for remainder match");
556                }
557                let (name, pattern) = param.split_at(idx);
558                (name, &pattern[1..])
559            }
560            None => (
561                param,
562                if tail {
563                    rem = &rem[1..];
564                    DEFAULT_PATTERN_TAIL
565                } else {
566                    DEFAULT_PATTERN
567                },
568            ),
569        };
570        (
571            PatternElement::Var(name.to_string()),
572            format!(r"(?P<{}>{})", &name, &pattern),
573            rem,
574            tail,
575        )
576    }
577
578    fn parse(
579        mut pattern: &str,
580        mut for_prefix: bool,
581    ) -> (String, Vec<PatternElement>, bool, usize) {
582        if pattern.find('{').is_none() {
583            return if pattern.ends_with('*') {
584                let path = &pattern[..pattern.len() - 1];
585                let re = String::from("^") + path + "(.*)";
586                (re, vec![PatternElement::Str(String::from(path))], true, 0)
587            } else {
588                (
589                    String::from(pattern),
590                    vec![PatternElement::Str(String::from(pattern))],
591                    false,
592                    pattern.chars().count(),
593                )
594            };
595        }
596
597        let mut elems = Vec::new();
598        let mut re = String::from("^");
599        let mut dyn_elems = 0;
600
601        while let Some(idx) = pattern.find('{') {
602            let (prefix, rem) = pattern.split_at(idx);
603            elems.push(PatternElement::Str(String::from(prefix)));
604            re.push_str(&escape(prefix));
605            let (param_pattern, re_part, rem, tail) = Self::parse_param(rem);
606            if tail {
607                for_prefix = true;
608            }
609
610            elems.push(param_pattern);
611            re.push_str(&re_part);
612            pattern = rem;
613            dyn_elems += 1;
614        }
615
616        elems.push(PatternElement::Str(String::from(pattern)));
617        re.push_str(&escape(pattern));
618
619        if dyn_elems > MAX_DYNAMIC_SEGMENTS {
620            panic!(
621                "Only {} dynanic segments are allowed, provided: {}",
622                MAX_DYNAMIC_SEGMENTS, dyn_elems
623            );
624        }
625
626        if !for_prefix {
627            re.push_str("$");
628        }
629        (re, elems, true, pattern.chars().count())
630    }
631}
632
633impl Eq for ResourceDef {}
634
635impl PartialEq for ResourceDef {
636    fn eq(&self, other: &ResourceDef) -> bool {
637        self.pattern == other.pattern
638    }
639}
640
641impl Hash for ResourceDef {
642    fn hash<H: Hasher>(&self, state: &mut H) {
643        self.pattern.hash(state);
644    }
645}
646
647impl<'a> From<&'a str> for ResourceDef {
648    fn from(path: &'a str) -> ResourceDef {
649        ResourceDef::new(path)
650    }
651}
652
653impl From<String> for ResourceDef {
654    fn from(path: String) -> ResourceDef {
655        ResourceDef::new(path)
656    }
657}
658
659pub(crate) fn insert_slash(path: &str) -> String {
660    let mut path = path.to_owned();
661    if !path.is_empty() && !path.starts_with('/') {
662        path.insert(0, '/');
663    };
664    path
665}
666
667#[cfg(test)]
668mod tests {
669    use super::*;
670    use http::Uri;
671    use std::convert::TryFrom;
672
673    #[test]
674    fn test_parse_static() {
675        let re = ResourceDef::new("/");
676        assert!(re.is_match("/"));
677        assert!(!re.is_match("/a"));
678
679        let re = ResourceDef::new("/name");
680        assert!(re.is_match("/name"));
681        assert!(!re.is_match("/name1"));
682        assert!(!re.is_match("/name/"));
683        assert!(!re.is_match("/name~"));
684
685        assert_eq!(re.is_prefix_match("/name"), Some(5));
686        assert_eq!(re.is_prefix_match("/name1"), None);
687        assert_eq!(re.is_prefix_match("/name/"), None);
688        assert_eq!(re.is_prefix_match("/name~"), None);
689
690        let re = ResourceDef::new("/name/");
691        assert!(re.is_match("/name/"));
692        assert!(!re.is_match("/name"));
693        assert!(!re.is_match("/name/gs"));
694
695        let re = ResourceDef::new("/user/profile");
696        assert!(re.is_match("/user/profile"));
697        assert!(!re.is_match("/user/profile/profile"));
698    }
699
700    #[test]
701    fn test_parse_param() {
702        let re = ResourceDef::new("/user/{id}");
703        assert!(re.is_match("/user/profile"));
704        assert!(re.is_match("/user/2345"));
705        assert!(!re.is_match("/user/2345/"));
706        assert!(!re.is_match("/user/2345/sdg"));
707
708        let mut path = Path::new("/user/profile");
709        assert!(re.match_path(&mut path));
710        assert_eq!(path.get("id").unwrap(), "profile");
711
712        let mut path = Path::new("/user/1245125");
713        assert!(re.match_path(&mut path));
714        assert_eq!(path.get("id").unwrap(), "1245125");
715
716        let re = ResourceDef::new("/v{version}/resource/{id}");
717        assert!(re.is_match("/v1/resource/320120"));
718        assert!(!re.is_match("/v/resource/1"));
719        assert!(!re.is_match("/resource"));
720
721        let mut path = Path::new("/v151/resource/adahg32");
722        assert!(re.match_path(&mut path));
723        assert_eq!(path.get("version").unwrap(), "151");
724        assert_eq!(path.get("id").unwrap(), "adahg32");
725
726        let re = ResourceDef::new("/{id:[[:digit:]]{6}}");
727        assert!(re.is_match("/012345"));
728        assert!(!re.is_match("/012"));
729        assert!(!re.is_match("/01234567"));
730        assert!(!re.is_match("/XXXXXX"));
731
732        let mut path = Path::new("/012345");
733        assert!(re.match_path(&mut path));
734        assert_eq!(path.get("id").unwrap(), "012345");
735    }
736
737    #[test]
738    fn test_dynamic_set() {
739        let re = ResourceDef::new(vec![
740            "/user/{id}",
741            "/v{version}/resource/{id}",
742            "/{id:[[:digit:]]{6}}",
743        ]);
744        assert!(re.is_match("/user/profile"));
745        assert!(re.is_match("/user/2345"));
746        assert!(!re.is_match("/user/2345/"));
747        assert!(!re.is_match("/user/2345/sdg"));
748
749        let mut path = Path::new("/user/profile");
750        assert!(re.match_path(&mut path));
751        assert_eq!(path.get("id").unwrap(), "profile");
752
753        let mut path = Path::new("/user/1245125");
754        assert!(re.match_path(&mut path));
755        assert_eq!(path.get("id").unwrap(), "1245125");
756
757        assert!(re.is_match("/v1/resource/320120"));
758        assert!(!re.is_match("/v/resource/1"));
759        assert!(!re.is_match("/resource"));
760
761        let mut path = Path::new("/v151/resource/adahg32");
762        assert!(re.match_path(&mut path));
763        assert_eq!(path.get("version").unwrap(), "151");
764        assert_eq!(path.get("id").unwrap(), "adahg32");
765
766        assert!(re.is_match("/012345"));
767        assert!(!re.is_match("/012"));
768        assert!(!re.is_match("/01234567"));
769        assert!(!re.is_match("/XXXXXX"));
770
771        let mut path = Path::new("/012345");
772        assert!(re.match_path(&mut path));
773        assert_eq!(path.get("id").unwrap(), "012345");
774
775        let re = ResourceDef::new([
776            "/user/{id}",
777            "/v{version}/resource/{id}",
778            "/{id:[[:digit:]]{6}}",
779        ]);
780        assert!(re.is_match("/user/profile"));
781        assert!(re.is_match("/user/2345"));
782        assert!(!re.is_match("/user/2345/"));
783        assert!(!re.is_match("/user/2345/sdg"));
784
785        let re = ResourceDef::new([
786            "/user/{id}".to_string(),
787            "/v{version}/resource/{id}".to_string(),
788            "/{id:[[:digit:]]{6}}".to_string(),
789        ]);
790        assert!(re.is_match("/user/profile"));
791        assert!(re.is_match("/user/2345"));
792        assert!(!re.is_match("/user/2345/"));
793        assert!(!re.is_match("/user/2345/sdg"));
794    }
795
796    #[test]
797    fn test_parse_tail() {
798        let re = ResourceDef::new("/user/-{id}*");
799
800        let mut path = Path::new("/user/-profile");
801        assert!(re.match_path(&mut path));
802        assert_eq!(path.get("id").unwrap(), "profile");
803
804        let mut path = Path::new("/user/-2345");
805        assert!(re.match_path(&mut path));
806        assert_eq!(path.get("id").unwrap(), "2345");
807
808        let mut path = Path::new("/user/-2345/");
809        assert!(re.match_path(&mut path));
810        assert_eq!(path.get("id").unwrap(), "2345/");
811
812        let mut path = Path::new("/user/-2345/sdg");
813        assert!(re.match_path(&mut path));
814        assert_eq!(path.get("id").unwrap(), "2345/sdg");
815    }
816
817    #[test]
818    fn test_static_tail() {
819        let re = ResourceDef::new("/user*");
820        assert!(re.is_match("/user/profile"));
821        assert!(re.is_match("/user/2345"));
822        assert!(re.is_match("/user/2345/"));
823        assert!(re.is_match("/user/2345/sdg"));
824
825        let re = ResourceDef::new("/user/*");
826        assert!(re.is_match("/user/profile"));
827        assert!(re.is_match("/user/2345"));
828        assert!(re.is_match("/user/2345/"));
829        assert!(re.is_match("/user/2345/sdg"));
830    }
831
832    #[test]
833    fn test_parse_urlencoded_param() {
834        let re = ResourceDef::new("/user/{id}/test");
835
836        let mut path = Path::new("/user/2345/test");
837        assert!(re.match_path(&mut path));
838        assert_eq!(path.get("id").unwrap(), "2345");
839
840        let mut path = Path::new("/user/qwe%25/test");
841        assert!(re.match_path(&mut path));
842        assert_eq!(path.get("id").unwrap(), "qwe%25");
843
844        let uri = Uri::try_from("/user/qwe%25/test").unwrap();
845        let mut path = Path::new(uri);
846        assert!(re.match_path(&mut path));
847        assert_eq!(path.get("id").unwrap(), "qwe%25");
848    }
849
850    #[test]
851    fn test_resource_prefix() {
852        let re = ResourceDef::prefix("/name");
853        assert!(re.is_match("/name"));
854        assert!(re.is_match("/name/"));
855        assert!(re.is_match("/name/test/test"));
856        assert!(re.is_match("/name1"));
857        assert!(re.is_match("/name~"));
858
859        assert_eq!(re.is_prefix_match("/name"), Some(5));
860        assert_eq!(re.is_prefix_match("/name/"), Some(5));
861        assert_eq!(re.is_prefix_match("/name/test/test"), Some(5));
862        assert_eq!(re.is_prefix_match("/name1"), None);
863        assert_eq!(re.is_prefix_match("/name~"), None);
864
865        let re = ResourceDef::prefix("/name/");
866        assert!(re.is_match("/name/"));
867        assert!(re.is_match("/name/gs"));
868        assert!(!re.is_match("/name"));
869
870        let re = ResourceDef::root_prefix("name/");
871        assert!(re.is_match("/name/"));
872        assert!(re.is_match("/name/gs"));
873        assert!(!re.is_match("/name"));
874    }
875
876    #[test]
877    fn test_reousrce_prefix_dynamic() {
878        let re = ResourceDef::prefix("/{name}/");
879        assert!(re.is_match("/name/"));
880        assert!(re.is_match("/name/gs"));
881        assert!(!re.is_match("/name"));
882
883        assert_eq!(re.is_prefix_match("/name/"), Some(6));
884        assert_eq!(re.is_prefix_match("/name/gs"), Some(6));
885        assert_eq!(re.is_prefix_match("/name"), None);
886
887        let mut path = Path::new("/test2/");
888        assert!(re.match_path(&mut path));
889        assert_eq!(&path["name"], "test2");
890        assert_eq!(&path[0], "test2");
891
892        let mut path = Path::new("/test2/subpath1/subpath2/index.html");
893        assert!(re.match_path(&mut path));
894        assert_eq!(&path["name"], "test2");
895        assert_eq!(&path[0], "test2");
896    }
897
898    #[test]
899    fn test_resource_path() {
900        let mut s = String::new();
901        let resource = ResourceDef::new("/user/{item1}/test");
902        assert!(resource.resource_path(&mut s, &mut (&["user1"]).into_iter()));
903        assert_eq!(s, "/user/user1/test");
904
905        let mut s = String::new();
906        let resource = ResourceDef::new("/user/{item1}/{item2}/test");
907        assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).into_iter()));
908        assert_eq!(s, "/user/item/item2/test");
909
910        let mut s = String::new();
911        let resource = ResourceDef::new("/user/{item1}/{item2}");
912        assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).into_iter()));
913        assert_eq!(s, "/user/item/item2");
914
915        let mut s = String::new();
916        let resource = ResourceDef::new("/user/{item1}/{item2}/");
917        assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).into_iter()));
918        assert_eq!(s, "/user/item/item2/");
919
920        let mut s = String::new();
921        assert!(!resource.resource_path(&mut s, &mut (&["item"]).into_iter()));
922
923        let mut s = String::new();
924        assert!(resource.resource_path(&mut s, &mut (&["item", "item2"]).into_iter()));
925        assert_eq!(s, "/user/item/item2/");
926        assert!(!resource.resource_path(&mut s, &mut (&["item"]).into_iter()));
927
928        let mut s = String::new();
929        assert!(resource.resource_path(&mut s, &mut vec!["item", "item2"].into_iter()));
930        assert_eq!(s, "/user/item/item2/");
931
932        let mut map = HashMap::new();
933        map.insert("item1", "item");
934
935        let mut s = String::new();
936        assert!(!resource.resource_path_named(&mut s, &map));
937
938        let mut s = String::new();
939        map.insert("item2", "item2");
940        assert!(resource.resource_path_named(&mut s, &map));
941        assert_eq!(s, "/user/item/item2/");
942    }
943}