1use crate::prelude::{AsClone, AsCopy};
4
5pub struct Secret<T> {
40 value: T,
41 serialize_redacted: bool,
42}
43
44impl<T> Secret<T> {
45 pub fn new(value: T) -> Self {
46 let serialize_redacted = {
47 #[cfg(feature = "secret-serialize-redacted")]
48 {
49 true
50 }
51 #[cfg(not(feature = "secret-serialize-redacted"))]
52 {
53 false
54 }
55 };
56
57 Self {
58 value,
59 serialize_redacted,
60 }
61 }
62
63 pub fn set_serialize_redacted(&mut self, serialize_redacted: bool) {
64 self.serialize_redacted = serialize_redacted;
65 }
66
67 pub fn with_serialize_redacted(self, serialize_redacted: bool) -> Self {
68 Self {
69 value: self.value,
70 serialize_redacted,
71 }
72 }
73
74 pub fn expose(self) -> T {
75 self.value
76 }
77
78 pub fn expose_ref(&self) -> &T {
79 &self.value
80 }
81
82 pub fn expose_as_ref<R: ?Sized>(&self) -> &R
83 where
84 T: AsRef<R>,
85 {
86 self.expose_ref().as_ref()
87 }
88}
89
90impl<T: Clone> Secret<T> {
91 pub fn expose_clone(&self) -> T {
92 self.value.clone()
93 }
94
95 pub fn expose_as_clone<R>(&self) -> R
96 where
97 for<'a> &'a T: AsClone<R>,
98 {
99 (&self.value).as_clone()
100 }
101}
102
103impl<T: Copy> Secret<T> {
104 pub fn expose_copy(&self) -> T {
105 self.value
106 }
107
108 pub fn expose_as_copy<R>(&self) -> R
109 where
110 for<'a> &'a T: AsCopy<R>,
111 {
112 (&self.value).as_copy()
113 }
114}
115
116impl<T> From<T> for Secret<T> {
117 fn from(value: T) -> Self {
118 Self::new(value)
119 }
120}
121
122impl<T: Clone> Clone for Secret<T> {
123 fn clone(&self) -> Self {
124 Self::new(self.value.clone())
125 }
126}
127
128impl<T: Copy> Copy for Secret<T> {}
129
130impl<T: PartialEq> PartialEq for Secret<T> {
131 fn eq(&self, other: &Self) -> bool {
132 self.value.eq(&other.value)
133 }
134}
135
136impl<T: Eq> Eq for Secret<T> {}
137
138impl<T: PartialOrd> PartialOrd for Secret<T> {
139 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
140 self.value.partial_cmp(&other.value)
141 }
142}
143
144impl<T: Ord> Ord for Secret<T> {
145 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
146 self.value.cmp(&other.value)
147 }
148}
149
150impl<T> core::fmt::Debug for Secret<T> {
151 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
152 f.write_fmt(format_args!("Secret<{}>", core::any::type_name::<T>()))
153 }
154}
155
156impl<T: core::hash::Hash> core::hash::Hash for Secret<T> {
157 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
158 self.value.hash(state)
159 }
160}
161
162#[cfg(feature = "serde")]
163mod serde {
164 use super::Secret;
165
166 impl<T: serde::Serialize> serde::Serialize for Secret<T> {
167 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
168 where
169 S: serde::Serializer,
170 {
171 if self.serialize_redacted {
172 format!("Secret<{}>", core::any::type_name::<T>()).serialize(serializer)
173 } else {
174 self.value.serialize(serializer)
175 }
176 }
177 }
178
179 #[cfg(feature = "serde")]
180 impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Secret<T> {
181 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
182 where
183 D: serde::Deserializer<'de>,
184 {
185 T::deserialize(deserializer).map(Secret::new)
186 }
187 }
188}
189
190#[cfg(feature = "schemars")]
191mod schemars {
192 use super::Secret;
193 extern crate alloc;
194
195 impl<T: schemars::JsonSchema> schemars::JsonSchema for Secret<T> {
196 fn schema_id() -> alloc::borrow::Cow<'static, str> {
197 T::schema_id()
198 }
199
200 fn schema_name() -> alloc::borrow::Cow<'static, str> {
201 T::schema_name()
202 }
203
204 fn inline_schema() -> bool {
205 T::inline_schema()
206 }
207
208 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
209 #[cfg(feature = "secret-serialize-redacted")]
210 {
211 schemars::json_schema!({
212 "anyOf": [
213 {
214 "const": format!("Secret<{}>", core::any::type_name::<T>())
215 },
216 T::json_schema(generator)
217 ]
218 })
219 }
220 #[cfg(not(feature = "secret-serialize-redacted"))]
221 {
222 T::json_schema(generator)
223 }
224 }
225 }
226}
227
228#[cfg(feature = "sqlx")]
229mod sqlx_impls {
230 impl<DB: sqlx::Database, T: sqlx::Type<DB>> sqlx::Type<DB> for super::Secret<T> {
231 fn type_info() -> DB::TypeInfo {
232 T::type_info()
233 }
234
235 fn compatible(ty: &DB::TypeInfo) -> bool {
236 T::compatible(ty)
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243
244 use super::*;
245
246 #[test]
247 fn debug_should_output_type_only() {
248 let secret = Secret::new("Hello, world!");
249
250 assert_eq!(format!("{secret:#?}"), "Secret<&str>");
251 }
252
253 #[test]
254 fn serialize_redacted_should_output_type_only() {
255 let secret = Secret::new("Hello, world!").with_serialize_redacted(true);
256
257 assert_eq!(serde_json::to_string(&secret).unwrap(), "\"Secret<&str>\"");
258 }
259
260 #[test]
261 fn serialize_unredacted_should_output_full_value() {
262 let secret = Secret::new("Hello, world!").with_serialize_redacted(false);
263
264 assert_eq!(serde_json::to_string(&secret).unwrap(), "\"Hello, world!\"");
265 }
266
267 #[test]
268 fn should_be_able_to_expose_as_ref() {
269 #[derive(Debug, PartialEq)]
270 struct Inner;
271 struct Outer(Inner);
272
273 impl AsRef<Inner> for Outer {
274 fn as_ref(&self) -> &Inner {
275 &self.0
276 }
277 }
278
279 assert_eq!(&Inner, Secret::new(Outer(Inner)).expose_as_ref());
280 }
281
282 #[test]
283 fn should_be_able_to_expose_as_clone() {
284 #[derive(Clone, Debug, PartialEq)]
285 struct Inner;
286 #[derive(Clone)]
287 struct Outer(Inner);
288
289 impl AsClone<Inner> for &Outer {
290 fn as_clone(self) -> Inner {
291 self.0.clone()
292 }
293 }
294
295 let secret = Secret::new(Outer(Inner));
296
297 assert_eq!(Inner, secret.expose_as_clone());
298 }
299
300 #[test]
301 fn should_be_able_to_expose_as_copy() {
302 #[derive(Clone, Copy, Debug, PartialEq)]
303 struct Inner;
304 #[derive(Clone, Copy)]
305 struct Outer(Inner);
306
307 impl AsCopy<Inner> for &Outer {
308 fn as_copy(self) -> Inner {
309 self.0
310 }
311 }
312
313 let secret = Secret::new(Outer(Inner));
314
315 assert_eq!(Inner, secret.expose_as_copy());
316 }
317}