1use crate::{Route, Request, Catcher};
2use crate::router::Collide;
3use crate::http::Status;
4use crate::route::Color;
5
6impl Route {
7 #[tracing::instrument(level = "trace", name = "matching", skip_all, ret)]
69 pub fn matches(&self, request: &Request<'_>) -> bool {
70 methods_match(self, request)
71 && paths_match(self, request)
72 && queries_match(self, request)
73 && formats_match(self, request)
74 }
75}
76
77impl Catcher {
78 pub fn matches(&self, status: Status, request: &Request<'_>) -> bool {
137 self.code.map_or(true, |code| code == status.code)
138 && self.base().segments().prefix_of(request.uri().path().segments())
139 }
140}
141
142fn methods_match(route: &Route, req: &Request<'_>) -> bool {
143 trace!(?route.method, request.method = %req.method());
144 route.method.map_or(true, |method| method == req.method())
145}
146
147fn paths_match(route: &Route, req: &Request<'_>) -> bool {
148 trace!(route.uri = %route.uri, request.uri = %req.uri());
149 let route_segments = &route.uri.metadata.uri_segments;
150 let req_segments = req.uri().path().segments();
151
152 if route_segments.len() > req_segments.num() {
155 return false;
156 }
157
158 if req_segments.num() > route_segments.len() && !route.uri.metadata.dynamic_trail {
160 return false;
161 }
162
163 for (route_seg, req_seg) in route_segments.iter().zip(req_segments.clone()) {
165 if route_seg.dynamic_trail {
166 return true;
167 }
168
169 if !route_seg.dynamic && route_seg.value != req_seg {
170 return false;
171 }
172 }
173
174 true
175}
176
177fn queries_match(route: &Route, req: &Request<'_>) -> bool {
178 trace!(
179 route.query = route.uri.query().map(display),
180 route.query.color = route.uri.metadata.query_color.map(debug),
181 request.query = req.uri().query().map(display),
182 );
183
184 if matches!(route.uri.metadata.query_color, None | Some(Color::Wild)) {
185 return true;
186 }
187
188 let route_query_fields = route.uri.metadata.static_query_fields.iter();
189 for (key, val) in route_query_fields {
190 if let Some(query) = req.uri().query() {
191 if !query.segments().any(|req_seg| req_seg == (key, val)) {
192 debug!(key, val, request.query = %query, "missing static query");
193 return false;
194 }
195 } else {
196 debug!(key, val, "missing static query (queryless request)");
197 return false;
198 }
199 }
200
201 true
202}
203
204fn formats_match(route: &Route, req: &Request<'_>) -> bool {
205 trace!(
206 route.format = route.format.as_ref().map(display),
207 request.format = req.format().map(display),
208 );
209
210 let route_format = match route.format {
211 Some(ref format) => format,
212 None => return true,
213 };
214
215 match route.method.and_then(|m| m.allows_request_body()) {
216 Some(true) => match req.format() {
217 Some(f) if f.specificity() == 2 => route_format.collides_with(f),
218 _ => false
219 },
220 _ => match req.format() {
221 Some(f) => route_format.collides_with(f),
222 None => true
223 }
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use crate::local::blocking::Client;
230 use crate::route::{Route, dummy_handler};
231 use crate::http::{Method, Method::*, MediaType, ContentType, Accept};
232
233 fn req_matches_route(a: &'static str, b: &'static str) -> bool {
234 let client = Client::debug_with(vec![]).expect("client");
235 let route = Route::ranked(0, Get, b, dummy_handler);
236 route.matches(&client.get(a))
237 }
238
239 #[test]
240 fn request_route_matching() {
241 assert!(req_matches_route("/a/b?a=b", "/a/b?<c>"));
242 assert!(req_matches_route("/a/b?a=b", "/<a>/b?<c>"));
243 assert!(req_matches_route("/a/b?a=b", "/<a>/<b>?<c>"));
244 assert!(req_matches_route("/a/b?a=b", "/a/<b>?<c>"));
245 assert!(req_matches_route("/?b=c", "/?<b>"));
246
247 assert!(req_matches_route("/a/b?a=b", "/a/b"));
248 assert!(req_matches_route("/a/b", "/a/b"));
249 assert!(req_matches_route("/a/b/c/d?", "/a/b/c/d"));
250 assert!(req_matches_route("/a/b/c/d?v=1&v=2", "/a/b/c/d"));
251
252 assert!(req_matches_route("/a/b", "/a/b?<c>"));
253 assert!(req_matches_route("/a/b", "/a/b?<c..>"));
254 assert!(req_matches_route("/a/b?c", "/a/b?c"));
255 assert!(req_matches_route("/a/b?c", "/a/b?<c>"));
256 assert!(req_matches_route("/a/b?c=foo&d=z", "/a/b?<c>"));
257 assert!(req_matches_route("/a/b?c=foo&d=z", "/a/b?<c..>"));
258 assert!(req_matches_route("/a/b?c=foo&d=z", "/a/b?c=foo&<c..>"));
259 assert!(req_matches_route("/a/b?c=foo&d=z", "/a/b?d=z&<c..>"));
260
261 assert!(req_matches_route("/", "/<foo>"));
262 assert!(req_matches_route("/a", "/<foo>"));
263 assert!(req_matches_route("/a", "/a"));
264 assert!(req_matches_route("/a/", "/a/"));
265
266 assert!(req_matches_route("//", "/"));
267 assert!(req_matches_route("/a///", "/a/"));
268 assert!(req_matches_route("/a/b", "/a/b"));
269
270 assert!(!req_matches_route("/a///", "/a"));
271 assert!(!req_matches_route("/a", "/a/"));
272 assert!(!req_matches_route("/a/", "/a"));
273 assert!(!req_matches_route("/a/b", "/a/b/"));
274
275 assert!(!req_matches_route("/a", "/<a>/"));
276 assert!(!req_matches_route("/a/", "/<a>"));
277 assert!(!req_matches_route("/a/b", "/<a>/b/"));
278 assert!(!req_matches_route("/a/b", "/<a>/<b>/"));
279
280 assert!(!req_matches_route("/a/b/c", "/a/b?<c>"));
281 assert!(!req_matches_route("/a?b=c", "/a/b?<c>"));
282 assert!(!req_matches_route("/?b=c", "/a/b?<c>"));
283 assert!(!req_matches_route("/?b=c", "/a?<c>"));
284
285 assert!(!req_matches_route("/a/", "/<a>/<b>/<c..>"));
286 assert!(!req_matches_route("/a/b", "/<a>/<b>/<c..>"));
287
288 assert!(!req_matches_route("/a/b?c=foo&d=z", "/a/b?a=b&<c..>"));
289 assert!(!req_matches_route("/a/b?c=foo&d=z", "/a/b?d=b&<c..>"));
290 assert!(!req_matches_route("/a/b", "/a/b?c"));
291 assert!(!req_matches_route("/a/b", "/a/b?foo"));
292 assert!(!req_matches_route("/a/b", "/a/b?foo&<rest..>"));
293 assert!(!req_matches_route("/a/b", "/a/b?<a>&b&<rest..>"));
294 }
295
296 fn req_matches_format<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
297 where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
298 {
299 let client = Client::debug_with(vec![]).expect("client");
300 let mut req = client.req(m, "/");
301 if let Some(mt_str) = mt1.into() {
302 if m.allows_request_body() == Some(true) {
303 req.replace_header(mt_str.parse::<ContentType>().unwrap());
304 } else {
305 req.replace_header(mt_str.parse::<Accept>().unwrap());
306 }
307 }
308
309 let mut route = Route::new(m, "/", dummy_handler);
310 if let Some(mt_str) = mt2.into() {
311 route.format = Some(mt_str.parse::<MediaType>().unwrap());
312 }
313
314 route.matches(&req)
315 }
316
317 #[test]
318 fn test_req_route_mt_collisions() {
319 assert!(req_matches_format(Post, "application/json", "application/json"));
320 assert!(req_matches_format(Post, "application/json", "application/*"));
321 assert!(req_matches_format(Post, "application/json", "*/json"));
322 assert!(req_matches_format(Post, "text/html", "*/*"));
323
324 assert!(req_matches_format(Get, "application/json", "application/json"));
325 assert!(req_matches_format(Get, "text/html", "text/html"));
326 assert!(req_matches_format(Get, "text/html", "*/*"));
327 assert!(req_matches_format(Get, None, "*/*"));
328 assert!(req_matches_format(Get, None, "text/*"));
329 assert!(req_matches_format(Get, None, "text/html"));
330 assert!(req_matches_format(Get, None, "application/json"));
331
332 assert!(req_matches_format(Post, "text/html", None));
333 assert!(req_matches_format(Post, "application/json", None));
334 assert!(req_matches_format(Post, "x-custom/anything", None));
335 assert!(req_matches_format(Post, None, None));
336
337 assert!(req_matches_format(Get, "text/html", None));
338 assert!(req_matches_format(Get, "application/json", None));
339 assert!(req_matches_format(Get, "x-custom/anything", None));
340 assert!(req_matches_format(Get, None, None));
341 assert!(req_matches_format(Get, None, "text/html"));
342 assert!(req_matches_format(Get, None, "application/json"));
343
344 assert!(req_matches_format(Get, "text/html, text/plain", "text/html"));
345 assert!(req_matches_format(Get, "text/html; q=0.5, text/xml", "text/xml"));
346
347 assert!(!req_matches_format(Post, None, "text/html"));
348 assert!(!req_matches_format(Post, None, "text/*"));
349 assert!(!req_matches_format(Post, None, "*/text"));
350 assert!(!req_matches_format(Post, None, "*/*"));
351 assert!(!req_matches_format(Post, None, "text/html"));
352 assert!(!req_matches_format(Post, None, "application/json"));
353
354 assert!(!req_matches_format(Post, "application/json", "text/html"));
355 assert!(!req_matches_format(Post, "application/json", "text/*"));
356 assert!(!req_matches_format(Post, "application/json", "*/xml"));
357 assert!(!req_matches_format(Get, "application/json", "text/html"));
358 assert!(!req_matches_format(Get, "application/json", "text/*"));
359 assert!(!req_matches_format(Get, "application/json", "*/xml"));
360
361 assert!(!req_matches_format(Post, None, "text/html"));
362 assert!(!req_matches_format(Post, None, "application/json"));
363 }
364}