ppoppo_sdk_core/token_cache/
credentials.rs1use std::time::Duration;
4
5use super::{TokenCacheError, TokenSource};
6
7pub struct ClientCredentialsSource {
13 http: reqwest::Client,
14 token_url: String,
15 client_id: String,
16 client_secret: String,
17}
18
19impl ClientCredentialsSource {
20 pub fn new(
21 token_url: impl Into<String>,
22 client_id: impl Into<String>,
23 client_secret: impl Into<String>,
24 ) -> Self {
25 Self {
26 http: {
27 crate::install_ring_provider();
28 reqwest::Client::new()
29 },
30 token_url: token_url.into(),
31 client_id: client_id.into(),
32 client_secret: client_secret.into(),
33 }
34 }
35}
36
37#[derive(serde::Deserialize)]
38struct TokenResponse {
39 access_token: String,
40 expires_in: u64,
41}
42
43#[async_trait::async_trait]
44impl TokenSource for ClientCredentialsSource {
45 async fn fetch_token(&self) -> Result<(String, Duration), TokenCacheError> {
46 let resp = self
47 .http
48 .post(&self.token_url)
49 .form(&[
50 ("grant_type", "client_credentials"),
51 ("client_id", self.client_id.as_str()),
52 ("client_secret", self.client_secret.as_str()),
53 ])
54 .send()
55 .await
56 .map_err(|e: reqwest::Error| TokenCacheError::Fetch(e.to_string()))?;
57
58 if !resp.status().is_success() {
59 let status = resp.status();
60 let body: String = resp.text().await.unwrap_or_default();
61 return Err(TokenCacheError::Fetch(format!("{status}: {body}")));
62 }
63
64 let body: TokenResponse = resp
65 .json::<TokenResponse>()
66 .await
67 .map_err(|e: reqwest::Error| TokenCacheError::Malformed(e.to_string()))?;
68
69 Ok((body.access_token, Duration::from_secs(body.expires_in)))
70 }
71}