uri_pattern_matcher/
lib.rs1mod uri_pattern_score;
42mod pattern_part;
43
44use core::cmp::Ordering;
45use crate::pattern_part::PatternPart;
46use crate::uri_pattern_score::UriPatternScore;
47
48#[derive(Debug, Clone)]
50pub struct UriPattern<'a> {
51 value: &'a str,
52 pub(crate) parts: Vec<PatternPart<'a>>,
53}
54
55impl<'a> From<&'a str> for UriPattern<'a> {
56 fn from(pattern: &'a str) -> Self {
57 let parts = pattern.split('/').map(|part| part.into()).collect();
58 Self { value : pattern, parts }
59 }
60}
61
62impl UriPattern<'_> {
63 pub fn is_match(&self, candidate: &str) -> bool {
74 !candidate.split('/').enumerate().map(|(key, value)| {
75 match self.parts[key] {
76 PatternPart::Joker => true,
77 PatternPart::Value(s) => if s == value {true} else {false},
78 }
79 })
80 .collect::<Vec<bool>>()
81 .contains(&false)
82 }
83}
84
85impl PartialEq for UriPattern<'_> {
86 fn eq(&self, other: &Self) -> bool {
87 let score: UriPatternScore = self.into();
88 let other_score: UriPatternScore = other.into();
89 score == other_score
90 }
91}
92
93impl PartialOrd for UriPattern<'_> {
94 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
95 let score: UriPatternScore = self.into();
96 let other_score: UriPatternScore = other.into();
97 score.partial_cmp(&other_score)
98 }
99}
100
101impl Ord for UriPattern<'_> {
102 fn cmp(&self, other: &Self) -> Ordering {
103 let score: UriPatternScore = self.into();
104 let other_score: UriPatternScore = other.into();
105 score.cmp(&other_score)
106 }
107}
108
109impl Eq for UriPattern<'_> {}
110
111
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn parsing_works() {
119 let pattern: UriPattern = "/a/{b}/{c}/d".into();
120 assert!(pattern.is_match("/a/resource/test/d"));
121 }
122
123 #[test]
124 fn non_equality_works() {
125 let pattern: UriPattern = "/a/{b}/{c}/d".into();
126 let pattern2: UriPattern = "/a/{b}/c/{d}".into();
127 assert_ne!(pattern, pattern2);
128 }
129
130 #[test]
131 fn equality_works() {
132 let pattern: UriPattern = "/a/{b}/{c}/d".into();
133 let pattern2: UriPattern = "/api/{resource}/{id}/details".into();
134 assert_eq!(pattern, pattern2);
135 }
136
137 #[test]
138 fn inequality_works() {
139 let pattern: UriPattern = "/a/{b}/{c}/d".into();
140 let pattern2: UriPattern = "/a/{b}/c/{d}".into();
141 assert!(pattern > pattern2);
142 }
143
144 #[test]
145 fn best_match_with_ord() {
146 let patterns: Vec<UriPattern> = vec![
147 "/api/{foo}/bar/{zzz}".into(),
148 "/api/{foo}/{bar}/zzz".into(),
149 "/{api}/{foo}/foo/{zzz}".into()
150 ];
151 let candidate = "/api/resource/bar/zzz";
152 let best_match = patterns.iter()
153 .filter(|p| p.is_match(candidate))
154 .max();
155 assert_eq!(best_match.unwrap(), &UriPattern::from("/api/{foo}/{bar}/zzz"));
156 }
157}