reqsign_google/provide_credential/
token.rs1use log::debug;
19use std::time::Duration;
20
21use reqsign_core::time::Timestamp;
22use reqsign_core::{Context, Error, ProvideCredential, Result};
23
24use crate::credential::{Credential, Token};
25
26#[derive(Debug, Clone)]
27enum TokenSource {
28 Inline(String),
29 Path(String),
30}
31
32#[derive(Debug, Clone, Copy)]
33enum Expiration {
34 At(Timestamp),
35 In(Duration),
36}
37
38#[derive(Debug, Clone)]
40pub struct TokenCredentialProvider {
41 source: TokenSource,
42 expiration: Option<Expiration>,
43}
44
45impl TokenCredentialProvider {
46 pub fn new(access_token: impl Into<String>) -> Self {
48 Self {
49 source: TokenSource::Inline(access_token.into()),
50 expiration: None,
51 }
52 }
53
54 pub fn from_path(path: impl Into<String>) -> Self {
56 Self {
57 source: TokenSource::Path(path.into()),
58 expiration: None,
59 }
60 }
61
62 pub fn with_expires_at(mut self, expires_at: Timestamp) -> Self {
64 self.expiration = Some(Expiration::At(expires_at));
65 self
66 }
67
68 pub fn with_expires_in(mut self, expires_in: Duration) -> Self {
72 self.expiration = Some(Expiration::In(expires_in));
73 self
74 }
75
76 fn build_token(&self, access_token: String) -> Result<Credential> {
77 let access_token = access_token.trim().to_string();
78 if access_token.is_empty() {
79 return Err(Error::credential_invalid("access token is empty"));
80 }
81
82 let expires_at = self.expiration.map(|expiration| match expiration {
83 Expiration::At(ts) => ts,
84 Expiration::In(duration) => Timestamp::now() + duration,
85 });
86
87 Ok(Credential::with_token(Token {
88 access_token,
89 expires_at,
90 }))
91 }
92}
93
94impl ProvideCredential for TokenCredentialProvider {
95 type Credential = Credential;
96
97 async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
98 let access_token = match &self.source {
99 TokenSource::Inline(access_token) => {
100 debug!("loading access token from static content");
101 access_token.clone()
102 }
103 TokenSource::Path(path) => {
104 debug!("loading access token from file path: {path}");
105 let content = ctx.file_read(path).await?;
106 String::from_utf8(content)
107 .map_err(|e| Error::unexpected("invalid UTF-8 in token file").with_source(e))?
108 }
109 };
110
111 self.build_token(access_token).map(Some)
112 }
113}