stefans_utils/
secret.rs

1//! Contains the `Secret<T>` struct, designed to wrap values that should never appear formatted with Display or Debug in logs or other output.
2
3/// Wraps a value that should never appear formatted with Display or Debug in logs or other output.
4///
5/// `Secret<T>` does implement `Debug`, but only outputs the inner type name, ie. `"Secret<String>"`;
6///
7/// Forwards the following core traits from its inner type:
8/// - `Clone`
9/// - `Copy`
10/// - `PartialeEq`
11/// - `Eq`
12/// - `PartialOrd`
13/// - `Ord`
14///
15/// ## Feature-flagged trait implementations
16///
17/// | Feature    | Trait         |
18/// | ---------- | ------------- |
19/// | `core`     | `Debug` (1)   |
20/// | `core`     | `Clone` (2)   |
21/// | `core`     | `Copy` (3)    |
22/// | `core`     | `PartialEq`   |
23/// | `core`     | `Eq`          |
24/// | `core`     | `PartialOrd`  |
25/// | `core`     | `Ord`         |
26/// | `core`     | `Hash`        |
27/// | `serde`    | `Serialize`   |
28/// | `serde`    | `Deserialize` |
29/// | `schemars` | `JsonSchema`  |
30/// | `sqlx`     | `Type`        |
31///
32/// All trait implementations besides `Debug` are forwarded from the inner type.
33///
34/// 1. `Debug::fmt` only produces the name of the wrapped type, ie. `"Secret<&str>"`
35/// 2. `T::Clone` enables the `expose_clone(&self) -> T` method of `Secret<T>`
36/// 3. `T::Copy` enables the `expose_copy(&self) -> T` method of `Secret<T>`
37pub struct Secret<T>(T);
38
39impl<T> Secret<T> {
40    pub fn new(value: T) -> Self {
41        Self(value)
42    }
43
44    pub fn expose(self) -> T {
45        self.0
46    }
47
48    pub fn expose_ref(&self) -> &T {
49        &self.0
50    }
51}
52
53impl<T: Clone> Secret<T> {
54    pub fn expose_clone(&self) -> T {
55        self.0.clone()
56    }
57}
58
59impl<T: Copy> Secret<T> {
60    pub fn expose_copy(&self) -> T {
61        self.0
62    }
63}
64
65impl<T> From<T> for Secret<T> {
66    fn from(value: T) -> Self {
67        Self::new(value)
68    }
69}
70
71impl<T: Clone> Clone for Secret<T> {
72    fn clone(&self) -> Self {
73        Self(self.0.clone())
74    }
75}
76
77impl<T: Copy> Copy for Secret<T> {}
78
79impl<T: PartialEq> PartialEq for Secret<T> {
80    fn eq(&self, other: &Self) -> bool {
81        self.0.eq(&other.0)
82    }
83}
84
85impl<T: Eq> Eq for Secret<T> {}
86
87impl<T: PartialOrd> PartialOrd for Secret<T> {
88    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
89        self.0.partial_cmp(&other.0)
90    }
91}
92
93impl<T: Ord> Ord for Secret<T> {
94    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
95        self.0.cmp(&other.0)
96    }
97}
98
99impl<T> core::fmt::Debug for Secret<T> {
100    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
101        f.write_fmt(format_args!("Secret<{}>", core::any::type_name::<T>()))
102    }
103}
104
105impl<T: core::hash::Hash> core::hash::Hash for Secret<T> {
106    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
107        self.0.hash(state)
108    }
109}
110
111#[cfg(feature = "serde")]
112mod serde {
113    use super::Secret;
114
115    impl<T: serde::Serialize> serde::Serialize for Secret<T> {
116        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
117        where
118            S: serde::Serializer,
119        {
120            self.0.serialize(serializer)
121        }
122    }
123
124    #[cfg(feature = "serde")]
125    impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Secret<T> {
126        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
127        where
128            D: serde::Deserializer<'de>,
129        {
130            T::deserialize(deserializer).map(Secret)
131        }
132    }
133}
134
135#[cfg(feature = "schemars")]
136mod schemars {
137    use super::Secret;
138    extern crate alloc;
139
140    impl<T: schemars::JsonSchema> schemars::JsonSchema for Secret<T> {
141        fn schema_id() -> alloc::borrow::Cow<'static, str> {
142            T::schema_id()
143        }
144
145        fn schema_name() -> alloc::borrow::Cow<'static, str> {
146            T::schema_name()
147        }
148
149        fn inline_schema() -> bool {
150            T::inline_schema()
151        }
152
153        fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
154            T::json_schema(generator)
155        }
156    }
157}
158
159#[cfg(feature = "sqlx")]
160mod sqlx_impls {
161    impl<DB: sqlx::Database, T: sqlx::Type<DB>> sqlx::Type<DB> for Secret<T> {
162        fn type_info() -> DB::TypeInfo {
163            T::type_info()
164        }
165
166        fn compatible(ty: &DB::TypeInfo) -> bool {
167            T::compatible(ty)
168        }
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::Secret;
175
176    #[test]
177    fn debug_should_output_type_only() {
178        let secret = Secret::new("Hello, world!");
179
180        assert_eq!(format!("{secret:#?}"), "Secret<&str>");
181    }
182}