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.value.as_ref()
87 }
88}
89
90impl<T: AsRef<str>> Secret<T> {
91 pub fn expose_as_str(&self) -> &str {
92 self.value.as_ref()
93 }
94}
95
96impl<T: ToString> Secret<T> {
97 pub fn expose_to_string(&self) -> String {
98 self.value.to_string()
99 }
100}
101
102impl<T: Clone> Secret<T> {
103 pub fn expose_clone(&self) -> T {
104 self.value.clone()
105 }
106
107 pub fn expose_as_clone<R>(&self) -> R
108 where
109 for<'a> &'a T: AsClone<R>,
110 {
111 (&self.value).as_clone()
112 }
113}
114
115impl<T: Copy> Secret<T> {
116 pub fn expose_copy(&self) -> T {
117 self.value
118 }
119
120 pub fn expose_as_copy<R>(&self) -> R
121 where
122 for<'a> &'a T: AsCopy<R>,
123 {
124 (&self.value).as_copy()
125 }
126}
127
128impl<T> From<T> for Secret<T> {
129 fn from(value: T) -> Self {
130 Self::new(value)
131 }
132}
133
134impl<T: Clone> Clone for Secret<T> {
135 fn clone(&self) -> Self {
136 Self::new(self.value.clone())
137 }
138}
139
140impl<T: Copy> Copy for Secret<T> {}
141
142impl<T: PartialEq> PartialEq for Secret<T> {
143 fn eq(&self, other: &Self) -> bool {
144 self.value.eq(&other.value)
145 }
146}
147
148impl<T: Eq> Eq for Secret<T> {}
149
150impl<T: PartialOrd> PartialOrd for Secret<T> {
151 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
152 self.value.partial_cmp(&other.value)
153 }
154}
155
156impl<T: Ord> Ord for Secret<T> {
157 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
158 self.value.cmp(&other.value)
159 }
160}
161
162impl<T> core::fmt::Debug for Secret<T> {
163 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
164 f.write_fmt(format_args!("Secret<{}>", core::any::type_name::<T>()))
165 }
166}
167
168impl<T: core::hash::Hash> core::hash::Hash for Secret<T> {
169 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
170 self.value.hash(state)
171 }
172}
173
174#[cfg(feature = "serde")]
175mod serde {
176 use super::Secret;
177
178 impl<T: serde::Serialize> serde::Serialize for Secret<T> {
179 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
180 where
181 S: serde::Serializer,
182 {
183 if self.serialize_redacted {
184 format!("Secret<{}>", core::any::type_name::<T>()).serialize(serializer)
185 } else {
186 self.value.serialize(serializer)
187 }
188 }
189 }
190
191 #[cfg(feature = "serde")]
192 impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Secret<T> {
193 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194 where
195 D: serde::Deserializer<'de>,
196 {
197 T::deserialize(deserializer).map(Secret::new)
198 }
199 }
200}
201
202#[cfg(feature = "schemars")]
203mod schemars {
204 use super::Secret;
205 extern crate alloc;
206
207 impl<T: schemars::JsonSchema> schemars::JsonSchema for Secret<T> {
208 fn schema_id() -> alloc::borrow::Cow<'static, str> {
209 T::schema_id()
210 }
211
212 fn schema_name() -> alloc::borrow::Cow<'static, str> {
213 T::schema_name()
214 }
215
216 fn inline_schema() -> bool {
217 T::inline_schema()
218 }
219
220 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
221 #[cfg(feature = "secret-serialize-redacted")]
222 {
223 schemars::json_schema!({
224 "anyOf": [
225 {
226 "const": format!("Secret<{}>", core::any::type_name::<T>())
227 },
228 T::json_schema(generator)
229 ]
230 })
231 }
232 #[cfg(not(feature = "secret-serialize-redacted"))]
233 {
234 T::json_schema(generator)
235 }
236 }
237 }
238}
239
240#[cfg(feature = "sqlx")]
241mod sqlx_impls {
242 impl<DB: sqlx::Database, T: sqlx::Type<DB>> sqlx::Type<DB> for super::Secret<T> {
243 fn type_info() -> DB::TypeInfo {
244 T::type_info()
245 }
246
247 fn compatible(ty: &DB::TypeInfo) -> bool {
248 T::compatible(ty)
249 }
250 }
251}
252
253#[cfg(test)]
254mod tests {
255
256 use super::*;
257
258 #[test]
259 fn debug_should_output_type_only() {
260 let secret = Secret::new("Hello, world!");
261
262 assert_eq!(format!("{secret:#?}"), "Secret<&str>");
263 }
264
265 #[test]
266 fn serialize_redacted_should_output_type_only() {
267 let secret = Secret::new("Hello, world!").with_serialize_redacted(true);
268
269 assert_eq!(serde_json::to_string(&secret).unwrap(), "\"Secret<&str>\"");
270 }
271
272 #[test]
273 fn serialize_unredacted_should_output_full_value() {
274 let secret = Secret::new("Hello, world!").with_serialize_redacted(false);
275
276 assert_eq!(serde_json::to_string(&secret).unwrap(), "\"Hello, world!\"");
277 }
278
279 #[test]
280 fn should_be_able_to_expose_as_ref() {
281 #[derive(Debug, PartialEq)]
282 struct Inner;
283 struct Outer(Inner);
284
285 impl AsRef<Inner> for Outer {
286 fn as_ref(&self) -> &Inner {
287 &self.0
288 }
289 }
290
291 assert_eq!(&Inner, Secret::new(Outer(Inner)).expose_as_ref());
292 }
293
294 #[test]
295 fn should_be_able_to_expose_as_clone() {
296 #[derive(Clone, Debug, PartialEq)]
297 struct Inner;
298 #[derive(Clone)]
299 struct Outer(Inner);
300
301 impl AsClone<Inner> for &Outer {
302 fn as_clone(self) -> Inner {
303 self.0.clone()
304 }
305 }
306
307 let secret = Secret::new(Outer(Inner));
308
309 assert_eq!(Inner, secret.expose_as_clone());
310 }
311
312 #[test]
313 fn should_be_able_to_expose_as_copy() {
314 #[derive(Clone, Copy, Debug, PartialEq)]
315 struct Inner;
316 #[derive(Clone, Copy)]
317 struct Outer(Inner);
318
319 impl AsCopy<Inner> for &Outer {
320 fn as_copy(self) -> Inner {
321 self.0
322 }
323 }
324
325 let secret = Secret::new(Outer(Inner));
326
327 assert_eq!(Inner, secret.expose_as_copy());
328 }
329
330 #[test]
331 fn should_be_able_to_expose_to_string_for_anything_implementing_to_string() {
332 struct X;
333
334 impl std::fmt::Display for X {
335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336 f.write_str("check")
337 }
338 }
339
340 assert_eq!(Secret::new(X).expose_to_string(), "check");
341 }
342
343 #[test]
344 fn should_be_able_to_expose_as_str_for_anything_implementing_as_ref_str() {
345 assert_eq!(Secret::new(String::from("check")).expose_as_str(), "check")
346 }
347}