Skip to main content

systemprompt_models/a2a/
security.rs

1//! `A2A` 1.0 security scheme definitions (API key, HTTP, `OAuth2`, `OpenID`
2//! Connect, mutual TLS) and security requirements for agent card auth.
3//!
4//! Copyright (c) systemprompt.io — Business Source License 1.1.
5//! See <https://systemprompt.io> for licensing details.
6
7use serde::{Deserialize, Serialize};
8
9use crate::errors::ParseEnumError;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13pub enum SecurityScheme {
14    #[serde(rename = "apiKeySecurityScheme")]
15    ApiKey(ApiKeySecurityScheme),
16    #[serde(rename = "httpAuthSecurityScheme")]
17    HttpAuth(HttpAuthSecurityScheme),
18    #[serde(rename = "oauth2SecurityScheme")]
19    OAuth2(OAuth2SecurityScheme),
20    #[serde(rename = "openIdConnectSecurityScheme")]
21    OpenIdConnect(OpenIdConnectSecurityScheme),
22    #[serde(rename = "mtlsSecurityScheme")]
23    MutualTls(MutualTlsSecurityScheme),
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
27#[serde(rename_all = "camelCase")]
28pub struct ApiKeySecurityScheme {
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub description: Option<String>,
31    pub location: ApiKeyLocation,
32    pub name: String,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
36#[serde(rename_all = "camelCase")]
37pub struct HttpAuthSecurityScheme {
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub description: Option<String>,
40    pub scheme: String,
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub bearer_format: Option<String>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46#[serde(rename_all = "camelCase")]
47pub struct OAuth2SecurityScheme {
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub description: Option<String>,
50    pub flows: OAuthFlows,
51    #[serde(
52        default,
53        rename = "oauth2MetadataUrl",
54        skip_serializing_if = "Option::is_none"
55    )]
56    pub oauth2_metadata_url: Option<String>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
60#[serde(rename_all = "camelCase")]
61pub struct OpenIdConnectSecurityScheme {
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub description: Option<String>,
64    pub open_id_connect_url: String,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
68#[serde(rename_all = "camelCase")]
69pub struct MutualTlsSecurityScheme {
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub description: Option<String>,
72}
73
74#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
75#[serde(rename_all = "camelCase")]
76pub enum ApiKeyLocation {
77    Query,
78    Header,
79    Cookie,
80}
81
82impl std::fmt::Display for ApiKeyLocation {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        match self {
85            Self::Query => write!(f, "query"),
86            Self::Header => write!(f, "header"),
87            Self::Cookie => write!(f, "cookie"),
88        }
89    }
90}
91
92impl std::str::FromStr for ApiKeyLocation {
93    type Err = ParseEnumError;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        match s {
97            "query" => Ok(Self::Query),
98            "header" => Ok(Self::Header),
99            "cookie" => Ok(Self::Cookie),
100            _ => Err(ParseEnumError::new("API key location", s)),
101        }
102    }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
106#[serde(rename_all = "camelCase")]
107pub enum OAuthFlows {
108    AuthorizationCode(AuthorizationCodeOAuthFlow),
109    ClientCredentials(ClientCredentialsOAuthFlow),
110    DeviceCode(DeviceCodeOAuthFlow),
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
114#[serde(rename_all = "camelCase")]
115pub struct AuthorizationCodeOAuthFlow {
116    pub authorization_url: String,
117    pub token_url: String,
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub refresh_url: Option<String>,
120    #[serde(default)]
121    pub scopes: HashMap<String, String>,
122    #[serde(default)]
123    pub pkce_required: bool,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
127#[serde(rename_all = "camelCase")]
128pub struct ClientCredentialsOAuthFlow {
129    pub token_url: String,
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub refresh_url: Option<String>,
132    #[serde(default)]
133    pub scopes: HashMap<String, String>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
137#[serde(rename_all = "camelCase")]
138pub struct DeviceCodeOAuthFlow {
139    pub device_authorization_url: String,
140    pub token_url: String,
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub refresh_url: Option<String>,
143    #[serde(default)]
144    pub scopes: HashMap<String, String>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
148pub struct SecurityRequirement {
149    #[serde(default)]
150    pub schemes: HashMap<String, StringList>,
151}
152
153impl SecurityRequirement {
154    #[must_use]
155    pub fn scheme(name: impl Into<String>, scopes: Vec<String>) -> Self {
156        Self {
157            schemes: HashMap::from([(name.into(), StringList { list: scopes })]),
158        }
159    }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
163pub struct StringList {
164    #[serde(default)]
165    pub list: Vec<String>,
166}