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#![allow(clippy::cognitive_complexity)] // See arti#2556
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    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318        (*self).debug_redacted(f)
319    }
320
321    // The default implementations for redacted() and maybe_redacted() are okay unconditionally.
322}
323
324/// A wrapper around a `Redactable` that displays it in redacted format.
325#[derive(Educe, Clone, Copy)]
326#[educe(
327    Default(bound),
328    Deref,
329    DerefMut,
330    Eq(bound),
331    Hash(bound),
332    Ord(bound),
333    PartialEq(bound),
334    PartialOrd(bound)
335)]
336#[derive(derive_more::From)]
337#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
338#[cfg_attr(feature = "serde", serde(transparent))]
339pub struct Redacted<T: Redactable>(T);
340
341impl<T: Redactable> Redacted<T> {
342    /// Create a new `Redacted`.
343    pub fn new(value: T) -> Self {
344        Self(value)
345    }
346
347    /// Consume this wrapper and return its inner value.
348    pub fn unwrap(self) -> T {
349        self.0
350    }
351
352    /// Converts `&Redacted<T>` to `Redacted<&T>`
353    pub fn as_ref(&self) -> Redacted<&T> {
354        Redacted(&self.0)
355    }
356
357    /// Return a reference to the inner value
358    //
359    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-redaction"
360    // via what is usually a semantically-neutral interface.
361    pub fn as_inner(&self) -> &T {
362        &self.0
363    }
364}
365
366impl<T: Redactable> std::fmt::Display for Redacted<T> {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        if flags::unsafe_logging_enabled() {
369            std::fmt::Display::fmt(&self.0, f)
370        } else {
371            self.0.display_redacted(f)
372        }
373    }
374}
375
376impl<T: Redactable> std::fmt::Debug for Redacted<T> {
377    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378        if flags::unsafe_logging_enabled() {
379            std::fmt::Debug::fmt(&self.0, f)
380        } else {
381            self.0.debug_redacted(f)
382        }
383    }
384}
385
386/// An object that may or may not be redacted.
387///
388/// Used to implement conditional redaction
389#[derive(Clone, derive_more::Display)]
390pub struct MaybeRedacted<T: Redactable>(either::Either<T, Redacted<T>>);
391
392impl<T: Redactable> std::fmt::Debug for MaybeRedacted<T> {
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        use std::fmt::Debug;
395        match &self.0 {
396            either::Either::Left(v) => Debug::fmt(v, f),
397            either::Either::Right(v) => Debug::fmt(v, f),
398        }
399    }
400}
401
402/// A type that can be displayed in a redacted or un-redacted form,
403/// but which forces the caller to choose.
404///
405/// See [`Redactable`] for more discussion on redaction.
406///
407/// Unlike [`Redactable`], this type is "inherently sensitive":
408/// Types implementing `DisplayRedacted` should not typically implement
409/// [`Display`](std::fmt::Display).
410///
411/// For external types that implement `Display`,
412/// or for types which are usually _not_ sensitive,
413/// `Redacted` is likely a better choice.
414pub trait DisplayRedacted {
415    /// As [`Display::fmt`](std::fmt::Display::fmt), but write this object
416    /// in its redacted form.
417    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
418    /// As [`Display::fmt`](std::fmt::Display::fmt), but write this object
419    /// in its un-redacted form.
420    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
421
422    // TODO: At some point in the future, when default values are supported for GATs,
423    // it might be good to turn these RPIT functions into associated types.
424
425    /// Return a pointer wrapping this object that can be Displayed in redacted form
426    /// if safe-logging is enabled.
427    ///
428    /// (If safe-logging is not enabled, it will de displayed in its unredacted form.)
429    fn display_redacted(&self) -> impl std::fmt::Display + '_ {
430        DispRedacted(self)
431    }
432    /// Return a pointer wrapping this object that can be Displayed in unredacted form.
433    fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
434        DispUnredacted(self)
435    }
436}
437
438impl<'a, T> DisplayRedacted for &'a T
439where
440    T: DisplayRedacted + ?Sized,
441{
442    fn display_redacted(&self) -> impl std::fmt::Display + '_ {
443        (*self).display_redacted()
444    }
445    fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
446        (*self).display_unredacted()
447    }
448    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449        (*self).fmt_redacted(f)
450    }
451    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
452        (*self).fmt_unredacted(f)
453    }
454}
455
456/// A wrapper around a [`DisplayRedacted`] that implements [`Display`](std::fmt::Display)
457/// by displaying the object in its redacted form
458/// if safe-logging is enabled.
459///
460/// (If safe-logging is not enabled, it will de displayed in its unredacted form.)
461#[allow(clippy::exhaustive_structs)]
462#[derive(derive_more::AsRef)]
463pub struct DispRedacted<T: ?Sized>(pub T);
464
465/// A wrapper around a [`DisplayRedacted`] that implements [`Display`](std::fmt::Display)
466/// by displaying the object in its un-redacted form.
467#[allow(clippy::exhaustive_structs)]
468#[derive(derive_more::AsRef)]
469pub struct DispUnredacted<T: ?Sized>(pub T);
470
471impl<T> std::fmt::Display for DispRedacted<T>
472where
473    T: DisplayRedacted + ?Sized,
474{
475    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476        if crate::flags::unsafe_logging_enabled() {
477            self.0.fmt_unredacted(f)
478        } else {
479            self.0.fmt_redacted(f)
480        }
481    }
482}
483
484impl<T> std::fmt::Display for DispUnredacted<T>
485where
486    T: DisplayRedacted + ?Sized,
487{
488    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489        self.0.fmt_unredacted(f)
490    }
491}
492
493/// A type that can be debugged in a redacted or un-redacted form,
494/// but which forces the caller to choose.
495///
496/// See [`Redactable`] for more discussion on redaction.
497///
498/// Unlike [`Redactable`], this type is "inherently sensitive":
499/// [`Debug`](std::fmt::Debug) will display it in redacted or un-redacted format
500/// depending on whether safe logging is enabled.
501///
502/// For external types that implement `Debug`,
503/// or for types which are usually _not_ sensitive,
504/// `Redacted` is likely a better choice.
505pub trait DebugRedacted {
506    /// As [`Debug::fmt`](std::fmt::Debug::fmt), but write this object
507    /// in its redacted form.
508    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
509    /// As [`Debug::fmt`](std::fmt::Debug::fmt), but write this object
510    /// in its unredacted form.
511    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
512}
513
514/// Implement [`std::fmt::Debug`] for a type that implements [`DebugRedacted`].
515///
516/// The implementation will use fmt_redacted() when safe-logging is enabled,
517/// and fmt_unredacted() otherwise.
518///
519/// (NOTE we can't just write 'impl<T:DebugRedacted> Debug for T`;
520/// Rust doesn't like it.)
521#[macro_export]
522macro_rules! derive_redacted_debug {
523    {$t:ty} => {
524    impl std::fmt::Debug for $t {
525        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
526            if $crate::unsafe_logging_enabled() {
527                $crate::DebugRedacted::fmt_unredacted(self, f)
528            } else {
529                $crate::DebugRedacted::fmt_redacted(self, f)
530            }
531        }
532    }
533}}
534
535#[cfg(test)]
536mod test {
537    // @@ begin test lint list maintained by maint/add_warning @@
538    #![allow(clippy::bool_assert_comparison)]
539    #![allow(clippy::clone_on_copy)]
540    #![allow(clippy::dbg_macro)]
541    #![allow(clippy::mixed_attributes_style)]
542    #![allow(clippy::print_stderr)]
543    #![allow(clippy::print_stdout)]
544    #![allow(clippy::single_char_pattern)]
545    #![allow(clippy::unwrap_used)]
546    #![allow(clippy::unchecked_time_subtraction)]
547    #![allow(clippy::useless_vec)]
548    #![allow(clippy::needless_pass_by_value)]
549    #![allow(clippy::string_slice)] // See arti#2571
550    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
551
552    use super::*;
553    use serial_test::serial;
554    use static_assertions::{assert_impl_all, assert_not_impl_any};
555
556    #[test]
557    fn clone_bound() {
558        // Here we'll make sure that educe bounds work about the way we expect.
559        #[derive(Clone)]
560        struct A;
561        struct B;
562
563        let _x = Sensitive(A).clone();
564        let _y = Sensitive(B);
565
566        assert_impl_all!(Sensitive<A> : Clone);
567        assert_not_impl_any!(Sensitive<B> : Clone);
568    }
569
570    #[test]
571    #[serial]
572    fn debug_vec() {
573        type SVec = Sensitive<Vec<u32>>;
574
575        let mut sv = SVec::default();
576        assert!(sv.is_empty());
577        sv.push(104);
578        sv.push(49);
579        assert_eq!(sv.len(), 2);
580
581        assert!(!flags::unsafe_logging_enabled());
582        assert_eq!(format!("{:?}", sv), "[scrubbed]");
583        assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
584        assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
585        let normal = with_safe_logging_suppressed(|| format!("{:?}", sv));
586        assert_eq!(normal, "[104, 49]");
587
588        let _g = disable_safe_logging().unwrap();
589        assert_eq!(format!("{:?}", sv), "[104, 49]");
590
591        assert_eq!(sv, SVec::from(vec![104, 49]));
592        assert_eq!(sv.clone().into_inner(), vec![104, 49]);
593        assert_eq!(*sv, vec![104, 49]);
594    }
595
596    #[test]
597    #[serial]
598    #[allow(deprecated)]
599    fn deprecated() {
600        type SVec = Sensitive<Vec<u32>>;
601        let sv = Sensitive(vec![104, 49]);
602
603        assert_eq!(SVec::unwrap(sv), vec![104, 49]);
604    }
605
606    #[test]
607    #[serial]
608    fn display_various() {
609        let val = Sensitive::<u32>::new(0x0ed19a);
610
611        let closure1 = || {
612            format!(
613                "{:?}, {}, {:o}, {:x}, {:X}, {:b}",
614                val, val, val, val, val, val,
615            )
616        };
617        let s1 = closure1();
618        let s2 = with_safe_logging_suppressed(closure1);
619        assert_eq!(
620            s1,
621            "[scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed]"
622        );
623        assert_eq!(
624            s2,
625            "971162, 971162, 3550632, ed19a, ED19A, 11101101000110011010"
626        );
627
628        let n = 1.0E32;
629        let val = Sensitive::<f64>::new(n);
630        let expect = format!("{:?}, {}, {:e}, {:E}", n, n, n, n);
631        let closure2 = || format!("{:?}, {}, {:e}, {:E}", val, val, val, val);
632        let s1 = closure2();
633        let s2 = with_safe_logging_suppressed(closure2);
634        assert_eq!(s1, "[scrubbed], [scrubbed], [scrubbed], [scrubbed]");
635        assert_eq!(s2, expect);
636
637        let ptr: *const u8 = std::ptr::null();
638        let val = Sensitive::new(ptr);
639        let expect = format!("{:?}, {:p}", ptr, ptr);
640        let closure3 = || format!("{:?}, {:p}", val, val);
641        let s1 = closure3();
642        let s2 = with_safe_logging_suppressed(closure3);
643        assert_eq!(s1, "[scrubbed], [scrubbed]");
644        assert_eq!(s2, expect);
645    }
646
647    #[test]
648    #[serial]
649    fn box_sensitive() {
650        let b: BoxSensitive<_> = "hello world".into();
651
652        assert_eq!(b.clone().into_inner(), "hello world");
653
654        let closure = || format!("{} {:?}", b, b);
655        assert_eq!(closure(), "[scrubbed] [scrubbed]");
656        assert_eq!(
657            with_safe_logging_suppressed(closure),
658            r#"hello world "hello world""#
659        );
660
661        assert_eq!(b.len(), 11);
662    }
663
664    #[test]
665    #[serial]
666    fn test_redacted() {
667        let localhost = std::net::Ipv4Addr::LOCALHOST;
668        let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
669
670        assert_eq!(closure(), "127.x.x.x 127.x.x.x");
671        assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
672
673        let closure = |b| {
674            format!(
675                "{} {:?}",
676                localhost.maybe_redacted(b),
677                localhost.maybe_redacted(b)
678            )
679        };
680        assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
681        assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
682
683        assert_eq!(Redacted::new(localhost).unwrap(), localhost);
684    }
685
686    struct RedactionCheck(u32);
687    impl DisplayRedacted for RedactionCheck {
688        fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
689            write!(f, "{}", self.0)
690        }
691
692        fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
693            let v = self.0.to_string();
694            write!(f, "{}xxx", v.chars().next().unwrap())
695        }
696    }
697    impl DebugRedacted for RedactionCheck {
698        fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
699            write!(f, "Num({})", self.display_redacted())
700        }
701
702        fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
703            write!(f, "Num({})", self.display_unredacted())
704        }
705    }
706    derive_redacted_debug!(RedactionCheck);
707
708    #[test]
709    #[serial]
710    fn display_redacted() {
711        let n = RedactionCheck(999);
712        assert_eq!(&n.display_unredacted().to_string(), "999");
713        assert_eq!(&n.display_redacted().to_string(), "9xxx");
714        with_safe_logging_suppressed(|| assert_eq!(&n.display_redacted().to_string(), "999"));
715
716        assert_eq!(DispRedacted(&n).to_string(), "9xxx");
717        assert_eq!(DispUnredacted(&n).to_string(), "999");
718
719        assert_eq!(&format!("{n:?}"), "Num(9xxx)");
720        with_safe_logging_suppressed(|| assert_eq!(&format!("{n:?}"), "Num(999)"));
721    }
722}