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#![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)] #![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
318#[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 pub fn new(value: T) -> Self {
338 Self(value)
339 }
340
341 pub fn unwrap(self) -> T {
343 self.0
344 }
345
346 pub fn as_ref(&self) -> Redacted<&T> {
348 Redacted(&self.0)
349 }
350
351 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#[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
396pub trait DisplayRedacted {
409 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
412 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
415
416 fn display_redacted(&self) -> impl std::fmt::Display + '_ {
424 DispRedacted(self)
425 }
426 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#[allow(clippy::exhaustive_structs)]
456#[derive(derive_more::AsRef)]
457pub struct DispRedacted<T: ?Sized>(pub T);
458
459#[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
487pub trait DebugRedacted {
500 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
503 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
506}
507
508#[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 #![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)] 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 #[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}