#![warn(clippy::all, clippy::pedantic, rust_2018_idioms, missing_docs)]
#![allow(clippy::expl_impl_clone_on_copy)]
#![doc = include_str!("../README.md")]
use std::{
cmp::Ordering,
fmt::{Debug, Display},
hash::Hash,
marker::PhantomData,
ops::{Deref, DerefMut},
};
#[cfg(feature = "serde_support")]
use serde::Deserialize;
#[cfg(feature = "serde_support")]
use serde_bytes::Serialize;
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
pub mod formatter;
pub use formatter::{Formatter, RedactContents};
pub type FullyRedacted<T> = Redacted<T, RedactContents>;
pub struct Redacted<T, F: Formatter> {
item: T,
_formatter: PhantomData<F>,
}
impl<T, F: Formatter> Redacted<T, F> {
pub fn new(item: T) -> Self {
Self {
item,
_formatter: PhantomData,
}
}
pub fn into_inner(self) -> T {
self.item
}
}
impl<T: AsRef<[u8]>, F: Formatter> Debug for Redacted<T, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
F::debug_fmt(self.item.as_ref(), std::any::type_name::<T>(), f)
}
}
impl<T: AsRef<[u8]>, F: Formatter> Display for Redacted<T, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
F::display_fmt(self.item.as_ref(), std::any::type_name::<T>(), f)
}
}
impl<T, F: Formatter> Deref for Redacted<T, F> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.item
}
}
impl<T, F: Formatter> DerefMut for Redacted<T, F> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.item
}
}
impl<T, F: Formatter> AsRef<T> for Redacted<T, F> {
fn as_ref(&self) -> &T {
&self.item
}
}
impl<T, F: Formatter> AsMut<T> for Redacted<T, F> {
fn as_mut(&mut self) -> &mut T {
&mut self.item
}
}
impl<T, F: Formatter> From<T> for Redacted<T, F> {
fn from(item: T) -> Self {
Self::new(item)
}
}
impl<T: Default, F: Formatter> Default for Redacted<T, F> {
fn default() -> Self {
Self {
item: Default::default(),
_formatter: PhantomData,
}
}
}
impl<T: Clone, F: Formatter> Clone for Redacted<T, F> {
fn clone(&self) -> Self {
Self {
item: self.item.clone(),
_formatter: PhantomData,
}
}
}
impl<T: Copy, F: Formatter> Copy for Redacted<T, F> {}
impl<T: AsRef<[u8]>, F: Formatter> AsRef<[u8]> for Redacted<T, F> {
fn as_ref(&self) -> &[u8] {
self.item.as_ref()
}
}
impl<T: AsMut<[u8]>, F: Formatter> AsMut<[u8]> for Redacted<T, F> {
fn as_mut(&mut self) -> &mut [u8] {
self.item.as_mut()
}
}
impl<T: PartialEq, F: Formatter> PartialEq for Redacted<T, F> {
fn eq(&self, other: &Self) -> bool {
self.item == other.item
}
}
impl<T: Eq, F: Formatter> Eq for Redacted<T, F> {}
impl<T: Hash, F: Formatter> Hash for Redacted<T, F> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.item.hash(state);
}
}
impl<T: PartialOrd, F: Formatter> PartialOrd for Redacted<T, F> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.item.partial_cmp(&other.item)
}
}
impl<T: Ord, F: Formatter> Ord for Redacted<T, F> {
fn cmp(&self, other: &Self) -> Ordering {
self.item.cmp(&other.item)
}
}
#[cfg(feature = "serde_support")]
impl<T: Serialize, F: Formatter> Serialize for Redacted<T, F> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.item.serialize(serializer)
}
}
#[cfg(feature = "serde_support")]
impl<'de, T: Deserialize<'de>, F: Formatter> Deserialize<'de> for Redacted<T, F> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let item: T = Deserialize::deserialize(deserializer)?;
Ok(Redacted {
item,
_formatter: PhantomData,
})
}
}
#[cfg(feature = "zeroize")]
impl<T: Zeroize, F: Formatter> Zeroize for Redacted<T, F> {
fn zeroize(&mut self) {
self.item.zeroize();
}
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use super::*;
#[test]
fn smoke_vec() {
let test = Redacted::<_, RedactContents>::new(vec![0_u8; 32]);
let debug_sample = format!("{:?}", test);
let display_sample = format!("{}", test);
assert_eq!(&debug_sample, "[32 BYTES REDACTED]");
assert_eq!(&display_sample, "[32 BYTES REDACTED]");
let test = Redacted::<_, RedactContents>::new(vec![0_u8; 64]);
let debug_sample = format!("{:?}", test);
let display_sample = format!("{}", test);
assert_eq!(&debug_sample, "[64 BYTES REDACTED]");
assert_eq!(&display_sample, "[64 BYTES REDACTED]");
}
#[test]
fn smoke_array() {
let test = Redacted::<_, RedactContents>::new([0_u8; 32]);
let debug_sample = format!("{:?}", test);
let display_sample = format!("{}", test);
assert_eq!(&debug_sample, "[32 BYTES REDACTED]");
assert_eq!(&display_sample, "[32 BYTES REDACTED]");
let test = Redacted::<_, RedactContents>::new([0_u8; 64]);
let debug_sample = format!("{:?}", test);
let display_sample = format!("{}", test);
assert_eq!(&debug_sample, "[64 BYTES REDACTED]");
assert_eq!(&display_sample, "[64 BYTES REDACTED]");
}
#[test]
fn smoke_slice() {
let array = [0_u8; 32];
let test = Redacted::<&[u8], RedactContents>::new(&array);
let debug_sample = format!("{:?}", test);
let display_sample = format!("{}", test);
assert_eq!(&debug_sample, "[32 BYTES REDACTED]");
assert_eq!(&display_sample, "[32 BYTES REDACTED]");
let array = [0_u8; 64];
let test = Redacted::<&[u8], RedactContents>::new(&array);
let debug_sample = format!("{:?}", test);
let display_sample = format!("{}", test);
assert_eq!(&debug_sample, "[64 BYTES REDACTED]");
assert_eq!(&display_sample, "[64 BYTES REDACTED]");
}
#[test]
fn smoke_cow() {
let cow: Cow<'_, [u8]> = vec![0_u8; 32].into();
let test = Redacted::<_, RedactContents>::new(cow);
let debug_sample = format!("{:?}", test);
let display_sample = format!("{}", test);
assert_eq!(&debug_sample, "[32 BYTES REDACTED]");
assert_eq!(&display_sample, "[32 BYTES REDACTED]");
let cow: Cow<'_, [u8]> = vec![0_u8; 64].into();
let test = Redacted::<_, RedactContents>::new(cow);
let debug_sample = format!("{:?}", test);
let display_sample = format!("{}", test);
assert_eq!(&debug_sample, "[64 BYTES REDACTED]");
assert_eq!(&display_sample, "[64 BYTES REDACTED]");
}
}