Skip to main content

safelog/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_time_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45#![allow(clippy::collapsible_if)] // See arti#2342
46#![deny(clippy::unused_async)]
47//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
48
49// TODO: Try making it not Deref and having expose+expose_mut instead; how bad is it?
50
51use educe::Educe;
52#[cfg(feature = "serde")]
53use serde::{Deserialize, Serialize};
54
55mod err;
56mod flags;
57mod impls;
58
59pub use err::Error;
60pub use flags::{Guard, disable_safe_logging, enforce_safe_logging, with_safe_logging_suppressed};
61
62use std::ops::Deref;
63
64/// A `Result` returned by the flag-manipulation functions in `safelog`.
65pub type Result<T> = std::result::Result<T, Error>;
66
67// Re-exported for macros.
68#[doc(hidden)]
69pub use flags::unsafe_logging_enabled;
70
71/// A wrapper type for a sensitive value.
72///
73/// By default, a `Sensitive<T>` behaves the same as a regular `T`, except that
74/// attempts to turn it into a string (via `Display`, `Debug`, etc) all produce
75/// the string `[scrubbed]`.
76///
77/// This behavior can be overridden locally by using
78/// [`with_safe_logging_suppressed`] and globally with [`disable_safe_logging`].
79#[derive(Educe, Clone, Copy)]
80#[educe(
81    Default(bound),
82    Deref,
83    DerefMut,
84    Eq(bound),
85    Hash(bound),
86    Ord(bound),
87    PartialEq(bound),
88    PartialOrd(bound)
89)]
90#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
91#[cfg_attr(feature = "serde", serde(transparent))]
92pub struct Sensitive<T>(T);
93
94impl<T> Sensitive<T> {
95    /// Create a new `Sensitive<T>`, wrapping a provided `value`.
96    pub fn new(value: T) -> Self {
97        Sensitive(value)
98    }
99
100    /// Extract the inner value from this `Sensitive<T>`.
101    pub fn into_inner(self) -> T {
102        self.0
103    }
104
105    /// Extract the inner value from this `Sensitive<T>`.
106    #[deprecated = "Use the new into_inner method instead"]
107    pub fn unwrap(sensitive: Sensitive<T>) -> T {
108        sensitive.into_inner()
109    }
110
111    /// Converts `&Sensitive<T>` to `Sensitive<&T>`
112    pub fn as_ref(&self) -> Sensitive<&T> {
113        Sensitive(&self.0)
114    }
115
116    /// Return a reference to the inner value
117    //
118    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-sensitivisation"
119    // via what is usually a semantically-neutral interface.
120    pub fn as_inner(&self) -> &T {
121        &self.0
122    }
123}
124
125/// Wrap a value as `Sensitive`.
126///
127/// This function is an alias for [`Sensitive::new`].
128pub fn sensitive<T>(value: T) -> Sensitive<T> {
129    Sensitive(value)
130}
131
132impl<T> From<T> for Sensitive<T> {
133    fn from(value: T) -> Self {
134        Sensitive::new(value)
135    }
136}
137
138/// Helper: Declare one or more Display-like implementations for a
139/// Sensitive-like type.  These implementations will delegate to their std::fmt
140/// types if safe logging is disabled, and write `[scrubbed]` otherwise.
141macro_rules! impl_display_traits {
142    { $($trait:ident),* } => {
143    $(
144        impl<T: std::fmt::$trait> std::fmt::$trait for Sensitive<T> {
145            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146                if flags::unsafe_logging_enabled() {
147                    std::fmt::$trait::fmt(&self.0, f)
148                } else {
149                    write!(f, "[scrubbed]")
150                }
151            }
152        }
153
154        impl<T: std::fmt::$trait> std::fmt::$trait for BoxSensitive<T> {
155            #[inline]
156            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157                std::fmt::$trait::fmt(&*self.0, f)
158            }
159        }
160   )*
161   }
162}
163
164/// A wrapper suitable for logging and including in errors
165///
166/// This is a newtype around `Box<Sensitive<T>>`.
167///
168/// This is useful particularly in errors,
169/// where the box can help reduce the size of error variants
170/// (for example ones containing large values like an `OwnedChanTarget`).
171///
172/// `BoxSensitive<T>` dereferences to [`Sensitive<T>`].
173//
174// Making it be a newtype rather than a type alias allows us to implement
175// `into_inner` and `From<T>` and so on.
176#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
177pub struct BoxSensitive<T>(Box<Sensitive<T>>);
178
179impl<T> From<T> for BoxSensitive<T> {
180    fn from(t: T) -> BoxSensitive<T> {
181        BoxSensitive(Box::new(sensitive(t)))
182    }
183}
184
185impl<T> BoxSensitive<T> {
186    /// Return the innermost `T`
187    pub fn into_inner(self) -> T {
188        // TODO want unstable Box::into_inner(self.0) rust-lang/rust/issues/80437
189        let unboxed = *self.0;
190        unboxed.into_inner()
191    }
192}
193
194impl<T> Deref for BoxSensitive<T> {
195    type Target = Sensitive<T>;
196
197    fn deref(&self) -> &Sensitive<T> {
198        &self.0
199    }
200}
201
202impl_display_traits! {
203    Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer
204}
205
206/// An object that may or may not be sensitive.
207///
208/// See [`Sensitive`] for the guarantees it provides for the sensitive case.
209#[derive(Clone, derive_more::Display)]
210pub struct MaybeSensitive<T>(either::Either<T, Sensitive<T>>);
211
212impl<T> MaybeSensitive<T> {
213    /// Build a sensitive container.
214    pub fn sensitive(t: T) -> Self {
215        Self(either::Either::Right(Sensitive::new(t)))
216    }
217
218    /// Build a non sensitive container.
219    pub fn not_sensitive(t: T) -> Self {
220        Self(either::Either::Left(t))
221    }
222
223    /// Return the innermost `T`
224    pub fn inner(self) -> T {
225        match self.0 {
226            either::Either::Left(t) => t,
227            either::Either::Right(s) => s.into_inner(),
228        }
229    }
230
231    /// Map a `MaybeSensitive<T>` to a `MaybeSensitive<U>`
232    /// by applying the supplied function `f` to the inner `T`
233    pub fn map<U, F>(self, f: F) -> MaybeSensitive<U>
234    where
235        F: FnOnce(T) -> U,
236    {
237        match self.0 {
238            either::Either::Left(t) => MaybeSensitive(either::Either::Left(f(t))),
239            either::Either::Right(s) => {
240                let new_inner = f(s.into_inner());
241                MaybeSensitive(either::Either::Right(Sensitive::new(new_inner)))
242            }
243        }
244    }
245}
246
247impl<T: std::fmt::Debug> std::fmt::Debug for MaybeSensitive<T> {
248    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249        use std::fmt::Debug;
250        match &self.0 {
251            either::Either::Left(v) => Debug::fmt(v, f),
252            either::Either::Right(v) => Debug::fmt(v, f),
253        }
254    }
255}
256
257impl<T> Deref for MaybeSensitive<T> {
258    type Target = T;
259
260    fn deref(&self) -> &T {
261        match &self.0 {
262            either::Either::Left(t) => t,
263            either::Either::Right(s) => s.as_inner(),
264        }
265    }
266}
267
268/// A `redactable` object is one where we know a way to display _part_ of it
269/// when we are running with safe logging enabled.
270///
271/// For example, instead of referring to a user as `So-and-So` or `[scrubbed]`,
272/// this trait would allow referring to the user as `S[...]`.
273///
274/// # Privacy notes
275///
276/// Displaying some information about an object is always less safe than
277/// displaying no information about it!
278///
279/// For example, in an environment with only a small number of users, the first
280/// letter of a user's name might be plenty of information to identify them
281/// uniquely.
282///
283/// Even if a piece of redacted information is safe on its own, several pieces
284/// of redacted information, when taken together, can be enough for an adversary
285/// to infer more than you want.  For example, if you log somebody's first
286/// initial, month of birth, and last-two-digits of ID number, you have just
287/// discarded 99.9% of potential individuals from the attacker's consideration.
288pub trait Redactable: std::fmt::Display + std::fmt::Debug {
289    /// As `Display::fmt`, but produce a redacted representation.
290    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
291    /// As `Debug::fmt`, but produce a redacted representation.
292    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293        self.display_redacted(f)
294    }
295    /// Return a smart pointer that will display or debug this object as its
296    /// redacted form.
297    fn redacted(&self) -> Redacted<&Self> {
298        Redacted(self)
299    }
300    /// Return a smart pointer that redacts this object if `redact` is true.
301    fn maybe_redacted(&self, redact: bool) -> MaybeRedacted<&Self> {
302        if redact {
303            MaybeRedacted(either::Either::Right(Redacted(self)))
304        } else {
305            MaybeRedacted(either::Either::Left(self))
306        }
307    }
308}
309
310impl<'a, T: Redactable + ?Sized> Redactable for &'a T {
311    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312        (*self).display_redacted(f)
313    }
314}
315
316/// A wrapper around a `Redactable` that displays it in redacted format.
317#[derive(Educe, Clone, Copy)]
318#[educe(
319    Default(bound),
320    Deref,
321    DerefMut,
322    Eq(bound),
323    Hash(bound),
324    Ord(bound),
325    PartialEq(bound),
326    PartialOrd(bound)
327)]
328#[derive(derive_more::From)]
329#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
330#[cfg_attr(feature = "serde", serde(transparent))]
331pub struct Redacted<T: Redactable>(T);
332
333impl<T: Redactable> Redacted<T> {
334    /// Create a new `Redacted`.
335    pub fn new(value: T) -> Self {
336        Self(value)
337    }
338
339    /// Consume this wrapper and return its inner value.
340    pub fn unwrap(self) -> T {
341        self.0
342    }
343
344    /// Converts `&Redacted<T>` to `Redacted<&T>`
345    pub fn as_ref(&self) -> Redacted<&T> {
346        Redacted(&self.0)
347    }
348
349    /// Return a reference to the inner value
350    //
351    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-redaction"
352    // via what is usually a semantically-neutral interface.
353    pub fn as_inner(&self) -> &T {
354        &self.0
355    }
356}
357
358impl<T: Redactable> std::fmt::Display for Redacted<T> {
359    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360        if flags::unsafe_logging_enabled() {
361            std::fmt::Display::fmt(&self.0, f)
362        } else {
363            self.0.display_redacted(f)
364        }
365    }
366}
367
368impl<T: Redactable> std::fmt::Debug for Redacted<T> {
369    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
370        if flags::unsafe_logging_enabled() {
371            std::fmt::Debug::fmt(&self.0, f)
372        } else {
373            self.0.debug_redacted(f)
374        }
375    }
376}
377
378/// An object that may or may not be redacted.
379///
380/// Used to implement conditional redaction
381#[derive(Clone, derive_more::Display)]
382pub struct MaybeRedacted<T: Redactable>(either::Either<T, Redacted<T>>);
383
384impl<T: Redactable> std::fmt::Debug for MaybeRedacted<T> {
385    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
386        use std::fmt::Debug;
387        match &self.0 {
388            either::Either::Left(v) => Debug::fmt(v, f),
389            either::Either::Right(v) => Debug::fmt(v, f),
390        }
391    }
392}
393
394/// A type that can be displayed in a redacted or un-redacted form,
395/// but which forces the caller to choose.
396///
397/// See [`Redactable`] for more discussion on redaction.
398///
399/// Unlike [`Redactable`], this type is "inherently sensitive":
400/// Types implementing `DisplayRedacted` should not typically implement
401/// [`Display`](std::fmt::Display).
402///
403/// For external types that implement `Display`,
404/// or for types which are usually _not_ sensitive,
405/// `Redacted` is likely a better choice.
406pub trait DisplayRedacted {
407    /// As [`Display::fmt`](std::fmt::Display::fmt), but write this object
408    /// in its redacted form.
409    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
410    /// As [`Display::fmt`](std::fmt::Display::fmt), but write this object
411    /// in its un-redacted form.
412    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
413
414    // TODO: At some point in the future, when default values are supported for GATs,
415    // it might be good to turn these RPIT functions into associated types.
416
417    /// Return a pointer wrapping this object that can be Displayed in redacted form
418    /// if safe-logging is enabled.
419    ///
420    /// (If safe-logging is not enabled, it will de displayed in its unredacted form.)
421    fn display_redacted(&self) -> impl std::fmt::Display + '_ {
422        DispRedacted(self)
423    }
424    /// Return a pointer wrapping this object that can be Displayed in unredacted form.
425    fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
426        DispUnredacted(self)
427    }
428}
429
430impl<'a, T> DisplayRedacted for &'a T
431where
432    T: DisplayRedacted + ?Sized,
433{
434    fn display_redacted(&self) -> impl std::fmt::Display + '_ {
435        (*self).display_redacted()
436    }
437    fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
438        (*self).display_unredacted()
439    }
440    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
441        (*self).fmt_redacted(f)
442    }
443    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444        (*self).fmt_unredacted(f)
445    }
446}
447
448/// A wrapper around a [`DisplayRedacted`] that implements [`Display`](std::fmt::Display)
449/// by displaying the object in its redacted form
450/// if safe-logging is enabled.
451///
452/// (If safe-logging is not enabled, it will de displayed in its unredacted form.)
453#[allow(clippy::exhaustive_structs)]
454#[derive(derive_more::AsRef)]
455pub struct DispRedacted<T: ?Sized>(pub T);
456
457/// A wrapper around a [`DisplayRedacted`] that implements [`Display`](std::fmt::Display)
458/// by displaying the object in its un-redacted form.
459#[allow(clippy::exhaustive_structs)]
460#[derive(derive_more::AsRef)]
461pub struct DispUnredacted<T: ?Sized>(pub T);
462
463impl<T> std::fmt::Display for DispRedacted<T>
464where
465    T: DisplayRedacted + ?Sized,
466{
467    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468        if crate::flags::unsafe_logging_enabled() {
469            self.0.fmt_unredacted(f)
470        } else {
471            self.0.fmt_redacted(f)
472        }
473    }
474}
475
476impl<T> std::fmt::Display for DispUnredacted<T>
477where
478    T: DisplayRedacted + ?Sized,
479{
480    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
481        self.0.fmt_unredacted(f)
482    }
483}
484
485/// A type that can be debugged in a redacted or un-redacted form,
486/// but which forces the caller to choose.
487///
488/// See [`Redactable`] for more discussion on redaction.
489///
490/// Unlike [`Redactable`], this type is "inherently sensitive":
491/// [`Debug`](std::fmt::Debug) will display it in redacted or un-redacted format
492/// depending on whether safe logging is enabled.
493///
494/// For external types that implement `Debug`,
495/// or for types which are usually _not_ sensitive,
496/// `Redacted` is likely a better choice.
497pub trait DebugRedacted {
498    /// As [`Debug::fmt`](std::fmt::Debug::fmt), but write this object
499    /// in its redacted form.
500    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
501    /// As [`Debug::fmt`](std::fmt::Debug::fmt), but write this object
502    /// in its unredacted form.
503    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
504}
505
506/// Implement [`std::fmt::Debug`] for a type that implements [`DebugRedacted`].
507///
508/// The implementation will use fmt_redacted() when safe-logging is enabled,
509/// and fmt_unredacted() otherwise.
510///
511/// (NOTE we can't just write 'impl<T:DebugRedacted> Debug for T`;
512/// Rust doesn't like it.)
513#[macro_export]
514macro_rules! derive_redacted_debug {
515    {$t:ty} => {
516    impl std::fmt::Debug for $t {
517        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518            if $crate::unsafe_logging_enabled() {
519                $crate::DebugRedacted::fmt_unredacted(self, f)
520            } else {
521                $crate::DebugRedacted::fmt_redacted(self, f)
522            }
523        }
524    }
525}}
526
527#[cfg(test)]
528mod test {
529    // @@ begin test lint list maintained by maint/add_warning @@
530    #![allow(clippy::bool_assert_comparison)]
531    #![allow(clippy::clone_on_copy)]
532    #![allow(clippy::dbg_macro)]
533    #![allow(clippy::mixed_attributes_style)]
534    #![allow(clippy::print_stderr)]
535    #![allow(clippy::print_stdout)]
536    #![allow(clippy::single_char_pattern)]
537    #![allow(clippy::unwrap_used)]
538    #![allow(clippy::unchecked_time_subtraction)]
539    #![allow(clippy::useless_vec)]
540    #![allow(clippy::needless_pass_by_value)]
541    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
542
543    use super::*;
544    use serial_test::serial;
545    use static_assertions::{assert_impl_all, assert_not_impl_any};
546
547    #[test]
548    fn clone_bound() {
549        // Here we'll make sure that educe bounds work about the way we expect.
550        #[derive(Clone)]
551        struct A;
552        struct B;
553
554        let _x = Sensitive(A).clone();
555        let _y = Sensitive(B);
556
557        assert_impl_all!(Sensitive<A> : Clone);
558        assert_not_impl_any!(Sensitive<B> : Clone);
559    }
560
561    #[test]
562    #[serial]
563    fn debug_vec() {
564        type SVec = Sensitive<Vec<u32>>;
565
566        let mut sv = SVec::default();
567        assert!(sv.is_empty());
568        sv.push(104);
569        sv.push(49);
570        assert_eq!(sv.len(), 2);
571
572        assert!(!flags::unsafe_logging_enabled());
573        assert_eq!(format!("{:?}", &sv), "[scrubbed]");
574        assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
575        assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
576        let normal = with_safe_logging_suppressed(|| format!("{:?}", &sv));
577        assert_eq!(normal, "[104, 49]");
578
579        let _g = disable_safe_logging().unwrap();
580        assert_eq!(format!("{:?}", &sv), "[104, 49]");
581
582        assert_eq!(sv, SVec::from(vec![104, 49]));
583        assert_eq!(sv.clone().into_inner(), vec![104, 49]);
584        assert_eq!(*sv, vec![104, 49]);
585    }
586
587    #[test]
588    #[serial]
589    #[allow(deprecated)]
590    fn deprecated() {
591        type SVec = Sensitive<Vec<u32>>;
592        let sv = Sensitive(vec![104, 49]);
593
594        assert_eq!(SVec::unwrap(sv), vec![104, 49]);
595    }
596
597    #[test]
598    #[serial]
599    fn display_various() {
600        let val = Sensitive::<u32>::new(0x0ed19a);
601
602        let closure1 = || {
603            format!(
604                "{:?}, {}, {:o}, {:x}, {:X}, {:b}",
605                &val, &val, &val, &val, &val, &val,
606            )
607        };
608        let s1 = closure1();
609        let s2 = with_safe_logging_suppressed(closure1);
610        assert_eq!(
611            s1,
612            "[scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed]"
613        );
614        assert_eq!(
615            s2,
616            "971162, 971162, 3550632, ed19a, ED19A, 11101101000110011010"
617        );
618
619        let n = 1.0E32;
620        let val = Sensitive::<f64>::new(n);
621        let expect = format!("{:?}, {}, {:e}, {:E}", n, n, n, n);
622        let closure2 = || format!("{:?}, {}, {:e}, {:E}", &val, &val, &val, &val);
623        let s1 = closure2();
624        let s2 = with_safe_logging_suppressed(closure2);
625        assert_eq!(s1, "[scrubbed], [scrubbed], [scrubbed], [scrubbed]");
626        assert_eq!(s2, expect);
627
628        let ptr: *const u8 = std::ptr::null();
629        let val = Sensitive::new(ptr);
630        let expect = format!("{:?}, {:p}", ptr, ptr);
631        let closure3 = || format!("{:?}, {:p}", val, val);
632        let s1 = closure3();
633        let s2 = with_safe_logging_suppressed(closure3);
634        assert_eq!(s1, "[scrubbed], [scrubbed]");
635        assert_eq!(s2, expect);
636    }
637
638    #[test]
639    #[serial]
640    fn box_sensitive() {
641        let b: BoxSensitive<_> = "hello world".into();
642
643        assert_eq!(b.clone().into_inner(), "hello world");
644
645        let closure = || format!("{} {:?}", b, b);
646        assert_eq!(closure(), "[scrubbed] [scrubbed]");
647        assert_eq!(
648            with_safe_logging_suppressed(closure),
649            r#"hello world "hello world""#
650        );
651
652        assert_eq!(b.len(), 11);
653    }
654
655    #[test]
656    #[serial]
657    fn test_redacted() {
658        let localhost = std::net::Ipv4Addr::LOCALHOST;
659        let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
660
661        assert_eq!(closure(), "127.x.x.x 127.x.x.x");
662        assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
663
664        let closure = |b| {
665            format!(
666                "{} {:?}",
667                localhost.maybe_redacted(b),
668                localhost.maybe_redacted(b)
669            )
670        };
671        assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
672        assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
673
674        assert_eq!(Redacted::new(localhost).unwrap(), localhost);
675    }
676
677    struct RedactionCheck(u32);
678    impl DisplayRedacted for RedactionCheck {
679        fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
680            write!(f, "{}", self.0)
681        }
682
683        fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
684            let v = self.0.to_string();
685            write!(f, "{}xxx", v.chars().next().unwrap())
686        }
687    }
688    impl DebugRedacted for RedactionCheck {
689        fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
690            write!(f, "Num({})", self.display_redacted())
691        }
692
693        fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
694            write!(f, "Num({})", self.display_unredacted())
695        }
696    }
697    derive_redacted_debug!(RedactionCheck);
698
699    #[test]
700    #[serial]
701    fn display_redacted() {
702        let n = RedactionCheck(999);
703        assert_eq!(&n.display_unredacted().to_string(), "999");
704        assert_eq!(&n.display_redacted().to_string(), "9xxx");
705        with_safe_logging_suppressed(|| assert_eq!(&n.display_redacted().to_string(), "999"));
706
707        assert_eq!(DispRedacted(&n).to_string(), "9xxx");
708        assert_eq!(DispUnredacted(&n).to_string(), "999");
709
710        assert_eq!(&format!("{n:?}"), "Num(9xxx)");
711        with_safe_logging_suppressed(|| assert_eq!(&format!("{n:?}"), "Num(999)"));
712    }
713}