mecha10_core/secrets/
environment.rs

1//! Environment Variable Secret Backend
2
3use super::SecretBackend;
4use crate::{Mecha10Error, Result};
5
6/// Secret backend that reads from environment variables
7///
8/// This is the simplest backend, reading secrets from environment variables.
9/// Secret keys are automatically uppercased and prefixed.
10///
11/// # Example
12///
13/// ```rust
14/// use mecha10::secrets::{EnvironmentBackend, SecretBackend};
15///
16/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
17/// // Creates backend with prefix "MECHA10_SECRET_"
18/// let backend = EnvironmentBackend::new("MECHA10_SECRET");
19///
20/// // Will read from env var: MECHA10_SECRET_API_KEY
21/// let api_key = backend.get("api_key").await?;
22/// # Ok(())
23/// # }
24/// ```
25pub struct EnvironmentBackend {
26    prefix: String,
27}
28
29impl EnvironmentBackend {
30    /// Create a new environment backend with a prefix
31    ///
32    /// The prefix will be prepended to all secret keys when reading from
33    /// environment variables. Keys are automatically uppercased.
34    pub fn new(prefix: &str) -> Self {
35        Self {
36            prefix: prefix.to_string(),
37        }
38    }
39
40    /// Create backend with default prefix "MECHA10_SECRET"
41    #[allow(clippy::should_implement_trait)]
42    pub fn default() -> Self {
43        Self::new("MECHA10_SECRET")
44    }
45
46    fn make_env_key(&self, key: &str) -> String {
47        format!("{}_{}", self.prefix, key.to_uppercase())
48    }
49}
50
51#[async_trait::async_trait]
52impl SecretBackend for EnvironmentBackend {
53    async fn get(&self, key: &str) -> Result<String> {
54        let env_key = self.make_env_key(key);
55        std::env::var(&env_key).map_err(|_| {
56            Mecha10Error::Configuration(format!(
57                "Secret '{}' not found in environment variable '{}'",
58                key, env_key
59            ))
60        })
61    }
62
63    async fn list(&self) -> Result<Vec<String>> {
64        let prefix_with_underscore = format!("{}_", self.prefix);
65        let keys: Vec<String> = std::env::vars()
66            .filter_map(|(k, _)| k.strip_prefix(&prefix_with_underscore).map(|s| s.to_lowercase()))
67            .collect();
68        Ok(keys)
69    }
70}