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};
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 registry = SourceRegistry::new();
85        bind_all(&mut config, &registry).unwrap();
86
87        assert_eq!(config.key_a.value().unwrap(), "alpha");
88        assert_eq!(config.key_b.value().unwrap(), "beta");
89
90        unsafe {
91            std::env::remove_var("BINDABLE_TEST_A");
92            std::env::remove_var("BINDABLE_TEST_B");
93        }
94    }
95
96    #[test]
97    fn bind_all_collects_all_errors() {
98        unsafe {
99            std::env::remove_var("BINDABLE_MISSING_A");
100            std::env::remove_var("BINDABLE_MISSING_B");
101        }
102
103        let mut config = Config {
104            key_a: Secret::new("urn:secrets-rs:env:BINDABLE_MISSING_A").unwrap(),
105            key_b: Secret::new("urn:secrets-rs:env:BINDABLE_MISSING_B").unwrap(),
106        };
107
108        let registry = SourceRegistry::new();
109        let errors = bind_all(&mut config, &registry).unwrap_err();
110        assert_eq!(errors.len(), 2);
111    }
112}