Skip to main content

siderite_config/
secret.rs

1//! Opaque wrapper that never prints its contents.
2
3use serde::Deserialize;
4use std::fmt;
5
6/// Printed in place of a [`Secret`] value.
7pub(crate) const REDACTED: &str = "[REDACTED]";
8
9/// A value that is never shown by [`std::fmt::Debug`] or [`std::fmt::Display`].
10///
11/// Deserialize is transparent: the wrapper is invisible to serde, so a
12/// `Secret<String>` reads as a JSON/TOML string. There is no [`serde::Serialize`]
13/// implementation, so secrets cannot be written back out through serde.
14#[derive(Clone, PartialEq, Eq, Deserialize)]
15#[serde(transparent)]
16pub struct Secret<T>(T);
17
18impl<T> Secret<T> {
19    /// Wrap `value`.
20    pub const fn new(value: T) -> Self {
21        Self(value)
22    }
23
24    /// Borrow the inner value.
25    ///
26    /// Callers must not log, display, or otherwise persist the returned
27    /// reference.
28    pub const 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(REDACTED)
36    }
37}
38
39impl<T> fmt::Display for Secret<T> {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        f.write_str(REDACTED)
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use serde::Deserialize;
49
50    #[test]
51    fn debug_and_display_are_redacted() {
52        let secret = Secret::new(String::from("super-secret-password"));
53        assert_eq!(format!("{secret:?}"), REDACTED);
54        assert_eq!(format!("{secret}"), REDACTED);
55        assert_eq!(secret.expose(), "super-secret-password");
56        assert!(!format!("{secret:?}").contains("super-secret"));
57        assert!(!format!("{secret}").contains("super-secret"));
58    }
59
60    #[test]
61    fn deserialize_is_transparent() {
62        #[derive(Deserialize)]
63        struct Wrap {
64            token: Secret<String>,
65        }
66        let wrap: Wrap = serde_json::from_str(r#"{"token":"abc"}"#).unwrap();
67        assert_eq!(wrap.token.expose(), "abc");
68        assert_eq!(format!("{:?}", wrap.token), REDACTED);
69    }
70}