1#[macro_export]
98macro_rules! router {
99 ($request:expr,
103 $(($method:ident) [$url_pattern:expr $(, $param:ident: $param_type:ty)*] => $handle:expr,)*
104 _ => $default:expr $(,)*) => {
105 {
106 let request = &$request;
107
108 let request_url = request.raw_url();
110 let request_url = {
111 let pos = request_url.find('?').unwrap_or(request_url.len());
112 &request_url[..pos]
113 };
114
115 let mut ret = None;
116 $({
117 if ret.is_none() && request.method() == stringify!($method) {
118 ret = $crate::router!(__param_dispatch request_url, $url_pattern => $handle ; $($param: $param_type),*);
119 }
120 })+
121
122 if let Some(ret) = ret {
123 ret
124 } else {
125 $default
126 }
127 }
128 };
129
130 (__param_dispatch $request_url:ident, $url_pattern:expr => $handle:expr ; ) => {
132 $crate::router!(__check_url_match $request_url, $url_pattern => $handle)
133 };
134
135 (__param_dispatch $request_url:ident, $url_pattern:expr => $handle:expr ; $($param:ident: $param_type:ty),*) => {
137 $crate::router!(__check_parse_pattern $request_url, $url_pattern => $handle ; $($param: $param_type),*)
138 };
139
140 (__check_url_match $request_url:ident, $url_pattern:expr => $handle:expr) => {
141 if $request_url == $url_pattern {
142 Some($handle)
143 } else {
144 None
145 }
146 };
147
148 (__check_parse_pattern $request_url_str:ident, $url_pattern:expr => $handle:expr ; $($param:ident: $param_type:ty),*) => {
156 {
157 let request_url = $request_url_str.split("/")
158 .map(|s| $crate::percent_encoding::percent_decode(s.as_bytes()).decode_utf8_lossy().into_owned())
159 .collect::<Vec<_>>();
160 let url_pattern = $url_pattern.split("/").collect::<Vec<_>>();
161 if request_url.len() != url_pattern.len() {
162 None
163 } else {
164 struct RouilleUrlParams {
165 $( $param: Option<$param_type> ),*
166 }
167 impl RouilleUrlParams {
168 fn new() -> Self {
169 Self {
170 $( $param: None ),*
171 }
172 }
173 }
174 let url_params = (|| {
175 let mut url_params = RouilleUrlParams::new();
176 for (actual, desired) in request_url.iter().zip(url_pattern.iter()) {
177 if let Some(key) = desired.strip_prefix("{").and_then(|d| d.strip_suffix("}")) {
178 $crate::router!(__insert_param $request_url_str, url_params, key, actual ; $($param: $param_type)*)
179 } else if actual != desired {
180 return None
181 }
182 }
183 Some(url_params)
184 })();
185 if let Some(url_params) = url_params {
186 $crate::router!(__build_resp $request_url_str, url_params, $handle ; $($param: $param_type)*)
187 } else {
188 None
189 }
190 }
191 }
192 };
193
194 (__insert_param $request_url:ident, $url_params:ident, $key:expr, $actual:expr ; ) => {
198 panic!("Unable to match url parameter name, `{}`, to an `identity: type` pair in url: {:?}", $key, $request_url);
199 };
200
201 (__insert_param $request_url:ident, $url_params:ident, $key:expr, $actual:expr ; $param:tt: $param_type:tt $($params:tt: $param_types:tt)*) => {
204 if $key == stringify!($param) {
205 $crate::router!(__bind_url_param $url_params, $actual, $param, $param_type)
206 } else {
207 $crate::router!(__insert_param $request_url, $url_params, $key, $actual ; $($params: $param_types)*);
208 }
209 };
210
211 (__bind_url_param $url_params:ident, $actual:expr, $param:ident, $param_type:ty) => {
212 {
213 match $actual.parse::<$param_type>() {
214 Ok(value) => $url_params.$param = Some(value),
215 Err(_) => return None,
217 }
218 }
219 };
220
221 (__build_resp $request_url:ident, $url_params:expr, $handle:expr ; ) => {
223 { Some($handle) }
224 };
225
226 (__build_resp $request_url:ident, $url_params:expr, $handle:expr ; $param:tt: $param_type:tt $($params:tt: $param_types:tt)*) => {
228 $crate::router!(__bind_param $request_url, $url_params, $handle, $param: $param_type ; $($params: $param_types)*)
229 };
230
231 (__bind_param $request_url:ident, $url_params:expr, $handle:expr, $param:ident: $param_type:ty ; $($params:tt: $param_types:tt)*) => {
233 {
234 let $param = match $url_params.$param {
235 Some(p) => p,
236 None => {
237 let param_name = stringify!($param);
238 panic!("Url parameter identity, `{}`, does not have a matching `{{{}}}` segment in url: {:?}",
239 param_name, param_name, $request_url);
240 }
241 };
242 $crate::router!(__build_resp $request_url, $url_params, $handle ; $($params: $param_types)*)
243 }
244 };
245
246
247 ($request:expr, $(($method:ident) ($($pat:tt)+) => $value:block,)* _ => $def:expr $(,)*) => {
251 {
252 let request = &$request;
253
254 let request_url = request.raw_url();
256 let request_url = {
257 let pos = request_url.find('?').unwrap_or(request_url.len());
258 &request_url[..pos]
259 };
260
261 let mut ret = None;
262
263 $({
264 if ret.is_none() && request.method() == stringify!($method) {
265 ret = $crate::router!(__check_pattern request_url $value $($pat)+);
266 }
267 })+
268
269 if let Some(ret) = ret {
270 ret
271 } else {
272 $def
273 }
274 }
275 };
276
277 (__check_pattern $url:ident $value:block /{$p:ident} $($rest:tt)*) => (
278 if let Some(url) = $url.strip_prefix('/') {
279 let url = &$url[1..];
280 let pat_end = url.find('/').unwrap_or(url.len());
281 let rest_url = &url[pat_end..];
282
283 if let Ok($p) = url[0 .. pat_end].parse() {
284 $crate::router!(__check_pattern rest_url $value $($rest)*)
285 } else {
286 None
287 }
288 } else {
289 None
290 }
291 );
292
293 (__check_pattern $url:ident $value:block /{$p:ident: $t:ty} $($rest:tt)*) => (
294 if let Some(url) = $url.strip_prefix('/') {
295 let url = &$url[1..];
296 let pat_end = url.find('/').unwrap_or(url.len());
297 let rest_url = &url[pat_end..];
298
299 if let Ok($p) = $crate::percent_encoding::percent_decode(url[0 .. pat_end].as_bytes())
300 .decode_utf8_lossy().parse() {
301 let $p: $t = $p;
302 $crate::router!(__check_pattern rest_url $value $($rest)*)
303 } else {
304 None
305 }
306 } else {
307 None
308 }
309 );
310
311 (__check_pattern $url:ident $value:block /$p:ident $($rest:tt)*) => (
312 {
313 let required = concat!("/", stringify!($p));
314 if let Some(rest_url) = $url.strip_prefix(required) {
315 $crate::router!(__check_pattern rest_url $value $($rest)*)
316 } else {
317 None
318 }
319 }
320 );
321
322 (__check_pattern $url:ident $value:block - $($rest:tt)*) => (
323 {
324 if let Some(rest_url) = $url.strip_prefix('-') {
325 $crate::router!(__check_pattern rest_url $value $($rest)*)
326 } else {
327 None
328 }
329 }
330 );
331
332 (__check_pattern $url:ident $value:block) => (
333 if $url.len() == 0 { Some($value) } else { None }
334 );
335
336 (__check_pattern $url:ident $value:block /) => (
337 if $url == "/" { Some($value) } else { None }
338 );
339
340 (__check_pattern $url:ident $value:block $p:ident $($rest:tt)*) => (
341 {
342 let required = stringify!($p);
343 if let Some(rest_url) = $url.strip_prefix(required) {
344 $crate::router!(__check_pattern rest_url $value $($rest)*)
345 } else {
346 None
347 }
348 }
349 );
350}
351
352#[allow(unused_variables)]
353#[cfg(test)]
354mod tests {
355 use Request;
356
357 #[test]
359 fn old_style_basic() {
360 let request = Request::fake_http("GET", "/", vec![], vec![]);
361
362 assert_eq!(
363 1,
364 router!(request,
365 (GET) (/hello) => { 0 },
366 (GET) (/{_val:u32}) => { 0 },
367 (GET) (/) => { 1 },
368 _ => 0
369 )
370 );
371 }
372
373 #[test]
374 fn old_style_dash() {
375 let request = Request::fake_http("GET", "/a-b", vec![], vec![]);
376
377 assert_eq!(
378 1,
379 router!(request,
380 (GET) (/a/b) => { 0 },
381 (GET) (/a_b) => { 0 },
382 (GET) (/a-b) => { 1 },
383 _ => 0
384 )
385 );
386 }
387
388 #[test]
389 fn old_style_params() {
390 let request = Request::fake_http("GET", "/hello/5", vec![], vec![]);
391
392 assert_eq!(
393 1,
394 router!(request,
395 (GET) (/hello/) => { 0 },
396 (GET) (/hello/{id:u32}) => { if id == 5 { 1 } else { 0 } },
397 (GET) (/hello/{_id:String}) => { 0 },
398 _ => 0
399 )
400 );
401 }
402
403 #[test]
404 fn old_style_trailing_comma() {
405 let request = Request::fake_http("GET", "/hello/5", vec![], vec![]);
406
407 assert_eq!(
408 1,
409 router!(request,
410 (GET) (/hello/) => { 0 },
411 (GET) (/hello/{id:u32}) => { if id == 5 { 1 } else { 0 } },
412 (GET) (/hello/{_id:String}) => { 0 },
413 _ => 0,
414 )
415 );
416 }
417
418 #[test]
419 fn old_style_trailing_commas() {
420 let request = Request::fake_http("GET", "/hello/5", vec![], vec![]);
421
422 assert_eq!(
423 1,
424 router!(request,
425 (GET) (/hello/) => { 0 },
426 (GET) (/hello/{id:u32}) => { if id == 5 { 1 } else { 0 } },
427 (GET) (/hello/{_id:String}) => { 0 },
428 _ => 0,,,,
429 )
430 );
431 }
432
433 #[test]
435 fn multiple_params() {
436 let request = Request::fake_http("GET", "/math/3.2/plus/4", vec![], vec![]);
437 let resp = router!(request,
438 (GET) ["/hello"] => { 1. },
439 (GET) ["/math/{a}/plus/{b}", a: u32 , b: u32] => { 7. },
440 (GET) ["/math/{a}/plus/{b}", a: f32 , b: u32] => { a + (b as f32) },
441 _ => 0.
442 );
443 assert_eq!(7.2, resp);
444 }
445
446 #[test]
447 fn basic() {
448 let request = Request::fake_http("GET", "/", vec![], vec![]);
449
450 assert_eq!(
451 1,
452 router!(request,
453 (GET) ["/hello"] => { 0 },
454 (GET) ["/{_val}", _val: u32] => { 0 },
455 (GET) ["/"] => { 1 },
456 _ => 0
457 )
458 );
459 }
460
461 #[test]
462 fn dash() {
463 let request = Request::fake_http("GET", "/a-b", vec![], vec![]);
464
465 assert_eq!(
466 1,
467 router!(request,
468 (GET) ["/a/b"] => { 0 },
469 (GET) ["/a_b"] => { 0 },
470 (GET) ["/a-b"] => { 1 },
471 _ => 0
472 )
473 );
474 }
475
476 #[test]
477 fn numbers() {
478 let request = Request::fake_http("GET", "/5", vec![], vec![]);
479
480 assert_eq!(
481 1,
482 router!(request,
483 (GET) ["/a"] => { 0 },
484 (GET) ["/3"] => { 0 },
485 (GET) ["/5"] => { 1 },
486 _ => 0
487 )
488 );
489 }
490
491 #[test]
492 fn trailing_comma() {
493 let request = Request::fake_http("GET", "/5", vec![], vec![]);
494
495 assert_eq!(
496 1,
497 router!(request,
498 (GET) ["/a"] => { 0 },
499 (GET) ["/3"] => { 0 },
500 (GET) ["/5"] => { 1 },
501 _ => 0,
502 )
503 );
504 }
505
506 #[test]
507 fn trailing_commas() {
508 let request = Request::fake_http("GET", "/5", vec![], vec![]);
509
510 assert_eq!(
511 1,
512 router!(request,
513 (GET) ["/a"] => { 0 },
514 (GET) ["/3"] => { 0 },
515 (GET) ["/5"] => { 1 },
516 _ => 0,,,,
517 )
518 );
519 }
520
521 #[test]
522 fn files() {
523 let request = Request::fake_http("GET", "/robots.txt", vec![], vec![]);
524
525 assert_eq!(
526 1,
527 router!(request,
528 (GET) ["/a"] => { 0 },
529 (GET) ["/3/2/1"] => { 0 },
530 (GET) ["/robots.txt"] => { 1 },
531 _ => 0
532 )
533 );
534 }
535
536 #[test]
537 fn skip_failed_parse_float() {
538 let request = Request::fake_http("GET", "/hello/5.1", vec![], vec![]);
539
540 assert_eq!(
541 1,
542 router!(request,
543 (GET) ["/hello/"] => { 0 },
544 (GET) ["/hello/{_id}", _id: u32] => { 0 },
545 (GET) ["/hello/{id}", id: f32] => { if id == 5.1 { 1 } else { 0 } },
546 _ => 0
547 )
548 );
549 }
550
551 #[test]
552 fn skip_failed_parse_string() {
553 let request = Request::fake_http("GET", "/word/wow", vec![], vec![]);
554 let resp = router!(request,
555 (GET) ["/hello"] => { "hello".to_string() },
556 (GET) ["/word/{int}", int: u32] => { int.to_string() },
557 (GET) ["/word/{word}", word: String] => { word },
558 _ => "default".to_string()
559 );
560 assert_eq!("wow", resp);
561 }
562
563 #[test]
564 fn url_parameter_ownership() {
565 let request = Request::fake_http("GET", "/word/one/two/three/four", vec![], vec![]);
566 let resp = router!(request,
567 (GET) ["/hello"] => { "hello".to_string() },
568 (GET) ["/word/{int}", int: u32] => { int.to_string() },
569 (GET) ["/word/{a}/{b}/{c}/{d}", a: String, b: String, c: String, d: String] => {
570 fn expects_strings(a: String, b: String, c: String, d: String) -> String {
571 format!("{}{}{}{}", a, b, c, d)
572 }
573 expects_strings(a, b, c, d)
574 },
575 _ => "default".to_string()
576 );
577 assert_eq!("onetwothreefour", resp);
578 }
579
580 #[test]
581 #[should_panic(
582 expected = "Url parameter identity, `id`, does not have a matching `{id}` segment in url: \"/hello/james\""
583 )]
584 fn identity_not_present_in_url_string() {
585 let request = Request::fake_http("GET", "/hello/james", vec![], vec![]);
586
587 assert_eq!(
588 1,
589 router!(request,
590 (GET) ["/hello/"] => { 0 },
591 (GET) ["/hello/{name}", name: String, id: u32] => { 1 }, _ => 0
593 )
594 );
595 }
596
597 #[test]
598 #[should_panic(
599 expected = "Unable to match url parameter name, `name`, to an `identity: type` pair in url: \"/hello/1/james\""
600 )]
601 fn parameter_with_no_matching_identity() {
602 let request = Request::fake_http("GET", "/hello/1/james", vec![], vec![]);
603
604 assert_eq!(
605 1,
606 router!(request,
607 (GET) ["/hello/"] => { 0 },
608 (GET) ["/hello/{id}/{name}"] => { 0 }, (GET) ["/hello/{id}/{name}", id: u32] => { id }, _ => 0
611 )
612 );
613 }
614
615 #[test]
616 fn encoded() {
617 let request = Request::fake_http("GET", "/hello/%3Fa/test", vec![], vec![]);
618
619 assert_eq!(
620 "?a",
621 router!(request,
622 (GET) ["/hello/{val}/test", val: String] => { val },
623 _ => String::from(""))
624 );
625 }
626
627 #[test]
628 fn encoded_old() {
629 let request = Request::fake_http("GET", "/hello/%3Fa/test", vec![], vec![]);
630
631 assert_eq!(
632 "?a",
633 router!(request,
634 (GET) (/hello/{val: String}/test) => { val },
635 _ => String::from(""))
636 );
637 }
638
639 #[test]
640 fn param_slash() {
641 let request = Request::fake_http("GET", "/hello%2F5", vec![], vec![]);
642
643 router!(request,
644 (GET) ["/{a}", a: String] => { assert_eq!(a, "hello/5") },
645 _ => panic!()
646 );
647 }
648}