1#![deny(warnings)]
15#![warn(unused_extern_crates)]
16#![deny(clippy::todo)]
17#![deny(clippy::unimplemented)]
18#![deny(clippy::unwrap_used)]
19#![deny(clippy::expect_used)]
20#![deny(clippy::panic)]
21#![deny(clippy::unreachable)]
22#![deny(clippy::await_holding_lock)]
23#![deny(clippy::needless_pass_by_value)]
24#![deny(clippy::trivially_copy_pass_by_ref)]
25
26use std::fmt::Debug;
27
28#[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
43pub struct SecretString<T>(T)
44where
45 T: AsRef<str>;
46
47impl<T: AsRef<str>> SecretString<T> {
48 pub fn new(s: T) -> Self {
49 SecretString(s)
50 }
51
52 pub fn value(&self) -> &str {
54 self.0.as_ref()
55 }
56
57 pub fn len(&self) -> usize {
58 self.0.as_ref().len()
59 }
60
61 pub fn is_empty(&self) -> bool {
62 self.len() == 0
63 }
64
65 pub fn as_stars(&self) -> String {
67 String::from("*").repeat(self.len())
68 }
69
70 pub fn as_stars_with_with_len(&self, len: usize) -> String {
72 String::from("*").repeat(len)
73 }
74}
75
76impl<T: AsRef<str>> From<T> for SecretString<T> {
77 fn from(s: T) -> Self {
78 SecretString(s)
79 }
80}
81
82impl<T: AsRef<str>> std::fmt::Display for SecretString<T> {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(f, "{}", self.as_stars())
85 }
86}
87
88impl<T: AsRef<str>> Debug for SecretString<T> {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 write!(f, "SecretString({})", self.as_stars())
91 }
92}
93
94#[cfg(feature = "serde")]
95impl<T: AsRef<str> + serde::Serialize> serde::Serialize for SecretString<T> {
96 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97 where
98 S: serde::Serializer,
99 {
100 serializer.serialize_str(self.value())
101 }
102}
103
104#[cfg(feature = "serde")]
105impl<'de, T: AsRef<str> + serde::Deserialize<'de>> serde::Deserialize<'de> for SecretString<T> {
106 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107 where
108 D: serde::Deserializer<'de>,
109 {
110 Ok(Self::new(serde::Deserialize::deserialize(deserializer)?))
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::SecretString;
117
118 #[test]
119 fn test_secret_string_display() {
120 let secret = SecretString::new("my_secret_password");
121 assert_eq!(format!("{}", secret), "******************");
122 }
123
124 #[test]
125 fn test_secret_string_debug() {
126 let secret = SecretString::new("my_secret_password");
127 assert_eq!(format!("{:?}", secret), "SecretString(******************)");
128 }
129 #[test]
130 fn test_secret_string_value() {
131 let secret = SecretString::new("my_secret_password");
132 assert_eq!(secret.value(), "my_secret_password");
133 }
134
135 #[cfg(feature = "serde")]
136 #[test]
137 fn test_secret_string_serde() {
138 let secret = SecretString::new("my_secret_password");
139 let serialized = serde_json::to_string(&secret).expect("Failed to serialize");
140 assert_eq!(serialized, format!("\"{}\"", secret.value()));
141
142 let deserialized: SecretString<_> =
143 serde_json::from_str("\"my_secret_password\"").expect("Failed to deserialize");
144 assert_eq!(deserialized, secret);
145 }
146}