use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Default, Clone, PartialEq, Eq, Hash)]
pub struct Redacted<T: Zeroize>(pub T);
impl<T: Zeroize> Zeroize for Redacted<T> {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl<T: Zeroize> ZeroizeOnDrop for Redacted<T> {}
impl<T: Zeroize> Drop for Redacted<T> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<T: Zeroize> fmt::Debug for Redacted<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[REDACTED]")
}
}
impl<T: Zeroize> fmt::Display for Redacted<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[REDACTED]")
}
}
impl<T: Zeroize> std::ops::Deref for Redacted<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Zeroize + Serialize> Serialize for Redacted<T> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
self.0.serialize(s)
}
}
impl<'de, T: Zeroize + Deserialize<'de>> Deserialize<'de> for Redacted<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
T::deserialize(deserializer).map(Self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_and_display_static_redacted() {
let secret = Redacted("my_secret_token".to_string());
assert_eq!(format!("{secret:?}"), "[REDACTED]");
assert_eq!(format!("{secret}"), "[REDACTED]");
}
#[test]
fn drop_erases_secret_before_releasing_storage() {
struct Secret<'a>(&'a mut [u8; 8]);
impl Zeroize for Secret<'_> {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
let mut bytes = [42; 8];
drop(Redacted(Secret(&mut bytes)));
assert_eq!(bytes, [0; 8]);
}
}