Skip to main content

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}