use std::fmt;
#[cfg(feature = "json")]
use serde::Serialize;
use tracing::field::{DisplayValue, display};
use crate::{
policy::RedactionPolicy,
redaction::{
NotSensitive, NotSensitiveDebug, NotSensitiveDisplay, NotSensitiveJson, Redactable,
RedactedJson, RedactedJsonRef, RedactedOutput, RedactedOutputRef, SensitiveValue,
SensitiveWithPolicy, ToRedactedOutput,
},
};
pub trait TracingRedacted {}
pub trait TracingRedactedExt {
fn tracing_redacted(&self) -> DisplayValue<String>;
}
impl<T> TracingRedactedExt for T
where
T: ToRedactedOutput,
{
fn tracing_redacted(&self) -> DisplayValue<String> {
let output = self.to_redacted_output();
let text = match output {
RedactedOutput::Text(text) => text,
#[cfg(feature = "json")]
RedactedOutput::Json(json) => json.to_string(),
};
display(text)
}
}
impl TracingRedacted for RedactedOutput {}
#[cfg(feature = "json")]
impl TracingRedacted for RedactedJson {}
impl<T, P> TracingRedacted for SensitiveValue<T, P>
where
T: SensitiveWithPolicy<P>,
P: RedactionPolicy,
{
}
impl<T> TracingRedacted for NotSensitiveDisplay<T> where T: fmt::Display {}
impl<T> TracingRedacted for NotSensitiveDebug<T> where T: fmt::Debug {}
impl<T> TracingRedacted for NotSensitive<T> where T: TracingRedacted {}
impl<T> TracingRedacted for RedactedOutputRef<'_, T> where T: Redactable + Clone + fmt::Debug {}
#[cfg(feature = "json")]
impl<T> TracingRedacted for NotSensitiveJson<'_, T> where T: Serialize + ?Sized {}
#[cfg(feature = "json")]
impl<T> TracingRedacted for RedactedJsonRef<'_, T> where T: Redactable + Clone + Serialize {}
#[cfg(feature = "tracing-valuable")]
#[derive(Clone, Debug)]
pub struct RedactedValuable<T> {
redacted: T,
}
#[cfg(feature = "tracing-valuable")]
impl<T> RedactedValuable<T> {
pub fn new(redacted: T) -> Self {
Self { redacted }
}
pub fn inner(&self) -> &T {
&self.redacted
}
}
#[cfg(feature = "tracing-valuable")]
impl<T: valuable::Valuable> valuable::Valuable for RedactedValuable<T> {
fn as_value(&self) -> valuable::Value<'_> {
self.redacted.as_value()
}
fn visit(&self, visit: &mut dyn valuable::Visit) {
self.redacted.visit(visit);
}
}
#[cfg(feature = "tracing-valuable")]
impl<T> TracingRedacted for RedactedValuable<T> {}
#[cfg(feature = "tracing-valuable")]
pub trait TracingValuableExt {
type Redacted: valuable::Valuable;
fn tracing_redacted_valuable(&self) -> RedactedValuable<Self::Redacted>;
}
#[cfg(feature = "tracing-valuable")]
impl<T> TracingValuableExt for T
where
T: Redactable + Clone + valuable::Valuable,
{
type Redacted = T;
fn tracing_redacted_valuable(&self) -> RedactedValuable<Self::Redacted> {
RedactedValuable::new(self.clone().redact())
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockRedactable {
value: String,
}
impl ToRedactedOutput for MockRedactable {
fn to_redacted_output(&self) -> RedactedOutput {
RedactedOutput::Text(format!("[REDACTED:{}]", self.value.len()))
}
}
#[test]
fn tracing_redacted_converts_to_display_string() {
let mock = MockRedactable {
value: "secret".into(),
};
let display_value = mock.tracing_redacted();
let _ = format!("{display_value:?}");
}
#[test]
fn tracing_redacted_handles_empty_value() {
let mock = MockRedactable {
value: String::new(),
};
let display_value = mock.tracing_redacted();
let _ = format!("{display_value:?}");
}
#[cfg(feature = "tracing-valuable")]
mod valuable_tests {
use super::*;
use crate::redaction::{RedactableMapper, RedactableWithMapper};
#[derive(Clone, Debug, valuable::Valuable)]
struct MockValuableRedactable {
username: String,
password: String,
}
impl RedactableWithMapper for MockValuableRedactable {
fn redact_with<M: RedactableMapper>(self, _mapper: &M) -> Self {
Self {
username: self.username,
password: "[REDACTED]".to_string(),
}
}
}
#[test]
fn tracing_redacted_valuable_creates_wrapper() {
let mock = MockValuableRedactable {
username: "alice".into(),
password: "secret".into(),
};
let valuable = mock.tracing_redacted_valuable();
assert_eq!(valuable.inner().username, "alice");
assert_eq!(valuable.inner().password, "[REDACTED]");
}
#[test]
fn redacted_valuable_implements_valuable() {
let mock = MockValuableRedactable {
username: "alice".into(),
password: "secret".into(),
};
let valuable_wrapper = mock.tracing_redacted_valuable();
let _ = valuable::Valuable::as_value(&valuable_wrapper);
}
}
}