yew_router_nested/matcher/
mod.rs1mod matcher_impl;
5mod util;
6
7use nom::IResult;
8use std::collections::HashSet;
9use yew_router_route_parser::{parse_str_and_optimize_tokens, PrettyParseError};
10
11pub use yew_router_route_parser::{CaptureVariant, Captures, MatcherToken};
12
13#[derive(Debug, PartialEq, Clone)]
15pub struct RouteMatcher {
16 pub tokens: Vec<MatcherToken>,
18 pub settings: MatcherSettings,
20}
21
22#[derive(Debug, PartialEq, Clone, Copy)]
24pub struct MatcherSettings {
25 pub case_insensitive: bool,
27}
28
29impl Default for MatcherSettings {
30 fn default() -> Self {
31 MatcherSettings {
32 case_insensitive: false,
33 }
34 }
35}
36
37impl RouteMatcher {
38 pub fn try_from(i: &str) -> Result<Self, PrettyParseError> {
40 let settings = MatcherSettings::default();
41 Self::new(i, settings)
42 }
43
44 pub fn new(i: &str, settings: MatcherSettings) -> Result<Self, PrettyParseError> {
46 Ok(RouteMatcher {
47 tokens: parse_str_and_optimize_tokens(
48 i,
49 yew_router_route_parser::FieldNamingScheme::Unnamed, )?, settings,
52 })
53 }
54
55 pub fn capture_route_into_map<'a, 'b: 'a>(
57 &'b self,
58 i: &'a str,
59 ) -> IResult<&'a str, Captures<'a>> {
60 matcher_impl::match_into_map(&self.tokens, &self.settings)(i)
61 }
62
63 pub fn capture_route_into_vec<'a, 'b: 'a>(
65 &'b self,
66 i: &'a str,
67 ) -> IResult<&'a str, Vec<String>> {
68 matcher_impl::match_into_vec(&self.tokens, &self.settings)(i)
69 }
70
71 pub fn capture_names(&self) -> HashSet<&str> {
75 fn capture_names_impl(tokens: &[MatcherToken]) -> HashSet<&str> {
76 tokens
77 .iter()
78 .fold(HashSet::new(), |mut acc: HashSet<&str>, token| {
79 match token {
80 MatcherToken::Exact(_) | MatcherToken::End => {}
81 MatcherToken::Capture(capture) => match &capture {
82 CaptureVariant::ManyNamed(name)
83 | CaptureVariant::Named(name)
84 | CaptureVariant::NumberedNamed { name, .. } => {
85 acc.insert(&name);
86 }
87 CaptureVariant::Unnamed
88 | CaptureVariant::ManyUnnamed
89 | CaptureVariant::NumberedUnnamed { .. } => {}
90 },
91 }
92 acc
93 })
94 }
95 capture_names_impl(&self.tokens)
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use yew_router_route_parser::{
103 convert_tokens,
104 parser::{RefCaptureVariant, RouteParserToken},
105 };
106
107 impl<'a> From<Vec<RouteParserToken<'a>>> for RouteMatcher {
108 fn from(tokens: Vec<RouteParserToken<'a>>) -> Self {
109 let settings = MatcherSettings::default();
110 RouteMatcher {
111 tokens: convert_tokens(&tokens),
112 settings,
113 }
114 }
115 }
116
117 #[test]
118 fn basic_separator() {
119 let tokens = vec![RouteParserToken::Separator];
120 let path_matcher = RouteMatcher::from(tokens);
121 path_matcher
122 .capture_route_into_map("/")
123 .expect("should parse");
124 }
125
126 #[test]
127 fn multiple_tokens() {
128 let tokens = vec![
129 RouteParserToken::Separator,
130 RouteParserToken::Exact("lorem"),
131 RouteParserToken::Separator,
132 ];
133
134 let path_matcher = RouteMatcher::from(tokens);
135 path_matcher
136 .capture_route_into_map("/lorem/")
137 .expect("should parse");
138 }
139
140 #[test]
141 fn simple_capture() {
142 let tokens = vec![
143 RouteParserToken::Separator,
144 RouteParserToken::Capture(RefCaptureVariant::Named("lorem")),
145 RouteParserToken::Separator,
146 ];
147 let path_matcher = RouteMatcher::from(tokens);
148 let (_, matches) = path_matcher
149 .capture_route_into_map("/ipsum/")
150 .expect("should parse");
151 assert_eq!(matches["lorem"], "ipsum".to_string())
152 }
153
154 #[test]
155 fn simple_capture_with_no_trailing_separator() {
156 let tokens = vec![
157 RouteParserToken::Separator,
158 RouteParserToken::Capture(RefCaptureVariant::Named("lorem")),
159 ];
160 let path_matcher = RouteMatcher::from(tokens);
161 let (_, matches) = path_matcher
162 .capture_route_into_map("/ipsum")
163 .expect("should parse");
164 assert_eq!(matches["lorem"], "ipsum".to_string())
165 }
166
167 #[test]
168 fn match_with_trailing_match_many() {
169 let tokens = vec![
170 RouteParserToken::Separator,
171 RouteParserToken::Exact("a"),
172 RouteParserToken::Separator,
173 RouteParserToken::Capture(RefCaptureVariant::ManyNamed("lorem")),
174 ];
175 let path_matcher = RouteMatcher::from(tokens);
176 let (_, _matches) = path_matcher
177 .capture_route_into_map("/a/")
178 .expect("should parse");
179 }
180
181 #[test]
182 fn fail_match_with_trailing_match_single() {
183 let tokens = vec![
184 RouteParserToken::Separator,
185 RouteParserToken::Exact("a"),
186 RouteParserToken::Separator,
187 RouteParserToken::Capture(RefCaptureVariant::Named("lorem")),
188 ];
189 let path_matcher = RouteMatcher::from(tokens);
190 path_matcher
191 .capture_route_into_map("/a/")
192 .expect_err("should not parse");
193 }
194
195 #[test]
196 fn match_n() {
197 let tokens = vec![
198 RouteParserToken::Separator,
199 RouteParserToken::Capture(RefCaptureVariant::NumberedNamed {
200 sections: 3,
201 name: "lorem",
202 }),
203 RouteParserToken::Separator,
204 RouteParserToken::Exact("a"),
205 ];
206 let path_matcher = RouteMatcher::from(tokens);
207 let (_, _matches) = path_matcher
208 .capture_route_into_map("/garbage1/garbage2/garbage3/a")
209 .expect("should parse");
210 }
211
212 #[test]
213 fn match_n_no_overrun() {
214 let tokens = vec![
215 RouteParserToken::Separator,
216 RouteParserToken::Capture(RefCaptureVariant::NumberedNamed {
217 sections: 3,
218 name: "lorem",
219 }),
220 ];
221 let path_matcher = RouteMatcher::from(tokens);
222 let (s, _matches) = path_matcher
223 .capture_route_into_map("/garbage1/garbage2/garbage3")
224 .expect("should parse");
225 assert_eq!(s.len(), 0)
226 }
227
228 #[test]
229 fn match_n_named() {
230 let tokens = vec![
231 RouteParserToken::Separator,
232 RouteParserToken::Capture(RefCaptureVariant::NumberedNamed {
233 sections: 3,
234 name: "captured",
235 }),
236 RouteParserToken::Separator,
237 RouteParserToken::Exact("a"),
238 ];
239 let path_matcher = RouteMatcher::from(tokens);
240 let (_, matches) = path_matcher
241 .capture_route_into_map("/garbage1/garbage2/garbage3/a")
242 .expect("should parse");
243 assert_eq!(
244 matches["captured"],
245 "garbage1/garbage2/garbage3".to_string()
246 )
247 }
248
249 #[test]
250 fn match_many() {
251 let tokens = vec![
252 RouteParserToken::Separator,
253 RouteParserToken::Capture(RefCaptureVariant::ManyNamed("lorem")),
254 RouteParserToken::Separator,
255 RouteParserToken::Exact("a"),
256 ];
257 let path_matcher = RouteMatcher::from(tokens);
258 let (_, _matches) = path_matcher
259 .capture_route_into_map("/garbage1/garbage2/garbage3/a")
260 .expect("should parse");
261 }
262
263 #[test]
264 fn match_many_named() {
265 let tokens = vec![
266 RouteParserToken::Separator,
267 RouteParserToken::Capture(RefCaptureVariant::ManyNamed("captured")),
268 RouteParserToken::Separator,
269 RouteParserToken::Exact("a"),
270 ];
271 let path_matcher = RouteMatcher::from(tokens);
272 let (_, matches) = path_matcher
273 .capture_route_into_map("/garbage1/garbage2/garbage3/a")
274 .expect("should parse");
275 assert_eq!(
276 matches["captured"],
277 "garbage1/garbage2/garbage3".to_string()
278 )
279 }
280}