use core::fmt::{Debug, Display, Formatter};
use data_privacy::IntoDataClass;
use crate::{Classified, DataClass, RedactedDebug, RedactedDisplay, RedactionEngine};
const STACK_BUFFER_SIZE: usize = 128;
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd)]
pub struct Sensitive<T> {
value: T,
data_class: DataClass,
}
impl<T> Debug for Sensitive<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Sensitive")
.field("value", &"***")
.field("data_class", &self.data_class)
.finish()
}
}
impl<T> Sensitive<T> {
pub fn new(value: T, data_class: impl IntoDataClass) -> Self {
Self {
value,
data_class: data_class.into_data_class(),
}
}
#[must_use]
pub fn reclassify(self, data_class: impl IntoDataClass) -> Self {
Self {
data_class: data_class.into_data_class(),
..self
}
}
#[must_use]
pub fn declassify_into(self) -> T {
self.value
}
#[must_use]
pub fn declassify_ref(&self) -> &T {
&self.value
}
#[must_use]
pub fn declassify_mut(&mut self) -> &mut T {
&mut self.value
}
}
impl<T> Classified for Sensitive<T> {
fn data_class(&self) -> &DataClass {
&self.data_class
}
}
impl<T> RedactedDebug for Sensitive<T>
where
T: Debug,
{
#[expect(
clippy::cast_possible_truncation,
reason = "Converting from u64 to usize, value is known to be <= STACK_BUFFER_SIZE"
)]
fn fmt(&self, engine: &RedactionEngine, f: &mut Formatter) -> std::fmt::Result {
let v = &self.value;
let mut local_buf = [0u8; STACK_BUFFER_SIZE];
let amount = {
let mut cursor = std::io::Cursor::new(&mut local_buf[..]);
if std::io::Write::write_fmt(&mut cursor, format_args!("{v:?}")).is_ok() {
cursor.position() as usize
} else {
local_buf.len() + 1 }
};
if amount <= local_buf.len() {
let s = unsafe { core::str::from_utf8_unchecked(&local_buf[..amount]) };
engine.redact(self.data_class(), s, f)
} else {
engine.redact(self.data_class(), format!("{v:?}"), f)
}
}
}
impl<T> RedactedDisplay for Sensitive<T>
where
T: Display,
{
#[expect(
clippy::cast_possible_truncation,
reason = "Converting from u64 to usize, value is known to be <= STACK_BUFFER_SIZE"
)]
fn fmt(&self, engine: &RedactionEngine, f: &mut Formatter) -> std::fmt::Result {
let v = &self.value;
let mut local_buf = [0u8; STACK_BUFFER_SIZE];
let amount = {
let mut cursor = std::io::Cursor::new(&mut local_buf[..]);
if std::io::Write::write_fmt(&mut cursor, format_args!("{v}")).is_ok() {
cursor.position() as usize
} else {
local_buf.len() + 1 }
};
if amount <= local_buf.len() {
let s = unsafe { core::str::from_utf8_unchecked(&local_buf[..amount]) };
engine.redact(self.data_class(), s, f)
} else {
engine.redact(self.data_class(), format!("{v}"), f)
}
}
}