systemprompt-api 0.55.0

Axum-based HTTP server and API gateway for systemprompt.io AI governance infrastructure. Exposes governed agents, MCP, A2A, and admin endpoints with rate limiting and RBAC.
Documentation
//! Generate schemas, cursor parameters and uniform problem responses together.
//!
//! Copyright (c) systemprompt.io — Business Source License 1.1.
//! See <https://systemprompt.io> for licensing details.
use schemars::generate::SchemaSettings;
use schemars::{JsonSchema, SchemaGenerator};
// JSON: OpenAPI itself is a standards-defined dynamic document. Endpoint bodies
// and responses below are generated from concrete Rust JsonSchema
// implementations.
use serde_json::{Map, Value, json};
pub(super) struct Document {
    generator: SchemaGenerator,
    paths: Map<String, Value>,
}
impl Document {
    pub(super) fn new() -> Self {
        let mut settings = SchemaSettings::draft2020_12();
        settings.definitions_path = "/components/schemas".into();
        Self {
            generator: settings.into_generator(),
            paths: Map::new(),
        }
    }
    pub(super) fn add<I: JsonSchema, O: JsonSchema>(
        &mut self,
        path: &str,
        method: &str,
        status: u16,
        consumer: bool,
    ) {
        let output = self.generator.subschema_for::<O>().to_value();
        let problem = self
            .generator
            .subschema_for::<super::super::Problem>()
            .to_value();
        let mut responses = Map::new();
        responses.insert(status.to_string(),if status==204 {json!({"description":"Mutation completed; no response body"})}else{json!({"description":"Retained result or current operation status","content":{"application/json":{"schema":output}}})});
        for code in [400, 401, 403, 404, 409, 413, 415, 422, 429, 500] {
            responses.insert(code.to_string(),json!({"description":"Problem details","content":{"application/problem+json":{"schema":problem}},"headers":{"Retry-After":{"description":"Retry delay when rate limited","schema":{"type":"string"}}}}));
        }
        let mut parameters = Vec::new();
        for part in path.split('/') {
            if part.starts_with('{') && part.ends_with('}') {
                parameters.push(json!({"name":&part[1..part.len()-1],"in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":512}}));
            }
        }
        let security = if consumer {
            json!([{"deviceCredential":[]}])
        } else {
            json!([{"adminBearer":[]},{"adminCookie":[]}])
        };
        let mut operation = json!({"operationId":format!("{}_{}",method,path.trim_start_matches('/').replace(['/', '{','}','-'],"_")),"tags":[if consumer{"Consumer evidence"}else{"Optimization administration"}],"security":security,"parameters":parameters,"responses":responses});
        if method != "get" {
            parameters.push(json!({"name":"Origin","in":"header","required":false,"schema":{"type":"string","format":"uri"},"description":"Required and exact-match validated whenever a Cookie header is present."}));
            operation["parameters"] = json!(parameters);
            operation["description"] = json!(
                "Administrative cookie mutations require an exact Origin match. Device evidence derives consumer identity from the credential; organizational ownership is retained server-side. Human publication approval remains a separate, explicit step."
            );
        }
        if std::any::type_name::<I>() != "()" {
            let input = self.generator.subschema_for::<I>().to_value();
            operation["requestBody"] =
                json!({"required":true,"content":{"application/json":{"schema":input}}});
        }
        self.paths
            .entry(path.to_owned())
            .or_insert_with(|| json!({}))[method] = operation;
    }
    pub(super) fn query<Q: JsonSchema>(&mut self, path: &str, method: &str) {
        let schema = Q::json_schema(&mut self.generator).to_value();
        let required = schema
            .get("required")
            .and_then(Value::as_array)
            .cloned()
            .unwrap_or_default();
        if let Some(properties) = schema.get("properties").and_then(Value::as_object) {
            for (name, schema) in properties {
                self.parameter(path,method,json!({"name":name,"in":"query","required":required.iter().any(|field|field.as_str()==Some(name)),"schema":schema}));
            }
        }
    }
    pub(super) fn parameter(&mut self, path: &str, method: &str, parameter: Value) {
        if let Some(parameters) = self
            .paths
            .get_mut(path)
            .and_then(|path| path.get_mut(method))
            .and_then(|operation| operation.get_mut("parameters"))
            .and_then(Value::as_array_mut)
        {
            parameters.push(parameter);
        }
    }
    pub(super) fn idempotent(&mut self, path: &str) {
        self.parameter(path,"post",json!({"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":1,"maxLength":200},"description":"Owner-scoped operation identity. Identical retries recover retained inputs/results; conflicting reuse is HTTP 409. Status is available at /operations/{id}. Credential tokens are delivered once; lost delivery requires deliberate rotation with a new key."}));
        self.operation_headers(path);
    }
    pub(super) fn operation_headers(&mut self, path: &str) {
        let responses = &mut self.paths[path]["post"]["responses"];
        let mut pending = responses["200"].clone();
        pending["description"] = json!(
            "Operation is already leased; poll its Location instead of replaying the mutation."
        );
        responses["202"] = pending;
        for status in ["200", "202"] {
            responses[status]["headers"] = json!({"Location":{"description":"Owner-scoped durable operation status","schema":{"type":"string","format":"uri-reference"}}});
        }
    }
    pub(super) fn stream<T: JsonSchema>(&mut self, path: &str) {
        self.add::<(), T>(path, "get", 200, false);
        let schema = self.generator.subschema_for::<T>().to_value();
        self.paths[path]["get"]["responses"]["200"] = json!({"description":"Server-sent snapshot and resync events. Reconnect uses durable composite generations; notifications are wake-up hints. Closing the connection releases its core connection guard.","content":{"text/event-stream":{"schema":{"type":"string"},"x-event-data-schema":schema}}});
        self.parameter(path,"get",json!({"name":"Last-Event-ID","in":"header","required":false,"schema":{"type":"string","maxLength":96,"pattern":"^[0-9]+[.][0-9]+[.][0-9]+$"}}));
    }
    pub(super) fn finish(self) -> Value {
        json!({"openapi":"3.1.0","jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","info":{"title":"Managed resources API","version":"1.0.0","description":"Organizational inventory, device-authenticated evidence, publication review and snapshot analytics. All errors use problem details; cookie writes require matching Origin. No automatic publication."},"servers":[{"url":"/api/v1"}],"paths":self.paths,"components":{"schemas":self.generator.definitions(),"securitySchemes":{"adminBearer":{"type":"http","scheme":"bearer","description":"Authenticated administrator credential; consumer grants do not confer administration."},"adminCookie":{"type":"apiKey","in":"cookie","name":"access_token","description":"Existing authenticated admin session; mutations require exact Origin."},"deviceCredential":{"type":"http","scheme":"bearer","bearerFormat":"sp_device_…","description":"Credential bound to an enrolled, non-revoked device. A user bridge secret or submitted device ID is insufficient."}}}})
    }
}