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)]
47use 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
64pub type Result<T> = std::result::Result<T, Error>;
66
67#[doc(hidden)]
69pub use flags::unsafe_logging_enabled;
70
71#[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 pub fn new(value: T) -> Self {
97 Sensitive(value)
98 }
99
100 pub fn into_inner(self) -> T {
102 self.0
103 }
104
105 #[deprecated = "Use the new into_inner method instead"]
107 pub fn unwrap(sensitive: Sensitive<T>) -> T {
108 sensitive.into_inner()
109 }
110
111 pub fn as_ref(&self) -> Sensitive<&T> {
113 Sensitive(&self.0)
114 }
115
116 pub fn as_inner(&self) -> &T {
121 &self.0
122 }
123}
124
125pub 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
138macro_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#[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 pub fn into_inner(self) -> T {
188 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#[derive(Clone, derive_more::Display)]
210pub struct MaybeSensitive<T>(either::Either<T, Sensitive<T>>);
211
212impl<T> MaybeSensitive<T> {
213 pub fn sensitive(t: T) -> Self {
215 Self(either::Either::Right(Sensitive::new(t)))
216 }
217
218 pub fn not_sensitive(t: T) -> Self {
220 Self(either::Either::Left(t))
221 }
222
223 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 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
268pub trait Redactable: std::fmt::Display + std::fmt::Debug {
289 fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
291 fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 self.display_redacted(f)
294 }
295 fn redacted(&self) -> Redacted<&Self> {
298 Redacted(self)
299 }
300 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#[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 pub fn new(value: T) -> Self {
336 Self(value)
337 }
338
339 pub fn unwrap(self) -> T {
341 self.0
342 }
343
344 pub fn as_ref(&self) -> Redacted<&T> {
346 Redacted(&self.0)
347 }
348
349 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#[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
394pub trait DisplayRedacted {
407 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
410 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
413
414 fn display_redacted(&self) -> impl std::fmt::Display + '_ {
422 DispRedacted(self)
423 }
424 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#[allow(clippy::exhaustive_structs)]
454#[derive(derive_more::AsRef)]
455pub struct DispRedacted<T: ?Sized>(pub T);
456
457#[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
485pub trait DebugRedacted {
498 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
501 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
504}
505
506#[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 #![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 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 #[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}