Skip to main content

polyoxide_venue/
secret.rs

1//! A value that never prints.
2
3use std::fmt;
4
5/// A credential or other value that must not reach a log.
6///
7/// Its `Debug` prints `Secret([REDACTED])` whatever it holds, and it has no
8/// `Display`, so a struct that derives `Debug` can carry one safely. Read the
9/// value with [`expose`](Self::expose), at the one place that needs it.
10///
11/// ```
12/// use polyoxide_venue::Secret;
13///
14/// let key = Secret::new(String::from("hunter2"));
15/// assert_eq!(format!("{key:?}"), "Secret([REDACTED])");
16/// assert_eq!(key.expose(), "hunter2");
17/// ```
18#[derive(Clone)]
19pub struct Secret<T>(T);
20
21impl<T> Secret<T> {
22    /// Wraps a value.
23    pub const fn new(value: T) -> Self {
24        Self(value)
25    }
26
27    /// The value. Every read goes through here, so each one is easy to find.
28    pub fn expose(&self) -> &T {
29        &self.0
30    }
31}
32
33impl<T> fmt::Debug for Secret<T> {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.write_str("Secret([REDACTED])")
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[derive(Debug)]
44    #[allow(dead_code)]
45    struct Credentials {
46        key: &'static str,
47        secret: Secret<&'static str>,
48    }
49
50    #[test]
51    fn debug_never_shows_the_value() {
52        let creds = Credentials {
53            key: "public",
54            secret: Secret::new("hunter2"),
55        };
56        let printed = format!("{creds:?} {creds:#?}");
57        assert!(!printed.contains("hunter2"), "{printed}");
58        assert!(printed.contains("public"), "{printed}");
59        assert!(printed.contains("Secret([REDACTED])"), "{printed}");
60    }
61
62    #[test]
63    fn a_clone_holds_the_same_value() {
64        let secret = Secret::new(String::from("hunter2"));
65        assert_eq!(secret.clone().expose(), secret.expose());
66    }
67}