Skip to main content

cuenv_secrets/
registry.rs

1//! Secret provider registry
2//!
3//! Provides a registry for dynamically registering and looking up secret resolvers.
4//! This allows consumers to register providers at runtime without hardcoding them.
5
6use crate::{SecretError, SecretResolver, SecretSpec};
7use std::collections::HashMap;
8use std::sync::Arc;
9
10/// Registry for secret resolvers
11///
12/// Allows dynamic registration of secret providers by name. Consumers can
13/// register their own resolvers and look them up by provider name at runtime.
14///
15/// # Example
16///
17/// ```ignore
18/// use cuenv_secrets::{SecretRegistry, EnvSecretResolver};
19///
20/// let mut registry = SecretRegistry::new();
21/// registry.register(Arc::new(EnvSecretResolver::new()));
22///
23/// let resolver = registry.get("env").unwrap();
24/// let secret = resolver.resolve("API_KEY", &spec).await?;
25/// ```
26#[derive(Default)]
27pub struct SecretRegistry {
28    resolvers: HashMap<&'static str, Arc<dyn SecretResolver>>,
29}
30
31impl SecretRegistry {
32    /// Create a new empty registry
33    #[must_use]
34    pub fn new() -> Self {
35        Self {
36            resolvers: HashMap::new(),
37        }
38    }
39
40    /// Register a resolver
41    ///
42    /// The resolver's `provider_name()` is used as the key. If a resolver
43    /// with the same name already exists, it is replaced.
44    pub fn register(&mut self, resolver: Arc<dyn SecretResolver>) {
45        self.resolvers.insert(resolver.provider_name(), resolver);
46    }
47
48    /// Get a resolver by provider name
49    ///
50    /// Returns `None` if no resolver is registered for the given name.
51    #[must_use]
52    pub fn get(&self, provider: &str) -> Option<Arc<dyn SecretResolver>> {
53        self.resolvers.get(provider).cloned()
54    }
55
56    /// Check if a resolver is registered for the given provider name
57    #[must_use]
58    pub fn has(&self, provider: &str) -> bool {
59        self.resolvers.contains_key(provider)
60    }
61
62    /// Get all registered provider names
63    #[must_use]
64    pub fn providers(&self) -> Vec<&'static str> {
65        self.resolvers.keys().copied().collect()
66    }
67
68    /// Resolve a secret using the appropriate resolver
69    ///
70    /// Looks up the resolver by provider name and delegates resolution.
71    ///
72    /// # Errors
73    ///
74    /// Returns `SecretError::UnsupportedResolver` if no resolver is registered
75    /// for the given provider name.
76    pub async fn resolve(
77        &self,
78        provider: &str,
79        name: &str,
80        spec: &SecretSpec,
81    ) -> Result<String, SecretError> {
82        let resolver = self
83            .get(provider)
84            .ok_or_else(|| SecretError::UnsupportedResolver {
85                resolver: provider.to_string(),
86            })?;
87
88        resolver.resolve(name, spec).await
89    }
90}
91
92impl std::fmt::Debug for SecretRegistry {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        f.debug_struct("SecretRegistry")
95            .field("providers", &self.providers())
96            .finish()
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::resolvers::EnvSecretResolver;
104
105    #[test]
106    fn test_registry_new() {
107        let registry = SecretRegistry::new();
108        assert!(registry.providers().is_empty());
109    }
110
111    #[test]
112    fn test_registry_default() {
113        let registry = SecretRegistry::default();
114        assert!(registry.providers().is_empty());
115    }
116
117    #[test]
118    fn test_registry_register() {
119        let mut registry = SecretRegistry::new();
120        registry.register(Arc::new(EnvSecretResolver::new()));
121
122        assert!(registry.has("env"));
123        assert_eq!(registry.providers(), vec!["env"]);
124    }
125
126    #[test]
127    fn test_registry_get() {
128        let mut registry = SecretRegistry::new();
129        registry.register(Arc::new(EnvSecretResolver::new()));
130
131        let resolver = registry.get("env");
132        assert!(resolver.is_some());
133        assert_eq!(resolver.unwrap().provider_name(), "env");
134    }
135
136    #[test]
137    fn test_registry_get_missing() {
138        let registry = SecretRegistry::new();
139        assert!(registry.get("nonexistent").is_none());
140    }
141
142    #[test]
143    fn test_registry_has() {
144        let mut registry = SecretRegistry::new();
145        registry.register(Arc::new(EnvSecretResolver::new()));
146
147        assert!(registry.has("env"));
148        assert!(!registry.has("vault"));
149    }
150
151    #[test]
152    fn test_registry_replace() {
153        let mut registry = SecretRegistry::new();
154        registry.register(Arc::new(EnvSecretResolver::new()));
155        registry.register(Arc::new(EnvSecretResolver::new()));
156
157        // Should still have only one "env" provider
158        assert_eq!(registry.providers().len(), 1);
159    }
160
161    #[test]
162    fn test_registry_debug() {
163        let mut registry = SecretRegistry::new();
164        registry.register(Arc::new(EnvSecretResolver::new()));
165
166        let debug = format!("{registry:?}");
167        assert!(debug.contains("SecretRegistry"));
168        assert!(debug.contains("env"));
169    }
170
171    #[tokio::test]
172    async fn test_registry_resolve_unsupported() {
173        let registry = SecretRegistry::new();
174        let spec = SecretSpec::new("source");
175
176        let result = registry.resolve("unknown", "secret", &spec).await;
177        assert!(result.is_err());
178
179        if let Err(SecretError::UnsupportedResolver { resolver }) = result {
180            assert_eq!(resolver, "unknown");
181        } else {
182            panic!("Expected UnsupportedResolver error");
183        }
184    }
185
186    #[tokio::test]
187    async fn test_registry_resolve_env() {
188        let mut registry = SecretRegistry::new();
189        registry.register(Arc::new(EnvSecretResolver::new()));
190
191        temp_env::async_with_vars([("TEST_SECRET_REGISTRY", Some("test_value"))], async {
192            let spec = SecretSpec::new("TEST_SECRET_REGISTRY");
193
194            let result = registry.resolve("env", "TEST_SECRET_REGISTRY", &spec).await;
195            assert!(result.is_ok());
196            assert_eq!(result.unwrap(), "test_value");
197        })
198        .await;
199    }
200}