oas-document 1.1.1

OAS Identity Document types and validation for the Open Agent Specification
Documentation
// OAS Spec §5.13 — Interoperability
//! Interoperability section for OAS Identity Documents.
//!
//! Describes "what I speak" — supported protocols, API schema URLs,
//! and authentication methods this entity accepts.

use serde::{Deserialize, Serialize};

/// A supported protocol with optional version and endpoint.
///
/// # Examples
///
/// ```
/// use oas_document::interop::ProtocolSupport;
///
/// let proto = ProtocolSupport {
///     protocol: "mcp".to_string(),
///     version: Some("2025-01".to_string()),
///     endpoint: Some("https://agent.acme.com/mcp".to_string()),
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProtocolSupport {
    /// Protocol identifier: `"mcp"`, `"a2a"`, `"rest"`, `"graphql"`,
    /// `"grpc"`, `"websocket"`, `"oas-map"`.
    pub protocol: String,

    /// Protocol version if applicable.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub version: Option<String>,

    /// Endpoint URL for this protocol.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub endpoint: Option<String>,
}

/// An API schema reference.
///
/// # Examples
///
/// ```
/// use oas_document::interop::ApiSchema;
///
/// let schema = ApiSchema {
///     schema_type: "openapi".to_string(),
///     url: "https://agent.acme.com/openapi.json".to_string(),
///     version: Some("3.1.0".to_string()),
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiSchema {
    /// Schema format: `"openapi"`, `"asyncapi"`, `"json-schema"`,
    /// `"protobuf"`, `"graphql-sdl"`.
    pub schema_type: String,

    /// URL to the schema document.
    pub url: String,

    /// Schema version.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub version: Option<String>,
}

/// Interoperability section of an OAS Identity Document.
///
/// # Examples
///
/// ```
/// use oas_document::interop::{InteroperabilitySection, ProtocolSupport, ApiSchema};
///
/// let interop = InteroperabilitySection {
///     protocols: vec![
///         ProtocolSupport {
///             protocol: "mcp".to_string(),
///             version: Some("2025-01".to_string()),
///             endpoint: Some("https://agent.acme.com/mcp".to_string()),
///         },
///         ProtocolSupport {
///             protocol: "rest".to_string(),
///             version: None,
///             endpoint: Some("https://api.acme.com/v1".to_string()),
///         },
///     ],
///     api_schemas: vec![ApiSchema {
///         schema_type: "openapi".to_string(),
///         url: "https://api.acme.com/v1/openapi.json".to_string(),
///         version: Some("3.1.0".to_string()),
///     }],
///     auth_methods: vec!["did-auth".to_string(), "api-key".to_string()],
/// };
/// assert_eq!(interop.protocols.len(), 2);
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InteroperabilitySection {
    /// Protocols this entity supports.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub protocols: Vec<ProtocolSupport>,

    /// API schema references.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub api_schemas: Vec<ApiSchema>,

    /// Authentication methods accepted: `"did-auth"`, `"api-key"`,
    /// `"oauth2"`, `"mtls"`, `"bearer-token"`.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub auth_methods: Vec<String>,
}

/// Standard protocol identifiers.
pub mod protocols {
    /// Model Context Protocol.
    pub const MCP: &str = "mcp";
    /// Google Agent-to-Agent protocol.
    pub const A2A: &str = "a2a";
    /// REST / HTTP API.
    pub const REST: &str = "rest";
    /// GraphQL API.
    pub const GRAPHQL: &str = "graphql";
    /// gRPC.
    pub const GRPC: &str = "grpc";
    /// WebSocket.
    pub const WEBSOCKET: &str = "websocket";
    /// OAS MAP protocol suite.
    pub const OAS_MAP: &str = "oas-map";
}

/// Standard authentication methods.
pub mod auth_methods {
    pub const DID_AUTH: &str = "did-auth";
    pub const API_KEY: &str = "api-key";
    pub const OAUTH2: &str = "oauth2";
    pub const MTLS: &str = "mtls";
    pub const BEARER_TOKEN: &str = "bearer-token";
}

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

    #[test]
    fn serde_round_trip() {
        let interop = InteroperabilitySection {
            protocols: vec![ProtocolSupport {
                protocol: protocols::MCP.to_string(),
                version: Some("2025-01".to_string()),
                endpoint: None,
            }],
            api_schemas: vec![ApiSchema {
                schema_type: "openapi".to_string(),
                url: "https://example.com/api.json".to_string(),
                version: None,
            }],
            auth_methods: vec![auth_methods::DID_AUTH.to_string()],
        };

        let json = serde_json::to_string(&interop).unwrap_or_default();
        let parsed: InteroperabilitySection =
            serde_json::from_str(&json).unwrap_or_else(|_| interop.clone());
        assert_eq!(interop, parsed);
    }

    #[test]
    fn empty_section_deserializes() {
        let json = r#"{}"#;
        let interop: Result<InteroperabilitySection, _> = serde_json::from_str(json);
        assert!(interop.is_ok());
    }
}