@@ -43,10 +43,14 @@ pub struct DuplicateRoute {
43
43
44
44
impl Router {
45
45
/// Builds a router based on application configuration.
46
+ ///
47
+ /// `duplicate_routes` is an optional mutable reference to a vector of `DuplicateRoute`
48
+ /// that will be populated with any duplicate routes found during the build process.
46
49
pub fn build < ' a > (
47
50
base : & str ,
48
51
component_routes : impl IntoIterator < Item = ( & ' a str , & ' a HttpTriggerRouteConfig ) > ,
49
- ) -> Result < ( Self , Vec < DuplicateRoute > ) > {
52
+ mut duplicate_routes : Option < & mut Vec < DuplicateRoute > > ,
53
+ ) -> Result < Self > {
50
54
// Some information we need to carry between stages of the builder.
51
55
struct RoutingEntry < ' a > {
52
56
based_route : String ,
@@ -55,7 +59,6 @@ impl Router {
55
59
}
56
60
57
61
let mut routes = IndexMap :: new ( ) ;
58
- let mut duplicates = vec ! [ ] ;
59
62
60
63
// Filter out private endpoints and capture the routes.
61
64
let routes_iter = component_routes
@@ -72,19 +75,24 @@ impl Router {
72
75
Some ( Err ( anyhow ! ( "route must be a string pattern or '{{ private = true }}': component '{component_id}' has {{ private = false }}" ) ) )
73
76
}
74
77
}
75
- } )
76
- . collect :: < Result < Vec < _ > > > ( ) ?;
78
+ } ) ;
77
79
78
80
// Remove duplicates.
79
81
for re in routes_iter {
80
- let effective_id = re. component_id . to_string ( ) ;
81
- let replaced = routes. insert ( re. raw_route , re) ;
82
- if let Some ( replaced) = replaced {
83
- duplicates. push ( DuplicateRoute {
84
- route : replaced. based_route ,
85
- replaced_id : replaced. component_id . to_string ( ) ,
86
- effective_id,
87
- } ) ;
82
+ let re = re?;
83
+ if let Some ( replaced) = routes. insert ( re. raw_route , re) {
84
+ if let Some ( duplicate_routes) = & mut duplicate_routes {
85
+ let effective_id = routes
86
+ . get ( replaced. raw_route )
87
+ . unwrap ( ) // Safe because we just inserted it
88
+ . component_id
89
+ . to_owned ( ) ;
90
+ duplicate_routes. push ( DuplicateRoute {
91
+ route : replaced. based_route ,
92
+ replaced_id : replaced. component_id . to_owned ( ) ,
93
+ effective_id,
94
+ } ) ;
95
+ }
88
96
}
89
97
}
90
98
@@ -115,7 +123,7 @@ impl Router {
115
123
router : std:: sync:: Arc :: new ( rf) ,
116
124
} ;
117
125
118
- Ok ( ( router, duplicates ) )
126
+ Ok ( router)
119
127
}
120
128
121
129
fn parse_route ( based_route : & str ) -> Result < ( routefinder:: RouteSpec , ParsedRoute ) , String > {
@@ -329,9 +337,10 @@ mod route_tests {
329
337
330
338
#[ test]
331
339
fn test_router_exact ( ) -> Result < ( ) > {
332
- let ( r , _dups ) = Router :: build (
340
+ let r = Router :: build (
333
341
"/" ,
334
342
[ ( "foo" , & "/foo" . into ( ) ) , ( "foobar" , & "/foo/bar" . into ( ) ) ] ,
343
+ None ,
335
344
) ?;
336
345
337
346
assert_eq ! ( r. route( "/foo" ) ?. component_id( ) , "foo" ) ;
@@ -341,9 +350,10 @@ mod route_tests {
341
350
342
351
#[ test]
343
352
fn test_router_respects_base ( ) -> Result < ( ) > {
344
- let ( r , _dups ) = Router :: build (
353
+ let r = Router :: build (
345
354
"/base" ,
346
355
[ ( "foo" , & "/foo" . into ( ) ) , ( "foobar" , & "/foo/bar" . into ( ) ) ] ,
356
+ None ,
347
357
) ?;
348
358
349
359
assert_eq ! ( r. route( "/base/foo" ) ?. component_id( ) , "foo" ) ;
@@ -353,7 +363,7 @@ mod route_tests {
353
363
354
364
#[ test]
355
365
fn test_router_wildcard ( ) -> Result < ( ) > {
356
- let ( r , _dups ) = Router :: build ( "/" , [ ( "all" , & "/..." . into ( ) ) ] ) ?;
366
+ let r = Router :: build ( "/" , [ ( "all" , & "/..." . into ( ) ) ] , None ) ?;
357
367
358
368
assert_eq ! ( r. route( "/foo/bar" ) ?. component_id( ) , "all" ) ;
359
369
assert_eq ! ( r. route( "/abc/" ) ?. component_id( ) , "all" ) ;
@@ -367,7 +377,7 @@ mod route_tests {
367
377
368
378
#[ test]
369
379
fn wildcard_routes_use_custom_display ( ) {
370
- let ( routes, _dups ) = Router :: build ( "/" , vec ! [ ( "comp" , & "/whee/..." . into( ) ) ] ) . unwrap ( ) ;
380
+ let routes = Router :: build ( "/" , vec ! [ ( "comp" , & "/whee/..." . into( ) ) ] , None ) . unwrap ( ) ;
371
381
372
382
let ( route, component_id) = routes. routes ( ) . next ( ) . unwrap ( ) ;
373
383
@@ -377,13 +387,14 @@ mod route_tests {
377
387
378
388
#[ test]
379
389
fn test_router_respects_longest_match ( ) -> Result < ( ) > {
380
- let ( r , _dups ) = Router :: build (
390
+ let r = Router :: build (
381
391
"/" ,
382
392
[
383
393
( "one_wildcard" , & "/one/..." . into ( ) ) ,
384
394
( "onetwo_wildcard" , & "/one/two/..." . into ( ) ) ,
385
395
( "onetwothree_wildcard" , & "/one/two/three/..." . into ( ) ) ,
386
396
] ,
397
+ None ,
387
398
) ?;
388
399
389
400
assert_eq ! (
@@ -392,13 +403,14 @@ mod route_tests {
392
403
) ;
393
404
394
405
// ...regardless of order
395
- let ( r , _dups ) = Router :: build (
406
+ let r = Router :: build (
396
407
"/" ,
397
408
[
398
409
( "onetwothree_wildcard" , & "/one/two/three/..." . into ( ) ) ,
399
410
( "onetwo_wildcard" , & "/one/two/..." . into ( ) ) ,
400
411
( "one_wildcard" , & "/one/..." . into ( ) ) ,
401
412
] ,
413
+ None ,
402
414
) ?;
403
415
404
416
assert_eq ! (
@@ -410,9 +422,10 @@ mod route_tests {
410
422
411
423
#[ test]
412
424
fn test_router_exact_beats_wildcard ( ) -> Result < ( ) > {
413
- let ( r , _dups ) = Router :: build (
425
+ let r = Router :: build (
414
426
"/" ,
415
427
[ ( "one_exact" , & "/one" . into ( ) ) , ( "wildcard" , & "/..." . into ( ) ) ] ,
428
+ None ,
416
429
) ?;
417
430
418
431
assert_eq ! ( r. route( "/one" ) ?. component_id( ) , "one_exact" ) ;
@@ -422,14 +435,16 @@ mod route_tests {
422
435
423
436
#[ test]
424
437
fn sensible_routes_are_reachable ( ) {
425
- let ( routes, duplicates) = Router :: build (
438
+ let mut duplicates = Vec :: new ( ) ;
439
+ let routes = Router :: build (
426
440
"/" ,
427
441
vec ! [
428
442
( "/" , & "/" . into( ) ) ,
429
443
( "/foo" , & "/foo" . into( ) ) ,
430
444
( "/bar" , & "/bar" . into( ) ) ,
431
445
( "/whee/..." , & "/whee/..." . into( ) ) ,
432
446
] ,
447
+ Some ( & mut duplicates) ,
433
448
)
434
449
. unwrap ( ) ;
435
450
@@ -439,14 +454,15 @@ mod route_tests {
439
454
440
455
#[ test]
441
456
fn order_of_reachable_routes_is_preserved ( ) {
442
- let ( routes, _ ) = Router :: build (
457
+ let routes = Router :: build (
443
458
"/" ,
444
459
vec ! [
445
460
( "comp-/" , & "/" . into( ) ) ,
446
461
( "comp-/foo" , & "/foo" . into( ) ) ,
447
462
( "comp-/bar" , & "/bar" . into( ) ) ,
448
463
( "comp-/whee/..." , & "/whee/..." . into( ) ) ,
449
464
] ,
465
+ None ,
450
466
)
451
467
. unwrap ( ) ;
452
468
@@ -458,14 +474,16 @@ mod route_tests {
458
474
459
475
#[ test]
460
476
fn duplicate_routes_are_unreachable ( ) {
461
- let ( routes, duplicates) = Router :: build (
477
+ let mut duplicates = Vec :: new ( ) ;
478
+ let routes = Router :: build (
462
479
"/" ,
463
480
vec ! [
464
481
( "comp-/" , & "/" . into( ) ) ,
465
482
( "comp-first /foo" , & "/foo" . into( ) ) ,
466
483
( "comp-second /foo" , & "/foo" . into( ) ) ,
467
484
( "comp-/whee/..." , & "/whee/..." . into( ) ) ,
468
485
] ,
486
+ Some ( & mut duplicates) ,
469
487
)
470
488
. unwrap ( ) ;
471
489
@@ -475,14 +493,16 @@ mod route_tests {
475
493
476
494
#[ test]
477
495
fn duplicate_routes_last_one_wins ( ) {
478
- let ( routes, duplicates) = Router :: build (
496
+ let mut duplicates = Vec :: new ( ) ;
497
+ let routes = Router :: build (
479
498
"/" ,
480
499
vec ! [
481
500
( "comp-/" , & "/" . into( ) ) ,
482
501
( "comp-first /foo" , & "/foo" . into( ) ) ,
483
502
( "comp-second /foo" , & "/foo" . into( ) ) ,
484
503
( "comp-/whee/..." , & "/whee/..." . into( ) ) ,
485
504
] ,
505
+ Some ( & mut duplicates) ,
486
506
)
487
507
. unwrap ( ) ;
488
508
@@ -493,7 +513,8 @@ mod route_tests {
493
513
494
514
#[ test]
495
515
fn duplicate_routes_reporting_is_faithful ( ) {
496
- let ( _, duplicates) = Router :: build (
516
+ let mut duplicates = Vec :: new ( ) ;
517
+ let _ = Router :: build (
497
518
"/" ,
498
519
vec ! [
499
520
( "comp-first /" , & "/" . into( ) ) ,
@@ -505,6 +526,7 @@ mod route_tests {
505
526
( "comp-first /whee/..." , & "/whee/..." . into( ) ) ,
506
527
( "comp-second /whee/..." , & "/whee/..." . into( ) ) ,
507
528
] ,
529
+ Some ( & mut duplicates) ,
508
530
)
509
531
. unwrap ( ) ;
510
532
@@ -523,7 +545,7 @@ mod route_tests {
523
545
524
546
#[ test]
525
547
fn unroutable_routes_are_skipped ( ) {
526
- let ( routes, _ ) = Router :: build (
548
+ let routes = Router :: build (
527
549
"/" ,
528
550
vec ! [
529
551
( "comp-/" , & "/" . into( ) ) ,
@@ -534,6 +556,7 @@ mod route_tests {
534
556
) ,
535
557
( "comp-/whee/..." , & "/whee/..." . into( ) ) ,
536
558
] ,
559
+ None ,
537
560
)
538
561
. unwrap ( ) ;
539
562
@@ -554,6 +577,7 @@ mod route_tests {
554
577
) ,
555
578
( "comp-/whee/..." , & "/whee/..." . into( ) ) ,
556
579
] ,
580
+ None ,
557
581
)
558
582
. expect_err ( "should not have accepted a 'route = true'" ) ;
559
583
@@ -562,11 +586,11 @@ mod route_tests {
562
586
563
587
#[ test]
564
588
fn trailing_wildcard_is_captured ( ) {
565
- let ( routes, _dups ) = Router :: build ( "/" , vec ! [ ( "comp" , & "/..." . into( ) ) ] ) . unwrap ( ) ;
589
+ let routes = Router :: build ( "/" , vec ! [ ( "comp" , & "/..." . into( ) ) ] , None ) . unwrap ( ) ;
566
590
let m = routes. route ( "/1/2/3" ) . expect ( "/1/2/3 should have matched" ) ;
567
591
assert_eq ! ( "/1/2/3" , m. trailing_wildcard( ) ) ;
568
592
569
- let ( routes, _dups ) = Router :: build ( "/" , vec ! [ ( "comp" , & "/1/..." . into( ) ) ] ) . unwrap ( ) ;
593
+ let routes = Router :: build ( "/" , vec ! [ ( "comp" , & "/1/..." . into( ) ) ] , None ) . unwrap ( ) ;
570
594
let m = routes. route ( "/1/2/3" ) . expect ( "/1/2/3 should have matched" ) ;
571
595
assert_eq ! ( "/2/3" , m. trailing_wildcard( ) ) ;
572
596
}
@@ -576,7 +600,7 @@ mod route_tests {
576
600
// We test this because it is the existing Spin behaviour but is *not*
577
601
// how routefinder behaves by default (routefinder prefers to ignore trailing
578
602
// slashes).
579
- let ( routes, _dups ) = Router :: build ( "/" , vec ! [ ( "comp" , & "/test/..." . into( ) ) ] ) . unwrap ( ) ;
603
+ let routes = Router :: build ( "/" , vec ! [ ( "comp" , & "/test/..." . into( ) ) ] , None ) . unwrap ( ) ;
580
604
let m = routes. route ( "/test" ) . expect ( "/test should have matched" ) ;
581
605
assert_eq ! ( "" , m. trailing_wildcard( ) ) ;
582
606
let m = routes. route ( "/test/" ) . expect ( "/test/ should have matched" ) ;
@@ -593,38 +617,41 @@ mod route_tests {
593
617
594
618
#[ test]
595
619
fn named_wildcard_is_captured ( ) {
596
- let ( routes, _dups ) = Router :: build ( "/" , vec ! [ ( "comp" , & "/1/:two/3" . into( ) ) ] ) . unwrap ( ) ;
620
+ let routes = Router :: build ( "/" , vec ! [ ( "comp" , & "/1/:two/3" . into( ) ) ] , None ) . unwrap ( ) ;
597
621
let m = routes. route ( "/1/2/3" ) . expect ( "/1/2/3 should have matched" ) ;
598
622
assert_eq ! ( "2" , m. named_wildcards( ) [ "two" ] ) ;
599
623
600
- let ( routes, _dups ) = Router :: build ( "/" , vec ! [ ( "comp" , & "/1/:two/..." . into( ) ) ] ) . unwrap ( ) ;
624
+ let routes = Router :: build ( "/" , vec ! [ ( "comp" , & "/1/:two/..." . into( ) ) ] , None ) . unwrap ( ) ;
601
625
let m = routes. route ( "/1/2/3" ) . expect ( "/1/2/3 should have matched" ) ;
602
626
assert_eq ! ( "2" , m. named_wildcards( ) [ "two" ] ) ;
603
627
}
604
628
605
629
#[ test]
606
630
fn reserved_routes_are_reserved ( ) {
607
- let ( routes, _dups ) =
608
- Router :: build ( "/" , vec ! [ ( "comp" , & "/.well-known/spin/..." . into( ) ) ] ) . unwrap ( ) ;
631
+ let routes =
632
+ Router :: build ( "/" , vec ! [ ( "comp" , & "/.well-known/spin/..." . into( ) ) ] , None ) . unwrap ( ) ;
609
633
assert ! ( routes. contains_reserved_route( ) ) ;
610
634
611
- let ( routes, _dups ) =
612
- Router :: build ( "/" , vec ! [ ( "comp" , & "/.well-known/spin/fie" . into( ) ) ] ) . unwrap ( ) ;
635
+ let routes =
636
+ Router :: build ( "/" , vec ! [ ( "comp" , & "/.well-known/spin/fie" . into( ) ) ] , None ) . unwrap ( ) ;
613
637
assert ! ( routes. contains_reserved_route( ) ) ;
614
638
}
615
639
616
640
#[ test]
617
641
fn unreserved_routes_are_unreserved ( ) {
618
- let ( routes, _dups) =
619
- Router :: build ( "/" , vec ! [ ( "comp" , & "/.well-known/spindle/..." . into( ) ) ] ) . unwrap ( ) ;
642
+ let routes = Router :: build (
643
+ "/" ,
644
+ vec ! [ ( "comp" , & "/.well-known/spindle/..." . into( ) ) ] ,
645
+ None ,
646
+ )
647
+ . unwrap ( ) ;
620
648
assert ! ( !routes. contains_reserved_route( ) ) ;
621
649
622
- let ( routes, _dups ) =
623
- Router :: build ( "/" , vec ! [ ( "comp" , & "/.well-known/spi/..." . into( ) ) ] ) . unwrap ( ) ;
650
+ let routes =
651
+ Router :: build ( "/" , vec ! [ ( "comp" , & "/.well-known/spi/..." . into( ) ) ] , None ) . unwrap ( ) ;
624
652
assert ! ( !routes. contains_reserved_route( ) ) ;
625
653
626
- let ( routes, _dups) =
627
- Router :: build ( "/" , vec ! [ ( "comp" , & "/.well-known/spin" . into( ) ) ] ) . unwrap ( ) ;
654
+ let routes = Router :: build ( "/" , vec ! [ ( "comp" , & "/.well-known/spin" . into( ) ) ] , None ) . unwrap ( ) ;
628
655
assert ! ( !routes. contains_reserved_route( ) ) ;
629
656
}
630
657
}
0 commit comments