matchit 0.7.3

A high performance, zero-copy URL router.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
use matchit::{InsertError, MatchError, Router};

#[test]
fn issue_31() {
    let mut router = Router::new();
    router.insert("/path/foo/:arg", "foo").unwrap();
    router.insert("/path/*rest", "wildcard").unwrap();

    assert_eq!(
        router.at("/path/foo/myarg/bar/baz").map(|m| *m.value),
        Ok("wildcard")
    );
}

#[test]
fn issue_22() {
    let mut x = Router::new();
    x.insert("/foo_bar", "Welcome!").unwrap();
    x.insert("/foo/bar", "Welcome!").unwrap();
    assert_eq!(x.at("/foo/").unwrap_err(), MatchError::NotFound);

    let mut x = Router::new();
    x.insert("/foo", "Welcome!").unwrap();
    x.insert("/foo/bar", "Welcome!").unwrap();
    assert_eq!(x.at("/foo/").unwrap_err(), MatchError::ExtraTrailingSlash);
}

match_tests! {
    basic {
        routes = [
            "/hi",
            "/contact",
            "/co",
            "/c",
            "/a",
            "/ab",
            "/doc/",
            "/doc/rust_faq.html",
            "/doc/rust1.26.html",
            "",
            "",
            "/sd!here",
            "/sd$here",
            "/sd&here",
            "/sd'here",
            "/sd(here",
            "/sd)here",
            "/sd+here",
            "/sd,here",
            "/sd;here",
            "/sd=here",
        ],
        "/a"       :: "/a"       => {},
        ""         :: "/"        => None,
        "/hi"      :: "/hi"      => {},
        "/contact" :: "/contact" => {},
        "/co"      :: "/co"      => {},
        ""         :: "/con"     => None,
        ""         :: "/cona"    => None,
        ""         :: "/no"      => None,
        "/ab"      :: "/ab"      => {},
        ""       :: ""       => {},
        ""       :: ""       => {},
        "/sd!here" :: "/sd!here" => {},
        "/sd$here" :: "/sd$here" => {},
        "/sd&here" :: "/sd&here" => {},
        "/sd'here" :: "/sd'here" => {},
        "/sd(here" :: "/sd(here" => {},
        "/sd)here" :: "/sd)here" => {},
        "/sd+here" :: "/sd+here" => {},
        "/sd,here" :: "/sd,here" => {},
        "/sd;here" :: "/sd;here" => {},
        "/sd=here" :: "/sd=here" => {},
    },
    wildcard {
        routes = [
            "/",
            "/cmd/:tool/",
            "/cmd/:tool2/:sub",
            "/cmd/whoami",
            "/cmd/whoami/root",
            "/cmd/whoami/root/",
            "/src",
            "/src/",
            "/src/*filepath",
            "/search/",
            "/search/:query",
            "/search/actix-web",
            "/search/google",
            "/user_:name",
            "/user_:name/about",
            "/files/:dir/*filepath",
            "/doc/",
            "/doc/rust_faq.html",
            "/doc/rust1.26.html",
            "/info/:user/public",
            "/info/:user/project/:project",
            "/info/:user/project/rustlang",
            "/aa/*xx",
            "/ab/*xx",
            "/:cc",
            "/c1/:dd/e",
            "/c1/:dd/e1",
            "/:cc/cc",
            "/:cc/:dd/ee",
            "/:cc/:dd/:ee/ff",
            "/:cc/:dd/:ee/:ff/gg",
            "/:cc/:dd/:ee/:ff/:gg/hh",
            "/get/test/abc/",
            "/get/:param/abc/",
            "/something/:paramname/thirdthing",
            "/something/secondthing/test",
            "/get/abc",
            "/get/:param",
            "/get/abc/123abc",
            "/get/abc/:param",
            "/get/abc/123abc/xxx8",
            "/get/abc/123abc/:param",
            "/get/abc/123abc/xxx8/1234",
            "/get/abc/123abc/xxx8/:param",
            "/get/abc/123abc/xxx8/1234/ffas",
            "/get/abc/123abc/xxx8/1234/:param",
            "/get/abc/123abc/xxx8/1234/kkdd/12c",
            "/get/abc/123abc/xxx8/1234/kkdd/:param",
            "/get/abc/:param/test",
            "/get/abc/123abd/:param",
            "/get/abc/123abddd/:param",
            "/get/abc/123/:param",
            "/get/abc/123abg/:param",
            "/get/abc/123abf/:param",
            "/get/abc/123abfff/:param",
        ],
        "/"                                     :: "/"                                     => {},
        "/cmd/test"                             :: "/cmd/:tool/"                           => None,
        "/cmd/test/"                            :: "/cmd/:tool/"                           => { "tool" => "test" },
        "/cmd/test/3"                           :: "/cmd/:tool2/:sub"                       => { "tool2" => "test", "sub" => "3" },
        "/cmd/who"                              :: "/cmd/:tool/"                           => None,
        "/cmd/who/"                             :: "/cmd/:tool/"                           => { "tool" => "who" },
        "/cmd/whoami"                           :: "/cmd/whoami"                           => {},
        "/cmd/whoami/"                          :: "/cmd/whoami"                           => None,
        "/cmd/whoami/r"                         :: "/cmd/:tool2/:sub"                       => { "tool2" => "whoami", "sub" => "r" },
        "/cmd/whoami/r/"                        :: "/cmd/:tool/:sub"                       => None,
        "/cmd/whoami/root"                      :: "/cmd/whoami/root"                      => {},
        "/cmd/whoami/root/"                     :: "/cmd/whoami/root/"                     => {},
        "/src"                                  :: "/src"                                  => {},
        "/src/"                                 :: "/src/"                                 => {},
        "/src/some/file.png"                    :: "/src/*filepath"                        => { "filepath" => "some/file.png" },
        "/search/"                              :: "/search/"                              => {},
        "/search/actix"                         :: "/search/:query"                        => { "query" => "actix" },
        "/search/actix-web"                     :: "/search/actix-web"                     => {},
        "/search/someth!ng+in+ünìcodé"          :: "/search/:query"                        => { "query" => "someth!ng+in+ünìcodé" },
        "/search/someth!ng+in+ünìcodé/"         :: ""                                      => None,
        "/user_rustacean"                       :: "/user_:name"                           => { "name" => "rustacean" },
        "/user_rustacean/about"                 :: "/user_:name/about"                     => { "name" => "rustacean" },
        "/files/js/inc/framework.js"            :: "/files/:dir/*filepath"                 => { "dir" => "js", "filepath" => "inc/framework.js" },
        "/info/gordon/public"                   :: "/info/:user/public"                    => { "user" => "gordon" },
        "/info/gordon/project/rust"             :: "/info/:user/project/:project"          => { "user" => "gordon", "project" => "rust" } ,
        "/info/gordon/project/rustlang"         :: "/info/:user/project/rustlang"          => { "user" => "gordon" },
        "/aa/"                                  :: "/"                                     => None,
        "/aa/aa"                                :: "/aa/*xx"                               => { "xx" => "aa" },
        "/ab/ab"                                :: "/ab/*xx"                               => { "xx" => "ab" },
        "/a"                                    :: "/:cc"                                  => { "cc" => "a" },
        "/all"                                  :: "/:cc"                                  => { "cc" => "all" },
        "/d"                                    :: "/:cc"                                  => { "cc" => "d" },
        "/ad"                                   :: "/:cc"                                  => { "cc" => "ad" },
        "/dd"                                   :: "/:cc"                                  => { "cc" => "dd" },
        "/dddaa"                                :: "/:cc"                                  => { "cc" => "dddaa" },
        "/aa"                                   :: "/:cc"                                  => { "cc" => "aa" },
        "/aaa"                                  :: "/:cc"                                  => { "cc" => "aaa" },
        "/aaa/cc"                               :: "/:cc/cc"                               => { "cc" => "aaa" },
        "/ab"                                   :: "/:cc"                                  => { "cc" => "ab" },
        "/abb"                                  :: "/:cc"                                  => { "cc" => "abb" },
        "/abb/cc"                               :: "/:cc/cc"                               => { "cc" => "abb" },
        "/allxxxx"                              :: "/:cc"                                  => { "cc" => "allxxxx" },
        "/alldd"                                :: "/:cc"                                  => { "cc" => "alldd" },
        "/all/cc"                               :: "/:cc/cc"                               => { "cc" => "all" },
        "/a/cc"                                 :: "/:cc/cc"                               => { "cc" => "a" },
        "/c1/d/e"                               :: "/c1/:dd/e"                             => { "dd" => "d" },
        "/c1/d/e1"                              :: "/c1/:dd/e1"                            => { "dd" => "d" },
        "/c1/d/ee"                              :: "/:cc/:dd/ee"                           => { "cc" => "c1", "dd" => "d" },
        "/cc/cc"                                :: "/:cc/cc"                               => { "cc" => "cc" },
        "/ccc/cc"                               :: "/:cc/cc"                               => { "cc" => "ccc" },
        "/deedwjfs/cc"                          :: "/:cc/cc"                               => { "cc" => "deedwjfs" },
        "/acllcc/cc"                            :: "/:cc/cc"                               => { "cc" => "acllcc" },
        "/get/test/abc/"                        :: "/get/test/abc/"                        => {},
        "/get/te/abc/"                          :: "/get/:param/abc/"                      => { "param" => "te" },
        "/get/testaa/abc/"                      :: "/get/:param/abc/"                      => { "param" => "testaa" },
        "/get/xx/abc/"                          :: "/get/:param/abc/"                      => { "param" => "xx" },
        "/get/tt/abc/"                          :: "/get/:param/abc/"                      => { "param" => "tt" },
        "/get/a/abc/"                           :: "/get/:param/abc/"                      => { "param" => "a" },
        "/get/t/abc/"                           :: "/get/:param/abc/"                      => { "param" => "t" },
        "/get/aa/abc/"                          :: "/get/:param/abc/"                      => { "param" => "aa" },
        "/get/abas/abc/"                        :: "/get/:param/abc/"                      => { "param" => "abas" },
        "/something/secondthing/test"           :: "/something/secondthing/test"           => {},
        "/something/abcdad/thirdthing"          :: "/something/:paramname/thirdthing"      => { "paramname" => "abcdad" },
        "/something/secondthingaaaa/thirdthing" :: "/something/:paramname/thirdthing"      => { "paramname" => "secondthingaaaa" },
        "/something/se/thirdthing"              :: "/something/:paramname/thirdthing"      => { "paramname" => "se" },
        "/something/s/thirdthing"               :: "/something/:paramname/thirdthing"      => { "paramname" => "s" },
        "/c/d/ee"                               :: "/:cc/:dd/ee"                           => { "cc" => "c", "dd" => "d" },
        "/c/d/e/ff"                             :: "/:cc/:dd/:ee/ff"                       => { "cc" => "c", "dd" => "d", "ee" => "e" },
        "/c/d/e/f/gg"                           :: "/:cc/:dd/:ee/:ff/gg"                   => { "cc" => "c", "dd" => "d", "ee" => "e", "ff" => "f" },
        "/c/d/e/f/g/hh"                         :: "/:cc/:dd/:ee/:ff/:gg/hh"               => { "cc" => "c", "dd" => "d", "ee" => "e", "ff" => "f", "gg" => "g" },
        "/cc/dd/ee/ff/gg/hh"                    :: "/:cc/:dd/:ee/:ff/:gg/hh"               => { "cc" => "cc", "dd" => "dd", "ee" => "ee", "ff" => "ff", "gg" => "gg" },
        "/get/abc"                              :: "/get/abc"                              => {},
        "/get/a"                                :: "/get/:param"                           => { "param" => "a" },
        "/get/abz"                              :: "/get/:param"                           => { "param" => "abz" },
        "/get/12a"                              :: "/get/:param"                           => { "param" => "12a" },
        "/get/abcd"                             :: "/get/:param"                           => { "param" => "abcd" },
        "/get/abc/123abc"                       :: "/get/abc/123abc"                       => {},
        "/get/abc/12"                           :: "/get/abc/:param"                       => { "param" => "12" },
        "/get/abc/123ab"                        :: "/get/abc/:param"                       => { "param" => "123ab" },
        "/get/abc/xyz"                          :: "/get/abc/:param"                       => { "param" => "xyz" },
        "/get/abc/123abcddxx"                   :: "/get/abc/:param"                       => { "param" => "123abcddxx" },
        "/get/abc/123abc/xxx8"                  :: "/get/abc/123abc/xxx8"                  => {},
        "/get/abc/123abc/x"                     :: "/get/abc/123abc/:param"                => { "param" => "x" },
        "/get/abc/123abc/xxx"                   :: "/get/abc/123abc/:param"                => { "param" => "xxx" },
        "/get/abc/123abc/abc"                   :: "/get/abc/123abc/:param"                => { "param" => "abc" },
        "/get/abc/123abc/xxx8xxas"              :: "/get/abc/123abc/:param"                => { "param" => "xxx8xxas" },
        "/get/abc/123abc/xxx8/1234"             :: "/get/abc/123abc/xxx8/1234"             => {},
        "/get/abc/123abc/xxx8/1"                :: "/get/abc/123abc/xxx8/:param"           => { "param" => "1" },
        "/get/abc/123abc/xxx8/123"              :: "/get/abc/123abc/xxx8/:param"           => { "param" => "123" },
        "/get/abc/123abc/xxx8/78k"              :: "/get/abc/123abc/xxx8/:param"           => { "param" => "78k" },
        "/get/abc/123abc/xxx8/1234xxxd"         :: "/get/abc/123abc/xxx8/:param"           => { "param" => "1234xxxd" },
        "/get/abc/123abc/xxx8/1234/ffas"        :: "/get/abc/123abc/xxx8/1234/ffas"        => {},
        "/get/abc/123abc/xxx8/1234/f"           :: "/get/abc/123abc/xxx8/1234/:param"      => { "param" => "f" },
        "/get/abc/123abc/xxx8/1234/ffa"         :: "/get/abc/123abc/xxx8/1234/:param"      => { "param" => "ffa" },
        "/get/abc/123abc/xxx8/1234/kka"         :: "/get/abc/123abc/xxx8/1234/:param"      => { "param" => "kka" },
        "/get/abc/123abc/xxx8/1234/ffas321"     :: "/get/abc/123abc/xxx8/1234/:param"      => { "param" => "ffas321" },
        "/get/abc/123abc/xxx8/1234/kkdd/12c"    :: "/get/abc/123abc/xxx8/1234/kkdd/12c"    => {},
        "/get/abc/123abc/xxx8/1234/kkdd/1"      :: "/get/abc/123abc/xxx8/1234/kkdd/:param" => { "param" => "1" },
        "/get/abc/123abc/xxx8/1234/kkdd/12"     :: "/get/abc/123abc/xxx8/1234/kkdd/:param" => { "param" => "12" },
        "/get/abc/123abc/xxx8/1234/kkdd/12b"    :: "/get/abc/123abc/xxx8/1234/kkdd/:param" => { "param" => "12b" },
        "/get/abc/123abc/xxx8/1234/kkdd/34"     :: "/get/abc/123abc/xxx8/1234/kkdd/:param" => { "param" => "34" },
        "/get/abc/123abc/xxx8/1234/kkdd/12c2e3" :: "/get/abc/123abc/xxx8/1234/kkdd/:param" => { "param" => "12c2e3" },
        "/get/abc/12/test"                      :: "/get/abc/:param/test"                  => { "param" => "12" },
        "/get/abc/123abdd/test"                 :: "/get/abc/:param/test"                  => { "param" => "123abdd" },
        "/get/abc/123abdddf/test"               :: "/get/abc/:param/test"                  => { "param" => "123abdddf" },
        "/get/abc/123ab/test"                   :: "/get/abc/:param/test"                  => { "param" => "123ab" },
        "/get/abc/123abgg/test"                 :: "/get/abc/:param/test"                  => { "param" => "123abgg" },
        "/get/abc/123abff/test"                 :: "/get/abc/:param/test"                  => { "param" => "123abff" },
        "/get/abc/123abffff/test"               :: "/get/abc/:param/test"                  => { "param" => "123abffff" },
        "/get/abc/123abd/test"                  :: "/get/abc/123abd/:param"                => { "param" => "test" },
        "/get/abc/123abddd/test"                :: "/get/abc/123abddd/:param"              => { "param" => "test" },
        "/get/abc/123/test22"                   :: "/get/abc/123/:param"                   => { "param" => "test22" },
        "/get/abc/123abg/test"                  :: "/get/abc/123abg/:param"                => { "param" => "test" },
        "/get/abc/123abf/testss"                :: "/get/abc/123abf/:param"                => { "param" => "testss" },
        "/get/abc/123abfff/te"                  :: "/get/abc/123abfff/:param"              => { "param" => "te" },
    },
    normalized {
        routes = [
            "/x/:foo/bar",
            "/x/:bar/baz",
            "/:foo/:baz/bax",
            "/:foo/:bar/baz",
            "/:fod/:baz/:bax/foo",
            "/:fod/baz/bax/foo",
            "/:foo/baz/bax",
            "/:bar/:bay/bay",
            "/s",
            "/s/s",
            "/s/s/s",
            "/s/s/s/s",
            "/s/s/:s/x",
            "/s/s/:y/d",
        ],
        "/x/foo/bar"      :: "/x/:foo/bar"         => { "foo" => "foo" },
        "/x/foo/baz"      :: "/x/:bar/baz"         => { "bar" => "foo" },
        "/y/foo/baz"      :: "/:foo/:bar/baz"      => { "foo" => "y", "bar" => "foo" },
        "/y/foo/bax"      :: "/:foo/:baz/bax"      => { "foo" => "y", "baz" => "foo" },
        "/y/baz/baz"      :: "/:foo/:bar/baz"      => { "foo" => "y", "bar" => "baz" },
        "/y/baz/bax/foo"  :: "/:fod/baz/bax/foo"   => { "fod" => "y" },
        "/y/baz/b/foo"    :: "/:fod/:baz/:bax/foo" => { "fod" => "y", "baz" => "baz", "bax" => "b" },
        "/y/baz/bax"      :: "/:foo/baz/bax"       => { "foo" => "y" },
        "/z/bar/bay"      :: "/:bar/:bay/bay"      => { "bar" => "z", "bay" => "bar" },
        "/s"              :: "/s"                  => { },
        "/s/s"            :: "/s/s"                => { },
        "/s/s/s"          :: "/s/s/s"              => { },
        "/s/s/s/s"        :: "/s/s/s/s"            => { },
        "/s/s/s/x"        :: "/s/s/:s/x"           => { "s" => "s" },
        "/s/s/s/d"        :: "/s/s/:y/d"           => { "y" => "s" },
    },
    blog {
        routes = [
            "/:page",
            "/posts/:year/:month/:post",
            "/posts/:year/:month/index",
            "/posts/:year/top",
            "/static/*path",
            "/favicon.ico",
        ],
        "/about"                :: "/:page"                    => { "page" => "about" },
        "/posts/2021/01/rust"   :: "/posts/:year/:month/:post" => { "year" => "2021", "month" => "01", "post" => "rust" },
        "/posts/2021/01/index"  :: "/posts/:year/:month/index" => { "year" => "2021", "month" => "01" },
        "/posts/2021/top"       :: "/posts/:year/top"          => { "year" => "2021" },
        "/static/foo.png"       :: "/static/*path"             => { "path" => "foo.png" },
        "/favicon.ico"          :: "/favicon.ico"              => {},
    },
    double_overlap {
        routes = [
            "/:object/:id",
            "/secret/:id/path",
            "/secret/978",
            "/other/:object/:id/",
            "/other/an_object/:id",
            "/other/static/path",
            "/other/long/static/path/"
        ],
        "/secret/978/path"                :: "/secret/:id/path"                    => { "id" => "978" },
        "/some_object/978"                :: "/:object/:id"                        => { "object" => "some_object", "id" => "978" },
        "/secret/978"                     :: "/secret/978"                         => {},
        "/super_secret/978/"              :: "/:object/:id"                        => None,
        "/other/object/1/"                :: "/other/:object/:id/"                 => { "object" => "object", "id" => "1" },
        "/other/object/1/2"               :: "/other/:object/:id"                  => None,
        "/other/an_object/1"              :: "/other/an_object/:id"                => { "id" => "1" },
        "/other/static/path"              :: "/other/static/path"                  => {},
        "/other/long/static/path/"        :: "/other/long/static/path/"            => {},
    },
    catchall_off_by_one {
        routes = [
            "/foo/*catchall",
            "/bar",
            "/bar/",
            "/bar/*catchall",
        ],
        "/foo"   :: ""               => None,
        "/foo/"  :: ""               => None,
        "/foo/x" :: "/foo/*catchall" => { "catchall" => "x" },
        "/bar"   :: "/bar"           => {},
        "/bar/"  :: "/bar/"          => {},
        "/bar/x" :: "/bar/*catchall" => { "catchall" => "x" },
    },
    catchall_static_overlap {
        routes = [
            "/foo",
            "/bar",
            "/*bar",
            "/baz",
            "/baz/",
            "/baz/x",
            "/baz/:xxx",
            "/",
            "/xxx/*x",
            "/xxx/",
        ],
        "/foo"    :: "/foo"    => {},
        "/bar"    :: "/bar"    => {},
        "/baz"    :: "/baz"    => {},
        "/baz/"   :: "/baz/"   => {},
        "/baz/x"  :: "/baz/x"  => {},
        "/???"    :: "/*bar"   => { "bar" => "???" },
        "/"       :: "/"       => {},
        ""        :: ""        => None,
        "/xxx/y"  :: "/xxx/*x" => { "x" => "y" },
        "/xxx/"   :: "/xxx/"   => {},
        "/xxx"    :: ""        => None
    }
}

