use log::debug;
use std::time::Duration;
use reqsign_core::time::Timestamp;
use reqsign_core::{Context, Error, ProvideCredential, Result};
use crate::credential::{Credential, Token};
#[derive(Debug, Clone)]
enum TokenSource {
Inline(String),
Path(String),
}
#[derive(Debug, Clone, Copy)]
enum Expiration {
At(Timestamp),
In(Duration),
}
#[derive(Debug, Clone)]
pub struct TokenCredentialProvider {
source: TokenSource,
expiration: Option<Expiration>,
}
impl TokenCredentialProvider {
pub fn new(access_token: impl Into<String>) -> Self {
Self {
source: TokenSource::Inline(access_token.into()),
expiration: None,
}
}
pub fn from_path(path: impl Into<String>) -> Self {
Self {
source: TokenSource::Path(path.into()),
expiration: None,
}
}
pub fn with_expires_at(mut self, expires_at: Timestamp) -> Self {
self.expiration = Some(Expiration::At(expires_at));
self
}
pub fn with_expires_in(mut self, expires_in: Duration) -> Self {
self.expiration = Some(Expiration::In(expires_in));
self
}
fn build_token(&self, access_token: String) -> Result<Credential> {
let access_token = access_token.trim().to_string();
if access_token.is_empty() {
return Err(Error::credential_invalid("access token is empty"));
}
let expires_at = self.expiration.map(|expiration| match expiration {
Expiration::At(ts) => ts,
Expiration::In(duration) => Timestamp::now() + duration,
});
Ok(Credential::with_token(Token {
access_token,
expires_at,
}))
}
}
impl ProvideCredential for TokenCredentialProvider {
type Credential = Credential;
async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
let access_token = match &self.source {
TokenSource::Inline(access_token) => {
debug!("loading access token from static content");
access_token.clone()
}
TokenSource::Path(path) => {
debug!("loading access token from file path: {path}");
let content = ctx.file_read(path).await?;
String::from_utf8(content)
.map_err(|e| Error::unexpected("invalid UTF-8 in token file").with_source(e))?
}
};
self.build_token(access_token).map(Some)
}
}