Skip to main content

a2a_rs/domain/core/
agent.rs

1use bon::Builder;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// Supported A2A transport protocols (v0.3.0).
6///
7/// Defines the transport protocols that agents can use for communication.
8/// Agents can support multiple transport protocols simultaneously.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub enum TransportProtocol {
11    #[serde(rename = "JSONRPC")]
12    JsonRpc,
13    #[serde(rename = "GRPC")]
14    Grpc,
15    #[serde(rename = "HTTP+JSON")]
16    HttpJson,
17}
18
19/// Declares a combination of URL and transport protocol for interacting with the agent (v0.3.0).
20///
21/// This allows agents to expose the same functionality over multiple transport mechanisms.
22/// Clients can select any interface based on their transport capabilities and preferences.
23///
24/// # Example
25/// ```rust
26/// use a2a_rs::AgentInterface;
27///
28/// let interface = AgentInterface {
29///     url: "https://api.example.com/grpc".to_string(),
30///     transport: "GRPC".to_string(),
31/// };
32/// ```
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct AgentInterface {
35    /// The URL where this interface is available
36    pub url: String,
37    /// The transport protocol supported at this URL
38    pub transport: String,
39}
40
41/// Declaration of a protocol extension supported by an agent (v0.3.0).
42///
43/// Extensions provide a mechanism for agents to declare support for
44/// additional capabilities beyond the core A2A protocol specification.
45///
46/// # Example
47/// ```rust
48/// use a2a_rs::AgentExtension;
49/// use std::collections::HashMap;
50///
51/// let extension = AgentExtension {
52///     uri: "https://example.com/extensions/custom-auth".to_string(),
53///     description: Some("Custom authentication extension".to_string()),
54///     required: Some(false),
55///     params: None,
56/// };
57/// ```
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct AgentExtension {
60    /// Unique URI identifying the extension
61    pub uri: String,
62    /// Human-readable description of the extension
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub description: Option<String>,
65    /// Whether the client must understand this extension to interact with the agent
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub required: Option<bool>,
68    /// Extension-specific configuration parameters
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub params: Option<HashMap<String, serde_json::Value>>,
71}
72
73/// JSON Web Signature for AgentCard integrity verification (RFC 7515).
74///
75/// This structure represents a digital signature that can be used to verify
76/// the integrity and authenticity of an AgentCard. It follows the JSON Web
77/// Signature (JWS) standard as defined in RFC 7515.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct AgentCardSignature {
80    /// Base64url-encoded protected JWS header
81    pub protected: String,
82    /// Base64url-encoded signature
83    pub signature: String,
84    /// Optional unprotected JWS header values
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub header: Option<HashMap<String, serde_json::Value>>,
87}
88
89/// Information about an agent provider, including organization details and contact URL.
90///
91/// This structure contains metadata about the organization or entity that provides
92/// the agent service, including contact information and organizational details.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct AgentProvider {
95    pub organization: String,
96    pub url: String,
97}
98
99/// Security scheme configurations for agent authentication.
100///
101/// Defines the various authentication methods supported by an agent,
102/// including API keys, HTTP authentication, and OAuth 2.0 flows.
103/// Each scheme specifies the required parameters and configuration
104/// for successful authentication.
105#[derive(Debug, Clone, Serialize, Deserialize)]
106#[serde(tag = "type", rename_all = "camelCase")]
107pub enum SecurityScheme {
108    #[serde(rename = "apiKey")]
109    ApiKey {
110        #[serde(rename = "in")]
111        location: String, // "query" | "header" | "cookie"
112        name: String,
113        #[serde(skip_serializing_if = "Option::is_none")]
114        description: Option<String>,
115    },
116    #[serde(rename = "http")]
117    Http {
118        scheme: String,
119        #[serde(skip_serializing_if = "Option::is_none", rename = "bearerFormat")]
120        bearer_format: Option<String>,
121        #[serde(skip_serializing_if = "Option::is_none")]
122        description: Option<String>,
123    },
124    #[serde(rename = "oauth2")]
125    OAuth2 {
126        flows: Box<OAuthFlows>,
127        #[serde(skip_serializing_if = "Option::is_none")]
128        description: Option<String>,
129        /// OAuth2 metadata discovery endpoint per RFC 8414 (v0.3.0)
130        #[serde(skip_serializing_if = "Option::is_none", rename = "metadataUrl")]
131        metadata_url: Option<String>,
132    },
133    #[serde(rename = "openIdConnect")]
134    OpenIdConnect {
135        #[serde(rename = "openIdConnectUrl")]
136        open_id_connect_url: String,
137        #[serde(skip_serializing_if = "Option::is_none")]
138        description: Option<String>,
139    },
140    /// Mutual TLS authentication (v0.3.0)
141    #[serde(rename = "mutualTLS")]
142    MutualTls {
143        #[serde(skip_serializing_if = "Option::is_none")]
144        description: Option<String>,
145    },
146}
147
148/// OAuth flow configurations supporting multiple authentication flows.
149///
150/// This structure contains optional configurations for different OAuth 2.0 flows
151/// that an agent may support. Each flow type has specific requirements and use cases:
152/// - Authorization Code: Most secure, requires user interaction
153/// - Client Credentials: For server-to-server authentication  
154/// - Implicit: For client-side applications (deprecated in OAuth 2.1)
155/// - Password: For trusted applications with user credentials
156#[derive(Debug, Clone, Serialize, Deserialize, Default)]
157pub struct OAuthFlows {
158    #[serde(skip_serializing_if = "Option::is_none", rename = "authorizationCode")]
159    pub authorization_code: Option<AuthorizationCodeOAuthFlow>,
160    #[serde(skip_serializing_if = "Option::is_none", rename = "clientCredentials")]
161    pub client_credentials: Option<ClientCredentialsOAuthFlow>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub implicit: Option<ImplicitOAuthFlow>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub password: Option<PasswordOAuthFlow>,
166}
167
168/// Configuration for OAuth 2.0 authorization code flow.
169///
170/// The authorization code flow is the most secure OAuth flow, involving
171/// a two-step process where the user is redirected to authorize the application,
172/// and then an authorization code is exchanged for an access token.
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct AuthorizationCodeOAuthFlow {
175    #[serde(rename = "authorizationUrl")]
176    pub authorization_url: String,
177    #[serde(rename = "tokenUrl")]
178    pub token_url: String,
179    #[serde(skip_serializing_if = "Option::is_none", rename = "refreshUrl")]
180    pub refresh_url: Option<String>,
181    pub scopes: HashMap<String, String>,
182}
183
184/// Configuration for OAuth 2.0 client credentials flow.
185///
186/// The client credentials flow is used for server-to-server authentication
187/// where no user interaction is required. The client authenticates using
188/// its own credentials to obtain an access token.
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct ClientCredentialsOAuthFlow {
191    #[serde(rename = "tokenUrl")]
192    pub token_url: String,
193    #[serde(skip_serializing_if = "Option::is_none", rename = "refreshUrl")]
194    pub refresh_url: Option<String>,
195    pub scopes: HashMap<String, String>,
196}
197
198/// Configuration for OAuth 2.0 implicit flow.
199///
200/// The implicit flow is designed for client-side applications that cannot
201/// securely store client secrets. Access tokens are returned directly
202/// from the authorization endpoint. Note: This flow is deprecated in OAuth 2.1.
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct ImplicitOAuthFlow {
205    #[serde(rename = "authorizationUrl")]
206    pub authorization_url: String,
207    #[serde(skip_serializing_if = "Option::is_none", rename = "refreshUrl")]
208    pub refresh_url: Option<String>,
209    pub scopes: HashMap<String, String>,
210}
211
212/// Configuration for OAuth 2.0 password flow.
213///
214/// The password flow allows the application to exchange the user's username
215/// and password for an access token. This flow should only be used by
216/// highly trusted applications as it requires handling user credentials directly.
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct PasswordOAuthFlow {
219    #[serde(rename = "tokenUrl")]
220    pub token_url: String,
221    #[serde(skip_serializing_if = "Option::is_none", rename = "refreshUrl")]
222    pub refresh_url: Option<String>,
223    pub scopes: HashMap<String, String>,
224}
225
226/// Capabilities supported by an agent, including streaming and push notifications.
227///
228/// This structure defines what features an agent supports:
229/// - `streaming`: Whether the agent supports real-time streaming updates
230/// - `push_notifications`: Whether the agent can send push notifications
231/// - `state_transition_history`: Whether the agent maintains task state history
232/// - `extensions`: List of protocol extensions supported by the agent (v0.3.0)
233#[derive(Debug, Clone, Serialize, Deserialize, Default)]
234pub struct AgentCapabilities {
235    #[serde(default)]
236    pub streaming: bool,
237    #[serde(default, rename = "pushNotifications")]
238    pub push_notifications: bool,
239    #[serde(default, rename = "stateTransitionHistory")]
240    pub state_transition_history: bool,
241    /// List of protocol extensions supported by the agent (v0.3.0)
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub extensions: Option<Vec<AgentExtension>>,
244}
245
246/// A skill provided by an agent with metadata and examples.\n///\n/// Skills define specific capabilities that an agent can perform,\n/// including natural language descriptions, categorization tags,\n/// usage examples, and supported input/output modes.\n///\n/// # Example\n/// ```rust\n/// use a2a_rs::AgentSkill;\n/// \n/// let skill = AgentSkill::new(\n///     \"text-generation\".to_string(),\n///     \"Text Generation\".to_string(), \n///     \"Generate natural language text based on prompts\".to_string(),\n///     vec![\"nlp\".to_string(), \"generation\".to_string()]\n/// );\n/// ```
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct AgentSkill {
249    pub id: String,
250    pub name: String,
251    pub description: String,
252    pub tags: Vec<String>,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub examples: Option<Vec<String>>,
255    #[serde(skip_serializing_if = "Option::is_none", rename = "inputModes")]
256    pub input_modes: Option<Vec<String>>,
257    #[serde(skip_serializing_if = "Option::is_none", rename = "outputModes")]
258    pub output_modes: Option<Vec<String>>,
259    /// Per-skill security requirements (v0.3.0) - maps security scheme names to required scopes
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub security: Option<Vec<HashMap<String, Vec<String>>>>,
262}
263
264impl AgentSkill {
265    /// Create a new skill with the minimum required fields
266    pub fn new(id: String, name: String, description: String, tags: Vec<String>) -> Self {
267        Self {
268            id,
269            name,
270            description,
271            tags,
272            examples: None,
273            input_modes: None,
274            output_modes: None,
275            security: None,
276        }
277    }
278
279    /// Add examples to the skill
280    pub fn with_examples(mut self, examples: Vec<String>) -> Self {
281        self.examples = Some(examples);
282        self
283    }
284
285    /// Add input modes to the skill
286    pub fn with_input_modes(mut self, input_modes: Vec<String>) -> Self {
287        self.input_modes = Some(input_modes);
288        self
289    }
290
291    /// Add output modes to the skill
292    pub fn with_output_modes(mut self, output_modes: Vec<String>) -> Self {
293        self.output_modes = Some(output_modes);
294        self
295    }
296
297    /// Add security requirements to the skill (v0.3.0)
298    pub fn with_security(mut self, security: Vec<HashMap<String, Vec<String>>>) -> Self {
299        self.security = Some(security);
300        self
301    }
302
303    /// Create a comprehensive skill with all details in one call
304    #[allow(clippy::too_many_arguments)]
305    pub fn comprehensive(
306        id: String,
307        name: String,
308        description: String,
309        tags: Vec<String>,
310        examples: Option<Vec<String>>,
311        input_modes: Option<Vec<String>>,
312        output_modes: Option<Vec<String>>,
313        security: Option<Vec<HashMap<String, Vec<String>>>>,
314    ) -> Self {
315        Self {
316            id,
317            name,
318            description,
319            tags,
320            examples,
321            input_modes,
322            output_modes,
323            security,
324        }
325    }
326}
327
328/// Card describing an agent's capabilities, metadata, and available skills.\n///\n/// The AgentCard is the primary descriptor for an agent, containing all the\n/// information needed for clients to understand what the agent can do and\n/// how to interact with it. This includes basic metadata like name and version,\n/// capabilities like streaming support, available skills, and security requirements.\n///\n/// # Example\n/// ```rust\n/// use a2a_rs::{AgentCard, AgentCapabilities, AgentSkill};\n/// \n/// let card = AgentCard::builder()\n///     .name(\"My Agent\".to_string())\n///     .description(\"A helpful AI agent\".to_string())\n///     .url(\"https://agent.example.com\".to_string())\n///     .version(\"1.0.0\".to_string())\n///     .capabilities(AgentCapabilities::default())\n///     .build();\n/// ```
329#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
330pub struct AgentCard {
331    pub name: String,
332    pub description: String,
333    pub url: String,
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub provider: Option<AgentProvider>,
336    pub version: String,
337    /// The version of the A2A protocol this agent supports (v0.3.0 required field)
338    #[serde(default = "default_protocol_version", rename = "protocolVersion")]
339    #[builder(default = default_protocol_version())]
340    pub protocol_version: String,
341    /// The transport protocol for the preferred endpoint (v0.3.0)
342    #[serde(default = "default_preferred_transport", rename = "preferredTransport")]
343    #[builder(default = default_preferred_transport())]
344    pub preferred_transport: String,
345    /// Additional supported interfaces (transport and URL combinations) (v0.3.0)
346    #[serde(
347        skip_serializing_if = "Option::is_none",
348        rename = "additionalInterfaces"
349    )]
350    pub additional_interfaces: Option<Vec<AgentInterface>>,
351    /// Optional URL to an icon for the agent (v0.3.0)
352    #[serde(skip_serializing_if = "Option::is_none", rename = "iconUrl")]
353    pub icon_url: Option<String>,
354    #[serde(skip_serializing_if = "Option::is_none", rename = "documentationUrl")]
355    pub documentation_url: Option<String>,
356    pub capabilities: AgentCapabilities,
357    #[serde(skip_serializing_if = "Option::is_none", rename = "securitySchemes")]
358    pub security_schemes: Option<HashMap<String, SecurityScheme>>,
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub security: Option<Vec<HashMap<String, Vec<String>>>>,
361    #[serde(default = "default_input_modes", rename = "defaultInputModes")]
362    pub default_input_modes: Vec<String>,
363    #[serde(default = "default_output_modes", rename = "defaultOutputModes")]
364    pub default_output_modes: Vec<String>,
365    pub skills: Vec<AgentSkill>,
366    /// JSON Web Signatures for this AgentCard (v0.3.0 - changed from singular to plural)
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub signatures: Option<Vec<AgentCardSignature>>,
369    #[serde(
370        skip_serializing_if = "Option::is_none",
371        rename = "supportsAuthenticatedExtendedCard"
372    )]
373    pub supports_authenticated_extended_card: Option<bool>,
374}
375
376fn default_input_modes() -> Vec<String> {
377    vec!["text".to_string()]
378}
379
380fn default_output_modes() -> Vec<String> {
381    vec!["text".to_string()]
382}
383
384fn default_protocol_version() -> String {
385    "0.3.0".to_string()
386}
387
388fn default_preferred_transport() -> String {
389    "JSONRPC".to_string()
390}
391
392/// Authentication information for push notification endpoints.
393///
394/// Specifies the authentication schemes and credentials required
395/// to send push notifications to a client endpoint. This allows
396/// agents to securely deliver notifications to authenticated endpoints.
397#[derive(Debug, Clone, Serialize, Deserialize)]
398pub struct PushNotificationAuthenticationInfo {
399    pub schemes: Vec<String>,
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub credentials: Option<String>,
402}
403
404/// Configuration for push notification delivery including URL and authentication.
405///
406/// Contains all the information needed to send push notifications to a client,
407/// including the destination URL, optional authentication token, and
408/// authentication scheme details.
409///
410/// # Example
411/// ```rust
412/// use a2a_rs::PushNotificationConfig;
413///
414/// let config = PushNotificationConfig {
415///     id: Some("config-123".to_string()),
416///     url: "https://client.example.com/notifications".to_string(),
417///     token: Some("bearer-token-123".to_string()),
418///     authentication: None,
419/// };
420/// ```
421#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct PushNotificationConfig {
423    /// Unique identifier for the push notification configuration (v0.3.0)
424    /// Allows multiple notification callbacks per task
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub id: Option<String>,
427    pub url: String,
428    pub token: Option<String>,
429    #[serde(skip_serializing_if = "Option::is_none")]
430    pub authentication: Option<PushNotificationAuthenticationInfo>,
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436    use serde_json::json;
437
438    #[test]
439    fn test_security_scheme_api_key_serialization() {
440        let scheme = SecurityScheme::ApiKey {
441            location: "header".to_string(),
442            name: "X-API-Key".to_string(),
443            description: Some("API Key authentication".to_string()),
444        };
445
446        let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
447        assert_eq!(json_value["type"], "apiKey");
448        assert_eq!(json_value["in"], "header");
449        assert_eq!(json_value["name"], "X-API-Key");
450    }
451
452    #[test]
453    fn test_security_scheme_http_serialization() {
454        let scheme = SecurityScheme::Http {
455            scheme: "bearer".to_string(),
456            bearer_format: Some("JWT".to_string()),
457            description: Some("Bearer token authentication".to_string()),
458        };
459
460        let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
461        assert_eq!(json_value["type"], "http");
462        assert_eq!(json_value["scheme"], "bearer");
463        assert_eq!(json_value["bearerFormat"], "JWT");
464    }
465
466    #[test]
467    fn test_security_scheme_mtls_serialization() {
468        let scheme = SecurityScheme::MutualTls {
469            description: Some("Mutual TLS authentication".to_string()),
470        };
471
472        let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
473        assert_eq!(json_value["type"], "mutualTLS");
474        assert_eq!(json_value["description"], "Mutual TLS authentication");
475    }
476
477    #[test]
478    fn test_security_scheme_oauth2_with_metadata() {
479        let flows = OAuthFlows {
480            authorization_code: Some(AuthorizationCodeOAuthFlow {
481                authorization_url: "https://example.com/oauth/authorize".to_string(),
482                token_url: "https://example.com/oauth/token".to_string(),
483                refresh_url: None,
484                scopes: HashMap::new(),
485            }),
486            client_credentials: None,
487            implicit: None,
488            password: None,
489        };
490
491        let scheme = SecurityScheme::OAuth2 {
492            flows: Box::new(flows),
493            description: Some("OAuth2 authentication".to_string()),
494            metadata_url: Some(
495                "https://example.com/.well-known/oauth-authorization-server".to_string(),
496            ),
497        };
498
499        let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
500        assert_eq!(json_value["type"], "oauth2");
501        assert_eq!(
502            json_value["metadataUrl"],
503            "https://example.com/.well-known/oauth-authorization-server"
504        );
505    }
506
507    #[test]
508    fn test_agent_card_with_signature() {
509        let mut signature_header = HashMap::new();
510        signature_header.insert("alg".to_string(), json!("RS256"));
511
512        let signature = AgentCardSignature {
513            protected: "eyJhbGciOiJSUzI1NiJ9".to_string(),
514            signature: "dGVzdF9zaWduYXR1cmU".to_string(),
515            header: Some(signature_header),
516        };
517
518        let card = AgentCard {
519            name: "Test Agent".to_string(),
520            description: "Test description".to_string(),
521            url: "https://example.com".to_string(),
522            provider: None,
523            version: "1.0.0".to_string(),
524            protocol_version: "0.3.0".to_string(),
525            preferred_transport: "JSONRPC".to_string(),
526            additional_interfaces: None,
527            icon_url: None,
528            documentation_url: None,
529            capabilities: AgentCapabilities::default(),
530            security_schemes: None,
531            security: None,
532            default_input_modes: vec!["text".to_string()],
533            default_output_modes: vec!["text".to_string()],
534            skills: Vec::new(),
535            signatures: Some(vec![signature]),
536            supports_authenticated_extended_card: Some(true),
537        };
538
539        let json_value = serde_json::to_value(&card).expect("Failed to serialize AgentCard");
540        assert!(json_value["signatures"].is_array());
541        assert_eq!(
542            json_value["signatures"][0]["protected"],
543            "eyJhbGciOiJSUzI1NiJ9"
544        );
545        assert_eq!(json_value["supportsAuthenticatedExtendedCard"], true);
546        assert_eq!(json_value["protocolVersion"], "0.3.0");
547    }
548
549    #[test]
550    fn test_agent_skill_with_security() {
551        let mut security_req = HashMap::new();
552        security_req.insert("oauth2".to_string(), vec!["read:users".to_string()]);
553
554        let skill = AgentSkill {
555            id: "test-skill".to_string(),
556            name: "Test Skill".to_string(),
557            description: "A test skill".to_string(),
558            tags: Vec::new(),
559            examples: None,
560            input_modes: None,
561            output_modes: None,
562            security: Some(vec![security_req]),
563        };
564
565        let json_value = serde_json::to_value(&skill).expect("Failed to serialize AgentSkill");
566        assert!(json_value["security"].is_array());
567        assert_eq!(json_value["security"][0]["oauth2"][0], "read:users");
568    }
569
570    #[test]
571    fn test_agent_card_with_security_schemes() {
572        let mut security_schemes = HashMap::new();
573        security_schemes.insert(
574            "bearer".to_string(),
575            SecurityScheme::Http {
576                scheme: "bearer".to_string(),
577                bearer_format: Some("JWT".to_string()),
578                description: None,
579            },
580        );
581        security_schemes.insert(
582            "mtls".to_string(),
583            SecurityScheme::MutualTls {
584                description: Some("Client certificate authentication".to_string()),
585            },
586        );
587
588        let mut security_req = HashMap::new();
589        security_req.insert("bearer".to_string(), Vec::new());
590
591        let card = AgentCard {
592            name: "Secure Agent".to_string(),
593            description: "Secure agent description".to_string(),
594            url: "https://example.com".to_string(),
595            provider: None,
596            version: "1.0.0".to_string(),
597            protocol_version: "0.3.0".to_string(),
598            preferred_transport: "JSONRPC".to_string(),
599            additional_interfaces: None,
600            icon_url: None,
601            documentation_url: None,
602            capabilities: AgentCapabilities::default(),
603            security_schemes: Some(security_schemes),
604            security: Some(vec![security_req]),
605            default_input_modes: vec!["text".to_string()],
606            default_output_modes: vec!["text".to_string()],
607            skills: Vec::new(),
608            signatures: None,
609            supports_authenticated_extended_card: None,
610        };
611
612        let json_value = serde_json::to_value(&card).expect("Failed to serialize AgentCard");
613        assert!(json_value["securitySchemes"].is_object());
614        assert_eq!(json_value["securitySchemes"]["bearer"]["type"], "http");
615        assert_eq!(json_value["securitySchemes"]["mtls"]["type"], "mutualTLS");
616        assert!(json_value["security"].is_array());
617    }
618}