Skip to main content

secrets_rs/
bindable.rs

1use crate::{error::BindError, source::SourceRegistry};
2
3/// Implemented by structs that contain one or more [`Secret`](crate::Secret) fields.
4///
5/// Call [`bind_all`] to bind every secret in the struct in one step, collecting
6/// all errors rather than stopping at the first failure.
7///
8/// Implement this trait manually until a derive macro is available:
9///
10/// ```rust
11/// use secrets_rs::{Bindable, BindError, Secret, SourceRegistry};
12///
13/// struct Config {
14///     api_key: Secret<String>,
15/// }
16///
17/// impl Bindable for Config {
18///     fn bind_secrets(&mut self, registry: &SourceRegistry) -> Result<(), Vec<BindError>> {
19///         let mut errors = Vec::new();
20///         if let Err(e) = self.api_key.bind(registry) {
21///             errors.push(e);
22///         }
23///         if errors.is_empty() { Ok(()) } else { Err(errors) }
24///     }
25/// }
26/// ```
27pub trait Bindable {
28    /// Bind all [`Secret`](crate::Secret) fields using sources from `registry`.
29    ///
30    /// Collects every error rather than stopping at the first failure so that
31    /// callers can report all missing secrets at once.
32    fn bind_secrets(&mut self, registry: &SourceRegistry) -> Result<(), Vec<BindError>>;
33}
34
35/// Binds all secrets in `target` using `registry`.
36///
37/// This is a convenience wrapper around [`Bindable::bind_secrets`].
38pub fn bind_all<T: Bindable>(
39    target: &mut T,
40    registry: &SourceRegistry,
41) -> Result<(), Vec<BindError>> {
42    target.bind_secrets(registry)
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use crate::{Secret, SourceRegistry, sources::env::EnvSource};
49
50    struct Config {
51        key_a: Secret<String>,
52        key_b: Secret<String>,
53    }
54
55    impl Bindable for Config {
56        fn bind_secrets(&mut self, registry: &SourceRegistry) -> Result<(), Vec<BindError>> {
57            let mut errors = Vec::new();
58            if let Err(e) = self.key_a.bind(registry) {
59                errors.push(e);
60            }
61            if let Err(e) = self.key_b.bind(registry) {
62                errors.push(e);
63            }
64            if errors.is_empty() {
65                Ok(())
66            } else {
67                Err(errors)
68            }
69        }
70    }
71
72    #[test]
73    fn bind_all_populates_both_secrets() {
74        unsafe {
75            std::env::set_var("BINDABLE_TEST_A", "alpha");
76            std::env::set_var("BINDABLE_TEST_B", "beta");
77        }
78
79        let mut config = Config {
80            key_a: Secret::new("urn:secrets-rs:env:BINDABLE_TEST_A").unwrap(),
81            key_b: Secret::new("urn:secrets-rs:env:BINDABLE_TEST_B").unwrap(),
82        };
83
84        let mut registry = SourceRegistry::new();
85        registry.register("env", EnvSource);
86
87        bind_all(&mut config, &registry).unwrap();
88
89        assert_eq!(config.key_a.value().unwrap(), "alpha");
90        assert_eq!(config.key_b.value().unwrap(), "beta");
91
92        unsafe {
93            std::env::remove_var("BINDABLE_TEST_A");
94            std::env::remove_var("BINDABLE_TEST_B");
95        }
96    }
97
98    #[test]
99    fn bind_all_collects_all_errors() {
100        unsafe {
101            std::env::remove_var("BINDABLE_MISSING_A");
102            std::env::remove_var("BINDABLE_MISSING_B");
103        }
104
105        let mut config = Config {
106            key_a: Secret::new("urn:secrets-rs:env:BINDABLE_MISSING_A").unwrap(),
107            key_b: Secret::new("urn:secrets-rs:env:BINDABLE_MISSING_B").unwrap(),
108        };
109
110        let mut registry = SourceRegistry::new();
111        registry.register("env", EnvSource);
112
113        let errors = bind_all(&mut config, &registry).unwrap_err();
114        assert_eq!(errors.len(), 2);
115    }
116}