gemini_adk_rs/auth/
credential.rs

1//! Full credential type hierarchy matching ADK-JS `AuthCredential`.
2
3use serde::{Deserialize, Serialize};
4
5/// The type of authentication credential.
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
8pub enum AuthCredentialType {
9    /// API key authentication.
10    ApiKey,
11    /// HTTP-based authentication (basic or bearer).
12    Http,
13    /// OAuth 2.0 authentication.
14    #[serde(rename = "OAUTH2")]
15    OAuth2,
16    /// OpenID Connect authentication.
17    OpenIdConnect,
18    /// Google Cloud service account authentication.
19    ServiceAccount,
20}
21
22/// HTTP credentials — basic auth or bearer token.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct HttpCredentials {
25    /// Username for basic auth.
26    pub username: Option<String>,
27    /// Password for basic auth.
28    pub password: Option<String>,
29    /// Bearer or other token.
30    pub token: Option<String>,
31}
32
33/// HTTP authentication with a named scheme (e.g. "bearer", "basic").
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct HttpAuth {
36    /// The authentication scheme name.
37    pub scheme: String,
38    /// The credentials for this scheme.
39    pub credentials: HttpCredentials,
40}
41
42/// OAuth2 authentication data.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct OAuth2Auth {
45    /// OAuth2 client identifier.
46    pub client_id: Option<String>,
47    /// OAuth2 client secret.
48    pub client_secret: Option<String>,
49    /// Authorization endpoint URL.
50    pub auth_uri: Option<String>,
51    /// Token endpoint URL.
52    pub token_uri: Option<String>,
53    /// Redirect URI for the OAuth2 flow.
54    pub redirect_uri: Option<String>,
55    /// Authorization code received from the auth flow.
56    pub auth_code: Option<String>,
57    /// The access token.
58    pub access_token: Option<String>,
59    /// Token used to refresh the access token.
60    pub refresh_token: Option<String>,
61    /// Unix timestamp (seconds) when the access token expires.
62    pub expires_at: Option<u64>,
63    /// OAuth2 scopes requested.
64    pub scopes: Option<Vec<String>>,
65    /// Full URI of the OAuth2 authorization response.
66    pub auth_response_uri: Option<String>,
67}
68
69/// Service account credential for Google Cloud.
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ServiceAccountCredential {
72    /// Path to the service account key JSON file.
73    pub service_account_key_file: Option<String>,
74    /// Inline service account key JSON.
75    pub service_account_key: Option<serde_json::Value>,
76    /// Scopes to request with the service account.
77    pub scopes: Option<Vec<String>>,
78    /// Whether to use Application Default Credentials.
79    pub use_default_credential: Option<bool>,
80    /// Google Cloud project ID.
81    pub project_id: Option<String>,
82    /// Universe domain for the credentials.
83    pub universe_domain: Option<String>,
84}
85
86/// Full auth credential — discriminated by `auth_type` with variant-specific data.
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct AuthCredential {
89    /// The type of this credential.
90    pub auth_type: AuthCredentialType,
91    /// Optional external resource reference.
92    pub resource_ref: Option<String>,
93    /// API key value (when `auth_type` is `ApiKey`).
94    pub api_key: Option<String>,
95    /// HTTP auth data (when `auth_type` is `Http`).
96    pub http: Option<HttpAuth>,
97    /// OAuth2 auth data (when `auth_type` is `OAuth2`).
98    pub oauth2: Option<OAuth2Auth>,
99    /// Service account data (when `auth_type` is `ServiceAccount`).
100    pub service_account: Option<ServiceAccountCredential>,
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn credential_type_screaming_snake_case() {
109        assert_eq!(
110            serde_json::to_string(&AuthCredentialType::ApiKey).unwrap(),
111            "\"API_KEY\""
112        );
113        assert_eq!(
114            serde_json::to_string(&AuthCredentialType::Http).unwrap(),
115            "\"HTTP\""
116        );
117        assert_eq!(
118            serde_json::to_string(&AuthCredentialType::OAuth2).unwrap(),
119            "\"OAUTH2\""
120        );
121        assert_eq!(
122            serde_json::to_string(&AuthCredentialType::OpenIdConnect).unwrap(),
123            "\"OPEN_ID_CONNECT\""
124        );
125        assert_eq!(
126            serde_json::to_string(&AuthCredentialType::ServiceAccount).unwrap(),
127            "\"SERVICE_ACCOUNT\""
128        );
129    }
130
131    #[test]
132    fn credential_type_roundtrip() {
133        let types = [
134            AuthCredentialType::ApiKey,
135            AuthCredentialType::Http,
136            AuthCredentialType::OAuth2,
137            AuthCredentialType::OpenIdConnect,
138            AuthCredentialType::ServiceAccount,
139        ];
140        for t in &types {
141            let json = serde_json::to_string(t).unwrap();
142            let parsed: AuthCredentialType = serde_json::from_str(&json).unwrap();
143            assert_eq!(&parsed, t);
144        }
145    }
146
147    #[test]
148    fn api_key_credential_roundtrip() {
149        let cred = AuthCredential {
150            auth_type: AuthCredentialType::ApiKey,
151            resource_ref: Some("my-resource".into()),
152            api_key: Some("sk-secret-123".into()),
153            http: None,
154            oauth2: None,
155            service_account: None,
156        };
157
158        let json = serde_json::to_string_pretty(&cred).unwrap();
159        let parsed: AuthCredential = serde_json::from_str(&json).unwrap();
160
161        assert_eq!(parsed.auth_type, AuthCredentialType::ApiKey);
162        assert_eq!(parsed.api_key.as_deref(), Some("sk-secret-123"));
163        assert_eq!(parsed.resource_ref.as_deref(), Some("my-resource"));
164        assert!(parsed.http.is_none());
165        assert!(parsed.oauth2.is_none());
166        assert!(parsed.service_account.is_none());
167    }
168
169    #[test]
170    fn http_credential_roundtrip() {
171        let cred = AuthCredential {
172            auth_type: AuthCredentialType::Http,
173            resource_ref: None,
174            api_key: None,
175            http: Some(HttpAuth {
176                scheme: "bearer".into(),
177                credentials: HttpCredentials {
178                    username: None,
179                    password: None,
180                    token: Some("eyJhbGciOi...".into()),
181                },
182            }),
183            oauth2: None,
184            service_account: None,
185        };
186
187        let json = serde_json::to_string(&cred).unwrap();
188        let parsed: AuthCredential = serde_json::from_str(&json).unwrap();
189
190        assert_eq!(parsed.auth_type, AuthCredentialType::Http);
191        let http = parsed.http.unwrap();
192        assert_eq!(http.scheme, "bearer");
193        assert_eq!(http.credentials.token.as_deref(), Some("eyJhbGciOi..."));
194    }
195
196    #[test]
197    fn oauth2_credential_roundtrip() {
198        let cred = AuthCredential {
199            auth_type: AuthCredentialType::OAuth2,
200            resource_ref: None,
201            api_key: None,
202            http: None,
203            oauth2: Some(OAuth2Auth {
204                client_id: Some("client-123".into()),
205                client_secret: Some("secret-456".into()),
206                auth_uri: Some("https://accounts.google.com/o/oauth2/auth".into()),
207                token_uri: Some("https://oauth2.googleapis.com/token".into()),
208                redirect_uri: Some("http://localhost:8080/callback".into()),
209                auth_code: None,
210                access_token: Some("ya29.access".into()),
211                refresh_token: Some("1//refresh".into()),
212                expires_at: Some(1700000000),
213                scopes: Some(vec!["openid".into(), "email".into()]),
214                auth_response_uri: None,
215            }),
216            service_account: None,
217        };
218
219        let json = serde_json::to_string(&cred).unwrap();
220        let parsed: AuthCredential = serde_json::from_str(&json).unwrap();
221
222        assert_eq!(parsed.auth_type, AuthCredentialType::OAuth2);
223        let oauth2 = parsed.oauth2.unwrap();
224        assert_eq!(oauth2.client_id.as_deref(), Some("client-123"));
225        assert_eq!(oauth2.scopes.as_ref().unwrap().len(), 2);
226        assert_eq!(oauth2.expires_at, Some(1700000000));
227    }
228
229    #[test]
230    fn service_account_credential_roundtrip() {
231        let cred = AuthCredential {
232            auth_type: AuthCredentialType::ServiceAccount,
233            resource_ref: None,
234            api_key: None,
235            http: None,
236            oauth2: None,
237            service_account: Some(ServiceAccountCredential {
238                service_account_key_file: Some("/path/to/key.json".into()),
239                service_account_key: None,
240                scopes: Some(vec!["https://www.googleapis.com/auth/cloud-platform".into()]),
241                use_default_credential: Some(true),
242                project_id: Some("my-project".into()),
243                universe_domain: Some("googleapis.com".into()),
244            }),
245        };
246
247        let json = serde_json::to_string(&cred).unwrap();
248        let parsed: AuthCredential = serde_json::from_str(&json).unwrap();
249
250        assert_eq!(parsed.auth_type, AuthCredentialType::ServiceAccount);
251        let sa = parsed.service_account.unwrap();
252        assert_eq!(
253            sa.service_account_key_file.as_deref(),
254            Some("/path/to/key.json")
255        );
256        assert_eq!(sa.use_default_credential, Some(true));
257        assert_eq!(sa.project_id.as_deref(), Some("my-project"));
258    }
259}