Skip to main content

jules_api/auth/
mod.rs

1//! Auth module.
2
3use crate::http::HttpRequest;
4
5/// The type of authentication to use.
6#[derive(Clone, PartialEq, Eq, Default)]
7pub enum AuthType {
8    /// Bearer token authentication (e.g. `OAuth2` or JWT).
9    Bearer(String),
10    /// API key authentication, usually provided in a custom header.
11    ApiKey {
12        /// The header name (e.g., "x-api-key").
13        header: String,
14        /// The API key value.
15        key: String,
16    },
17    /// Custom header authentication.
18    Custom {
19        /// The header name.
20        header: String,
21        /// The header value.
22        value: String,
23    },
24    /// No authentication.
25    #[default]
26    None,
27}
28
29impl std::fmt::Debug for AuthType {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Self::Bearer(_) => f.debug_tuple("Bearer").field(&"***REDACTED***").finish(),
33            Self::ApiKey { header, key: _ } => f
34                .debug_struct("ApiKey")
35                .field("header", header)
36                .field("key", &"***REDACTED***")
37                .finish(),
38            Self::Custom { header, value: _ } => f
39                .debug_struct("Custom")
40                .field("header", header)
41                .field("value", &"***REDACTED***")
42                .finish(),
43            Self::None => write!(f, "None"),
44        }
45    }
46}
47
48impl AuthType {
49    /// Applies the authentication to the given HTTP request.
50    #[must_use]
51    pub fn apply(self, request: HttpRequest) -> HttpRequest {
52        match self {
53            Self::Bearer(token) => request.with_header("Authorization", format!("Bearer {token}")),
54            Self::ApiKey { header, key } => request.with_header(header, key),
55            Self::Custom { header, value } => request.with_header(header, value),
56            Self::None => request,
57        }
58    }
59
60    /// Creates an [`AuthType::ApiKey`] using the `X-Goog-Api-Key` header, which is the header
61    /// the real Jules `v1alpha` API accepts an API key through (verified against the live API).
62    #[must_use]
63    pub fn google_api_key(key: impl Into<String>) -> Self {
64        Self::ApiKey {
65            header: "X-Goog-Api-Key".to_string(),
66            key: key.into(),
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::http::Method;
75
76    #[test]
77    fn test_auth_type_bearer() {
78        let auth = AuthType::Bearer("secret-token".to_string());
79        let req = HttpRequest::new(Method::Get, "https://api.example.com");
80        let req = auth.apply(req);
81
82        assert_eq!(req.headers.len(), 1);
83        assert_eq!(req.headers[0].0, "Authorization");
84        assert_eq!(req.headers[0].1, "Bearer secret-token");
85    }
86
87    #[test]
88    fn test_auth_type_api_key() {
89        let auth = AuthType::ApiKey {
90            header: "x-api-key".to_string(),
91            key: "my-key".to_string(),
92        };
93        let req = HttpRequest::new(Method::Get, "https://api.example.com");
94        let req = auth.apply(req);
95
96        assert_eq!(req.headers.len(), 1);
97        assert_eq!(req.headers[0].0, "x-api-key");
98        assert_eq!(req.headers[0].1, "my-key");
99    }
100
101    #[test]
102    fn test_auth_type_custom() {
103        let auth = AuthType::Custom {
104            header: "X-Custom-Auth".to_string(),
105            value: "custom-value".to_string(),
106        };
107        let req = HttpRequest::new(Method::Get, "https://api.example.com");
108        let req = auth.apply(req);
109
110        assert_eq!(req.headers.len(), 1);
111        assert_eq!(req.headers[0].0, "X-Custom-Auth");
112        assert_eq!(req.headers[0].1, "custom-value");
113    }
114
115    #[test]
116    fn test_auth_type_none() {
117        let auth = AuthType::None;
118        let req = HttpRequest::new(Method::Get, "https://api.example.com");
119        let req = auth.apply(req);
120
121        assert!(req.headers.is_empty());
122    }
123}