Skip to main content

soma_api/
gateway.rs

1use axum::{
2    extract::{rejection::JsonRejection, Extension, Path, State},
3    response::{IntoResponse, Json},
4};
5#[cfg(feature = "auth")]
6use soma_auth::AuthContext;
7#[cfg(not(feature = "auth"))]
8pub struct AuthContext {
9    pub sub: String,
10    pub scopes: Vec<String>,
11}
12use serde_json::{json, Value};
13use soma_application::{ApplicationError, GatewayExecuteRequest};
14use soma_http_api::json::{json_body_or_else, JsonBodyOutcome};
15
16use crate::{responses::application_error_status, ApiState};
17
18pub async fn v1_gateway_action(
19    State(state): State<ApiState>,
20    auth: Option<Extension<AuthContext>>,
21    Path(action): Path<String>,
22    body: Result<Json<Value>, JsonRejection>,
23) -> axum::response::Response {
24    let params = match json_body_or_else(body, true, || json!({})) {
25        JsonBodyOutcome::Params(value) => value,
26        JsonBodyOutcome::Response(response) => return response,
27    };
28    let auth = auth.as_ref().map(|Extension(auth)| auth);
29    let scopes = auth.map(|auth| auth.scopes.as_slice()).unwrap_or_default();
30    let context = state.execution_context(auth.map(|auth| auth.sub.as_str()), scopes);
31
32    match state
33        .application()
34        .gateway_execute(
35            GatewayExecuteRequest {
36                action: action.clone(),
37                params,
38            },
39            context,
40        )
41        .await
42    {
43        Ok(response) => Json(response.output).into_response(),
44        Err(error) => gateway_error_response(&action, error),
45    }
46}
47
48fn gateway_error_response(action: &str, error: ApplicationError) -> axum::response::Response {
49    let status = application_error_status(&error);
50    let kind = match error.code.as_str() {
51        "admin_required" | "not_exposed" => "authorization",
52        "invalid_param"
53        | "unknown_action"
54        | "spawn_validation_failed"
55        | "upstream_exists"
56        | "upstream_missing"
57        | "invalid_config"
58        | "unknown_upstream" => "validation",
59        "unsupported_transport" => "unsupported",
60        "response_too_large" => "limits",
61        "store_not_mounted" => "configuration",
62        _ => "runtime",
63    };
64    (
65        status,
66        Json(json!({
67            "isError": true,
68            "schema_version": "mcp.gateway.error.v1",
69            "code": error.code,
70            "kind": kind,
71            "tool": "gateway",
72            "action": action,
73            "remediation": error.remediation,
74        })),
75    )
76        .into_response()
77}
78
79#[cfg(test)]
80#[path = "gateway_tests.rs"]
81mod tests;