From f3c2bec78fc352dfe8afba2c509e985933f24d88 Mon Sep 17 00:00:00 2001 From: Lubaba Khalid <129605617+LubabaKhalid@users.noreply.github.com> Date: Thu, 22 Oct 2026 18:26:40 +0500 Subject: [PATCH] feat(telemetry): expose a check for collisions with built-in server routes Built-in telemetry server routes are inserted before custom routes, so a custom route on the same path and method is silently ignored. Add `TelemetryServerRoute::collides_with_built_in_route` so services can detect this at startup. The check inserts the route into a copy of each built-in router, using the same path handling as the server, so it follows the same conflict rules with and without `telemetry-server-pattern-routing`. --- foundations/src/telemetry/server/router.rs | 58 +++++++++++++++++----- foundations/tests/telemetry_server.rs | 14 ++++++ 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/foundations/src/telemetry/server/router.rs b/foundations/src/telemetry/server/router.rs index 5316d1c7..8c31dc3d 100644 --- a/foundations/src/telemetry/server/router.rs +++ b/foundations/src/telemetry/server/router.rs @@ -54,7 +54,9 @@ type RouteHandlerShared = Arc< /// /// New built-in routes may be added from time to time. We reserve the `/foundations/` /// prefix for this purpose, but other paths may be used if there are existing conventions -/// for a feature (such as `/pprof/*` and `/metrics`.) +/// for a feature (such as `/pprof/*` and `/metrics`.) Use +/// [`collides_with_built_in_route`](Self::collides_with_built_in_route) to check a route +/// against the built-in routes at startup. /// /// # Pattern-Based Routing /// @@ -91,6 +93,35 @@ pub struct TelemetryServerRoute { pub handler: TelemetryRouteHandler, } +impl TelemetryServerRoute { + /// Returns `true` if this route collides with a built-in route for any of its methods. + /// + /// Built-in routes are inserted before custom routes, so a route with the same path and + /// method as a built-in route is silently ignored for that method. Services can call this + /// at startup to catch such collisions, including with built-in routes added in later + /// releases. + pub fn collides_with_built_in_route(&self) -> bool { + let mut built_in = Routes(Default::default()); + built_in + .init_built_in_routes() + .expect("built-in routes should not conflict with each other"); + + let path = router_path(&self.path); + let placeholder: RouteHandlerShared = Arc::new(|_, _| unreachable!()); + + self.methods.iter().any(|method| { + built_in.0.get(method).is_some_and(|router| { + matches!( + router + .clone() + .insert(path.clone(), Arc::clone(&placeholder)), + Err(matchit::InsertError::Conflict { .. }) + ) + }) + }) + } +} + struct Routes(HashMap>); impl Routes { @@ -203,29 +234,23 @@ impl Routes { Ok(()) } - #[allow(unused_mut, reason = "conditional mutation")] - fn set(&mut self, mut route: TelemetryServerRoute) -> BootstrapResult<()> { + fn set(&mut self, route: TelemetryServerRoute) -> BootstrapResult<()> { let handler = Arc::from(route.handler); - - #[cfg(not(feature = "telemetry-server-pattern-routing"))] - { - // Escape parameter delimiters so `matchit` interprets them literally - route.path = route.path.replace('{', "{{").replace('}', "}}"); - } + let path = router_path(&route.path); for method in route.methods { let res = self .0 .entry(method) .or_default() - .insert(route.path.clone(), Arc::clone(&handler)); + .insert(path.clone(), Arc::clone(&handler)); match res { Ok(()) => {} // Exact matches were allowed and ignored historically. Any other errors // should be reported up. - Err(matchit::InsertError::Conflict { with }) if with == route.path => {} - Err(e) => anyhow::bail!("tried to insert route `{}`, but {}", route.path, e), + Err(matchit::InsertError::Conflict { with }) if with == path => {} + Err(e) => anyhow::bail!("tried to insert route `{}`, but {}", path, e), } } @@ -233,6 +258,15 @@ impl Routes { } } +fn router_path(path: &str) -> String { + if cfg!(feature = "telemetry-server-pattern-routing") { + path.to_owned() + } else { + // Escape parameter delimiters so `matchit` interprets them literally + path.replace('{', "{{").replace('}', "}}") + } +} + #[derive(Clone)] pub(super) struct Router { routes: Arc, diff --git a/foundations/tests/telemetry_server.rs b/foundations/tests/telemetry_server.rs index daf652f9..9e111116 100644 --- a/foundations/tests/telemetry_server.rs +++ b/foundations/tests/telemetry_server.rs @@ -252,3 +252,17 @@ async fn telemetry_server() { assert!(trace_output.contains("child_span_outliving_parent")); assert!(trace_output.contains("my_root_span")); } + +#[test] +fn custom_route_collides_with_built_in_route() { + let route = |path: &str, method| TelemetryServerRoute { + path: path.into(), + methods: vec![method], + handler: Box::new(|_, _| unreachable!()), + }; + + assert!(route("/health", Method::GET).collides_with_built_in_route()); + assert!(route("/metrics", Method::GET).collides_with_built_in_route()); + assert!(!route("/health", Method::POST).collides_with_built_in_route()); + assert!(!route("/custom-route", Method::GET).collides_with_built_in_route()); +}