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