1pub struct Secret<T> {
38 value: T,
39 serialize_redacted: bool,
40}
41
42impl<T> Secret<T> {
43 pub fn new(value: T) -> Self {
44 let serialize_redacted = {
45 #[cfg(feature = "secret-serialize-redacted")]
46 {
47 true
48 }
49 #[cfg(not(feature = "secret-serialize-redacted"))]
50 {
51 false
52 }
53 };
54
55 Self {
56 value,
57 serialize_redacted,
58 }
59 }
60
61 pub fn set_serialize_redacted(&mut self, serialize_redacted: bool) {
62 self.serialize_redacted = serialize_redacted;
63 }
64
65 pub fn with_serialize_redacted(self, serialize_redacted: bool) -> Self {
66 Self {
67 value: self.value,
68 serialize_redacted,
69 }
70 }
71
72 pub fn expose(self) -> T {
73 self.value
74 }
75
76 pub fn expose_ref(&self) -> &T {
77 &self.value
78 }
79}
80
81impl<T: Clone> Secret<T> {
82 pub fn expose_clone(&self) -> T {
83 self.value.clone()
84 }
85}
86
87impl<T: Copy> Secret<T> {
88 pub fn expose_copy(&self) -> T {
89 self.value
90 }
91}
92
93impl<T> From<T> for Secret<T> {
94 fn from(value: T) -> Self {
95 Self::new(value)
96 }
97}
98
99impl<T: Clone> Clone for Secret<T> {
100 fn clone(&self) -> Self {
101 Self::new(self.value.clone())
102 }
103}
104
105impl<T: Copy> Copy for Secret<T> {}
106
107impl<T: PartialEq> PartialEq for Secret<T> {
108 fn eq(&self, other: &Self) -> bool {
109 self.value.eq(&other.value)
110 }
111}
112
113impl<T: Eq> Eq for Secret<T> {}
114
115impl<T: PartialOrd> PartialOrd for Secret<T> {
116 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
117 self.value.partial_cmp(&other.value)
118 }
119}
120
121impl<T: Ord> Ord for Secret<T> {
122 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
123 self.value.cmp(&other.value)
124 }
125}
126
127impl<T> core::fmt::Debug for Secret<T> {
128 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129 f.write_fmt(format_args!("Secret<{}>", core::any::type_name::<T>()))
130 }
131}
132
133impl<T: core::hash::Hash> core::hash::Hash for Secret<T> {
134 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
135 self.value.hash(state)
136 }
137}
138
139#[cfg(feature = "serde")]
140mod serde {
141 use super::Secret;
142
143 impl<T: serde::Serialize> serde::Serialize for Secret<T> {
144 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
145 where
146 S: serde::Serializer,
147 {
148 if self.serialize_redacted {
149 format!("Secret<{}>", core::any::type_name::<T>()).serialize(serializer)
150 } else {
151 self.value.serialize(serializer)
152 }
153 }
154 }
155
156 #[cfg(feature = "serde")]
157 impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Secret<T> {
158 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159 where
160 D: serde::Deserializer<'de>,
161 {
162 T::deserialize(deserializer).map(Secret::new)
163 }
164 }
165}
166
167#[cfg(feature = "schemars")]
168mod schemars {
169 use super::Secret;
170 extern crate alloc;
171
172 impl<T: schemars::JsonSchema> schemars::JsonSchema for Secret<T> {
173 fn schema_id() -> alloc::borrow::Cow<'static, str> {
174 T::schema_id()
175 }
176
177 fn schema_name() -> alloc::borrow::Cow<'static, str> {
178 T::schema_name()
179 }
180
181 fn inline_schema() -> bool {
182 T::inline_schema()
183 }
184
185 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
186 #[cfg(feature = "secret-serialize-redacted")]
187 {
188 schemars::json_schema!({
189 "anyOf": [
190 {
191 "const": format!("Secret<{}>", core::any::type_name::<T>())
192 },
193 T::json_schema(generator)
194 ]
195 })
196 }
197 #[cfg(not(feature = "secret-serialize-redacted"))]
198 {
199 T::json_schema(generator)
200 }
201 }
202 }
203}
204
205#[cfg(feature = "sqlx")]
206mod sqlx_impls {
207 impl<DB: sqlx::Database, T: sqlx::Type<DB>> sqlx::Type<DB> for super::Secret<T> {
208 fn type_info() -> DB::TypeInfo {
209 T::type_info()
210 }
211
212 fn compatible(ty: &DB::TypeInfo) -> bool {
213 T::compatible(ty)
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::Secret;
221
222 #[test]
223 fn debug_should_output_type_only() {
224 let secret = Secret::new("Hello, world!");
225
226 assert_eq!(format!("{secret:#?}"), "Secret<&str>");
227 }
228
229 #[test]
230 fn serialize_redacted_should_output_type_only() {
231 let secret = Secret::new("Hello, world!").with_serialize_redacted(true);
232
233 assert_eq!(serde_json::to_string(&secret).unwrap(), "\"Secret<&str>\"");
234 }
235
236 #[test]
237 fn serialize_unredacted_should_output_full_value() {
238 let secret = Secret::new("Hello, world!").with_serialize_redacted(false);
239
240 assert_eq!(serde_json::to_string(&secret).unwrap(), "\"Hello, world!\"");
241 }
242}