1use alloc::string::String;
4use core::fmt;
5
6use serde::{Deserialize, Deserializer};
7use zeroize::Zeroize;
8
9#[derive(Clone, Default, PartialEq, Eq)]
34pub struct Secret(String);
35
36impl Secret {
37 pub fn new(token: impl Into<String>) -> Self {
39 Self(token.into())
40 }
41
42 pub fn expose(&self) -> &str {
44 &self.0
45 }
46
47 pub fn is_empty(&self) -> bool {
49 self.0.is_empty()
50 }
51}
52
53impl From<String> for Secret {
54 fn from(token: String) -> Self {
55 Self(token)
56 }
57}
58
59impl From<&str> for Secret {
60 fn from(token: &str) -> Self {
61 Self(token.into())
62 }
63}
64
65impl Drop for Secret {
66 fn drop(&mut self) {
67 self.0.zeroize();
68 }
69}
70
71impl fmt::Debug for Secret {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 f.write_str("Secret(<redacted>)")
74 }
75}
76
77impl fmt::Display for Secret {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 f.write_str("<redacted>")
80 }
81}
82
83impl<'de> Deserialize<'de> for Secret {
84 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
85 String::deserialize(deserializer).map(Self)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn never_prints() {
95 let s = Secret::new("abc123");
96 assert_eq!(format!("{s}"), "<redacted>");
97 assert_eq!(format!("{s:?}"), "Secret(<redacted>)");
98 assert_eq!(format!("{:?}", Some(s.clone())), "Some(Secret(<redacted>))");
99 assert_eq!(s.expose(), "abc123");
100 }
101
102 #[test]
103 fn deserializes_from_a_string() {
104 let s: Secret = serde_json::from_str(r#""tok""#).unwrap();
105 assert_eq!(s.expose(), "tok");
106 assert!(serde_json::from_str::<Secret>("1").is_err());
107 }
108}