use super::TokenExpiry;
use miette::Diagnostic;
use std::{
fs,
path::{Path, PathBuf},
};
use thiserror::Error;
use tracing::instrument;
#[derive(Diagnostic, Error, Debug)]
pub enum SetTokenError {
#[error("IOWriteError: {0}")]
IOWriteError(#[from] std::io::Error),
#[error("JsonError: {0}")]
JsonError(#[from] serde_json::Error),
}
#[derive(Error, Debug, Diagnostic)]
pub enum ClearTokenError {
#[error("IOWriteError: {0}")]
IoError(#[from] std::io::Error),
}
#[derive(Debug)]
pub struct TokenStore<TK: for<'a> TokenExpiry<'a>> {
token_path: PathBuf,
cached_token: Option<TK>,
}
impl<TK: for<'a> TokenExpiry<'a>> TokenStore<TK> {
pub fn new(token_path: &Path) -> Self {
let cache_dir = token_path.parent().unwrap_or_else(|| {
panic!(
"Token path must have a parent directory: {}",
token_path.display()
)
});
if !fs::exists(cache_dir).expect("Cannot access token directory") {
fs::create_dir_all(cache_dir).expect("Failed to create token directory");
}
let cached_token: Option<TK> = std::fs::read_to_string(token_path)
.ok()
.and_then(|x| serde_json::from_str(&x).ok());
let token_path = token_path.to_owned();
Self {
token_path,
cached_token,
}
}
#[instrument(level = "debug", skip(self), fields(path = ?self.token_path))]
pub fn get(&mut self) -> Option<TK> {
if let Some(token) = &self.cached_token {
if !token.is_expired() {
return Some(token.clone());
}
}
let token_from_disk: Option<TK> = std::fs::read_to_string(&self.token_path)
.ok()
.and_then(|x| serde_json::from_str(&x).ok());
if let Some(token) = &token_from_disk {
self.cached_token = Some(token.clone());
return Some(token.clone());
}
tracing::debug!("No valid token found in cache or disk");
None
}
#[instrument(level = "debug", skip(self, token), fields(path = ?self.token_path))]
pub fn set(&mut self, token: &TK) -> Result<(), SetTokenError> {
self.cached_token = Some(token.clone());
let json_string = serde_json::to_string_pretty(token)?;
std::fs::write(&self.token_path, json_string)?;
Ok(())
}
pub fn clear(&mut self) -> Result<(), ClearTokenError> {
self.cached_token = None;
if self.token_path.exists() {
std::fs::remove_file(&self.token_path)?;
}
Ok(())
}
}