gemini_adk_rs/auth/
schemes.rs

1//! OpenAPI 3.0-style security scheme definitions.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// OAuth2 grant type.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum OAuthGrantType {
10    /// Client credentials grant (machine-to-machine).
11    ClientCredentials,
12    /// Authorization code grant (user login flow).
13    AuthorizationCode,
14    /// Implicit grant (legacy browser flow).
15    Implicit,
16    /// Resource owner password grant.
17    Password,
18}
19
20/// OpenAPI 3.0-style security scheme — internally tagged on `"type"`.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22#[serde(tag = "type")]
23pub enum AuthScheme {
24    /// API key passed via header, query, or cookie.
25    #[serde(rename = "apiKey")]
26    ApiKey {
27        /// Where the API key is sent: "header", "query", or "cookie".
28        #[serde(rename = "in")]
29        location: String,
30        /// The header/param/cookie name.
31        name: String,
32    },
33    /// HTTP authentication (bearer, basic, etc.).
34    #[serde(rename = "http")]
35    Http {
36        /// The HTTP auth scheme (e.g. "bearer", "basic").
37        scheme: String,
38        /// Optional format hint for bearer tokens.
39        #[serde(skip_serializing_if = "Option::is_none")]
40        bearer_format: Option<String>,
41    },
42    /// OAuth2 authentication.
43    #[serde(rename = "oauth2")]
44    OAuth2 {
45        /// The OAuth2 grant type.
46        #[serde(skip_serializing_if = "Option::is_none")]
47        grant_type: Option<OAuthGrantType>,
48        /// Authorization endpoint URL.
49        #[serde(skip_serializing_if = "Option::is_none")]
50        authorization_url: Option<String>,
51        /// Token endpoint URL.
52        #[serde(skip_serializing_if = "Option::is_none")]
53        token_url: Option<String>,
54        /// Available scopes (name to description mapping).
55        #[serde(skip_serializing_if = "Option::is_none")]
56        scopes: Option<HashMap<String, String>>,
57    },
58    /// OpenID Connect Discovery.
59    #[serde(rename = "openIdConnect")]
60    OpenIdConnect {
61        /// URL of the OpenID Connect discovery document.
62        open_id_connect_url: String,
63    },
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn oauth_grant_type_snake_case() {
72        assert_eq!(
73            serde_json::to_string(&OAuthGrantType::ClientCredentials).unwrap(),
74            "\"client_credentials\""
75        );
76        assert_eq!(
77            serde_json::to_string(&OAuthGrantType::AuthorizationCode).unwrap(),
78            "\"authorization_code\""
79        );
80        assert_eq!(
81            serde_json::to_string(&OAuthGrantType::Implicit).unwrap(),
82            "\"implicit\""
83        );
84        assert_eq!(
85            serde_json::to_string(&OAuthGrantType::Password).unwrap(),
86            "\"password\""
87        );
88    }
89
90    #[test]
91    fn oauth_grant_type_roundtrip() {
92        let types = [
93            OAuthGrantType::ClientCredentials,
94            OAuthGrantType::AuthorizationCode,
95            OAuthGrantType::Implicit,
96            OAuthGrantType::Password,
97        ];
98        for t in &types {
99            let json = serde_json::to_string(t).unwrap();
100            let parsed: OAuthGrantType = serde_json::from_str(&json).unwrap();
101            assert_eq!(&parsed, t);
102        }
103    }
104
105    #[test]
106    fn api_key_scheme_tagged_serialization() {
107        let scheme = AuthScheme::ApiKey {
108            location: "header".into(),
109            name: "X-API-Key".into(),
110        };
111
112        let json = serde_json::to_value(&scheme).unwrap();
113        assert_eq!(json["type"], "apiKey");
114        assert_eq!(json["in"], "header");
115        assert_eq!(json["name"], "X-API-Key");
116
117        // Roundtrip
118        let parsed: AuthScheme = serde_json::from_value(json).unwrap();
119        match parsed {
120            AuthScheme::ApiKey { location, name } => {
121                assert_eq!(location, "header");
122                assert_eq!(name, "X-API-Key");
123            }
124            _ => panic!("expected ApiKey variant"),
125        }
126    }
127
128    #[test]
129    fn http_scheme_tagged_serialization() {
130        let scheme = AuthScheme::Http {
131            scheme: "bearer".into(),
132            bearer_format: Some("JWT".into()),
133        };
134
135        let json = serde_json::to_value(&scheme).unwrap();
136        assert_eq!(json["type"], "http");
137        assert_eq!(json["scheme"], "bearer");
138        assert_eq!(json["bearer_format"], "JWT");
139
140        let parsed: AuthScheme = serde_json::from_value(json).unwrap();
141        match parsed {
142            AuthScheme::Http {
143                scheme,
144                bearer_format,
145            } => {
146                assert_eq!(scheme, "bearer");
147                assert_eq!(bearer_format.as_deref(), Some("JWT"));
148            }
149            _ => panic!("expected Http variant"),
150        }
151    }
152
153    #[test]
154    fn http_scheme_omits_none_bearer_format() {
155        let scheme = AuthScheme::Http {
156            scheme: "basic".into(),
157            bearer_format: None,
158        };
159
160        let json = serde_json::to_value(&scheme).unwrap();
161        assert_eq!(json["type"], "http");
162        assert_eq!(json["scheme"], "basic");
163        assert!(json.get("bearer_format").is_none());
164    }
165
166    #[test]
167    fn oauth2_scheme_tagged_serialization() {
168        let mut scopes = HashMap::new();
169        scopes.insert("read".into(), "Read access".into());
170        scopes.insert("write".into(), "Write access".into());
171
172        let scheme = AuthScheme::OAuth2 {
173            grant_type: Some(OAuthGrantType::AuthorizationCode),
174            authorization_url: Some("https://example.com/authorize".into()),
175            token_url: Some("https://example.com/token".into()),
176            scopes: Some(scopes),
177        };
178
179        let json = serde_json::to_value(&scheme).unwrap();
180        assert_eq!(json["type"], "oauth2");
181        assert_eq!(json["grant_type"], "authorization_code");
182        assert_eq!(json["authorization_url"], "https://example.com/authorize");
183
184        let parsed: AuthScheme = serde_json::from_value(json).unwrap();
185        match parsed {
186            AuthScheme::OAuth2 {
187                grant_type, scopes, ..
188            } => {
189                assert_eq!(grant_type, Some(OAuthGrantType::AuthorizationCode));
190                assert_eq!(scopes.as_ref().unwrap().len(), 2);
191            }
192            _ => panic!("expected OAuth2 variant"),
193        }
194    }
195
196    #[test]
197    fn oauth2_scheme_omits_none_fields() {
198        let scheme = AuthScheme::OAuth2 {
199            grant_type: None,
200            authorization_url: None,
201            token_url: None,
202            scopes: None,
203        };
204
205        let json = serde_json::to_value(&scheme).unwrap();
206        assert_eq!(json["type"], "oauth2");
207        // All optional fields should be absent
208        assert!(json.get("grant_type").is_none());
209        assert!(json.get("authorization_url").is_none());
210        assert!(json.get("token_url").is_none());
211        assert!(json.get("scopes").is_none());
212    }
213
214    #[test]
215    fn openid_connect_scheme_tagged_serialization() {
216        let scheme = AuthScheme::OpenIdConnect {
217            open_id_connect_url: "https://example.com/.well-known/openid-configuration".into(),
218        };
219
220        let json = serde_json::to_value(&scheme).unwrap();
221        assert_eq!(json["type"], "openIdConnect");
222        assert_eq!(
223            json["open_id_connect_url"],
224            "https://example.com/.well-known/openid-configuration"
225        );
226
227        let parsed: AuthScheme = serde_json::from_value(json).unwrap();
228        match parsed {
229            AuthScheme::OpenIdConnect {
230                open_id_connect_url,
231            } => {
232                assert_eq!(
233                    open_id_connect_url,
234                    "https://example.com/.well-known/openid-configuration"
235                );
236            }
237            _ => panic!("expected OpenIdConnect variant"),
238        }
239    }
240}