// https://github.com/ibraheemdev/matchit/issues/12
#[test]
fn issue_12() {
    let mut matcher = Router::new();

    matcher.insert("/:object/:id", "object with id").unwrap();
    matcher
        .insert("/secret/:id/path", "secret with id and path")
        .unwrap();

    let matched = matcher.at("/secret/978/path").unwrap();
    assert_eq!(matched.params.get("id"), Some("978"));

    let matched = matcher.at("/something/978").unwrap();
    assert_eq!(matched.params.get("id"), Some("978"));
    assert_eq!(matched.params.get("object"), Some("something"));

    let matched = matcher.at("/secret/978").unwrap();
    assert_eq!(matched.params.get("id"), Some("978"));
}

insert_tests! {
    wildcard_conflict {
        "/cmd/:tool/:sub"     => Ok(()),
        "/cmd/vet"            => Ok(()),
        "/foo/bar"            => Ok(()),
        "/foo/:name"          => Ok(()),
        "/foo/:names"         => Err(InsertError::Conflict { with: "/foo/:name".into() }),
        "/cmd/*path"          => Err(InsertError::Conflict { with: "/cmd/:tool/:sub".into() }),
        "/cmd/:xxx/names"     => Ok(()),
        "/cmd/:tool/:xxx/foo" => Ok(()),
        "/src/*filepath"      => Ok(()),
        "/src/:file"          => Err(InsertError::Conflict { with: "/src/*filepath".into() }),
        "/src/static.json"    => Ok(()),
        "/src/$filepathx"     => Ok(()),
        "/src/"               => Ok(()),
        "/src/foo/bar"        => Ok(()),
        "/src1/"              => Ok(()),
        "/src1/*filepath"     => Ok(()),
        "/src2*filepath"      => Err(InsertError::InvalidCatchAll),
        "/src2/*filepath"     => Ok(()),
        "/src2/"              => Ok(()),
        "/src2"               => Ok(()),
        "/src3"               => Ok(()),
        "/src3/*filepath"     => Ok(()),
        "/search/:query"      => Ok(()),
        "/search/valid"       => Ok(()),
        "/user_:name"         => Ok(()),
        "/user_x"             => Ok(()),
        "/user_:bar"          => Err(InsertError::Conflict { with: "/user_:name".into() }),
        "/id:id"              => Ok(()),
        "/id/:id"             => Ok(()),
    },
    invalid_catchall {
        "/non-leading-*catchall" => Err(InsertError::InvalidCatchAll),
        "/foo/bar*catchall"      => Err(InsertError::InvalidCatchAll),
        "/src/*filepath/x"       => Err(InsertError::InvalidCatchAll),
        "/src2/"                 => Ok(()),
        "/src2/*filepath/x"      => Err(InsertError::InvalidCatchAll),
    },
    invalid_catchall2 {
        "*x" => Err(InsertError::InvalidCatchAll)
    },
    catchall_root_conflict {
        "/"          => Ok(()),
        "/*filepath" => Ok(()),
    },
    child_conflict {
        "/cmd/vet"        => Ok(()),
        "/cmd/:tool"      => Ok(()),
        "/cmd/:tool/:sub" => Ok(()),
        "/cmd/:tool/misc" => Ok(()),
        "/cmd/:tool/:bad" => Err(InsertError::Conflict { with: "/cmd/:tool/:sub".into() }),
        "/src/AUTHORS"    => Ok(()),
        "/src/*filepath"  => Ok(()),
        "/user_x"         => Ok(()),
        "/user_:name"     => Ok(()),
        "/id/:id"         => Ok(()),
        "/id:id"          => Ok(()),
        "/:id"            => Ok(()),
        "/*filepath"      => Err(InsertError::Conflict { with: "/:id".into() }),
    },
    duplicates {
        "/"              => Ok(()),
        "/"              => Err(InsertError::Conflict { with: "/".into() }),
        "/doc/"          => Ok(()),
        "/doc/"          => Err(InsertError::Conflict { with: "/doc/".into() }),
        "/src/*filepath" => Ok(()),
        "/src/*filepath" => Err(InsertError::Conflict { with: "/src/*filepath".into() }),
        "/search/:query" => Ok(()),
        "/search/:query" => Err(InsertError::Conflict { with: "/search/:query".into() }),
        "/user_:name"    => Ok(()),
        "/user_:name"    => Err(InsertError::Conflict { with: "/user_:name".into() }),
    },
    unnamed_param {
        "/user:"  => Err(InsertError::UnnamedParam),
        "/user:/" => Err(InsertError::UnnamedParam),
        "/cmd/:/" => Err(InsertError::UnnamedParam),
        "/src/*"  => Err(InsertError::UnnamedParam),
    },
    double_params {
        "/:foo:bar"  => Err(InsertError::TooManyParams),
        "/:foo:bar/" => Err(InsertError::TooManyParams),
        "/:foo*bar/" => Err(InsertError::TooManyParams),
    },
    normalized_conflict {
        "/x/:foo/bar"  => Ok(()),
        "/x/:bar/bar"  => Err(InsertError::Conflict { with: "/x/:foo/bar".into() }),
        "/:y/bar/baz"  => Ok(()),
        "/:y/baz/baz"  => Ok(()),
        "/:z/bar/bat"  => Ok(()),
        "/:z/bar/baz"  => Err(InsertError::Conflict { with: "/:y/bar/baz".into() }),
    },
    more_conflicts {
        "/con:tact"           => Ok(()),
        "/who/are/*you"       => Ok(()),
        "/who/foo/hello"      => Ok(()),
        "/whose/:users/:name" => Ok(()),
        "/who/are/foo"        => Ok(()),
        "/who/are/foo/bar"    => Ok(()),
        "/con:nection"        => Err(InsertError::Conflict { with: "/con:tact".into() }),
        "/whose/:users/:user" => Err(InsertError::Conflict { with: "/whose/:users/:name".into() }),
    },
    catchall_static_overlap1 {
        "/bar"      => Ok(()),
        "/bar/"     => Ok(()),
        "/bar/*foo" => Ok(()),
    },
    catchall_static_overlap2 {
        "/foo"            => Ok(()),
        "/*bar"           => Ok(()),
        "/bar"            => Ok(()),
        "/baz"            => Ok(()),
        "/baz/:split"     => Ok(()),
        "/"               => Ok(()),
        "/*bar"           => Err(InsertError::Conflict { with: "/*bar".into() }),
        "/*zzz"           => Err(InsertError::Conflict { with: "/*bar".into() }),
        "/:xxx"           => Err(InsertError::Conflict { with: "/*bar".into() }),
    },
    catchall_static_overlap3 {
        "/*bar"           => Ok(()),
        "/bar"            => Ok(()),
        "/bar/x"          => Ok(()),
        "/bar_:x"         => Ok(()),
        "/bar_:x"         => Err(InsertError::Conflict { with: "/bar_:x".into() }),
        "/bar_:x/y"       => Ok(()),
        "/bar/:x"         => Ok(()),
    },
}

