siderite_config/
secret.rs1use serde::Deserialize;
4use std::fmt;
5
6pub(crate) const REDACTED: &str = "[REDACTED]";
8
9#[derive(Clone, PartialEq, Eq, Deserialize)]
15#[serde(transparent)]
16pub struct Secret<T>(T);
17
18impl<T> Secret<T> {
19 pub const fn new(value: T) -> Self {
21 Self(value)
22 }
23
24 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}