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