vitaminc_protected/debug/mod.rs
1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use zeroize::Zeroize;
3
4/// Opaque `Debug` for secret-bearing types.
5///
6/// This module provides the [`OpaqueDebug`] marker trait and a `#[derive(OpaqueDebug)]`
7/// macro that **also** implements [`core::fmt::Debug`] for your type in a way that
8/// **never** reveals internal data.
9///
10/// By default, the generated `Debug` implementation prints a placeholder that includes
11/// the type's fully-qualified name via [`core::any::type_name`]:
12///
13/// You can override this placeholder with an attribute on the type.
14///
15/// ## Why use this?
16///
17/// - Prevents accidental leakage of secrets (keys, tokens, passwords) via `Debug`
18/// in logs or error messages.
19/// - Keeps a meaningful breadcrumb (the type name) for diagnostics without exposing data.
20/// - Allows the usage of [`Debug`] on outer types without risk.
21///
22/// ## What it generates
23///
24/// `#[derive(OpaqueDebug)]` generates:
25///
26/// 1. An impl of the marker trait:
27/// ```ignore
28/// impl OpaqueDebug for YourType {}
29/// ```
30/// 2. An impl of `core::fmt::Debug` that **never** formats internal fields.
31///
32/// ## Usage
33///
34/// ### Basic: default placeholder uses the fully-qualified type name
35///
36/// ```rust
37/// # mod vitaminc { pub mod protected { pub use vitaminc_protected::*; } }
38/// use vitaminc::protected::OpaqueDebug;
39///
40/// #[derive(OpaqueDebug)]
41/// struct ApiToken([u8; 32]);
42///
43/// let t = ApiToken([0; 32]);
44/// let out = format!("{t:?}");
45/// assert!(out.contains("ApiToken(\"***\")"));
46/// ```
47///
48/// ### Works with generics
49///
50/// The `Debug` impl includes the instantiated type parameters in the placeholder.
51///
52/// ```rust
53/// # mod vitaminc { pub mod protected { pub use vitaminc_protected::*; } }
54/// use vitaminc::protected::OpaqueDebug;
55///
56/// #[derive(OpaqueDebug)]
57/// struct Key<const N: usize>([u8; N]);
58///
59/// let env = Key::<32>([0u8; 32]);
60/// let out = format!("{env:?}");
61/// assert!(out.contains("Key<32>(\"***\")"));
62/// ```
63///
64/// ### Enums and unit-like types are supported
65///
66/// The internal representation is still hidden.
67///
68/// ```rust
69/// # mod vitaminc { pub mod protected { pub use vitaminc_protected::*; } }
70/// use vitaminc::protected::OpaqueDebug;
71///
72/// #[derive(OpaqueDebug)]
73/// enum SecretThing {
74/// A(u32),
75/// B { x: u8, y: u8 },
76/// C,
77/// }
78///
79/// let s = SecretThing::B { x: 7, y: 9 };
80/// let out = format!("{s:?}");
81/// assert!(out.contains("SecretThing::B {x: \"***\", y: \"***\"}"));
82/// ```
83///
84/// ### Marking non-sensitive fields
85///
86/// You can mark individual fields as non-sensitive using the `#[non_sensitive]` attribute.
87/// This is useful when you actually want to include certain fields in the debug output.
88///
89/// ```rust
90/// # mod vitaminc { pub mod protected { pub use vitaminc_protected::*; } }
91/// use vitaminc::protected::OpaqueDebug;
92///
93/// #[derive(OpaqueDebug)]
94/// struct HasNonSensitiveField {
95/// sensitive: String,
96/// #[non_sensitive]
97/// value: String,
98/// }
99///
100/// let out = format!(
101/// "{:?}",
102/// HasNonSensitiveField {
103/// sensitive: "do-not-print".into(),
104/// value: "ok-to-print".into(),
105/// }
106/// );
107/// assert!(out.contains("HasNonSensitiveField { sensitive: \"***\", value: \"ok-to-print\" }"));
108/// ```
109///
110/// ### Using with Redacted wrapper (optional)
111///
112/// This is useful to manage external types.
113///
114/// ```rust
115/// # mod vitaminc { pub mod protected { pub use vitaminc_protected::*; } }
116/// use core::fmt;
117/// use vitaminc::protected::{OpaqueDebug, Redacted};
118///
119/// let safe = Redacted::new([0u8; 32]);
120/// assert_eq!(format!("{:?}", safe), "Redacted<[u8; 32] ***>");
121/// ```
122///
123/// See also [`Redacted`].
124///
125/// ## Notes
126///
127/// - The derive intentionally **replaces** any `Debug` you might otherwise derive or write.
128/// - The default placeholder is computed with `core::any::type_name::<T>()` at runtime.
129/// - The marker trait has no methods; it exists to make trait bounds and wrapper impls
130/// straightforward (e.g., a wrapper can `impl<T: OpaqueDebug> Debug for Redacted<T>`).
131///
132/// ### Feature compatibility
133///
134/// This module works in `no_std` environments; it only depends on `core`.
135///
136/// Happy redacting 👋
137pub trait OpaqueDebug {}
138
139/// Wrapper type for redacting debug output which implements [`OpaqueDebug`] for all types.
140///
141/// # Example
142///
143/// ```
144/// # mod vitaminc { pub mod protected { pub use vitaminc_protected::*; } }
145/// use vitaminc::protected::Redacted;
146///
147/// let redacted = Redacted::new([0u8; 32]);
148/// assert_eq!(format!("{:?}", redacted), "Redacted<[u8; 32] ***>");
149/// ```
150///
151#[repr(transparent)]
152pub struct Redacted<T>(T);
153
154impl<T> Redacted<T> {
155 /// Create a new `Redacted` instance.
156 pub const fn new(value: T) -> Self {
157 Self(value)
158 }
159
160 /// Consume the `Redacted` instance and return the inner value.
161 /// CAUTION: this will remove the opaque redaction if the inner type implements `Debug`.
162 pub fn into_inner(self) -> T {
163 self.0
164 }
165}
166
167impl<T> AsRef<T> for Redacted<T> {
168 /// Get a reference to the inner value.
169 /// CAUTION: this will remove the opaque redaction if the inner type implements `Debug`.
170 fn as_ref(&self) -> &T {
171 &self.0
172 }
173}
174
175impl<T> core::fmt::Debug for Redacted<T> {
176 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
177 write!(f, "Redacted<{} ***>", std::any::type_name::<T>())
178 }
179}
180
181impl<T> OpaqueDebug for Redacted<T> {}
182
183impl<T> Zeroize for Redacted<T>
184where
185 T: Zeroize,
186{
187 fn zeroize(&mut self) {
188 self.0.zeroize();
189 }
190}
191
192impl<T> Clone for Redacted<T>
193where
194 T: Clone,
195{
196 fn clone(&self) -> Self {
197 Redacted(self.0.clone())
198 }
199}
200
201impl<T> Serialize for Redacted<T>
202where
203 T: Serialize,
204{
205 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
206 where
207 S: Serializer,
208 {
209 serializer.serialize_str("Redacted")
210 }
211}
212
213impl<'de, T> Deserialize<'de> for Redacted<T>
214where
215 T: Deserialize<'de>,
216{
217 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
218 where
219 D: Deserializer<'de>,
220 {
221 let x = T::deserialize(deserializer)?;
222 Ok(Redacted(x))
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_redacted_debug() {
232 let redacted = Redacted(42);
233 assert_eq!(format!("{redacted:?}"), "Redacted<i32 ***>");
234 }
235}