implementation

This commit is contained in:
Brandon Thomas committed 2026-05-11 21:14:13 -04:00
1 parent 4dc1f0b7e3
commit 3a4044d2ab
2 files changed
+142 -14

No files matched your search

@@ -1,7 +1,7 @@
use serde_derive::{Deserialize, Serialize};
use crate::core::providers::provider_credential_type::ProviderCredentialType;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProviderCredentialKey {
@@ -1,46 +1,174 @@
use std::collections::HashMap;
use std::error::Error;
use std::fmt::Display;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
use log::{info, warn};
use crate::core::providers::payload::api_key::{ApiKeyData, ApiKeyDataError};
use crate::core::providers::payload::provider_credential_payload::ProviderCredentialPayload;
use crate::core::providers::payload::web_login::{WebLoginData, WebLoginDataError};
use crate::core::providers::provider_credential_key::ProviderCredentialKey;
use crate::core::providers::provider_credential_type::ProviderCredentialType;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
struct CacheEntry {
payload: ProviderCredentialPayload,
loaded_at: Instant,
}
#[derive(Clone)]
pub struct ProviderCredentialLoadingCache {
// TODO: Some kind of Arc<Lock<Cache<key,value>>>
inner: Arc<RwLock<HashMap<ProviderCredentialKey, CacheEntry>>>,
app_data_root: AppDataRoot,
ttl: Duration,
}
impl ProviderCredentialLoadingCache {
pub fn new(app_data_root: AppDataRoot) -> Self {
Self {
inner: Arc::new(RwLock::new(HashMap::new())),
app_data_root,
}
ttl: Duration::from_secs(300), // 5 minutes
}
}
pub fn get_credentials(&self, key: ProviderCredentialKey) -> Result<Option<ProviderCredentialPayload>, ProviderCredentialLoadingCacheError> {
unimplemented!()
pub fn new_with_ttl(app_data_root: AppDataRoot, ttl: Duration) -> Self {
Self {
inner: Arc::new(RwLock::new(HashMap::new())),
app_data_root,
ttl,
}
}
pub fn save_credentials(&self, key: ProviderCredentialKey, payload: ProviderCredentialPayload) -> Result<(), ProviderCredentialLoadingCacheError> {
unimplemented!()
pub fn get_credentials(
&self,
key: ProviderCredentialKey,
) -> Result<Option<ProviderCredentialPayload>, ProviderCredentialLoadingCacheError> {
// Check cache first.
{
let cache = self.inner.read()
.map_err(|_| ProviderCredentialLoadingCacheError::LockPoisoned)?;
if let Some(entry) = cache.get(&key) {
if entry.loaded_at.elapsed() < self.ttl {
return Ok(Some(entry.payload.clone()));
}
}
}
// Cache miss or expired — load from disk.
let file_path = self.app_data_root.credentials_dir().path().join(key.get_filename());
if !file_path.exists() {
return Ok(None);
}
info!("Loading credential from disk: {:?}", file_path);
let payload = match key.get_type() {
ProviderCredentialType::ApiKey => {
let data = ApiKeyData::load_from_file(&file_path)
.map_err(ProviderCredentialLoadingCacheError::ApiKeyError)?;
ProviderCredentialPayload::ApiKey(data)
}
ProviderCredentialType::WebLogin => {
let data = WebLoginData::load_from_file(&file_path)
.map_err(ProviderCredentialLoadingCacheError::WebLoginError)?;
ProviderCredentialPayload::WebLogin(data)
}
};
// Store in cache.
{
let mut cache = self.inner.write()
.map_err(|_| ProviderCredentialLoadingCacheError::LockPoisoned)?;
cache.insert(key, CacheEntry {
payload: payload.clone(),
loaded_at: Instant::now(),
});
}
Ok(Some(payload))
}
pub fn save_credentials(
&self,
key: ProviderCredentialKey,
payload: ProviderCredentialPayload,
) -> Result<(), ProviderCredentialLoadingCacheError> {
let file_path = self.app_data_root.credentials_dir().path().join(key.get_filename());
info!("Saving credential to disk: {:?}", file_path);
match (&payload, key.get_type()) {
(ProviderCredentialPayload::ApiKey(data), ProviderCredentialType::ApiKey) => {
data.save_to_file(&file_path)
.map_err(ProviderCredentialLoadingCacheError::ApiKeyError)?;
}
(ProviderCredentialPayload::WebLogin(data), ProviderCredentialType::WebLogin) => {
data.save_to_file(&file_path)
.map_err(ProviderCredentialLoadingCacheError::WebLoginError)?;
}
_ => {
warn!("Credential type mismatch for key {:?}", key);
return Err(ProviderCredentialLoadingCacheError::TypeMismatch);
}
}
// Update cache.
{
let mut cache = self.inner.write()
.map_err(|_| ProviderCredentialLoadingCacheError::LockPoisoned)?;
cache.insert(key, CacheEntry {
payload,
loaded_at: Instant::now(),
});
}
Ok(())
}
/// Invalidate a cached entry, forcing a re-read from disk on next access.
pub fn invalidate(&self, key: ProviderCredentialKey) {
if let Ok(mut cache) = self.inner.write() {
cache.remove(&key);
}
}
}
#[derive(Debug)]
pub enum ProviderCredentialLoadingCacheError {
IoError(std::io::Error),
ApiKeyError(ApiKeyDataError),
WebLoginError(WebLoginDataError),
TypeMismatch,
LockPoisoned,
}
impl Error for ProviderCredentialLoadingCacheError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ProviderCredentialLoadingCacheError::IoError(err) => Some(err),
Self::IoError(err) => Some(err),
Self::ApiKeyError(err) => Some(err),
Self::WebLoginError(err) => Some(err),
_ => None,
}
}
}
}
impl Display for ProviderCredentialLoadingCacheError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::IoError(e) => write!(f, "IO error: {}", e),
Self::ApiKeyError(e) => write!(f, "API key error: {}", e),
Self::WebLoginError(e) => write!(f, "Web login error: {}", e),
Self::TypeMismatch => write!(f, "Credential type does not match key type"),
Self::LockPoisoned => write!(f, "Internal lock poisoned"),
}
}
}