Skip to main content

soma_gateway/gateway/manager/
protected_routes.rs

1use 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;