hyperi_rustlib/secrets/
provider.rs1use std::future::Future;
12use std::path::Path;
13
14use super::error::{SecretsError, SecretsResult};
15use super::types::{SecretMetadata, SecretValue};
16
17pub trait SecretProvider: Send + Sync {
19 fn get(
21 &self,
22 path: &str,
23 key: Option<&str>,
24 ) -> impl Future<Output = SecretsResult<SecretValue>> + Send;
25
26 fn health_check(&self) -> impl Future<Output = SecretsResult<()>> + Send;
28
29 fn name(&self) -> &'static str;
31}
32
33#[derive(Debug, Default)]
46pub struct FileProvider;
47
48impl FileProvider {
49 #[must_use]
51 pub fn new() -> Self {
52 Self
53 }
54
55 pub async fn get(&self, path: &str) -> SecretsResult<SecretValue> {
61 let path = Path::new(path);
62
63 if !path.exists() {
64 return Err(SecretsError::NotFound(format!(
65 "file not found: {}",
66 path.display()
67 )));
68 }
69
70 let data = tokio::fs::read(path).await.map_err(|e| {
71 SecretsError::IoError(std::io::Error::new(
72 e.kind(),
73 format!("failed to read secret file {}: {e}", path.display()),
74 ))
75 })?;
76
77 let metadata = SecretMetadata {
78 version: None,
79 source_path: Some(path.display().to_string()),
80 provider: Some("file".into()),
81 };
82
83 Ok(SecretValue::with_metadata(data, metadata))
84 }
85}
86
87impl SecretProvider for FileProvider {
88 async fn get(&self, path: &str, _key: Option<&str>) -> SecretsResult<SecretValue> {
89 self.get(path).await
90 }
91
92 async fn health_check(&self) -> SecretsResult<()> {
93 Ok(())
95 }
96
97 fn name(&self) -> &'static str {
98 "file"
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[tokio::test]
107 async fn test_file_provider_missing_file() {
108 let provider = FileProvider::new();
109 let result = provider.get("/nonexistent/path/secret.txt").await;
110 assert!(result.is_err());
111 assert!(matches!(result, Err(SecretsError::NotFound(_))));
112 }
113
114 #[tokio::test]
115 async fn test_file_provider_read_file() {
116 let temp_dir = tempfile::tempdir().unwrap();
117 let secret_path = temp_dir.path().join("test-secret.txt");
118 std::fs::write(&secret_path, "my-secret-value").unwrap();
119
120 let provider = FileProvider::new();
121 let result = provider.get(secret_path.to_str().unwrap()).await;
122
123 assert!(result.is_ok());
124 let value = result.unwrap();
125 assert_eq!(value.as_str().unwrap(), "my-secret-value");
126 assert_eq!(value.metadata.provider.as_deref(), Some("file"));
127 }
128
129 #[tokio::test]
130 async fn test_file_provider_binary_content() {
131 let temp_dir = tempfile::tempdir().unwrap();
132 let secret_path = temp_dir.path().join("binary-secret");
133 let binary_data: Vec<u8> = vec![0x00, 0x01, 0x02, 0xFF, 0xFE];
134 std::fs::write(&secret_path, &binary_data).unwrap();
135
136 let provider = FileProvider::new();
137 let result = provider.get(secret_path.to_str().unwrap()).await;
138
139 assert!(result.is_ok());
140 let value = result.unwrap();
141 assert_eq!(value.as_bytes(), &binary_data);
142 assert!(value.as_str().is_err());
144 }
145
146 #[tokio::test]
147 async fn test_file_provider_health_check() {
148 let provider = FileProvider::new();
149 assert!(provider.health_check().await.is_ok());
150 }
151
152 #[test]
153 fn test_file_provider_name() {
154 let provider = FileProvider::new();
155 assert_eq!(provider.name(), "file");
156 }
157}