Skip to main content

effect_config/
secret.rs

1//! Redacted wrapper for sensitive configuration values (Effect `Config.secret`).
2
3use std::fmt;
4
5/// Wraps a sensitive value so it is never exposed via [`fmt::Debug`] or [`fmt::Display`].
6///
7/// Mirrors Effect.ts `Config.secret` / `ConfigSecret`.
8///
9/// ```rust
10/// use effect_config::Secret;
11///
12/// let token = Secret::new("my-api-key".to_string());
13/// assert_eq!(format!("{token:?}"), "<redacted>");
14/// assert_eq!(token.expose(), "my-api-key");
15/// ```
16pub struct Secret<T>(T);
17
18impl<T> Secret<T> {
19  /// Wrap `value` in a [`Secret`].
20  #[inline]
21  pub fn new(value: T) -> Self {
22    Self(value)
23  }
24
25  /// Access the inner value.  Call sites should be minimal and auditable.
26  #[inline]
27  pub fn expose(&self) -> &T {
28    &self.0
29  }
30
31  /// Consume the wrapper and return the inner value.
32  #[inline]
33  pub fn into_inner(self) -> T {
34    self.0
35  }
36}
37
38impl<T> fmt::Debug for Secret<T> {
39  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40    write!(f, "<redacted>")
41  }
42}
43
44impl<T> fmt::Display for Secret<T> {
45  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46    write!(f, "<redacted>")
47  }
48}
49
50impl<T: Clone> Clone for Secret<T> {
51  fn clone(&self) -> Self {
52    Self(self.0.clone())
53  }
54}
55
56impl<T: PartialEq> PartialEq for Secret<T> {
57  fn eq(&self, other: &Self) -> bool {
58    self.0 == other.0
59  }
60}
61
62impl<T: Eq> Eq for Secret<T> {}
63
64#[cfg(test)]
65mod tests {
66  use super::*;
67
68  #[test]
69  fn debug_is_redacted() {
70    let s = Secret::new("hunter2".to_string());
71    assert_eq!(format!("{s:?}"), "<redacted>");
72  }
73
74  #[test]
75  fn display_is_redacted() {
76    let s = Secret::new(42u32);
77    assert_eq!(format!("{s}"), "<redacted>");
78  }
79
80  #[test]
81  fn expose_returns_inner() {
82    let s = Secret::new("value");
83    assert_eq!(*s.expose(), "value");
84  }
85
86  #[test]
87  fn into_inner_unwraps() {
88    let s = Secret::new(99u8);
89    assert_eq!(s.into_inner(), 99);
90  }
91
92  #[test]
93  fn clone_and_eq() {
94    let a = Secret::new("x".to_string());
95    let b = a.clone();
96    assert_eq!(a, b);
97  }
98}