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 #[allow(clippy::literal_string_with_formatting_args)]
1025 if unprocessed.ends_with('*') {
1026 #[cfg(not(test))]
1029 tracing::warn!(
1030 "Tail segments must have names. \
1031 Consider `.../{{tail}}*`. \
1032 This may become a panic in the future."
1033 );
1034
1035 #[cfg(test)]
1037 panic!("Tail segments must have names");
1038 } else if !has_tail_segment && !unprocessed.is_empty() {
1039 segments.push(PatternSegment::Const(unprocessed.to_owned()));
1042 re.push_str(&escape(unprocessed));
1043 }
1044
1045 assert!(
1046 dyn_segment_count <= MAX_DYNAMIC_SEGMENTS,
1047 "Only {} dynamic segments are allowed, provided: {}",
1048 MAX_DYNAMIC_SEGMENTS,
1049 dyn_segment_count
1050 );
1051
1052 let mut re = format!("({})", re);
1054
1055 if !has_tail_segment {
1057 if is_prefix {
1058 re.push_str(r"(/|$)");
1059 } else {
1060 re.push('$');
1061 }
1062 }
1063
1064 let re = match Regex::new(&re) {
1065 Ok(re) => re,
1066 Err(err) => panic!("Wrong path pattern: \"{}\" {}", pattern, err),
1067 };
1068
1069 let names = re
1074 .capture_names()
1075 .filter_map(|name| name.map(|name| Box::leak(Box::new(name.to_owned())).as_str()))
1076 .collect();
1077
1078 (PatternType::Dynamic(re, names), segments)
1079 }
1080}
1081
1082impl Eq for ResourceDef {}
1083
1084impl PartialEq for ResourceDef {
1085 fn eq(&self, other: &ResourceDef) -> bool {
1086 self.patterns == other.patterns && self.is_prefix == other.is_prefix
1087 }
1088}
1089
1090impl Hash for ResourceDef {
1091 fn hash<H: Hasher>(&self, state: &mut H) {
1092 self.patterns.hash(state);
1093 }
1094}
1095
1096impl<'a> From<&'a str> for ResourceDef {
1097 fn from(path: &'a str) -> ResourceDef {
1098 ResourceDef::new(path)
1099 }
1100}
1101
1102impl From<String> for ResourceDef {
1103 fn from(path: String) -> ResourceDef {
1104 ResourceDef::new(path)
1105 }
1106}
1107
1108pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
1109 if !path.is_empty() && !path.starts_with('/') {
1110 let mut new_path = String::with_capacity(path.len() + 1);
1111 new_path.push('/');
1112 new_path.push_str(path);
1113 Cow::Owned(new_path)
1114 } else {
1115 Cow::Borrowed(path)
1116 }
1117}
1118
1119#[cfg(test)]
1120mod tests {
1121 use super::*;
1122 use crate::Path;
1123
1124 #[test]
1125 fn equivalence() {
1126 assert_eq!(
1127 ResourceDef::root_prefix("/root"),
1128 ResourceDef::prefix("/root")
1129 );
1130 assert_eq!(
1131 ResourceDef::root_prefix("root"),
1132 ResourceDef::prefix("/root")
1133 );
1134 assert_eq!(
1135 ResourceDef::root_prefix("/{id}"),
1136 ResourceDef::prefix("/{id}")
1137 );
1138 assert_eq!(
1139 ResourceDef::root_prefix("{id}"),
1140 ResourceDef::prefix("/{id}")
1141 );
1142
1143 assert_eq!(ResourceDef::new("/"), ResourceDef::new(["/"]));
1144 assert_eq!(ResourceDef::new("/"), ResourceDef::new(vec!["/"]));
1145 assert_eq!(
1146 ResourceDef::from("/from-str"),
1147 ResourceDef::new("/from-str")
1148 );
1149 assert_eq!(
1150 ResourceDef::from("/from-string".to_owned()),
1151 ResourceDef::new("/from-string")
1152 );
1153
1154 assert_ne!(ResourceDef::new(""), ResourceDef::prefix(""));
1155 assert_ne!(ResourceDef::new("/"), ResourceDef::prefix("/"));
1156 assert_ne!(ResourceDef::new("/{id}"), ResourceDef::prefix("/{id}"));
1157 }
1158
1159 #[test]
1160 fn metadata_accessors_update_resource_identity() {
1161 let mut resource = ResourceDef::new("/root");
1162 assert_eq!(resource.id(), 0);
1163 resource.set_id(42);
1164 assert_eq!(resource.id(), 42);
1165 assert!(!resource.is_prefix());
1166
1167 assert!(resource.name().is_none());
1168 resource.set_name("root");
1169 assert_eq!(resource.name(), Some("root"));
1170 }
1171
1172 #[test]
1173 fn pattern_iterators_report_registered_patterns() {
1174 let resource = ResourceDef::new("/root");
1175 assert_eq!(resource.pattern(), Some("/root"));
1176 let mut patterns = resource.pattern_iter();
1177 assert_eq!(patterns.size_hint(), (1, Some(1)));
1178 assert_eq!(patterns.next(), Some("/root"));
1179 assert_eq!(patterns.next(), None);
1180
1181 let resource = ResourceDef::new(["/first", "/second"]);
1182 assert_eq!(resource.pattern(), Some("/first"));
1183 let mut patterns = resource.pattern_iter();
1184 assert_eq!(patterns.size_hint(), (2, Some(2)));
1185 assert_eq!(patterns.next(), Some("/first"));
1186 assert_eq!(patterns.next(), Some("/second"));
1187 assert_eq!(patterns.next(), None);
1188 assert_eq!(patterns.next(), None);
1189 }
1190
1191 #[test]
1192 fn empty_pattern_sets_never_match() {
1193 let empty = ResourceDef::new(Vec::<&str>::new());
1194 assert_eq!(empty.pattern(), None);
1195 assert_eq!(empty.pattern_iter().next(), None);
1196 assert!(!empty.is_match("/root"));
1197 assert_eq!(empty.find_match("/root"), None);
1198 }
1199
1200 #[test]
1201 fn equal_resources_have_equal_hashes() {
1202 fn hash(resource: &ResourceDef) -> u64 {
1203 let mut hasher = std::collections::hash_map::DefaultHasher::new();
1204 resource.hash(&mut hasher);
1205 hasher.finish()
1206 }
1207
1208 let mut first = ResourceDef::new("/root");
1209 first.set_id(1);
1210 first.set_name("first");
1211
1212 let mut second = ResourceDef::new("/root");
1213 second.set_id(2);
1214 second.set_name("second");
1215
1216 assert_eq!(first, second);
1217 assert_eq!(hash(&first), hash(&second));
1218 }
1219
1220 #[test]
1221 #[should_panic(expected = "resource name should not be empty")]
1222 fn empty_resource_names_are_rejected() {
1223 ResourceDef::new("/root").set_name("");
1224 }
1225
1226 #[test]
1227 fn parse_static() {
1228 let re = ResourceDef::new("");
1229
1230 assert!(!re.is_prefix());
1231
1232 assert!(re.is_match(""));
1233 assert!(!re.is_match("/"));
1234 assert_eq!(re.find_match(""), Some(0));
1235 assert_eq!(re.find_match("/"), None);
1236
1237 let re = ResourceDef::new("/");
1238 assert!(re.is_match("/"));
1239 assert!(!re.is_match(""));
1240 assert!(!re.is_match("/foo"));
1241
1242 let re = ResourceDef::new("/name");
1243 assert!(re.is_match("/name"));
1244 assert!(!re.is_match("/name1"));
1245 assert!(!re.is_match("/name/"));
1246 assert!(!re.is_match("/name~"));
1247
1248 let mut path = Path::new("/name");
1249 assert!(re.capture_match_info(&mut path));
1250 assert_eq!(path.unprocessed(), "");
1251
1252 assert_eq!(re.find_match("/name"), Some(5));
1253 assert_eq!(re.find_match("/name1"), None);
1254 assert_eq!(re.find_match("/name/"), None);
1255 assert_eq!(re.find_match("/name~"), None);
1256
1257 let re = ResourceDef::new("/name/");
1258 assert!(re.is_match("/name/"));
1259 assert!(!re.is_match("/name"));
1260 assert!(!re.is_match("/name/gs"));
1261
1262 let re = ResourceDef::new("/user/profile");
1263 assert!(re.is_match("/user/profile"));
1264 assert!(!re.is_match("/user/profile/profile"));
1265
1266 let mut path = Path::new("/user/profile");
1267 assert!(re.capture_match_info(&mut path));
1268 assert_eq!(path.unprocessed(), "");
1269 }
1270
1271 #[test]
1272 fn parse_param() {
1273 let re = ResourceDef::new("/user/{id}");
1274 assert!(re.is_match("/user/profile"));
1275 assert!(re.is_match("/user/2345"));
1276 assert!(!re.is_match("/user/2345/"));
1277 assert!(!re.is_match("/user/2345/sdg"));
1278
1279 let mut path = Path::new("/user/profile");
1280 assert!(re.capture_match_info(&mut path));
1281 assert_eq!(path.get("id").unwrap(), "profile");
1282 assert_eq!(path.unprocessed(), "");
1283
1284 let mut path = Path::new("/user/1245125");
1285 assert!(re.capture_match_info(&mut path));
1286 assert_eq!(path.get("id").unwrap(), "1245125");
1287 assert_eq!(path.unprocessed(), "");
1288
1289 let re = ResourceDef::new("/v{version}/resource/{id}");
1290 assert!(re.is_match("/v1/resource/320120"));
1291 assert!(!re.is_match("/v/resource/1"));
1292 assert!(!re.is_match("/resource"));
1293
1294 let mut path = Path::new("/v151/resource/adage32");
1295 assert!(re.capture_match_info(&mut path));
1296 assert_eq!(path.get("version").unwrap(), "151");
1297 assert_eq!(path.get("id").unwrap(), "adage32");
1298 assert_eq!(path.unprocessed(), "");
1299
1300 let re = ResourceDef::new("/{id:[[:digit:]]{6}}");
1301 assert!(re.is_match("/012345"));
1302 assert!(!re.is_match("/012"));
1303 assert!(!re.is_match("/01234567"));
1304 assert!(!re.is_match("/XXXXXX"));
1305
1306 let mut path = Path::new("/012345");
1307 assert!(re.capture_match_info(&mut path));
1308 assert_eq!(path.get("id").unwrap(), "012345");
1309 assert_eq!(path.unprocessed(), "");
1310 }
1311
1312 #[allow(clippy::cognitive_complexity)]
1313 #[test]
1314 fn dynamic_set() {
1315 let re = ResourceDef::new(vec![
1316 "/user/{id}",
1317 "/v{version}/resource/{id}",
1318 "/{id:[[:digit:]]{6}}",
1319 "/static",
1320 ]);
1321 assert!(re.is_match("/user/profile"));
1322 assert!(re.is_match("/user/2345"));
1323 assert!(!re.is_match("/user/2345/"));
1324 assert!(!re.is_match("/user/2345/sdg"));
1325
1326 let mut path = Path::new("/user/profile");
1327 assert!(re.capture_match_info(&mut path));
1328 assert_eq!(path.get("id").unwrap(), "profile");
1329 assert_eq!(path.unprocessed(), "");
1330
1331 let mut path = Path::new("/user/1245125");
1332 assert!(re.capture_match_info(&mut path));
1333 assert_eq!(path.get("id").unwrap(), "1245125");
1334 assert_eq!(path.unprocessed(), "");
1335
1336 assert!(re.is_match("/v1/resource/320120"));
1337 assert!(!re.is_match("/v/resource/1"));
1338 assert!(!re.is_match("/resource"));
1339
1340 let mut path = Path::new("/v151/resource/adage32");
1341 assert!(re.capture_match_info(&mut path));
1342 assert_eq!(path.get("version").unwrap(), "151");
1343 assert_eq!(path.get("id").unwrap(), "adage32");
1344
1345 assert!(re.is_match("/012345"));
1346 assert!(!re.is_match("/012"));
1347 assert!(!re.is_match("/01234567"));
1348 assert!(!re.is_match("/XXXXXX"));
1349
1350 assert!(re.is_match("/static"));
1351 assert!(!re.is_match("/a/static"));
1352 assert!(!re.is_match("/static/a"));
1353
1354 let mut path = Path::new("/012345");
1355 assert!(re.capture_match_info(&mut path));
1356 assert_eq!(path.get("id").unwrap(), "012345");
1357
1358 let re = ResourceDef::new([
1359 "/user/{id}",
1360 "/v{version}/resource/{id}",
1361 "/{id:[[:digit:]]{6}}",
1362 ]);
1363 assert!(re.is_match("/user/profile"));
1364 assert!(re.is_match("/user/2345"));
1365 assert!(!re.is_match("/user/2345/"));
1366 assert!(!re.is_match("/user/2345/sdg"));
1367
1368 let re = ResourceDef::new([
1369 "/user/{id}".to_string(),
1370 "/v{version}/resource/{id}".to_string(),
1371 "/{id:[[:digit:]]{6}}".to_string(),
1372 ]);
1373 assert!(re.is_match("/user/profile"));
1374 assert!(re.is_match("/user/2345"));
1375 assert!(!re.is_match("/user/2345/"));
1376 assert!(!re.is_match("/user/2345/sdg"));
1377 }
1378
1379 #[test]
1380 fn dynamic_set_prefix() {
1381 let re = ResourceDef::prefix(vec!["/u/{id}", "/{id:[[:digit:]]{3}}"]);
1382
1383 assert_eq!(re.find_match("/u/abc"), Some(6));
1384 assert_eq!(re.find_match("/u/abc/123"), Some(6));
1385 assert_eq!(re.find_match("/s/user/profile"), None);
1386
1387 assert_eq!(re.find_match("/123"), Some(4));
1388 assert_eq!(re.find_match("/123/456"), Some(4));
1389 assert_eq!(re.find_match("/12345"), None);
1390
1391 let mut path = Path::new("/151/res");
1392 assert!(re.capture_match_info(&mut path));
1393 assert_eq!(path.get("id").unwrap(), "151");
1394 assert_eq!(path.unprocessed(), "/res");
1395 }
1396
1397 #[test]
1398 fn parse_tail() {
1399 let re = ResourceDef::new("/user/-{id}*");
1400
1401 let mut path = Path::new("/user/-profile");
1402 assert!(re.capture_match_info(&mut path));
1403 assert_eq!(path.get("id").unwrap(), "profile");
1404
1405 let mut path = Path::new("/user/-2345");
1406 assert!(re.capture_match_info(&mut path));
1407 assert_eq!(path.get("id").unwrap(), "2345");
1408
1409 let mut path = Path::new("/user/-2345/");
1410 assert!(re.capture_match_info(&mut path));
1411 assert_eq!(path.get("id").unwrap(), "2345/");
1412
1413 let mut path = Path::new("/user/-2345/sdg");
1414 assert!(re.capture_match_info(&mut path));
1415 assert_eq!(path.get("id").unwrap(), "2345/sdg");
1416 }
1417
1418 #[test]
1419 fn static_tail() {
1420 let re = ResourceDef::new("/user{tail}*");
1421 assert!(re.is_match("/users"));
1422 assert!(re.is_match("/user-foo"));
1423 assert!(re.is_match("/user/profile"));
1424 assert!(re.is_match("/user/2345"));
1425 assert!(re.is_match("/user/2345/"));
1426 assert!(re.is_match("/user/2345/sdg"));
1427 assert!(!re.is_match("/foo/profile"));
1428
1429 let re = ResourceDef::new("/user/{tail}*");
1430 assert!(re.is_match("/user/profile"));
1431 assert!(re.is_match("/user/2345"));
1432 assert!(re.is_match("/user/2345/"));
1433 assert!(re.is_match("/user/2345/sdg"));
1434 assert!(!re.is_match("/foo/profile"));
1435 }
1436
1437 #[test]
1438 fn dynamic_tail() {
1439 let re = ResourceDef::new("/user/{id}/{tail}*");
1440 assert!(!re.is_match("/user/2345"));
1441 let mut path = Path::new("/user/2345/sdg");
1442 assert!(re.capture_match_info(&mut path));
1443 assert_eq!(path.get("id").unwrap(), "2345");
1444 assert_eq!(path.get("tail").unwrap(), "sdg");
1445 assert_eq!(path.unprocessed(), "");
1446 }
1447
1448 #[test]
1449 fn capture_check_can_reject_a_match_without_mutating_path() {
1450 let resource = ResourceDef::prefix("/user/{id}");
1451 let mut path = Path::new("/user/admin/stars");
1452
1453 assert!(
1454 !resource.capture_match_info_fn(&mut path, |path| { !path.as_str().contains("admin") })
1455 );
1456 assert_eq!(path.unprocessed(), "/user/admin/stars");
1457 assert!(path.is_empty());
1458 }
1459
1460 #[test]
1461 fn dynamic_set_capture_rejects_unmatched_paths() {
1462 let resource = ResourceDef::new(["/user/{id}", r"/number/{id:\d+}"]);
1463 let mut path = Path::new("/");
1464 assert!(!resource.capture_match_info(&mut path));
1465 assert_eq!(path.unprocessed(), "/");
1466 }
1467
1468 #[allow(clippy::literal_string_with_formatting_args)]
1469 #[test]
1470 fn newline_patterns_and_paths() {
1471 let re = ResourceDef::new("/user/a\nb");
1472 assert!(re.is_match("/user/a\nb"));
1473 assert!(!re.is_match("/user/a\nb/profile"));
1474
1475 let re = ResourceDef::new("/a{x}b/test/a{y}b");
1476 let mut path = Path::new("/a\nb/test/a\nb");
1477 assert!(re.capture_match_info(&mut path));
1478 assert_eq!(path.get("x").unwrap(), "\n");
1479 assert_eq!(path.get("y").unwrap(), "\n");
1480
1481 let re = ResourceDef::new("/user/{tail}*");
1482 assert!(re.is_match("/user/a\nb/"));
1483
1484 let re = ResourceDef::new("/user/{id}*");
1485 let mut path = Path::new("/user/a\nb/a\nb");
1486 assert!(re.capture_match_info(&mut path));
1487 assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
1488
1489 let re = ResourceDef::new("/user/{id:.*}");
1490 let mut path = Path::new("/user/a\nb/a\nb");
1491 assert!(re.capture_match_info(&mut path));
1492 assert_eq!(path.get("id").unwrap(), "a\nb/a\nb");
1493 }
1494
1495 #[cfg(feature = "http")]
1496 #[test]
1497 fn parse_urlencoded_param() {
1498 let re = ResourceDef::new("/user/{id}/test");
1499
1500 let mut path = Path::new("/user/2345/test");
1501 assert!(re.capture_match_info(&mut path));
1502 assert_eq!(path.get("id").unwrap(), "2345");
1503
1504 let mut path = Path::new("/user/qwe%25/test");
1505 assert!(re.capture_match_info(&mut path));
1506 assert_eq!(path.get("id").unwrap(), "qwe%25");
1507
1508 let uri = http::Uri::try_from("/user/qwe%25/test").unwrap();
1509 let mut path = Path::new(uri);
1510 assert!(re.capture_match_info(&mut path));
1511 assert_eq!(path.get("id").unwrap(), "qwe%25");
1512 }
1513
1514 #[test]
1515 fn prefix_static() {
1516 let re = ResourceDef::prefix("/name");
1517
1518 assert!(re.is_prefix());
1519
1520 assert!(re.is_match("/name"));
1521 assert!(re.is_match("/name/"));
1522 assert!(re.is_match("/name/test/test"));
1523 assert!(!re.is_match("/name1"));
1524 assert!(!re.is_match("/name~"));
1525
1526 let mut path = Path::new("/name");
1527 assert!(re.capture_match_info(&mut path));
1528 assert_eq!(path.unprocessed(), "");
1529
1530 let mut path = Path::new("/name/test");
1531 assert!(re.capture_match_info(&mut path));
1532 assert_eq!(path.unprocessed(), "/test");
1533
1534 assert_eq!(re.find_match("/name"), Some(5));
1535 assert_eq!(re.find_match("/name/"), Some(5));
1536 assert_eq!(re.find_match("/name/test/test"), Some(5));
1537 assert_eq!(re.find_match("/name1"), None);
1538 assert_eq!(re.find_match("/name~"), None);
1539
1540 let re = ResourceDef::prefix("/name/");
1541 assert!(re.is_match("/name/"));
1542 assert!(re.is_match("/name//gs"));
1543 assert!(!re.is_match("/name/gs"));
1544 assert!(!re.is_match("/name"));
1545
1546 let mut path = Path::new("/name/gs");
1547 assert!(!re.capture_match_info(&mut path));
1548
1549 let mut path = Path::new("/name//gs");
1550 assert!(re.capture_match_info(&mut path));
1551 assert_eq!(path.unprocessed(), "/gs");
1552
1553 let re = ResourceDef::root_prefix("name/");
1554 assert!(re.is_match("/name/"));
1555 assert!(re.is_match("/name//gs"));
1556 assert!(!re.is_match("/name/gs"));
1557 assert!(!re.is_match("/name"));
1558
1559 let mut path = Path::new("/name/gs");
1560 assert!(!re.capture_match_info(&mut path));
1561 }
1562
1563 #[test]
1564 fn prefix_dynamic() {
1565 let re = ResourceDef::prefix("/{name}");
1566
1567 assert!(re.is_prefix());
1568
1569 assert!(re.is_match("/name/"));
1570 assert!(re.is_match("/name/gs"));
1571 assert!(re.is_match("/name"));
1572
1573 assert_eq!(re.find_match("/name/"), Some(5));
1574 assert_eq!(re.find_match("/name/gs"), Some(5));
1575 assert_eq!(re.find_match("/name"), Some(5));
1576 assert_eq!(re.find_match(""), None);
1577
1578 let mut path = Path::new("/test2/");
1579 assert!(re.capture_match_info(&mut path));
1580 assert_eq!(&path["name"], "test2");
1581 assert_eq!(&path[0], "test2");
1582 assert_eq!(path.unprocessed(), "/");
1583
1584 let mut path = Path::new("/test2/subpath1/subpath2/index.html");
1585 assert!(re.capture_match_info(&mut path));
1586 assert_eq!(&path["name"], "test2");
1587 assert_eq!(&path[0], "test2");
1588 assert_eq!(path.unprocessed(), "/subpath1/subpath2/index.html");
1589
1590 let resource = ResourceDef::prefix("/user");
1591 assert!(resource.find_match("/foo").is_none());
1593 }
1594
1595 #[test]
1596 fn prefix_empty() {
1597 let re = ResourceDef::prefix("");
1598
1599 assert!(re.is_prefix());
1600
1601 assert!(re.is_match(""));
1602 assert!(re.is_match("/"));
1603 assert!(re.is_match("/name/test/test"));
1604 }
1605
1606 #[test]
1607 fn build_path_list() {
1608 let mut s = String::new();
1609 let resource = ResourceDef::new("/user/{item1}/test");
1610 assert!(resource.resource_path_from_iter(&mut s, &mut ["user1"].iter()));
1611 assert_eq!(s, "/user/user1/test");
1612
1613 let mut s = String::new();
1614 let resource = ResourceDef::new("/user/{item1}/{item2}/test");
1615 assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter()));
1616 assert_eq!(s, "/user/item/item2/test");
1617
1618 let mut s = String::new();
1619 let resource = ResourceDef::new("/user/{item1}/{item2}");
1620 assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter()));
1621 assert_eq!(s, "/user/item/item2");
1622
1623 let mut s = String::new();
1624 let resource = ResourceDef::new("/user/{item1}/{item2}/");
1625 assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter()));
1626 assert_eq!(s, "/user/item/item2/");
1627
1628 let mut s = String::new();
1629 assert!(!resource.resource_path_from_iter(&mut s, &mut ["item"].iter()));
1630
1631 let mut s = String::new();
1632 assert!(resource.resource_path_from_iter(&mut s, &mut ["item", "item2"].iter()));
1633 assert_eq!(s, "/user/item/item2/");
1634 assert!(!resource.resource_path_from_iter(&mut s, &mut ["item"].iter()));
1635
1636 let mut s = String::new();
1637
1638 assert!(resource.resource_path_from_iter(
1639 &mut s,
1640 #[allow(clippy::useless_vec)]
1641 &mut vec!["item", "item2"].iter()
1642 ));
1643 assert_eq!(s, "/user/item/item2/");
1644 }
1645
1646 #[test]
1647 fn multi_pattern_build_path() {
1648 let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]);
1649 let mut s = String::new();
1650 assert!(resource.resource_path_from_iter(&mut s, &mut ["123"].iter()));
1651 assert_eq!(s, "/user/123");
1652 }
1653
1654 #[test]
1655 fn multi_pattern_capture_segment_values() {
1656 let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]);
1657
1658 let mut path = Path::new("/user/123");
1659 assert!(resource.capture_match_info(&mut path));
1660 assert!(path.get("id").is_some());
1661
1662 let mut path = Path::new("/profile/123");
1663 assert!(resource.capture_match_info(&mut path));
1664 assert!(path.get("id").is_some());
1665
1666 let resource = ResourceDef::new(["/user/{id}", "/profile/{uid}"]);
1667
1668 let mut path = Path::new("/user/123");
1669 assert!(resource.capture_match_info(&mut path));
1670 assert!(path.get("id").is_some());
1671 assert!(path.get("uid").is_none());
1672
1673 let mut path = Path::new("/profile/123");
1674 assert!(resource.capture_match_info(&mut path));
1675 assert!(path.get("id").is_none());
1676 assert!(path.get("uid").is_some());
1677 }
1678
1679 #[test]
1680 fn dynamic_prefix_proper_segmentation() {
1681 let resource = ResourceDef::prefix(r"/id/{id:\d{3}}");
1682
1683 assert!(resource.is_match("/id/123"));
1684 assert!(resource.is_match("/id/123/foo"));
1685 assert!(!resource.is_match("/id/1234"));
1686 assert!(!resource.is_match("/id/123a"));
1687
1688 assert_eq!(resource.find_match("/id/123"), Some(7));
1689 assert_eq!(resource.find_match("/id/123/foo"), Some(7));
1690 assert_eq!(resource.find_match("/id/1234"), None);
1691 assert_eq!(resource.find_match("/id/123a"), None);
1692 }
1693
1694 #[test]
1695 fn build_path_map() {
1696 let resource = ResourceDef::new("/user/{item1}/{item2}/");
1697
1698 let mut map = HashMap::new();
1699 map.insert("item1", "item");
1700
1701 let mut s = String::new();
1702 assert!(!resource.resource_path_from_map(&mut s, &map));
1703
1704 map.insert("item2", "item2");
1705
1706 let mut s = String::new();
1707 assert!(resource.resource_path_from_map(&mut s, &map));
1708 assert_eq!(s, "/user/item/item2/");
1709 }
1710
1711 #[test]
1712 fn build_path_tail() {
1713 let resource = ResourceDef::new("/user/{item1}*");
1714
1715 let mut s = String::new();
1716 assert!(!resource.resource_path_from_iter(&mut s, &mut [""; 0].iter()));
1717
1718 let mut s = String::new();
1719 assert!(resource.resource_path_from_iter(&mut s, &mut ["user1"].iter()));
1720 assert_eq!(s, "/user/user1");
1721
1722 let mut s = String::new();
1723 let mut map = HashMap::new();
1724 map.insert("item1", "item");
1725 assert!(resource.resource_path_from_map(&mut s, &map));
1726 assert_eq!(s, "/user/item");
1727 }
1728
1729 #[test]
1730 fn prefix_trailing_slash() {
1731 let re = ResourceDef::prefix("/abc/");
1735 assert_eq!(re.find_match("/abc/def"), None);
1736 assert_eq!(re.find_match("/abc//def"), Some(5));
1737
1738 let re = ResourceDef::prefix("/{id}/");
1739 assert_eq!(re.find_match("/abc/def"), None);
1740 assert_eq!(re.find_match("/abc//def"), Some(5));
1741 }
1742
1743 #[test]
1744 fn join() {
1745 fn seq_find_match(re1: &ResourceDef, re2: &ResourceDef, path: &str) -> Option<usize> {
1748 let len1 = re1.find_match(path)?;
1749 let len2 = re2.find_match(&path[len1..])?;
1750 Some(len1 + len2)
1751 }
1752
1753 macro_rules! join_test {
1754 ($pat1:expr, $pat2:expr => $($test:expr),+) => {{
1755 let pat1 = $pat1;
1756 let pat2 = $pat2;
1757 $({
1758 let _path = $test;
1759 let (re1, re2) = (ResourceDef::prefix(pat1), ResourceDef::new(pat2));
1760 let _seq = seq_find_match(&re1, &re2, _path);
1761 let _join = re1.join(&re2).find_match(_path);
1762 assert_eq!(
1763 _seq, _join,
1764 "patterns: prefix {:?}, {:?}; mismatch on \"{}\"; seq={:?}; join={:?}",
1765 pat1, pat2, _path, _seq, _join
1766 );
1767 assert!(!re1.join(&re2).is_prefix());
1768
1769 let (re1, re2) = (ResourceDef::prefix(pat1), ResourceDef::prefix(pat2));
1770 let _seq = seq_find_match(&re1, &re2, _path);
1771 let _join = re1.join(&re2).find_match(_path);
1772 assert_eq!(
1773 _seq, _join,
1774 "patterns: prefix {:?}, prefix {:?}; mismatch on \"{}\"; seq={:?}; join={:?}",
1775 pat1, pat2, _path, _seq, _join
1776 );
1777 assert!(re1.join(&re2).is_prefix());
1778 })+
1779 }}
1780 }
1781
1782 join_test!("", "" => "", "/hello", "/");
1783 join_test!("/user", "" => "", "/user", "/user/123", "/user11", "user", "user/123");
1784 join_test!("", "/user" => "", "/user", "foo", "/user11", "user", "user/123");
1785 join_test!("/user", "/xx" => "", "", "/", "/user", "/xx", "/userxx", "/user/xx");
1786
1787 join_test!(["/ver/{v}", "/v{v}"], ["/req/{req}", "/{req}"] => "/v1/abc",
1788 "/ver/1/abc", "/v1/req/abc", "/ver/1/req/abc", "/v1/abc/def",
1789 "/ver1/req/abc/def", "", "/", "/v1/");
1790 }
1791
1792 #[test]
1793 fn match_methods_agree() {
1794 macro_rules! match_methods_agree {
1795 ($pat:expr => $($test:expr),+) => {{
1796 match_methods_agree!(finish $pat, ResourceDef::new($pat), $($test),+);
1797 }};
1798 (prefix $pat:expr => $($test:expr),+) => {{
1799 match_methods_agree!(finish $pat, ResourceDef::prefix($pat), $($test),+);
1800 }};
1801 (finish $pat:expr, $re:expr, $($test:expr),+) => {{
1802 let re = $re;
1803 $({
1804 let _is = re.is_match($test);
1805 let _find = re.find_match($test).is_some();
1806 assert_eq!(
1807 _is, _find,
1808 "pattern: {:?}; mismatch on \"{}\"; is={}; find={}",
1809 $pat, $test, _is, _find
1810 );
1811 })+
1812 }}
1813 }
1814
1815 match_methods_agree!("" => "", "/", "/foo");
1816 match_methods_agree!("/" => "", "/", "/foo");
1817 match_methods_agree!("/user" => "user", "/user", "/users", "/user/123", "/foo");
1818 match_methods_agree!("/v{v}" => "v", "/v", "/v1", "/v222", "/foo");
1819 match_methods_agree!(["/v{v}", "/version/{v}"] => "/v", "/v1", "/version", "/version/1", "/foo");
1820
1821 match_methods_agree!("/path{tail}*" => "/path", "/path1", "/path/123");
1822 match_methods_agree!("/path/{tail}*" => "/path", "/path1", "/path/123");
1823
1824 match_methods_agree!(prefix "" => "", "/", "/foo");
1825 match_methods_agree!(prefix "/user" => "user", "/user", "/users", "/user/123", "/foo");
1826 match_methods_agree!(prefix r"/id/{id:\d{3}}" => "/id/123", "/id/1234");
1827 match_methods_agree!(["/v{v}", "/ver/{v}"] => "", "s/v", "/v1", "/v1/xx", "/ver/i3/5", "/ver/1");
1828 }
1829
1830 #[test]
1831 #[should_panic]
1832 fn duplicate_segment_name() {
1833 ResourceDef::new("/user/{id}/post/{id}");
1834 }
1835
1836 #[test]
1837 #[should_panic]
1838 fn invalid_dynamic_segment_delimiter() {
1839 ResourceDef::new("/user/{username");
1840 }
1841
1842 #[test]
1843 #[should_panic]
1844 fn invalid_dynamic_segment_name() {
1845 ResourceDef::new("/user/{}");
1846 }
1847
1848 #[test]
1849 #[should_panic]
1850 fn invalid_too_many_dynamic_segments() {
1851 ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}");
1853
1854 ResourceDef::new("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}");
1856 }
1857
1858 #[test]
1859 #[should_panic]
1860 fn invalid_custom_regex_for_tail() {
1861 ResourceDef::new(r"/{tail:\d+}*");
1862 }
1863
1864 #[test]
1865 #[should_panic]
1866 fn invalid_unnamed_tail_segment() {
1867 ResourceDef::new("/*");
1868 }
1869
1870 #[test]
1871 #[should_panic]
1872 fn prefix_plus_tail_match_disallowed() {
1873 ResourceDef::prefix("/user/{id}*");
1874 }
1875}