use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::time::Duration;
use bytes::Bytes;
pub trait Secret {
fn describe(&self) -> &'static str;
}
impl<T> Secret for Option<T> {
fn describe(&self) -> &'static str {
if self.is_some() {
"Some(<redacted>)"
} else {
"None"
}
}
}
macro_rules! always_redacted {
($($ty:ty),+ $(,)?) => {
$( impl Secret for $ty {
fn describe(&self) -> &'static str { "<redacted>" }
} )+
};
}
always_redacted!(Vec<u8>, Bytes, String);
pub struct Redacted<'a, T: Secret + ?Sized>(pub &'a T);
impl<T: Secret + ?Sized> std::fmt::Debug for Redacted<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.describe())
}
}
#[macro_export]
macro_rules! redacted_debug {
($ty:ident { $($field:ident),* $(,)? } secret { $($secret:ident),+ $(,)? }) => {
impl ::core::fmt::Debug for $ty {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_struct(stringify!($ty))
$( .field(stringify!($field), &self.$field) )*
$( .field(stringify!($secret), &$crate::support::Redacted(&self.$secret)) )+
.finish()
}
}
};
}
pub fn guard_callback<R>(what: &'static str, f: impl FnOnce() -> R) -> Option<R> {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(value) => Some(value),
Err(_) => {
tracing::error!(callback = what, "foreign callback panicked; ignoring");
None
}
}
}
pub trait LockExt<T> {
fn lock_safe(&self) -> MutexGuard<'_, T>;
}
impl<T> LockExt<T> for Mutex<T> {
fn lock_safe(&self) -> MutexGuard<'_, T> {
self.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
pub trait RwLockExt<T> {
fn read_safe(&self) -> RwLockReadGuard<'_, T>;
fn write_safe(&self) -> RwLockWriteGuard<'_, T>;
}
impl<T> RwLockExt<T> for RwLock<T> {
fn read_safe(&self) -> RwLockReadGuard<'_, T> {
self.read().unwrap_or_else(|poisoned| poisoned.into_inner())
}
fn write_safe(&self) -> RwLockWriteGuard<'_, T> {
self.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
pub fn secs_or(value: u32, default_secs: u32) -> Duration {
Duration::from_secs(u64::from(if value == 0 { default_secs } else { value }))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn secs_or_uses_default_for_zero() {
assert_eq!(secs_or(0, 15), Duration::from_secs(15));
assert_eq!(secs_or(3, 15), Duration::from_secs(3));
}
#[test]
fn guard_callback_swallows_panics() {
assert_eq!(guard_callback("ok", || 7), Some(7));
assert_eq!(guard_callback("boom", || -> u8 { panic!("boom") }), None);
}
#[test]
fn poisoned_mutex_is_still_usable() {
let m = std::sync::Arc::new(Mutex::new(1));
let m2 = m.clone();
let _ = std::thread::spawn(move || {
let _g = m2.lock().unwrap();
panic!("poison it");
})
.join();
assert!(m.is_poisoned());
assert_eq!(*m.lock_safe(), 1);
}
#[test]
fn option_secret_keeps_presence_signal() {
assert_eq!(Some(vec![1u8]).describe(), "Some(<redacted>)");
assert_eq!(None::<Vec<u8>>.describe(), "None");
}
}