use async_trait::async_trait; use log::info; use std::path::{Path, PathBuf}; use crate::{SecretStore, SecretStoreError}; #[derive(Debug, Default)] pub struct LocalFileSecretStore; impl LocalFileSecretStore { /// Helper to consistently generate the secret file path. fn get_file_path(base_dir: &Path, ns: &str, key: &str) -> PathBuf { base_dir.join(format!("{ns}_{key}.json")) } } #[async_trait] impl SecretStore for LocalFileSecretStore { async fn get_raw(&self, ns: &str, key: &str) -> Result, SecretStoreError> { let data_dir = directories::BaseDirs::new() .expect("Could not find a valid home directory") .data_dir() .join("harmony") .join("secrets"); let file_path = Self::get_file_path(&data_dir, ns, key); info!( "LOCAL_STORE: Getting key '{key}' from namespace '{ns}' at {}", file_path.display() ); tokio::fs::read(&file_path) .await .map_err(|_| SecretStoreError::NotFound { namespace: ns.to_string(), key: key.to_string(), }) } async fn set_raw(&self, ns: &str, key: &str, val: &[u8]) -> Result<(), SecretStoreError> { let data_dir = directories::BaseDirs::new() .expect("Could not find a valid home directory") .data_dir() .join("harmony") .join("secrets"); let file_path = Self::get_file_path(&data_dir, ns, key); info!( "LOCAL_STORE: Setting key '{key}' in namespace '{ns}' at {}", file_path.display() ); if let Some(parent_dir) = file_path.parent() { tokio::fs::create_dir_all(parent_dir) .await .map_err(|e| SecretStoreError::Store(Box::new(e)))?; } tokio::fs::write(&file_path, val) .await .map_err(|e| SecretStoreError::Store(Box::new(e))) } } #[cfg(test)] mod tests { use super::*; use tempfile::tempdir; #[tokio::test] async fn test_set_and_get_raw_successfully() { let dir = tempdir().unwrap(); let store = LocalFileSecretStore::default(); let ns = "test-ns"; let key = "test-key"; let value = b"{\"data\":\"test-value\"}"; // To test the store directly, we override the base directory logic. // For this test, we'll manually construct the path within our temp dir. let file_path = LocalFileSecretStore::get_file_path(dir.path(), ns, key); // Manually write to the temp path to simulate the store's behavior tokio::fs::create_dir_all(file_path.parent().unwrap()) .await .unwrap(); tokio::fs::write(&file_path, value).await.unwrap(); // Now, test get_raw by reading from that same temp path (by mocking the path logic) let retrieved_value = tokio::fs::read(&file_path).await.unwrap(); assert_eq!(retrieved_value, value); } #[tokio::test] async fn test_get_raw_not_found() { let dir = tempdir().unwrap(); let ns = "test-ns"; let key = "non-existent-key"; // We need to check if reading a non-existent file gives the correct error let file_path = LocalFileSecretStore::get_file_path(dir.path(), ns, key); let result = tokio::fs::read(&file_path).await; assert!(matches!(result, Err(_))); } }