Skip to content

Commit bde256d

Browse files
authored
Merge pull request #3100 from spinframework/less-allocation-routes
2 parents 2d62139 + 3978be7 commit bde256d

File tree

3 files changed

+72
-44
lines changed

3 files changed

+72
-44
lines changed

crates/http/src/routes.rs

Lines changed: 68 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,14 @@ pub struct DuplicateRoute {
4343

4444
impl 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
}

crates/trigger-http/src/headers.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ mod tests {
177177
.uri(req_uri)
178178
.body("")?;
179179

180-
let (router, _) = Router::build("/", [("DUMMY", &trigger_route.into())])?;
180+
let router = Router::build("/", [("DUMMY", &trigger_route.into())], None)?;
181181
let route_match = router.route("/foo/bar")?;
182182

183183
let default_headers = compute_default_headers(req.uri(), host, &route_match, client_addr)?;
@@ -233,7 +233,7 @@ mod tests {
233233
.uri(req_uri)
234234
.body("")?;
235235

236-
let (router, _) = Router::build("/", [("DUMMY", &trigger_route.into())])?;
236+
let router = Router::build("/", [("DUMMY", &trigger_route.into())], None)?;
237237
let route_match = router.route("/foo/42/bar")?;
238238

239239
let default_headers = compute_default_headers(req.uri(), host, &route_match, client_addr)?;

crates/trigger-http/src/server.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ impl<F: RuntimeFactors> HttpServer<F> {
7777
let component_routes = component_trigger_configs
7878
.iter()
7979
.map(|(component_id, config)| (component_id.as_str(), &config.route));
80-
let (router, duplicate_routes) = Router::build("/", component_routes)?;
80+
let mut duplicate_routes = Vec::new();
81+
let router = Router::build("/", component_routes, Some(&mut duplicate_routes))?;
8182
if !duplicate_routes.is_empty() {
8283
tracing::error!(
8384
"The following component routes are duplicates and will never be used:"

0 commit comments

Comments
 (0)