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