1use std::{
2    borrow::{Borrow, Cow},
3    collections::HashMap,
4    hash::{BuildHasher, Hash, Hasher},
5    mem,
6};
7
8use tracing::error;
9
10use crate::{
11    path::PathItem,
12    regex_set::{escape, Regex, RegexSet},
13    IntoPatterns, Patterns, Resource, ResourcePath,
14};
15
16const MAX_DYNAMIC_SEGMENTS: usize = 16;
17
18const REGEX_FLAGS: &str = "(?s-m)";
22
23#[derive(Clone, Debug)]
212pub struct ResourceDef {
213    id: u16,
214
215    name: Option<String>,
217
218    patterns: Patterns,
220
221    is_prefix: bool,
222
223    pat_type: PatternType,
225
226    segments: Vec<PatternSegment>,
228}
229
230#[derive(Debug, Clone, PartialEq)]
231enum PatternSegment {
232    Const(String),
234
235    Var(String),
237}
238
239#[derive(Debug, Clone)]
240#[allow(clippy::large_enum_variant)]
241enum PatternType {
242    Static(String),
244
245    Dynamic(Regex, Vec<&'static str>),
247
248    DynamicSet(RegexSet, Vec<(Regex, Vec<&'static str>)>),
250}
251
252impl ResourceDef {
253    pub fn new<T: IntoPatterns>(paths: T) -> Self {
277        Self::construct(paths, false)
278    }
279
280    pub fn prefix<T: IntoPatterns>(paths: T) -> Self {
304        ResourceDef::construct(paths, true)
305    }
306
307    pub fn root_prefix(path: &str) -> Self {
328        ResourceDef::prefix(insert_slash(path).into_owned())
329    }
330
331    pub fn id(&self) -> u16 {
345        self.id
346    }
347
348    pub fn set_id(&mut self, id: u16) {
358        self.id = id;
359    }
360
361    pub fn name(&self) -> Option<&str> {
372        self.name.as_deref()
373    }
374
375    pub fn set_name(&mut self, name: impl Into<String>) {
388        let name = name.into();
389
390        assert!(!name.is_empty(), "resource name should not be empty");
391
392        self.name = Some(name)
393    }
394
395    pub fn is_prefix(&self) -> bool {
404        self.is_prefix
405    }
406
407    pub fn pattern(&self) -> Option<&str> {
422        match &self.patterns {
423            Patterns::Single(pattern) => Some(pattern.as_str()),
424            Patterns::List(patterns) => patterns.first().map(AsRef::as_ref),
425        }
426    }
427
428    pub fn pattern_iter(&self) -> impl Iterator<Item = &str> {
444        struct PatternIter<'a> {
445            patterns: &'a Patterns,
446            list_idx: usize,
447            done: bool,
448        }
449
450        impl<'a> Iterator for PatternIter<'a> {
451            type Item = &'a str;
452
453            fn next(&mut self) -> Option<Self::Item> {
454                match &self.patterns {
455                    Patterns::Single(pattern) => {
456                        if self.done {
457                            return None;
458                        }
459
460                        self.done = true;
461                        Some(pattern.as_str())
462                    }
463                    Patterns::List(patterns) if patterns.is_empty() => None,
464                    Patterns::List(patterns) => match patterns.get(self.list_idx) {
465                        Some(pattern) => {
466                            self.list_idx += 1;
467                            Some(pattern.as_str())
468                        }
469                        None => {
470                            self.done = true;
472                            None
473                        }
474                    },
475                }
476            }
477
478            fn size_hint(&self) -> (usize, Option<usize>) {
479                match &self.patterns {
480                    Patterns::Single(_) => (1, Some(1)),
481                    Patterns::List(patterns) => (patterns.len(), Some(patterns.len())),
482                }
483            }
484        }
485
486        PatternIter {
487            patterns: &self.patterns,
488            list_idx: 0,
489            done: false,
490        }
491    }
492
493    pub fn join(&self, other: &ResourceDef) -> ResourceDef {
504        let patterns = self
505            .pattern_iter()
506            .flat_map(move |this| other.pattern_iter().map(move |other| (this, other)))
507            .map(|(this, other)| {
508                let mut pattern = String::with_capacity(this.len() + other.len());
509                pattern.push_str(this);
510                pattern.push_str(other);
511                pattern
512            })
513            .collect::<Vec<_>>();
514
515        match patterns.len() {
516            1 => ResourceDef::construct(&patterns[0], other.is_prefix()),
517            _ => ResourceDef::construct(patterns, other.is_prefix()),
518        }
519    }
520
521    #[inline]
555    pub fn is_match(&self, path: &str) -> bool {
556        match &self.pat_type {
561            PatternType::Static(pattern) => self.static_match(pattern, path).is_some(),
562            PatternType::Dynamic(re, _) => re.is_match(path),
563            PatternType::DynamicSet(re, _) => re.is_match(path),
564        }
565    }
566
567    pub fn find_match(&self, path: &str) -> Option<usize> {
603        match &self.pat_type {
604            PatternType::Static(pattern) => self.static_match(pattern, path),
605
606            PatternType::Dynamic(re, _) => Some(re.captures(path)?[1].len()),
607
608            PatternType::DynamicSet(re, params) => {
609                let idx = re.first_match_idx(path)?;
610                let (ref pattern, _) = params[idx];
611                Some(pattern.captures(path)?[1].len())
612            }
613        }
614    }
615
616    pub fn capture_match_info<R: Resource>(&self, resource: &mut R) -> bool {
637        self.capture_match_info_fn(resource, |_| true)
638    }
639
640    pub fn capture_match_info_fn<R, F>(&self, resource: &mut R, check_fn: F) -> bool
678    where
679        R: Resource,
680        F: FnOnce(&R) -> bool,
681    {
682        let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
683        let path = resource.resource_path();
684        let path_str = path.unprocessed();
685
686        let (matched_len, matched_vars) = match &self.pat_type {
687            PatternType::Static(pattern) => match self.static_match(pattern, path_str) {
688                Some(len) => (len, None),
689                None => return false,
690            },
691
692            PatternType::Dynamic(re, names) => {
693                let captures = match re.captures(path.unprocessed()) {
694                    Some(captures) => captures,
695                    _ => return false,
696                };
697
698                for (no, name) in names.iter().enumerate() {
699                    if let Some(m) = captures.name(name) {
700                        segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
701                    } else {
702                        error!("Dynamic path match but not all segments found: {}", name);
703                        return false;
704                    }
705                }
706
707                (captures[1].len(), Some(names))
708            }
709
710            PatternType::DynamicSet(re, params) => {
711                let path = path.unprocessed();
712                let (pattern, names) = match re.first_match_idx(path) {
713                    Some(idx) => ¶ms[idx],
714                    _ => return false,
715                };
716
717                let captures = match pattern.captures(path.path()) {
718                    Some(captures) => captures,
719                    _ => return false,
720                };
721
722                for (no, name) in names.iter().enumerate() {
723                    if let Some(m) = captures.name(name) {
724                        segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
725                    } else {
726                        error!("Dynamic path match but not all segments found: {}", name);
727                        return false;
728                    }
729                }
730
731                (captures[1].len(), Some(names))
732            }
733        };
734
735        if !check_fn(resource) {
736            return false;
737        }
738
739        let path = resource.resource_path();
741
742        if let Some(vars) = matched_vars {
743            for i in 0..vars.len() {
744                path.add(vars[i], mem::take(&mut segments[i]));
745            }
746        }
747
748        path.skip(matched_len as u16);
749
750        true
751    }
752
753    fn build_resource_path<F, I>(&self, path: &mut String, mut vars: F) -> bool
755    where
756        F: FnMut(&str) -> Option<I>,
757        I: AsRef<str>,
758    {
759        for segment in &self.segments {
760            match segment {
761                PatternSegment::Const(val) => path.push_str(val),
762                PatternSegment::Var(name) => match vars(name) {
763                    Some(val) => path.push_str(val.as_ref()),
764                    _ => return false,
765                },
766            }
767        }
768
769        true
770    }
771
772    pub fn resource_path_from_iter<I>(&self, path: &mut String, values: I) -> bool
789    where
790        I: IntoIterator,
791        I::Item: AsRef<str>,
792    {
793        let mut iter = values.into_iter();
794        self.build_resource_path(path, |_| iter.next())
795    }
796
797    pub fn resource_path_from_map<K, V, S>(
819        &self,
820        path: &mut String,
821        values: &HashMap<K, V, S>,
822    ) -> bool
823    where
824        K: Borrow<str> + Eq + Hash,
825        V: AsRef<str>,
826        S: BuildHasher,
827    {
828        self.build_resource_path(path, |name| values.get(name))
829    }
830
831    fn static_match(&self, pattern: &str, path: &str) -> Option<usize> {
833        let rem = path.strip_prefix(pattern)?;
834
835        match self.is_prefix {
836            false if rem.is_empty() => Some(pattern.len()),
838
839            true if rem.is_empty() || rem.starts_with('/') => Some(pattern.len()),
841
842            _ => None,
844        }
845    }
846
847    fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
848        let patterns = paths.patterns();
849
850        let (pat_type, segments) = match &patterns {
851            Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false),
852
853            Patterns::List(patterns) if patterns.is_empty() => (
856                PatternType::DynamicSet(RegexSet::empty(), Vec::new()),
857                Vec::new(),
858            ),
859
860            Patterns::List(patterns) => {
861                let mut re_set = Vec::with_capacity(patterns.len());
862                let mut pattern_data = Vec::new();
863                let mut segments = None;
864
865                for pattern in patterns {
866                    match ResourceDef::parse(pattern, is_prefix, true) {
867                        (PatternType::Dynamic(re, names), segs) => {
868                            re_set.push(re.as_str().to_owned());
869                            pattern_data.push((re, names));
870                            segments.get_or_insert(segs);
871                        }
872                        _ => unreachable!(),
873                    }
874                }
875
876                let pattern_re_set = RegexSet::new(re_set);
877                let segments = segments.unwrap_or_default();
878
879                (
880                    PatternType::DynamicSet(pattern_re_set, pattern_data),
881                    segments,
882                )
883            }
884        };
885
886        ResourceDef {
887            id: 0,
888            name: None,
889            patterns,
890            is_prefix,
891            pat_type,
892            segments,
893        }
894    }
895
896    fn parse_param(pattern: &str) -> (PatternSegment, String, &str, bool) {
907        const DEFAULT_PATTERN: &str = "[^/]+";
908        const DEFAULT_PATTERN_TAIL: &str = ".*";
909
910        let mut params_nesting = 0usize;
911        let close_idx = pattern
912            .find(|c| match c {
913                '{' => {
914                    params_nesting += 1;
915                    false
916                }
917                '}' => {
918                    params_nesting -= 1;
919                    params_nesting == 0
920                }
921                _ => false,
922            })
923            .unwrap_or_else(|| {
924                panic!(
925                    r#"pattern "{}" contains malformed dynamic segment"#,
926                    pattern
927                )
928            });
929
930        let (mut param, mut unprocessed) = pattern.split_at(close_idx + 1);
931
932        param = ¶m[1..param.len() - 1];
934
935        let tail = unprocessed == "*";
936
937        let (name, pattern) = match param.find(':') {
938            Some(idx) => {
939                assert!(!tail, "custom regex is not supported for tail match");
940
941                let (name, pattern) = param.split_at(idx);
942                (name, &pattern[1..])
943            }
944            None => (
945                param,
946                if tail {
947                    unprocessed = &unprocessed[1..];
948                    DEFAULT_PATTERN_TAIL
949                } else {
950                    DEFAULT_PATTERN
951                },
952            ),
953        };
954
955        let segment = PatternSegment::Var(name.to_string());
956        let regex = format!(r"(?P<{}>{})", &name, &pattern);
957
958        (segment, regex, unprocessed, tail)
959    }
960
961    fn parse(
972        pattern: &str,
973        is_prefix: bool,
974        force_dynamic: bool,
975    ) -> (PatternType, Vec<PatternSegment>) {
976        if !force_dynamic && pattern.find('{').is_none() && !pattern.ends_with('*') {
977            return (
979                PatternType::Static(pattern.to_owned()),
980                vec![PatternSegment::Const(pattern.to_owned())],
981            );
982        }
983
984        let mut unprocessed = pattern;
985        let mut segments = Vec::new();
986        let mut re = format!("{}^", REGEX_FLAGS);
987        let mut dyn_segment_count = 0;
988        let mut has_tail_segment = false;
989
990        while let Some(idx) = unprocessed.find('{') {
991            let (prefix, rem) = unprocessed.split_at(idx);
992
993            segments.push(PatternSegment::Const(prefix.to_owned()));
994            re.push_str(&escape(prefix));
995
996            let (param_pattern, re_part, rem, tail) = Self::parse_param(rem);
997
998            if tail {
999                has_tail_segment = true;
1000            }
1001
1002            segments.push(param_pattern);
1003            re.push_str(&re_part);
1004
1005            unprocessed = rem;
1006            dyn_segment_count += 1;
1007        }
1008
1009        if is_prefix && has_tail_segment {
1010            #[cfg(not(test))]
1013            tracing::warn!(
1014                "Prefix resources should not have tail segments. \
1015                Use `ResourceDef::new` constructor. \
1016                This may become a panic in the future."
1017            );
1018
1019            #[cfg(test)]
1021            panic!("prefix resource definitions should not have tail segments");
1022        }
1023
1024        if unprocessed.ends_with('*') {
1025            #[cfg(not(test))]
1028            tracing::warn!(
1029                "Tail segments must have names. \
1030                Consider `.../{{tail}}*`. \
1031                This may become a panic in the future."
1032            );
1033
1034            #[cfg(test)]
1036            panic!("tail segments must have names");
1037        } else if !has_tail_segment && !unprocessed.is_empty() {
1038            segments.push(PatternSegment::Const(unprocessed.to_owned()));
1041            re.push_str(&escape(unprocessed));
1042        }
1043
1044        assert!(
1045            dyn_segment_count <= MAX_DYNAMIC_SEGMENTS,
1046            "Only {} dynamic segments are allowed, provided: {}",
1047            MAX_DYNAMIC_SEGMENTS,
1048            dyn_segment_count
1049        );
1050
1051        let mut re = format!("({})", re);
1053
1054        if !has_tail_segment {
1056            if is_prefix {
1057                re.push_str(r"(/|$)");
1058            } else {
1059                re.push('$');
1060            }
1061        }
1062
1063        let re = match Regex::new(&re) {
1064            Ok(re) => re,
1065            Err(err) => panic!("Wrong path pattern: \"{}\" {}", pattern, err),
1066        };
1067
1068        let names = re
1073            .capture_names()
1074            .filter_map(|name| name.map(|name| Box::leak(Box::new(name.to_owned())).as_str()))
1075            .collect();
1076
1077        (PatternType::Dynamic(re, names), segments)
1078    }
1079}
1080
1081impl Eq for ResourceDef {}
1082
1083impl PartialEq for ResourceDef {
1084    fn eq(&self, other: &ResourceDef) -> bool {
1085        self.patterns == other.patterns && self.is_prefix == other.is_prefix
1086    }
1087}
1088
1089impl Hash for ResourceDef {
1090    fn hash<H: Hasher>(&self, state: &mut H) {
1091        self.patterns.hash(state);
1092    }
1093}
1094
1095impl<'a> From<&'a str> for ResourceDef {
1096    fn from(path: &'a str) -> ResourceDef {
1097        ResourceDef::new(path)
1098    }
1099}
1100
1101impl From<String> for ResourceDef {
1102    fn from(path: String) -> ResourceDef {
1103        ResourceDef::new(path)
1104    }
1105}
1106
1107pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
1108    if !path.is_empty() && !path.starts_with('/') {
1109        let mut new_path = String::with_capacity(path.len() + 1);
1110        new_path.push('/');
1111        new_path.push_str(path);
1112        Cow::Owned(new_path)
1113    } else {
1114        Cow::Borrowed(path)
1115    }
1116}
1117
1118#[cfg(test)]
1119mod tests {
1120    use super::*;
1121    use crate::Path;
1122
1123    #[test]
1124    fn equivalence() {
1125        assert_eq!(
1126            ResourceDef::root_prefix("/root"),
1127            ResourceDef::prefix("/root")
1128        );
1129        assert_eq!(
1130            ResourceDef::root_prefix("root"),
1131            ResourceDef::prefix("/root")
1132        );
1133        assert_eq!(
1134            ResourceDef::root_prefix("/{id}"),
1135            ResourceDef::prefix("/{id}")
1136        );
1137        assert_eq!(
1138            ResourceDef::root_prefix("{id}"),
1139            ResourceDef::prefix("/{id}")
1140        );
1141
1142        assert_eq!(ResourceDef::new("/"), ResourceDef::new(["/"]));
1143        assert_eq!(ResourceDef::new("/"), ResourceDef::new(vec!["/"]));
1144
1145        assert_ne!(ResourceDef::new(""), ResourceDef::prefix(""));
1146        assert_ne!(ResourceDef::new("/"), ResourceDef::prefix("/"));
1147        assert_ne!(ResourceDef::new("/{id}"), ResourceDef::prefix("/{id}"));
1148    }
1149
1150    #[test]
1151    fn parse_static() {
1152        let re = ResourceDef::new("");
1153
1154        assert!(!re.is_prefix());
1155
1156        assert!(re.is_match(""));
1157        assert!(!re.is_match("/"));
1158        assert_eq!(re.find_match(""), Some(0));
1159        assert_eq!(re.find_match("/"), None);
1160
1161        let re = ResourceDef::new("/");
1162        assert!(re.is_match("/"));
1163        assert!(!re.is_match(""));
1164        assert!(!re.is_match("/foo"));
1165
1166        let re = ResourceDef::new("/name");
1167        assert!(re.is_match("/name"));
1168        assert!(!re.is_match("/name1"));
1169        assert!(!re.is_match("/name/"));
1170        assert!(!re.is_match("/name~"));
1171
1172        let mut path = Path::new("/name");
1173        assert!(re.capture_match_info(&mut path));
1174        assert_eq!(path.unprocessed(), "");
1175
1176        assert_eq!(re.find_match("/name"), Some(5));
1177        assert_eq!(re.find_match("/name1"), None);
1178        assert_eq!(re.find_match("/name/"), None);
1179        assert_eq!(re.find_match("/name~"), None);
1180
1181        let re = ResourceDef::new("/name/");
1182        assert!(re.is_match("/name/"));
1183        assert!(!re.is_match("/name"));
1184        assert!(!re.is_match("/name/gs"));
1185
1186        let re = ResourceDef::new("/user/profile");
1187        assert!(re.is_match("/user/profile"));
1188        assert!(!re.is_match("/user/profile/profile"));
1189
1190        let mut path = Path::new("/user/profile");
1191        assert!(re.capture_match_info(&mut path));
1192        assert_eq!(path.unprocessed(), "");
1193    }
1194
1195    #[test]
1196    fn parse_param() {
1197        let re = ResourceDef::new("/user/{id}");
1198        assert!(re.is_match("/user/profile"));
1199        assert!(re.is_match("/user/2345"));
1200        assert!(!re.is_match("/user/2345/"));
1201        assert!(!re.is_match("/user/2345/sdg"));
1202
1203        let mut path = Path::new("/user/profile");
1204        assert!(re.capture_match_info(&mut path));
1205        assert_eq!(path.get("id").unwrap(), "profile");
1206        assert_eq!(path.unprocessed(), "");
1207
1208        let mut path = Path::new("/user/1245125");
1209        assert!(re.capture_match_info(&mut path));
1210        assert_eq!(path.get("id").unwrap(), "1245125");
1211        assert_eq!(path.unprocessed(), "");
1212
1213        let re = ResourceDef::new("/v{version}/resource/{id}");
1214        assert!(re.is_match("/v1/resource/320120"));
1215        assert!(!re.is_match("/v/resource/1"));
1216        assert!(!re.is_match("/resource"));
1217
1218        let mut path = Path::new("/v151/resource/adage32");
1219        assert!(re.capture_match_info(&mut path));
1220        assert_eq!(path.get("version").unwrap(), "151");
1221        assert_eq!(path.get("id").unwrap(), "adage32");
1222        assert_eq!(path.unprocessed(), "");
1223
1224        let re = ResourceDef::new("/{id:[[:digit:]]{6}}");
1225        assert!(re.is_match("/012345"));
1226        assert!(!re.is_match("/012"));
1227        assert!(!re.is_match("/01234567"));
1228        assert!(!re.is_match("/XXXXXX"));
1229
1230        let mut path = Path::new("/012345");
1231        assert!(re.capture_match_info(&mut path));
1232        assert_eq!(path.get("id").unwrap(), "012345");
1233        assert_eq!(path.unprocessed(), "");
1234    }
1235
1236    #[allow(clippy::cognitive_complexity)]
1237    #[test]
1238    fn dynamic_set() {
1239        let re = ResourceDef::new(vec![
1240            "/user/{id}",
1241            "/v{version}/resource/{id}",
1242            "/{id:[[:digit:]]{6}}",
1243            "/static",
1244        ]);
1245        assert!(re.is_match("/user/profile"));
1246        assert!(re.is_match("/user/2345"));
1247        assert!(!re.is_match("/user/2345/"));
1248        assert!(!re.is_match("/user/2345/sdg"));
1249
1250        let mut path = Path::new("/user/profile");
1251        assert!(re.capture_match_info(&mut path));
1252        assert_eq!(path.get("id").unwrap(), "profile");
1253        assert_eq!(path.unprocessed(), "");
1254
1255        let mut path = Path::new("/user/1245125");
1256        assert!(re.capture_match_info(&mut path));
1257        assert_eq!(path.get("id").unwrap(), "1245125");
1258        assert_eq!(path.unprocessed(), "");
1259
1260        assert!(re.is_match("/v1/resource/320120"));
1261        assert!(!re.is_match("/v/resource/1"));
1262        assert!(!re.is_match("/resource"));
1263
1264        let mut path = Path::new("/v151/resource/adage32");
1265        assert!(re.capture_match_info(&mut path));
1266        assert_eq!(path.get("version").unwrap(), "151");
1267        assert_eq!(path.get("id").unwrap(), "adage32");
1268
1269        assert!(re.is_match("/012345"));
1270        assert!(!re.is_match("/012"));
1271        assert!(!re.is_match("/01234567"));
1272        assert!(!re.is_match("/XXXXXX"));
1273
1274        assert!(re.is_match("/static"));
1275        assert!(!re.is_match("/a/static"));
1276        assert!(!re.is_match("/static/a"));
1277
1278        let mut path = Path::new("/012345");
1279        assert!(re.capture_match_info(&mut path));
1280        assert_eq!(path.get("id").unwrap(), "012345");
1281
1282        let re = ResourceDef::new([
1283            "/user/{id}",
1284            "/v{version}/resource/{id}",
1285            "/{id:[[:digit:]]{6}}",
1286        ]);
1287        assert!(re.is_match("/user/profile"));
1288        assert!(re.is_match("/user/2345"));
1289        assert!(!re.is_match("/user/2345/"));
1290        assert!(!re.is_match("/user/2345/sdg"));
1291
1292        let re = ResourceDef::new([
1293            "/user/{id}".to_string(),
1294            "/v{version}/resource/{id}".to_string(),
1295            "/{id:[[:digit:]]{6}}".to_string(),
1296        ]);
1297        assert!(re.is_match("/user/profile"));
1298        assert!(re.is_match("/user/2345"));
1299        assert!(!re.is_match("/user/2345/"));
1300        assert!(!re.is_match("/user/2345/sdg"));
1301    }
1302
1303    #[test]
1304    fn dynamic_set_prefix() {
1305        let re = ResourceDef::prefix(vec!["/u/{id}", "/{id:[[:digit:]]{3}}"]);
1306
1307        assert_eq!(re.find_match("/u/abc"), Some(6));
1308        assert_eq!(re.find_match("/u/abc/123"), Some(6));
1309        assert_eq!(re.find_match("/s/user/profile"), None);
1310
1311        assert_eq!(re.find_match("/123"), Some(4));
1312        assert_eq!(re.find_match("/123/456"), Some(4));
1313        assert_eq!(re.find_match("/12345"), None);
1314
1315        let mut path = Path::new("/151/res");
1316        assert!(re.capture_match_info(&mut path));
1317        assert_eq!(path.get("id").unwrap(), "151");
1318        assert_eq!(path.unprocessed(), "/res");
1319    }
1320
1321    #[test]
1322    fn parse_tail() {
1323        let re = ResourceDef::new("/user/-{id}*");
1324
1325        let mut path = Path::new("/user/-profile");
1326        assert!(re.capture_match_info(&mut path));
1327        assert_eq!(path.get("id").unwrap(), "profile");
1328
1329        let mut path = Path::new("/user/-2345");
1330        assert!(re.capture_match_info(&mut path));
1331        assert_eq!(path.get("id").unwrap(), "2345");
1332
1333        let mut path = Path::new("/user/-2345/");
1334        assert!(re.capture_match_info(&mut path));
1335        assert_eq!(path.get("id").unwrap(), "2345/");
1336
1337        let mut path = Path::new("/user/-2345/sdg");
1338        assert!(re.capture_match_info(&mut path));
1339        assert_eq!(path.get("id").unwrap(), "2345/sdg");
1340    }
1341
1342    #[test]
1343    fn static_tail() {
1344        let re = ResourceDef::new("/user{tail}*");
1345        assert!(re.is_match("/users"));
1346        assert!(re.is_match("/user-foo"));
1347        assert!(re.is_match("/user/profile"));
1348        assert!(re.is_match("/user/2345"));
1349        assert!(re.is_match("/user/2345/"));
1350        assert!(re.is_match("/user/2345/sdg"));
1351        assert!(!re.is_match("/foo/profile"));
1352
1353        let re = ResourceDef::new("/user/{tail}*");
1354        assert!(re.is_match("/user/profile"));
1355        assert!(re.is_match("/user/2345"));
1356        assert!(re.is_match("/user/2345/"));
1357        assert!(re.is_match("/user/2345/sdg"));
1358        assert!(!re.is_match("/foo/profile"));
1359    }
1360
1361    #[test]
1362    fn dynamic_tail() {
1363        let re = ResourceDef::new("/user/{id}/{tail}*");
1364        assert!(!re.is_match("/user/2345"));
1365        let mut path = Path::new("/user/2345/sdg");
1366        assert!(re.capture_match_info(&mut path));
1367        assert_eq!(path.get("id").unwrap(), "2345");
1368        assert_eq!(path.get("tail").unwrap(), "sdg");
1369        assert_eq!(path.unprocessed(), "");
1370    }
1371
1372    #[test]
1373    fn newline_patterns_and_paths() {
1374        let re = ResourceDef::new("/user/a\nb");
1375        assert!(re.is_match("/user/a\nb"));
1376        assert!(!re.is_match("/user/a\nb/profile"));
1377
1378        let re = ResourceDef::new("/a{x}b/test/a{y}b");
1379        let mut path = Path::new("/a\nb/test/a\nb");
1380        assert!(re.capture_match_info(&mut path));
1381        assert_eq!(path.get("x").unwrap(), "\n");
1382        assert_eq!(path.get("y").unwrap(), "\n");
1383
1384        let re = ResourceDef::new("/user/{tail}*");
1385        assert!(re.is_match("/user/a\nb/"));
1386
1387        let re = ResourceDef::new("/user/{id}*");
1388        let mut path = Path::new("/user/a\nb/a\nb");
1389        assert!(re.capture_match_info(&mut path));
1390        assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
1391
1392        let re = ResourceDef::new("/user/{id:.*}");
1393        let mut path = Path::new("/user/a\nb/a\nb");
1394        assert!(re.capture_match_info(&mut path));
1395        assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
1396    }
1397
1398    #[cfg(feature = "http")]
1399    #[test]
1400    fn parse_urlencoded_param() {
1401        let re = ResourceDef::new("/user/{id}/test");
1402
1403        let mut path = Path::new("/user/2345/test");
1404        assert!(re.capture_match_info(&mut path));
1405        assert_eq!(path.get("id").unwrap(), "2345");
1406
1407        let mut path = Path::new("/user/qwe%25/test");
1408        assert!(re.capture_match_info(&mut path));
1409        assert_eq!(path.get("id").unwrap(), "qwe%25");
1410
1411        let uri = http::Uri::try_from("/user/qwe%25/test").unwrap();
1412        let mut path = Path::new(uri);
1413        assert!(re.capture_match_info(&mut path));
1414        assert_eq!(path.get("id").unwrap(), "qwe%25");
1415    }
1416
1417    #[test]
1418    fn prefix_static() {
1419        let re = ResourceDef::prefix("/name");
1420
1421        assert!(re.is_prefix());
1422
1423        assert!(re.is_match("/name"));
1424        assert!(re.is_match("/name/"));
1425        assert!(re.is_match("/name/test/test"));
1426        assert!(!re.is_match("/name1"));
1427        assert!(!re.is_match("/name~"));
1428
1429        let mut path = Path::new("/name");
1430        assert!(re.capture_match_info(&mut path));
1431        assert_eq!(path.unprocessed(), "");
1432
1433        let mut path = Path::new("/name/test");
1434        assert!(re.capture_match_info(&mut path));
1435        assert_eq!(path.unprocessed(), "/test");
1436
1437        assert_eq!(re.find_match("/name"), Some(5));
1438        assert_eq!(re.find_match("/name/"), Some(5));
1439        assert_eq!(re.find_match("/name/test/test"), Some(5));
1440        assert_eq!(re.find_match("/name1"), None);
1441        assert_eq!(re.find_match("/name~"), None);
1442
1443        let re = ResourceDef::prefix("/name/");
1444        assert!(re.is_match("/name/"));
1445        assert!(re.is_match("/name//gs"));
1446        assert!(!re.is_match("/name/gs"));
1447        assert!(!re.is_match("/name"));
1448
1449        let mut path = Path::new("/name/gs");
1450        assert!(!re.capture_match_info(&mut path));
1451
1452        let mut path = Path::new("/name//gs");
1453        assert!(re.capture_match_info(&mut path));
1454        assert_eq!(path.unprocessed(), "/gs");
1455
1456        let re = ResourceDef::root_prefix("name/");
1457        assert!(re.is_match("/name/"));
1458        assert!(re.is_match("/name//gs"));
1459        assert!(!re.is_match("/name/gs"));
1460        assert!(!re.is_match("/name"));
1461
1462        let mut path = Path::new("/name/gs");
1463        assert!(!re.capture_match_info(&mut path));
1464    }
1465
1466    #[test]
1467    fn prefix_dynamic() {
1468        let re = ResourceDef::prefix("/{name}");
1469
1470        assert!(re.is_prefix());
1471
1472        assert!(re.is_match("/name/"));
1473        assert!(re.is_match("/name/gs"));
1474        assert!(re.is_match("/name"));
1475
1476        assert_eq!(re.find_match("/name/"), Some(5));
1477        assert_eq!(re.find_match("/name/gs"), Some(5));
1478        assert_eq!(re.find_match("/name"), Some(5));
1479        assert_eq!(re.find_match(""), None);
1480
1481        let mut path = Path::new("/test2/");
1482        assert!(re.capture_match_info(&mut path));
1483        assert_eq!(&path["name"], "test2");
1484        assert_eq!(&path[0], "test2");
1485        assert_eq!(path.unprocessed(), "/");
1486
1487        let mut path = Path::new("/test2/subpath1/subpath2/index.html");
1488        assert!(re.capture_match_info(&mut path));
1489        assert_eq!(&path["name"], "test2");
1490        assert_eq!(&path[0], "test2");
1491        assert_eq!(path.unprocessed(), "/subpath1/subpath2/index.html");
1492
1493        let resource = ResourceDef::prefix("/user");
1494        assert!(resource.find_match("/foo").is_none());
1496    }
1497
1498    #[test]
1499    fn prefix_empty() {
1500        let re = ResourceDef::prefix("");
1501
1502        assert!(re.is_prefix());
1503
1504        assert!(re.is_match(""));
1505        assert!(re.is_match("/"));
1506        assert!(re.is_match("/name/test/test"));
1507    }
1508
1509    #[test]
1510    fn build_path_list() {
1511        let mut s = String::new();
1512        let resource = ResourceDef::new("/user/{item1}/test");
1513        assert!(resource.resource_path_from_iter(&mut s, &mut ["user1"].iter()));
1514        assert_eq!(s, "/user/user1/test");
1515
1516        let mut s = String::new();
1517        let resource = ResourceDef::new("/user/{item1}/{item2}/test");
1518        assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter()));
1519        assert_eq!(s, "/user/item/item2/test");
1520
1521        let mut s = String::new();
1522        let resource = ResourceDef::new("/user/{item1}/{item2}");
1523        assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter()));
1524        assert_eq!(s, "/user/item/item2");
1525
1526        let mut s = String::new();
1527        let resource = ResourceDef::new("/user/{item1}/{item2}/");
1528        assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter()));
1529        assert_eq!(s, "/user/item/item2/");
1530
1531        let mut s = String::new();
1532        assert!(!resource.resource_path_from_iter(&mut s, &mut ["item"].iter()));
1533
1534        let mut s = String::new();
1535        assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter()));
1536        assert_eq!(s, "/user/item/item2/");
1537        assert!(!resource.resource_path_from_iter(&mut s, &mut ["item"].iter()));
1538
1539        let mut s = String::new();
1540
1541        assert!(resource.resource_path_from_iter(
1542            &mut s,
1543            #[allow(clippy::useless_vec)]
1544            &mut vec!["item", "item2"].iter()
1545        ));
1546        assert_eq!(s, "/user/item/item2/");
1547    }
1548
1549    #[test]
1550    fn multi_pattern_build_path() {
1551        let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]);
1552        let mut s = String::new();
1553        assert!(resource.resource_path_from_iter(&mut s, &mut ["123"].iter()));
1554        assert_eq!(s, "/user/123");
1555    }
1556
1557    #[test]
1558    fn multi_pattern_capture_segment_values() {
1559        let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]);
1560
1561        let mut path = Path::new("/user/123");
1562        assert!(resource.capture_match_info(&mut path));
1563        assert!(path.get("id").is_some());
1564
1565        let mut path = Path::new("/profile/123");
1566        assert!(resource.capture_match_info(&mut path));
1567        assert!(path.get("id").is_some());
1568
1569        let resource = ResourceDef::new(["/user/{id}", "/profile/{uid}"]);
1570
1571        let mut path = Path::new("/user/123");
1572        assert!(resource.capture_match_info(&mut path));
1573        assert!(path.get("id").is_some());
1574        assert!(path.get("uid").is_none());
1575
1576        let mut path = Path::new("/profile/123");
1577        assert!(resource.capture_match_info(&mut path));
1578        assert!(path.get("id").is_none());
1579        assert!(path.get("uid").is_some());
1580    }
1581
1582    #[test]
1583    fn dynamic_prefix_proper_segmentation() {
1584        let resource = ResourceDef::prefix(r"/id/{id:\d{3}}");
1585
1586        assert!(resource.is_match("/id/123"));
1587        assert!(resource.is_match("/id/123/foo"));
1588        assert!(!resource.is_match("/id/1234"));
1589        assert!(!resource.is_match("/id/123a"));
1590
1591        assert_eq!(resource.find_match("/id/123"), Some(7));
1592        assert_eq!(resource.find_match("/id/123/foo"), Some(7));
1593        assert_eq!(resource.find_match("/id/1234"), None);
1594        assert_eq!(resource.find_match("/id/123a"), None);
1595    }
1596
1597    #[test]
1598    fn build_path_map() {
1599        let resource = ResourceDef::new("/user/{item1}/{item2}/");
1600
1601        let mut map = HashMap::new();
1602        map.insert("item1", "item");
1603
1604        let mut s = String::new();
1605        assert!(!resource.resource_path_from_map(&mut s, &map));
1606
1607        map.insert("item2", "item2");
1608
1609        let mut s = String::new();
1610        assert!(resource.resource_path_from_map(&mut s, &map));
1611        assert_eq!(s, "/user/item/item2/");
1612    }
1613
1614    #[test]
1615    fn build_path_tail() {
1616        let resource = ResourceDef::new("/user/{item1}*");
1617
1618        let mut s = String::new();
1619        assert!(!resource.resource_path_from_iter(&mut s, &mut [""; 0].iter()));
1620
1621        let mut s = String::new();
1622        assert!(resource.resource_path_from_iter(&mut s, &mut ["user1"].iter()));
1623        assert_eq!(s, "/user/user1");
1624
1625        let mut s = String::new();
1626        let mut map = HashMap::new();
1627        map.insert("item1", "item");
1628        assert!(resource.resource_path_from_map(&mut s, &map));
1629        assert_eq!(s, "/user/item");
1630    }
1631
1632    #[test]
1633    fn prefix_trailing_slash() {
1634        let re = ResourceDef::prefix("/abc/");
1638        assert_eq!(re.find_match("/abc/def"), None);
1639        assert_eq!(re.find_match("/abc//def"), Some(5));
1640
1641        let re = ResourceDef::prefix("/{id}/");
1642        assert_eq!(re.find_match("/abc/def"), None);
1643        assert_eq!(re.find_match("/abc//def"), Some(5));
1644    }
1645
1646    #[test]
1647    fn join() {
1648        fn seq_find_match(re1: &ResourceDef, re2: &ResourceDef, path: &str) -> Option<usize> {
1651            let len1 = re1.find_match(path)?;
1652            let len2 = re2.find_match(&path[len1..])?;
1653            Some(len1 + len2)
1654        }
1655
1656        macro_rules! join_test {
1657            ($pat1:expr, $pat2:expr => $($test:expr),+) => {{
1658                let pat1 = $pat1;
1659                let pat2 = $pat2;
1660                $({
1661                    let _path = $test;
1662                    let (re1, re2) = (ResourceDef::prefix(pat1), ResourceDef::new(pat2));
1663                    let _seq = seq_find_match(&re1, &re2, _path);
1664                    let _join = re1.join(&re2).find_match(_path);
1665                    assert_eq!(
1666                        _seq, _join,
1667                        "patterns: prefix {:?}, {:?}; mismatch on \"{}\"; seq={:?}; join={:?}",
1668                        pat1, pat2, _path, _seq, _join
1669                    );
1670                    assert!(!re1.join(&re2).is_prefix());
1671
1672                    let (re1, re2) = (ResourceDef::prefix(pat1), ResourceDef::prefix(pat2));
1673                    let _seq = seq_find_match(&re1, &re2, _path);
1674                    let _join = re1.join(&re2).find_match(_path);
1675                    assert_eq!(
1676                        _seq, _join,
1677                        "patterns: prefix {:?}, prefix {:?}; mismatch on \"{}\"; seq={:?}; join={:?}",
1678                        pat1, pat2, _path, _seq, _join
1679                    );
1680                    assert!(re1.join(&re2).is_prefix());
1681                })+
1682            }}
1683        }
1684
1685        join_test!("", "" => "", "/hello", "/");
1686        join_test!("/user", "" => "", "/user", "/user/123", "/user11", "user", "user/123");
1687        join_test!("",  "/user" => "", "/user", "foo", "/user11", "user", "user/123");
1688        join_test!("/user",  "/xx" => "", "",  "/", "/user", "/xx", "/userxx", "/user/xx");
1689
1690        join_test!(["/ver/{v}", "/v{v}"], ["/req/{req}", "/{req}"] => "/v1/abc", 
1691                   "/ver/1/abc", "/v1/req/abc", "/ver/1/req/abc", "/v1/abc/def",
1692                   "/ver1/req/abc/def", "", "/", "/v1/");
1693    }
1694
1695    #[test]
1696    fn match_methods_agree() {
1697        macro_rules! match_methods_agree {
1698            ($pat:expr => $($test:expr),+) => {{
1699                match_methods_agree!(finish $pat, ResourceDef::new($pat), $($test),+);
1700            }};
1701            (prefix $pat:expr => $($test:expr),+) => {{
1702                match_methods_agree!(finish $pat, ResourceDef::prefix($pat), $($test),+);
1703            }};
1704            (finish $pat:expr, $re:expr, $($test:expr),+) => {{
1705                let re = $re;
1706                $({
1707                    let _is = re.is_match($test);
1708                    let _find = re.find_match($test).is_some();
1709                    assert_eq!(
1710                        _is, _find,
1711                        "pattern: {:?}; mismatch on \"{}\"; is={}; find={}",
1712                        $pat, $test, _is, _find
1713                    );
1714                })+
1715            }}
1716        }
1717
1718        match_methods_agree!("" => "", "/", "/foo");
1719        match_methods_agree!("/" => "", "/", "/foo");
1720        match_methods_agree!("/user" => "user", "/user", "/users", "/user/123", "/foo");
1721        match_methods_agree!("/v{v}" => "v", "/v", "/v1", "/v222", "/foo");
1722        match_methods_agree!(["/v{v}", "/version/{v}"] => "/v", "/v1", "/version", "/version/1", "/foo");
1723
1724        match_methods_agree!("/path{tail}*" => "/path", "/path1", "/path/123");
1725        match_methods_agree!("/path/{tail}*" => "/path", "/path1", "/path/123");
1726
1727        match_methods_agree!(prefix "" => "", "/", "/foo");
1728        match_methods_agree!(prefix "/user" => "user", "/user", "/users", "/user/123", "/foo");
1729        match_methods_agree!(prefix r"/id/{id:\d{3}}" => "/id/123", "/id/1234");
1730        match_methods_agree!(["/v{v}", "/ver/{v}"] => "", "s/v", "/v1", "/v1/xx", "/ver/i3/5", "/ver/1");
1731    }
1732
1733    #[test]
1734    #[should_panic]
1735    fn duplicate_segment_name() {
1736        ResourceDef::new("/user/{id}/post/{id}");
1737    }
1738
1739    #[test]
1740    #[should_panic]
1741    fn invalid_dynamic_segment_delimiter() {
1742        ResourceDef::new("/user/{username");
1743    }
1744
1745    #[test]
1746    #[should_panic]
1747    fn invalid_dynamic_segment_name() {
1748        ResourceDef::new("/user/{}");
1749    }
1750
1751    #[test]
1752    #[should_panic]
1753    fn invalid_too_many_dynamic_segments() {
1754        ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}");
1756
1757        ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}");
1759    }
1760
1761    #[test]
1762    #[should_panic]
1763    fn invalid_custom_regex_for_tail() {
1764        ResourceDef::new(r"/{tail:\d+}*");
1765    }
1766
1767    #[test]
1768    #[should_panic]
1769    fn invalid_unnamed_tail_segment() {
1770        ResourceDef::new("/*");
1771    }
1772
1773    #[test]
1774    #[should_panic]
1775    fn prefix_plus_tail_match_disallowed() {
1776        ResourceDef::prefix("/user/{id}*");
1777    }
1778}