@@ -43,10 +43,14 @@ pub struct DuplicateRoute {
4343
4444impl Router {
4545 /// 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.
4649 pub fn build < ' a > (
4750 base : & str ,
4851 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 > {
5054 // Some information we need to carry between stages of the builder.
5155 struct RoutingEntry < ' a > {
5256 based_route : String ,
@@ -55,7 +59,6 @@ impl Router {
5559 }
5660
5761 let mut routes = IndexMap :: new ( ) ;
58- let mut duplicates = vec ! [ ] ;
5962
6063 // Filter out private endpoints and capture the routes.
6164 let routes_iter = component_routes
@@ -72,19 +75,24 @@ impl Router {
7275 Some ( Err ( anyhow ! ( "route must be a string pattern or '{{ private = true }}': component '{component_id}' has {{ private = false }}" ) ) )
7376 }
7477 }
75- } )
76- . collect :: < Result < Vec < _ > > > ( ) ?;
78+ } ) ;
7779
7880 // Remove duplicates.
7981 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+ }
8896 }
8997 }
9098
@@ -115,7 +123,7 @@ impl Router {
115123 router : std:: sync:: Arc :: new ( rf) ,
116124 } ;
117125
118- Ok ( ( router, duplicates ) )
126+ Ok ( router)
119127 }
120128
121129 fn parse_route ( based_route : & str ) -> Result < ( routefinder:: RouteSpec , ParsedRoute ) , String > {
@@ -329,9 +337,10 @@ mod route_tests {
329337
330338 #[ test]
331339 fn test_router_exact ( ) -> Result < ( ) > {
332- let ( r , _dups ) = Router :: build (
340+ let r = Router :: build (
333341 "/" ,
334342 [ ( "foo" , & "/foo" . into ( ) ) , ( "foobar" , & "/foo/bar" . into ( ) ) ] ,
343+ None ,
335344 ) ?;
336345
337346 assert_eq ! ( r. route( "/foo" ) ?. component_id( ) , "foo" ) ;
@@ -341,9 +350,10 @@ mod route_tests {
341350
342351 #[ test]
343352 fn test_router_respects_base ( ) -> Result < ( ) > {
344- let ( r , _dups ) = Router :: build (
353+ let r = Router :: build (
345354 "/base" ,
346355 [ ( "foo" , & "/foo" . into ( ) ) , ( "foobar" , & "/foo/bar" . into ( ) ) ] ,
356+ None ,
347357 ) ?;
348358
349359 assert_eq ! ( r. route( "/base/foo" ) ?. component_id( ) , "foo" ) ;
@@ -353,7 +363,7 @@ mod route_tests {
353363
354364 #[ test]
355365 fn test_router_wildcard ( ) -> Result < ( ) > {
356- let ( r , _dups ) = Router :: build ( "/" , [ ( "all" , & "/..." . into ( ) ) ] ) ?;
366+ let r = Router :: build ( "/" , [ ( "all" , & "/..." . into ( ) ) ] , None ) ?;
357367
358368 assert_eq ! ( r. route( "/foo/bar" ) ?. component_id( ) , "all" ) ;
359369 assert_eq ! ( r. route( "/abc/" ) ?. component_id( ) , "all" ) ;
@@ -367,7 +377,7 @@ mod route_tests {
367377
368378 #[ test]
369379 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 ( ) ;
371381
372382 let ( route, component_id) = routes. routes ( ) . next ( ) . unwrap ( ) ;
373383
@@ -377,13 +387,14 @@ mod route_tests {
377387
378388 #[ test]
379389 fn test_router_respects_longest_match ( ) -> Result < ( ) > {
380- let ( r , _dups ) = Router :: build (
390+ let r = Router :: build (
381391 "/" ,
382392 [
383393 ( "one_wildcard" , & "/one/..." . into ( ) ) ,
384394 ( "onetwo_wildcard" , & "/one/two/..." . into ( ) ) ,
385395 ( "onetwothree_wildcard" , & "/one/two/three/..." . into ( ) ) ,
386396 ] ,
397+ None ,
387398 ) ?;
388399
389400 assert_eq ! (
@@ -392,13 +403,14 @@ mod route_tests {
392403 ) ;
393404
394405 // ...regardless of order
395- let ( r , _dups ) = Router :: build (
406+ let r = Router :: build (
396407 "/" ,
397408 [
398409 ( "onetwothree_wildcard" , & "/one/two/three/..." . into ( ) ) ,
399410 ( "onetwo_wildcard" , & "/one/two/..." . into ( ) ) ,
400411 ( "one_wildcard" , & "/one/..." . into ( ) ) ,
401412 ] ,
413+ None ,
402414 ) ?;
403415
404416 assert_eq ! (
@@ -410,9 +422,10 @@ mod route_tests {
410422
411423 #[ test]
412424 fn test_router_exact_beats_wildcard ( ) -> Result < ( ) > {
413- let ( r , _dups ) = Router :: build (
425+ let r = Router :: build (
414426 "/" ,
415427 [ ( "one_exact" , & "/one" . into ( ) ) , ( "wildcard" , & "/..." . into ( ) ) ] ,
428+ None ,
416429 ) ?;
417430
418431 assert_eq ! ( r. route( "/one" ) ?. component_id( ) , "one_exact" ) ;
@@ -422,14 +435,16 @@ mod route_tests {
422435
423436 #[ test]
424437 fn sensible_routes_are_reachable ( ) {
425- let ( routes, duplicates) = Router :: build (
438+ let mut duplicates = Vec :: new ( ) ;
439+ let routes = Router :: build (
426440 "/" ,
427441 vec ! [
428442 ( "/" , & "/" . into( ) ) ,
429443 ( "/foo" , & "/foo" . into( ) ) ,
430444 ( "/bar" , & "/bar" . into( ) ) ,
431445 ( "/whee/..." , & "/whee/..." . into( ) ) ,
432446 ] ,
447+ Some ( & mut duplicates) ,
433448 )
434449 . unwrap ( ) ;
435450
@@ -439,14 +454,15 @@ mod route_tests {
439454
440455 #[ test]
441456 fn order_of_reachable_routes_is_preserved ( ) {
442- let ( routes, _ ) = Router :: build (
457+ let routes = Router :: build (
443458 "/" ,
444459 vec ! [
445460 ( "comp-/" , & "/" . into( ) ) ,
446461 ( "comp-/foo" , & "/foo" . into( ) ) ,
447462 ( "comp-/bar" , & "/bar" . into( ) ) ,
448463 ( "comp-/whee/..." , & "/whee/..." . into( ) ) ,
449464 ] ,
465+ None ,
450466 )
451467 . unwrap ( ) ;
452468
@@ -458,14 +474,16 @@ mod route_tests {
458474
459475 #[ test]
460476 fn duplicate_routes_are_unreachable ( ) {
461- let ( routes, duplicates) = Router :: build (
477+ let mut duplicates = Vec :: new ( ) ;
478+ let routes = Router :: build (
462479 "/" ,
463480 vec ! [
464481 ( "comp-/" , & "/" . into( ) ) ,
465482 ( "comp-first /foo" , & "/foo" . into( ) ) ,
466483 ( "comp-second /foo" , & "/foo" . into( ) ) ,
467484 ( "comp-/whee/..." , & "/whee/..." . into( ) ) ,
468485 ] ,
486+ Some ( & mut duplicates) ,
469487 )
470488 . unwrap ( ) ;
471489
@@ -475,14 +493,16 @@ mod route_tests {
475493
476494 #[ test]
477495 fn duplicate_routes_last_one_wins ( ) {
478- let ( routes, duplicates) = Router :: build (
496+ let mut duplicates = Vec :: new ( ) ;
497+ let routes = Router :: build (
479498 "/" ,
480499 vec ! [
481500 ( "comp-/" , & "/" . into( ) ) ,
482501 ( "comp-first /foo" , & "/foo" . into( ) ) ,
483502 ( "comp-second /foo" , & "/foo" . into( ) ) ,
484503 ( "comp-/whee/..." , & "/whee/..." . into( ) ) ,
485504 ] ,
505+ Some ( & mut duplicates) ,
486506 )
487507 . unwrap ( ) ;
488508
@@ -493,7 +513,8 @@ mod route_tests {
493513
494514 #[ test]
495515 fn duplicate_routes_reporting_is_faithful ( ) {
496- let ( _, duplicates) = Router :: build (
516+ let mut duplicates = Vec :: new ( ) ;
517+ let _ = Router :: build (
497518 "/" ,
498519 vec ! [
499520 ( "comp-first /" , & "/" . into( ) ) ,
@@ -505,6 +526,7 @@ mod route_tests {
505526 ( "comp-first /whee/..." , & "/whee/..." . into( ) ) ,
506527 ( "comp-second /whee/..." , & "/whee/..." . into( ) ) ,
507528 ] ,
529+ Some ( & mut duplicates) ,
508530 )
509531 . unwrap ( ) ;
510532
@@ -523,7 +545,7 @@ mod route_tests {
523545
524546 #[ test]
525547 fn unroutable_routes_are_skipped ( ) {
526- let ( routes, _ ) = Router :: build (
548+ let routes = Router :: build (
527549 "/" ,
528550 vec ! [
529551 ( "comp-/" , & "/" . into( ) ) ,
@@ -534,6 +556,7 @@ mod route_tests {
534556 ) ,
535557 ( "comp-/whee/..." , & "/whee/..." . into( ) ) ,
536558 ] ,
559+ None ,
537560 )
538561 . unwrap ( ) ;
539562
@@ -554,6 +577,7 @@ mod route_tests {
554577 ) ,
555578 ( "comp-/whee/..." , & "/whee/..." . into( ) ) ,
556579 ] ,
580+ None ,
557581 )
558582 . expect_err ( "should not have accepted a 'route = true'" ) ;
559583
@@ -562,11 +586,11 @@ mod route_tests {
562586
563587 #[ test]
564588 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 ( ) ;
566590 let m = routes. route ( "/1/2/3" ) . expect ( "/1/2/3 should have matched" ) ;
567591 assert_eq ! ( "/1/2/3" , m. trailing_wildcard( ) ) ;
568592
569- let ( routes, _dups ) = Router :: build ( "/" , vec ! [ ( "comp" , & "/1/..." . into( ) ) ] ) . unwrap ( ) ;
593+ let routes = Router :: build ( "/" , vec ! [ ( "comp" , & "/1/..." . into( ) ) ] , None ) . unwrap ( ) ;
570594 let m = routes. route ( "/1/2/3" ) . expect ( "/1/2/3 should have matched" ) ;
571595 assert_eq ! ( "/2/3" , m. trailing_wildcard( ) ) ;
572596 }
@@ -576,7 +600,7 @@ mod route_tests {
576600 // We test this because it is the existing Spin behaviour but is *not*
577601 // how routefinder behaves by default (routefinder prefers to ignore trailing
578602 // slashes).
579- let ( routes, _dups ) = Router :: build ( "/" , vec ! [ ( "comp" , & "/test/..." . into( ) ) ] ) . unwrap ( ) ;
603+ let routes = Router :: build ( "/" , vec ! [ ( "comp" , & "/test/..." . into( ) ) ] , None ) . unwrap ( ) ;
580604 let m = routes. route ( "/test" ) . expect ( "/test should have matched" ) ;
581605 assert_eq ! ( "" , m. trailing_wildcard( ) ) ;
582606 let m = routes. route ( "/test/" ) . expect ( "/test/ should have matched" ) ;
@@ -593,38 +617,41 @@ mod route_tests {
593617
594618 #[ test]
595619 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 ( ) ;
597621 let m = routes. route ( "/1/2/3" ) . expect ( "/1/2/3 should have matched" ) ;
598622 assert_eq ! ( "2" , m. named_wildcards( ) [ "two" ] ) ;
599623
600- let ( routes, _dups ) = Router :: build ( "/" , vec ! [ ( "comp" , & "/1/:two/..." . into( ) ) ] ) . unwrap ( ) ;
624+ let routes = Router :: build ( "/" , vec ! [ ( "comp" , & "/1/:two/..." . into( ) ) ] , None ) . unwrap ( ) ;
601625 let m = routes. route ( "/1/2/3" ) . expect ( "/1/2/3 should have matched" ) ;
602626 assert_eq ! ( "2" , m. named_wildcards( ) [ "two" ] ) ;
603627 }
604628
605629 #[ test]
606630 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 ( ) ;
609633 assert ! ( routes. contains_reserved_route( ) ) ;
610634
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 ( ) ;
613637 assert ! ( routes. contains_reserved_route( ) ) ;
614638 }
615639
616640 #[ test]
617641 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 ( ) ;
620648 assert ! ( !routes. contains_reserved_route( ) ) ;
621649
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 ( ) ;
624652 assert ! ( !routes. contains_reserved_route( ) ) ;
625653
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 ( ) ;
628655 assert ! ( !routes. contains_reserved_route( ) ) ;
629656 }
630657}
0 commit comments