tsr_tests! {
    tsr {
        routes = [
            "/hi",
            "/b/",
            "/search/:query",
            "/cmd/:tool/",
            "/src/*filepath",
            "/x",
            "/x/y",
            "/y/",
            "/y/z",
            "/0/:id",
            "/0/:id/1",
            "/1/:id/",
            "/1/:id/2",
            "/aa",
            "/a/",
            "/admin",
            "/admin/static",
            "/admin/:category",
            "/admin/:category/:page",
            "/doc",
            "/doc/rust_faq.html",
            "/doc/rust1.26.html",
            "/no/a",
            "/no/b",
            "/no/a/b/*other",
            "/api/:page/:name",
            "/api/hello/:name/bar/",
            "/api/bar/:name",
            "/api/baz/foo",
            "/api/baz/foo/bar",
            "/foo/:p",
        ],
        "/hi/"               => ExtraTrailingSlash,
        "/b"                 => MissingTrailingSlash,
        "/search/rustacean/" => ExtraTrailingSlash,
        "/cmd/vet"           => MissingTrailingSlash,
        "/src"               => NotFound,
        "/src/"              => NotFound,
        "/x/"                => ExtraTrailingSlash,
        "/y"                 => MissingTrailingSlash,
        "/0/rust/"           => ExtraTrailingSlash,
        "/1/rust"            => MissingTrailingSlash,
        "/a"                 => MissingTrailingSlash,
        "/admin/"            => ExtraTrailingSlash,
        "/doc/"              => ExtraTrailingSlash,
        "/admin/static/"     => ExtraTrailingSlash,
        "/admin/cfg/"        => ExtraTrailingSlash,
        "/admin/cfg/users/"  => ExtraTrailingSlash,
        "/api/hello/x/bar"   => MissingTrailingSlash,
        "/api/baz/foo/"      => ExtraTrailingSlash,
        "/api/baz/bax/"      => ExtraTrailingSlash,
        "/api/bar/huh/"      => ExtraTrailingSlash,
        "/api/baz/foo/bar/"  => ExtraTrailingSlash,
        "/api/world/abc/"    => ExtraTrailingSlash,
        "/foo/pp/"           => ExtraTrailingSlash,
        "/"                  => NotFound,
        "/no"                => NotFound,
        "/no/"               => NotFound,
        "/no/a/b"            => NotFound,
        "/no/a/b/"           => NotFound,
        "/_"                 => NotFound,
        "/_/"                => NotFound,
        "/api"               => NotFound,
        "/api/"              => NotFound,
        "/api/hello/x/foo"   => NotFound,
        "/api/baz/foo/bad"   => NotFound,
        "/foo/p/p"           => NotFound,
    },
    backtracking_tsr {
        routes = [
            "/a/:b/:c",
            "/a/b/:c/d/",
        ],
        "/a/b/c/d"   => MissingTrailingSlash,
    },
    same_len {
        routes = ["/foo", "/bar/"],
        "/baz" => NotFound,
    },
    root_tsr_wildcard {
        routes = ["/:foo"],
        "/" => NotFound,
    },
    root_tsr_static {
        routes = ["/foo"],
        "/" => NotFound,
    },
    root_tsr {
        routes = [
            "/foo",
            "/bar",
            "/:baz"
        ],
        "/" => NotFound,
    },
    double_overlap_tsr {
        routes = [
            "/:object/:id",
            "/secret/:id/path",
            "/secret/978/",
            "/other/:object/:id/",
            "/other/an_object/:id",
            "/other/static/path",
            "/other/long/static/path/"
        ],
        "/secret/978/path/"          => ExtraTrailingSlash,
        "/object/id/"                => ExtraTrailingSlash,
        "/object/id/path"            => NotFound,
        "/secret/978"                => MissingTrailingSlash,
        "/other/object/1"            => MissingTrailingSlash,
        "/other/object/1/2"          => NotFound,
        "/other/an_object/1/"        => ExtraTrailingSlash,
        "/other/static/path/"        => ExtraTrailingSlash,
        "/other/long/static/path"    => MissingTrailingSlash,
        "/other/object/static/path"  => NotFound,
    },
}

