1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![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)] #![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)] #![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::needless_lifetimes)] #![allow(mismatched_lifetime_syntaxes)] #![allow(clippy::collapsible_if)] #![deny(clippy::unused_async)]
47#![deny(clippy::string_slice)] use 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
66pub type Result<T> = std::result::Result<T, Error>;
68
69#[doc(hidden)]
71pub use flags::unsafe_logging_enabled;
72
73#[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 pub fn new(value: T) -> Self {
99 Sensitive(value)
100 }
101
102 pub fn into_inner(self) -> T {
104 self.0
105 }
106
107 #[deprecated = "Use the new into_inner method instead"]
109 pub fn unwrap(sensitive: Sensitive<T>) -> T {
110 sensitive.into_inner()
111 }
112
113 pub fn as_ref(&self) -> Sensitive<&T> {
115 Sensitive(&self.0)
116 }
117
118 pub fn as_inner(&self) -> &T {
123 &self.0
124 }
125}
126
127pub 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
140macro_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#[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 pub fn into_inner(self) -> T {
190 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#[derive(Clone, derive_more::Display)]
212pub struct MaybeSensitive<T>(either::Either<T, Sensitive<T>>);
213
214impl<T> MaybeSensitive<T> {
215 pub fn sensitive(t: T) -> Self {
217 Self(either::Either::Right(Sensitive::new(t)))
218 }
219
220 pub fn not_sensitive(t: T) -> Self {
222 Self(either::Either::Left(t))
223 }
224
225 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 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
270pub trait Redactable: std::fmt::Display + std::fmt::Debug {
291 fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
293 fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 self.display_redacted(f)
296 }
297 fn redacted(&self) -> Redacted<&Self> {
300 Redacted(self)
301 }
302 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 }
323
324#[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 pub fn new(value: T) -> Self {
344 Self(value)
345 }
346
347 pub fn unwrap(self) -> T {
349 self.0
350 }
351
352 pub fn as_ref(&self) -> Redacted<&T> {
354 Redacted(&self.0)
355 }
356
357 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#[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
402pub trait DisplayRedacted {
415 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
418 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
421
422 fn display_redacted(&self) -> impl std::fmt::Display + '_ {
430 DispRedacted(self)
431 }
432 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#[allow(clippy::exhaustive_structs)]
462#[derive(derive_more::AsRef)]
463pub struct DispRedacted<T: ?Sized>(pub T);
464
465#[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
493pub trait DebugRedacted {
506 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
509 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
512}
513
514#[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 #![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)] 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 #[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}