1#![cfg_attr(docsrs, feature(doc_auto_cfg, 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_duration_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)] use educe::Educe;
50#[cfg(feature = "serde")]
51use serde::{Deserialize, Serialize};
52
53mod err;
54mod flags;
55mod impls;
56
57pub use err::Error;
58pub use flags::{Guard, disable_safe_logging, enforce_safe_logging, with_safe_logging_suppressed};
59
60use std::ops::Deref;
61
62pub type Result<T> = std::result::Result<T, Error>;
64
65#[doc(hidden)]
67pub use flags::unsafe_logging_enabled;
68
69#[derive(Educe, Clone, Copy)]
78#[educe(
79 Default(bound),
80 Deref,
81 DerefMut,
82 Eq(bound),
83 Hash(bound),
84 Ord(bound),
85 PartialEq(bound),
86 PartialOrd(bound)
87)]
88#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
89#[cfg_attr(feature = "serde", serde(transparent))]
90pub struct Sensitive<T>(T);
91
92impl<T> Sensitive<T> {
93 pub fn new(value: T) -> Self {
95 Sensitive(value)
96 }
97
98 pub fn into_inner(self) -> T {
100 self.0
101 }
102
103 #[deprecated = "Use the new into_inner method instead"]
105 pub fn unwrap(sensitive: Sensitive<T>) -> T {
106 sensitive.into_inner()
107 }
108
109 pub fn as_ref(&self) -> Sensitive<&T> {
111 Sensitive(&self.0)
112 }
113
114 pub fn as_inner(&self) -> &T {
119 &self.0
120 }
121}
122
123pub fn sensitive<T>(value: T) -> Sensitive<T> {
127 Sensitive(value)
128}
129
130impl<T> From<T> for Sensitive<T> {
131 fn from(value: T) -> Self {
132 Sensitive::new(value)
133 }
134}
135
136macro_rules! impl_display_traits {
140 { $($trait:ident),* } => {
141 $(
142 impl<T: std::fmt::$trait> std::fmt::$trait for Sensitive<T> {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 if flags::unsafe_logging_enabled() {
145 std::fmt::$trait::fmt(&self.0, f)
146 } else {
147 write!(f, "[scrubbed]")
148 }
149 }
150 }
151
152 impl<T: std::fmt::$trait> std::fmt::$trait for BoxSensitive<T> {
153 #[inline]
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 std::fmt::$trait::fmt(&*self.0, f)
156 }
157 }
158 )*
159 }
160}
161
162#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
175pub struct BoxSensitive<T>(Box<Sensitive<T>>);
176
177impl<T> From<T> for BoxSensitive<T> {
178 fn from(t: T) -> BoxSensitive<T> {
179 BoxSensitive(Box::new(sensitive(t)))
180 }
181}
182
183impl<T> BoxSensitive<T> {
184 pub fn into_inner(self) -> T {
186 let unboxed = *self.0;
188 unboxed.into_inner()
189 }
190}
191
192impl<T> Deref for BoxSensitive<T> {
193 type Target = Sensitive<T>;
194
195 fn deref(&self) -> &Sensitive<T> {
196 &self.0
197 }
198}
199
200impl_display_traits! {
201 Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer
202}
203
204pub trait Redactable: std::fmt::Display + std::fmt::Debug {
225 fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
227 fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 self.display_redacted(f)
230 }
231 fn redacted(&self) -> Redacted<&Self> {
234 Redacted(self)
235 }
236 fn maybe_redacted(&self, redact: bool) -> MaybeRedacted<&Self> {
238 if redact {
239 MaybeRedacted(either::Either::Right(Redacted(self)))
240 } else {
241 MaybeRedacted(either::Either::Left(self))
242 }
243 }
244}
245
246impl<'a, T: Redactable + ?Sized> Redactable for &'a T {
247 fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248 (*self).display_redacted(f)
249 }
250}
251
252#[derive(Educe, Clone, Copy)]
254#[educe(
255 Default(bound),
256 Deref,
257 DerefMut,
258 Eq(bound),
259 Hash(bound),
260 Ord(bound),
261 PartialEq(bound),
262 PartialOrd(bound)
263)]
264#[derive(derive_more::From)]
265#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
266#[cfg_attr(feature = "serde", serde(transparent))]
267pub struct Redacted<T: Redactable>(T);
268
269impl<T: Redactable> Redacted<T> {
270 pub fn new(value: T) -> Self {
272 Self(value)
273 }
274
275 pub fn unwrap(self) -> T {
277 self.0
278 }
279
280 pub fn as_ref(&self) -> Redacted<&T> {
282 Redacted(&self.0)
283 }
284
285 pub fn as_inner(&self) -> &T {
290 &self.0
291 }
292}
293
294impl<T: Redactable> std::fmt::Display for Redacted<T> {
295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296 if flags::unsafe_logging_enabled() {
297 std::fmt::Display::fmt(&self.0, f)
298 } else {
299 self.0.display_redacted(f)
300 }
301 }
302}
303
304impl<T: Redactable> std::fmt::Debug for Redacted<T> {
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 if flags::unsafe_logging_enabled() {
307 std::fmt::Debug::fmt(&self.0, f)
308 } else {
309 self.0.debug_redacted(f)
310 }
311 }
312}
313
314#[derive(Clone, derive_more::Display)]
318pub struct MaybeRedacted<T: Redactable>(either::Either<T, Redacted<T>>);
319
320impl<T: Redactable> std::fmt::Debug for MaybeRedacted<T> {
321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322 use std::fmt::Debug;
323 match &self.0 {
324 either::Either::Left(v) => Debug::fmt(v, f),
325 either::Either::Right(v) => Debug::fmt(v, f),
326 }
327 }
328}
329
330pub trait DisplayRedacted {
343 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
346 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
349
350 fn display_redacted(&self) -> impl std::fmt::Display + '_ {
358 DispRedacted(self)
359 }
360 fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
362 DispUnredacted(self)
363 }
364}
365
366impl<'a, T> DisplayRedacted for &'a T
367where
368 T: DisplayRedacted + ?Sized,
369{
370 fn display_redacted(&self) -> impl std::fmt::Display + '_ {
371 (*self).display_redacted()
372 }
373 fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
374 (*self).display_unredacted()
375 }
376 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377 (*self).fmt_redacted(f)
378 }
379 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 (*self).fmt_unredacted(f)
381 }
382}
383
384#[allow(clippy::exhaustive_structs)]
390#[derive(derive_more::AsRef)]
391pub struct DispRedacted<T: ?Sized>(pub T);
392
393#[allow(clippy::exhaustive_structs)]
396#[derive(derive_more::AsRef)]
397pub struct DispUnredacted<T: ?Sized>(pub T);
398
399impl<T> std::fmt::Display for DispRedacted<T>
400where
401 T: DisplayRedacted + ?Sized,
402{
403 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404 if crate::flags::unsafe_logging_enabled() {
405 self.0.fmt_unredacted(f)
406 } else {
407 self.0.fmt_redacted(f)
408 }
409 }
410}
411
412impl<T> std::fmt::Display for DispUnredacted<T>
413where
414 T: DisplayRedacted + ?Sized,
415{
416 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417 self.0.fmt_unredacted(f)
418 }
419}
420
421pub trait DebugRedacted {
434 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
437 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
440}
441
442#[macro_export]
450macro_rules! derive_redacted_debug {
451 {$t:ty} => {
452 impl std::fmt::Debug for $t {
453 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454 if $crate::unsafe_logging_enabled() {
455 $crate::DebugRedacted::fmt_unredacted(self, f)
456 } else {
457 $crate::DebugRedacted::fmt_redacted(self, f)
458 }
459 }
460 }
461}}
462
463#[cfg(test)]
464mod test {
465 #![allow(clippy::bool_assert_comparison)]
467 #![allow(clippy::clone_on_copy)]
468 #![allow(clippy::dbg_macro)]
469 #![allow(clippy::mixed_attributes_style)]
470 #![allow(clippy::print_stderr)]
471 #![allow(clippy::print_stdout)]
472 #![allow(clippy::single_char_pattern)]
473 #![allow(clippy::unwrap_used)]
474 #![allow(clippy::unchecked_duration_subtraction)]
475 #![allow(clippy::useless_vec)]
476 #![allow(clippy::needless_pass_by_value)]
477 use super::*;
480 use serial_test::serial;
481 use static_assertions::{assert_impl_all, assert_not_impl_any};
482
483 #[test]
484 fn clone_bound() {
485 #[derive(Clone)]
487 struct A;
488 struct B;
489
490 let _x = Sensitive(A).clone();
491 let _y = Sensitive(B);
492
493 assert_impl_all!(Sensitive<A> : Clone);
494 assert_not_impl_any!(Sensitive<B> : Clone);
495 }
496
497 #[test]
498 #[serial]
499 fn debug_vec() {
500 type SVec = Sensitive<Vec<u32>>;
501
502 let mut sv = SVec::default();
503 assert!(sv.is_empty());
504 sv.push(104);
505 sv.push(49);
506 assert_eq!(sv.len(), 2);
507
508 assert!(!flags::unsafe_logging_enabled());
509 assert_eq!(format!("{:?}", &sv), "[scrubbed]");
510 assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
511 assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
512 let normal = with_safe_logging_suppressed(|| format!("{:?}", &sv));
513 assert_eq!(normal, "[104, 49]");
514
515 let _g = disable_safe_logging().unwrap();
516 assert_eq!(format!("{:?}", &sv), "[104, 49]");
517
518 assert_eq!(sv, SVec::from(vec![104, 49]));
519 assert_eq!(sv.clone().into_inner(), vec![104, 49]);
520 assert_eq!(*sv, vec![104, 49]);
521 }
522
523 #[test]
524 #[serial]
525 #[allow(deprecated)]
526 fn deprecated() {
527 type SVec = Sensitive<Vec<u32>>;
528 let sv = Sensitive(vec![104, 49]);
529
530 assert_eq!(SVec::unwrap(sv), vec![104, 49]);
531 }
532
533 #[test]
534 #[serial]
535 fn display_various() {
536 let val = Sensitive::<u32>::new(0x0ed19a);
537
538 let closure1 = || {
539 format!(
540 "{:?}, {}, {:o}, {:x}, {:X}, {:b}",
541 &val, &val, &val, &val, &val, &val,
542 )
543 };
544 let s1 = closure1();
545 let s2 = with_safe_logging_suppressed(closure1);
546 assert_eq!(
547 s1,
548 "[scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed]"
549 );
550 assert_eq!(
551 s2,
552 "971162, 971162, 3550632, ed19a, ED19A, 11101101000110011010"
553 );
554
555 let n = 1.0E32;
556 let val = Sensitive::<f64>::new(n);
557 let expect = format!("{:?}, {}, {:e}, {:E}", n, n, n, n);
558 let closure2 = || format!("{:?}, {}, {:e}, {:E}", &val, &val, &val, &val);
559 let s1 = closure2();
560 let s2 = with_safe_logging_suppressed(closure2);
561 assert_eq!(s1, "[scrubbed], [scrubbed], [scrubbed], [scrubbed]");
562 assert_eq!(s2, expect);
563
564 let ptr: *const u8 = std::ptr::null();
565 let val = Sensitive::new(ptr);
566 let expect = format!("{:?}, {:p}", ptr, ptr);
567 let closure3 = || format!("{:?}, {:p}", val, val);
568 let s1 = closure3();
569 let s2 = with_safe_logging_suppressed(closure3);
570 assert_eq!(s1, "[scrubbed], [scrubbed]");
571 assert_eq!(s2, expect);
572 }
573
574 #[test]
575 #[serial]
576 fn box_sensitive() {
577 let b: BoxSensitive<_> = "hello world".into();
578
579 assert_eq!(b.clone().into_inner(), "hello world");
580
581 let closure = || format!("{} {:?}", b, b);
582 assert_eq!(closure(), "[scrubbed] [scrubbed]");
583 assert_eq!(
584 with_safe_logging_suppressed(closure),
585 r#"hello world "hello world""#
586 );
587
588 assert_eq!(b.len(), 11);
589 }
590
591 #[test]
592 #[serial]
593 fn test_redacted() {
594 let localhost = std::net::Ipv4Addr::LOCALHOST;
595 let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
596
597 assert_eq!(closure(), "127.x.x.x 127.x.x.x");
598 assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
599
600 let closure = |b| {
601 format!(
602 "{} {:?}",
603 localhost.maybe_redacted(b),
604 localhost.maybe_redacted(b)
605 )
606 };
607 assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
608 assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
609
610 assert_eq!(Redacted::new(localhost).unwrap(), localhost);
611 }
612
613 struct RedactionCheck(u32);
614 impl DisplayRedacted for RedactionCheck {
615 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
616 write!(f, "{}", self.0)
617 }
618
619 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
620 let v = self.0.to_string();
621 write!(f, "{}xxx", v.chars().next().unwrap())
622 }
623 }
624 impl DebugRedacted for RedactionCheck {
625 fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
626 write!(f, "Num({})", self.display_redacted())
627 }
628
629 fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630 write!(f, "Num({})", self.display_unredacted())
631 }
632 }
633 derive_redacted_debug!(RedactionCheck);
634
635 #[test]
636 #[serial]
637 fn display_redacted() {
638 let n = RedactionCheck(999);
639 assert_eq!(&n.display_unredacted().to_string(), "999");
640 assert_eq!(&n.display_redacted().to_string(), "9xxx");
641 with_safe_logging_suppressed(|| assert_eq!(&n.display_redacted().to_string(), "999"));
642
643 assert_eq!(DispRedacted(&n).to_string(), "9xxx");
644 assert_eq!(DispUnredacted(&n).to_string(), "999");
645
646 assert_eq!(&format!("{n:?}"), "Num(9xxx)");
647 with_safe_logging_suppressed(|| assert_eq!(&format!("{n:?}"), "Num(999)"));
648 }
649}