macro_rules! match_tests {
    ($($name:ident {
        routes = $routes:expr,
        $( $path:literal :: $route:literal =>
            $( $(@$none:tt)? None )?
            $( $(@$some:tt)? { $( $key:literal => $val:literal ),* $(,)? } )?
        ),* $(,)?
    }),* $(,)?) => { $(
        #[test]
        fn $name() {
            let mut router = Router::new();

            for route in $routes {
                router.insert(route, route.to_owned())
                    .unwrap_or_else(|e| panic!("error when inserting route '{}': {:?}", route, e));
            }

            $(match router.at($path) {
                Err(_) => {
                    $($( @$some )?
                        panic!("Expected value for route '{}'", $path)
                    )?
                }
                Ok(result) => {
                    $($( @$some )?
                        if result.value != $route {
                            panic!(
                                "Wrong value for route '{}'. Expected '{}', found '{}')",
                                $path, result.value, $route
                            );
                        }

                        let expected_params = vec![$(($key, $val)),*];
                        let got_params = result.params.iter().collect::<Vec<_>>();

                        assert_eq!(
                            got_params, expected_params,
                            "Wrong params for route '{}'",
                            $path
                        );

                        router.at_mut($path).unwrap().value.push_str("CHECKED");
                        assert!(router.at($path).unwrap().value.contains("CHECKED"));

                        let val = router.at_mut($path).unwrap().value;
                        *val = val.replace("CHECKED", "");
                    )?

                    $($( @$none )?
                        panic!(
                            "Unexpected value for route '{}', got: {:?}",
                            $path,
                            result.params.iter().collect::<Vec<_>>()
                        );
                    )?
                }
            })*

            if let Err((got, expected)) = router.check_priorities() {
                panic!(
                    "priority mismatch for node: got '{}', expected '{}'",
                    got, expected
                )
            }
        }
   )* };
}

macro_rules! insert_tests {
    ($($name:ident {
        $($route:literal => $res:expr),* $(,)?
    }),* $(,)?) => { $(
        #[test]
        fn $name() {
            let mut router = Router::new();

            $(
                let res = router.insert($route, $route.to_owned());
                assert_eq!(res, $res, "unexpected result for path '{}'", $route);
            )*
        }
   )* };
}

macro_rules! tsr_tests {
    ($($name:ident {
        routes = $routes:expr,
        $($path:literal => $tsr:ident),* $(,)?
    }),* $(,)?) => { $(
        #[test]
        fn $name() {
            let mut router = Router::new();

            for route in $routes {
                router.insert(route, route.to_owned())
                    .unwrap_or_else(|e| panic!("error when inserting route '{}': {:?}", route, e));
            }

            $(
                match router.at($path) {
                    Err(MatchError::$tsr) => {},
                    Err(e) => panic!("wrong tsr value for '{}', expected {}, found {}", $path, MatchError::$tsr, e),
                    res => panic!("unexpected result for '{}': {:?}", $path, res)
                }
            )*
        }
   )* };
}

pub(self) use {insert_tests, match_tests, tsr_tests};