roder-protocol 0.1.0

Agentic software development tools and SDKs for Roder.
Documentation
use serde_json::{Value, json};

use crate::methods::{AppServerMethodManifest, app_server_method_manifest};

pub fn app_server_manifest_json() -> Value {
    serde_json::to_value(app_server_method_manifest()).expect("manifest serializes")
}

pub fn app_server_json_schema() -> Value {
    let manifest = app_server_method_manifest();
    json!({
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$id": "https://roder.dev/schemas/app-server/roder-app-server.v1.json",
        "title": "Roder App Server Manifest",
        "type": "object",
        "required": ["schemaVersion", "unknownMethodsAllowed", "methods"],
        "properties": {
            "schemaVersion": { "const": manifest.schema_version },
            "unknownMethodsAllowed": { "type": "boolean" },
            "methods": {
                "type": "array",
                "items": method_spec_schema(&manifest),
            },
        },
        "additionalProperties": false,
    })
}

fn method_spec_schema(manifest: &AppServerMethodManifest) -> Value {
    let methods = manifest
        .methods
        .iter()
        .map(|spec| spec.method)
        .collect::<Vec<_>>();
    json!({
        "type": "object",
        "required": [
            "method",
            "paramsType",
            "resultType",
            "stability",
            "featureGroup",
            "idempotency",
            "sideEffect",
        ],
        "properties": {
            "method": { "type": "string", "enum": methods },
            "paramsType": { "type": "string" },
            "resultType": { "type": "string" },
            "stability": { "enum": ["stable", "experimental"] },
            "featureGroup": { "type": "string" },
            "idempotency": { "enum": ["idempotent", "nonIdempotent"] },
            "sideEffect": { "enum": ["readOnly", "localState", "externalProcess"] },
            "notifications": {
                "type": "array",
                "items": { "type": "string" },
            },
        },
        "additionalProperties": false,
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn schema_contains_manifest_version_and_method_enum() {
        let schema = app_server_json_schema();
        assert_eq!(schema["properties"]["schemaVersion"]["const"], 1);
        let method_enum = schema["properties"]["methods"]["items"]["properties"]["method"]["enum"]
            .as_array()
            .expect("method enum");
        assert!(method_enum.iter().any(|value| value == "thread/start"));
        assert!(method_enum.iter().any(|value| value == "memory/query"));
    }

    #[test]
    fn manifest_json_allows_unknown_methods_for_raw_clients() {
        let manifest = app_server_manifest_json();
        assert_eq!(manifest["unknownMethodsAllowed"], true);
        assert!(manifest["methods"].as_array().unwrap().len() > 100);
    }

    #[test]
    fn checked_app_server_schema_file_matches_generator() {
        let checked: Value = serde_json::from_str(include_str!(
            "../../../schemas/app-server/methods.schema.json"
        ))
        .expect("checked schema json");
        assert_eq!(checked, app_server_json_schema());
    }

    #[test]
    fn checked_app_server_manifest_file_matches_generator() {
        let checked: Value = serde_json::from_str(include_str!(
            "../../../schemas/app-server/roder-app-server.v1.json"
        ))
        .expect("checked manifest json");
        assert_eq!(checked, app_server_manifest_json());
    }
}