soma_gateway/gateway/manager/
protected_routes.rs1use std::cmp::Reverse;
2
3use crate::gateway::manager::GatewayManager;
4use crate::gateway::protected_routes::{project_route, route_matches, ProtectedRouteProjection};
5use crate::upstream::UpstreamHealth;
6
7impl GatewayManager {
8 pub fn protected_route_list(&self) -> Vec<crate::config::ProtectedMcpRouteConfig> {
9 self.config
10 .read()
11 .expect("gateway config poisoned")
12 .protected_mcp_routes
13 .clone()
14 }
15
16 pub fn resolve_protected_route(
17 &self,
18 host: &str,
19 path: &str,
20 ) -> Option<crate::config::ProtectedMcpRouteConfig> {
21 let mut routes = self.protected_route_list();
22 routes.sort_by_key(|route| Reverse(route.public_path.len()));
23 routes
24 .into_iter()
25 .filter(|route| route.enabled)
26 .find(|route| route_matches(route, host, path).is_ok())
27 }
28
29 pub fn resolve_protected_route_metadata(
30 &self,
31 host: &str,
32 path: &str,
33 ) -> Option<crate::config::ProtectedMcpRouteConfig> {
34 const PREFIX: &str = "/.well-known/oauth-protected-resource";
35 let suffix = path.strip_prefix(PREFIX)?;
36 let public_path = if suffix.is_empty() { "/mcp" } else { suffix };
37 self.protected_route_list()
38 .into_iter()
39 .filter(|route| route.enabled && route.public_path == public_path)
40 .find(|route| route_matches(route, host, &route.public_path).is_ok())
41 }
42
43 pub async fn protected_route_projections(&self) -> Vec<ProtectedRouteProjection> {
44 let routes = self
45 .config
46 .read()
47 .expect("gateway config poisoned")
48 .protected_mcp_routes
49 .clone();
50 let snapshots = self.discover().await.unwrap_or_default();
51 routes
52 .iter()
53 .map(|route| {
54 let upstream_connected = route.upstream.as_deref().is_some_and(|name| {
55 snapshots.iter().any(|snapshot| {
56 snapshot.name == name && snapshot.health == UpstreamHealth::Connected
57 })
58 });
59 project_route(route, upstream_connected)
60 })
61 .collect()
62 }
63}
64
65#[cfg(test)]
66#[path = "protected_routes_tests.rs"]
67mod tests;