Derive Debug and Display for Rust structs while honoring sensitive field annotations. Designed for one job: keeping
PHI, PII, secrets, and tokens out of log lines.
I wrote this because none of the crates I could find (including my own safe-debug ) did exactly what I wanted as simply as I wanted to do it. This sensitive data redactor:
- derives both
DebugandDisplay - leans on existing implementations of both for non-primitive types, or lets you skip the field
- is as stupid and simple as I could make it
use sensitive_fmt::{SensitiveDebug, SensitiveDisplay};
#[derive(SensitiveDebug, SensitiveDisplay)]
struct Patient {
id: u64,
name: String,
#[sensitive(truncate = 4)]
mrn: String,
#[sensitive(redact)]
email: String,
#[sensitive(skip)]
raw: NotFormattable,
}
struct NotFormattable;
let p = Patient {
id: 42,
name: "Alice".into(),
mrn: "MRN-12345-WXYZ".into(),
email: "alice@example.com".into(),
raw: NotFormattable,
};
assert_eq!(
format!("{p:?}"),
r#"Patient { id: 42, name: "Alice", mrn: ****WXYZ, email: REDACTED, raw: <skipped> }"#,
);
assert_eq!(
format!("{p}"),
"Patient { id: 42, name: Alice, mrn: ****WXYZ, email: REDACTED, raw: <skipped> }",
);One attribute, three modifiers (mutually exclusive):
| Attribute | Effect | required trait |
|---|---|---|
| (none) | normal Debug / Display |
Debug for SensitiveDebug, Display for SensitiveDisplay |
#[sensitive(redact)] |
writes REDACTED |
none |
#[sensitive(truncate = N)] |
writes ****<last N code points>, or REDACTED if value has fewer than N code points |
Display (in both derives) |
#[sensitive(skip)] |
writes <skipped> |
none |
- Tuples fail at compile time with a clear message. Wrap the data in a named-field struct or enum instead.
- Container types (
Option<T>,Vec<T>, etc.) are treated as opaque. If you have anOption<String>field you can'tDisplay-format directly, reach forredactorskip. - Generic structs are supported by adding trait bounds on the actual formatted field type. Plain fields require
DebugforSensitiveDebugorDisplayforSensitiveDisplay;truncatealways requiresDisplay;redactandskipdo not add formatting bounds. no_stdcompatible. Requiresallocfor thetruncatemodifier.- Minimum supported Rust version: 1.88.0.
- Truncate counts code points, not graphemes.
"café"is 4 code points.
| Crate | Debug | Display | Truncate | Why this crate exists |
|---|---|---|---|---|
veil |
yes | no | head + tail of *s |
no Display, no last-N |
redactable |
yes | yes | named policies (Token / PII / …) | grew slog/tracing/json features |
secrecy / redact |
wrapper | wrapper | none | wrapper-type model, not derives |
safe-debug |
yes | no | n/a | requires the facet ecosystem |
MIT OR Apache-2.0