Skip to main content

soma_http_api/
route_inventory.rs

1//! Generic route inventory metadata and documentation helpers.
2//!
3//! `RestRoute` describes one route's shape (method, path, auth posture,
4//! description) generically. Products own their own concrete route list
5//! (Soma's lives in `soma-api::route_inventory::REST_ROUTES`) and pass it to
6//! [`capabilities_response`] to build a `/v1/capabilities`-style discovery
7//! payload.
8
9use serde::Serialize;
10
11#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
12pub struct RestRoute {
13    pub method: &'static str,
14    pub path: &'static str,
15    pub action: Option<&'static str>,
16    pub auth: &'static str,
17    pub description: &'static str,
18}
19
20#[derive(Debug, Serialize)]
21pub struct CapabilitiesResponse {
22    pub server: &'static str,
23    pub version: &'static str,
24    pub preferred_rest_style: &'static str,
25    pub supported_routes: Vec<String>,
26    pub routes: &'static [RestRoute],
27}
28
29/// Build a `CapabilitiesResponse` from a product's own static route table.
30pub fn capabilities_response(
31    server: &'static str,
32    version: &'static str,
33    preferred_rest_style: &'static str,
34    routes: &'static [RestRoute],
35) -> CapabilitiesResponse {
36    CapabilitiesResponse {
37        server,
38        version,
39        preferred_rest_style,
40        supported_routes: routes
41            .iter()
42            .map(|route| format!("{} {}", route.method, route.path))
43            .collect(),
44        routes,
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    const ROUTES: &[RestRoute] = &[
53        RestRoute {
54            method: "GET",
55            path: "/health",
56            action: None,
57            auth: "public",
58            description: "Liveness probe.",
59        },
60        RestRoute {
61            method: "POST",
62            path: "/v1/echo",
63            action: Some("echo"),
64            auth: "mounted auth policy",
65            description: "Echo a message back unchanged.",
66        },
67    ];
68
69    #[test]
70    fn builds_supported_routes_from_method_and_path() {
71        let response = capabilities_response("demo", "1.2.3", "direct_routes", ROUTES);
72        assert_eq!(response.server, "demo");
73        assert_eq!(response.version, "1.2.3");
74        assert_eq!(
75            response.supported_routes,
76            vec!["GET /health".to_owned(), "POST /v1/echo".to_owned()]
77        );
78        assert_eq!(response.routes.len(), 2);
79    }
80}