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)] use educe::Educe;
49#[cfg(feature = "serde")]
50use serde::{Deserialize, Serialize};
51
52mod err;
53mod flags;
54mod impls;
55
56pub use err::Error;
57pub use flags::{disable_safe_logging, enforce_safe_logging, with_safe_logging_suppressed, Guard};
58
59use std::ops::Deref;
60
61pub type Result<T> = std::result::Result<T, Error>;
63
64#[derive(Educe, Clone, Copy)]
73#[educe(
74 Default(bound),
75 Deref,
76 DerefMut,
77 Eq(bound),
78 Hash(bound),
79 Ord(bound),
80 PartialEq(bound),
81 PartialOrd(bound)
82)]
83#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
84#[cfg_attr(feature = "serde", serde(transparent))]
85pub struct Sensitive<T>(T);
86
87impl<T> Sensitive<T> {
88 pub fn new(value: T) -> Self {
90 Sensitive(value)
91 }
92
93 pub fn into_inner(self) -> T {
95 self.0
96 }
97
98 #[deprecated = "Use the new into_inner method instead"]
100 pub fn unwrap(sensitive: Sensitive<T>) -> T {
101 sensitive.into_inner()
102 }
103
104 pub fn as_ref(&self) -> Sensitive<&T> {
106 Sensitive(&self.0)
107 }
108
109 pub fn as_inner(&self) -> &T {
114 &self.0
115 }
116}
117
118pub fn sensitive<T>(value: T) -> Sensitive<T> {
122 Sensitive(value)
123}
124
125impl<T> From<T> for Sensitive<T> {
126 fn from(value: T) -> Self {
127 Sensitive::new(value)
128 }
129}
130
131macro_rules! impl_display_traits {
135 { $($trait:ident),* } => {
136 $(
137 impl<T: std::fmt::$trait> std::fmt::$trait for Sensitive<T> {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 if flags::unsafe_logging_enabled() {
140 std::fmt::$trait::fmt(&self.0, f)
141 } else {
142 write!(f, "[scrubbed]")
143 }
144 }
145 }
146
147 impl<T: std::fmt::$trait> std::fmt::$trait for BoxSensitive<T> {
148 #[inline]
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 std::fmt::$trait::fmt(&*self.0, f)
151 }
152 }
153 )*
154 }
155}
156
157#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
170pub struct BoxSensitive<T>(Box<Sensitive<T>>);
171
172impl<T> From<T> for BoxSensitive<T> {
173 fn from(t: T) -> BoxSensitive<T> {
174 BoxSensitive(Box::new(sensitive(t)))
175 }
176}
177
178impl<T> BoxSensitive<T> {
179 pub fn into_inner(self) -> T {
181 let unboxed = *self.0;
183 unboxed.into_inner()
184 }
185}
186
187impl<T> Deref for BoxSensitive<T> {
188 type Target = Sensitive<T>;
189
190 fn deref(&self) -> &Sensitive<T> {
191 &self.0
192 }
193}
194
195impl_display_traits! {
196 Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer
197}
198
199pub trait Redactable: std::fmt::Display + std::fmt::Debug {
220 fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
222 fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 self.display_redacted(f)
225 }
226 fn redacted(&self) -> Redacted<&Self> {
229 Redacted(self)
230 }
231 fn maybe_redacted(&self, redact: bool) -> MaybeRedacted<&Self> {
233 if redact {
234 MaybeRedacted(either::Either::Right(Redacted(self)))
235 } else {
236 MaybeRedacted(either::Either::Left(self))
237 }
238 }
239}
240
241impl<'a, T: Redactable + ?Sized> Redactable for &'a T {
242 fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 (*self).display_redacted(f)
244 }
245}
246
247#[derive(Educe, Clone, Copy)]
249#[educe(
250 Default(bound),
251 Deref,
252 DerefMut,
253 Eq(bound),
254 Hash(bound),
255 Ord(bound),
256 PartialEq(bound),
257 PartialOrd(bound)
258)]
259#[derive(derive_more::From)]
260#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
261#[cfg_attr(feature = "serde", serde(transparent))]
262pub struct Redacted<T: Redactable>(T);
263
264impl<T: Redactable> Redacted<T> {
265 pub fn new(value: T) -> Self {
267 Self(value)
268 }
269
270 pub fn unwrap(self) -> T {
272 self.0
273 }
274
275 pub fn as_ref(&self) -> Redacted<&T> {
277 Redacted(&self.0)
278 }
279
280 pub fn as_inner(&self) -> &T {
285 &self.0
286 }
287}
288
289impl<T: Redactable> std::fmt::Display for Redacted<T> {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 if flags::unsafe_logging_enabled() {
292 std::fmt::Display::fmt(&self.0, f)
293 } else {
294 self.0.display_redacted(f)
295 }
296 }
297}
298
299impl<T: Redactable> std::fmt::Debug for Redacted<T> {
300 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301 if flags::unsafe_logging_enabled() {
302 std::fmt::Debug::fmt(&self.0, f)
303 } else {
304 self.0.debug_redacted(f)
305 }
306 }
307}
308
309#[derive(Clone, derive_more::Display)]
313pub struct MaybeRedacted<T: Redactable>(either::Either<T, Redacted<T>>);
314
315impl<T: Redactable> std::fmt::Debug for MaybeRedacted<T> {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 use std::fmt::Debug;
318 match &self.0 {
319 either::Either::Left(v) => Debug::fmt(v, f),
320 either::Either::Right(v) => Debug::fmt(v, f),
321 }
322 }
323}
324
325#[cfg(test)]
326mod test {
327 #![allow(clippy::bool_assert_comparison)]
329 #![allow(clippy::clone_on_copy)]
330 #![allow(clippy::dbg_macro)]
331 #![allow(clippy::mixed_attributes_style)]
332 #![allow(clippy::print_stderr)]
333 #![allow(clippy::print_stdout)]
334 #![allow(clippy::single_char_pattern)]
335 #![allow(clippy::unwrap_used)]
336 #![allow(clippy::unchecked_duration_subtraction)]
337 #![allow(clippy::useless_vec)]
338 #![allow(clippy::needless_pass_by_value)]
339 use super::*;
341 use serial_test::serial;
342 use static_assertions::{assert_impl_all, assert_not_impl_any};
343
344 #[test]
345 fn clone_bound() {
346 #[derive(Clone)]
348 struct A;
349 struct B;
350
351 let _x = Sensitive(A).clone();
352 let _y = Sensitive(B);
353
354 assert_impl_all!(Sensitive<A> : Clone);
355 assert_not_impl_any!(Sensitive<B> : Clone);
356 }
357
358 #[test]
359 #[serial]
360 fn debug_vec() {
361 type SVec = Sensitive<Vec<u32>>;
362
363 let mut sv = SVec::default();
364 assert!(sv.is_empty());
365 sv.push(104);
366 sv.push(49);
367 assert_eq!(sv.len(), 2);
368
369 assert!(!flags::unsafe_logging_enabled());
370 assert_eq!(format!("{:?}", &sv), "[scrubbed]");
371 assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
372 assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
373 let normal = with_safe_logging_suppressed(|| format!("{:?}", &sv));
374 assert_eq!(normal, "[104, 49]");
375
376 let _g = disable_safe_logging().unwrap();
377 assert_eq!(format!("{:?}", &sv), "[104, 49]");
378
379 assert_eq!(sv, SVec::from(vec![104, 49]));
380 assert_eq!(sv.clone().into_inner(), vec![104, 49]);
381 assert_eq!(*sv, vec![104, 49]);
382 }
383
384 #[test]
385 #[serial]
386 #[allow(deprecated)]
387 fn deprecated() {
388 type SVec = Sensitive<Vec<u32>>;
389 let sv = Sensitive(vec![104, 49]);
390
391 assert_eq!(SVec::unwrap(sv), vec![104, 49]);
392 }
393
394 #[test]
395 #[serial]
396 fn display_various() {
397 let val = Sensitive::<u32>::new(0x0ed19a);
398
399 let closure1 = || {
400 format!(
401 "{:?}, {}, {:o}, {:x}, {:X}, {:b}",
402 &val, &val, &val, &val, &val, &val,
403 )
404 };
405 let s1 = closure1();
406 let s2 = with_safe_logging_suppressed(closure1);
407 assert_eq!(
408 s1,
409 "[scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed]"
410 );
411 assert_eq!(
412 s2,
413 "971162, 971162, 3550632, ed19a, ED19A, 11101101000110011010"
414 );
415
416 let n = 1.0E32;
417 let val = Sensitive::<f64>::new(n);
418 let expect = format!("{:?}, {}, {:e}, {:E}", n, n, n, n);
419 let closure2 = || format!("{:?}, {}, {:e}, {:E}", &val, &val, &val, &val);
420 let s1 = closure2();
421 let s2 = with_safe_logging_suppressed(closure2);
422 assert_eq!(s1, "[scrubbed], [scrubbed], [scrubbed], [scrubbed]");
423 assert_eq!(s2, expect);
424
425 let ptr: *const u8 = std::ptr::null();
426 let val = Sensitive::new(ptr);
427 let expect = format!("{:?}, {:p}", ptr, ptr);
428 let closure3 = || format!("{:?}, {:p}", val, val);
429 let s1 = closure3();
430 let s2 = with_safe_logging_suppressed(closure3);
431 assert_eq!(s1, "[scrubbed], [scrubbed]");
432 assert_eq!(s2, expect);
433 }
434
435 #[test]
436 #[serial]
437 fn box_sensitive() {
438 let b: BoxSensitive<_> = "hello world".into();
439
440 assert_eq!(b.clone().into_inner(), "hello world");
441
442 let closure = || format!("{} {:?}", b, b);
443 assert_eq!(closure(), "[scrubbed] [scrubbed]");
444 assert_eq!(
445 with_safe_logging_suppressed(closure),
446 r#"hello world "hello world""#
447 );
448
449 assert_eq!(b.len(), 11);
450 }
451
452 #[test]
453 #[serial]
454 fn test_redacted() {
455 let localhost = std::net::Ipv4Addr::LOCALHOST;
456 let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
457
458 assert_eq!(closure(), "127.x.x.x 127.x.x.x");
459 assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
460
461 let closure = |b| {
462 format!(
463 "{} {:?}",
464 localhost.maybe_redacted(b),
465 localhost.maybe_redacted(b)
466 )
467 };
468 assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
469 assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
470
471 assert_eq!(Redacted::new(localhost).unwrap(), localhost);
472 }
473}