use std::{
borrow::Cow,
cell::{Cell, RefCell},
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
hash::{BuildHasher, Hash},
rc::Rc,
sync::Arc,
};
use super::traits::{RedactableWithMapper, SensitiveWithPolicy};
use crate::policy::RedactionPolicy;
#[doc(hidden)]
pub trait RedactableMapper {
fn map_sensitive<V, P>(&self, value: V) -> V
where
V: SensitiveWithPolicy<P>,
P: RedactionPolicy;
fn map_scalar<S>(&self, value: S) -> S
where
S: Default + ScalarRedaction;
}
#[derive(Clone, Copy, Debug)]
struct PolicyMapper;
impl RedactableMapper for PolicyMapper {
fn map_sensitive<V, P>(&self, value: V) -> V
where
V: SensitiveWithPolicy<P>,
P: RedactionPolicy,
{
value.redact_with_policy(&P::policy())
}
fn map_scalar<S>(&self, value: S) -> S
where
S: Default + ScalarRedaction,
{
ScalarRedaction::redact(value)
}
}
#[doc(hidden)]
pub trait ScalarRedaction: Default {
#[must_use]
fn redact(self) -> Self {
Self::default()
}
}
impl ScalarRedaction for i8 {}
impl ScalarRedaction for i16 {}
impl ScalarRedaction for i32 {}
impl ScalarRedaction for i64 {}
impl ScalarRedaction for i128 {}
impl ScalarRedaction for isize {}
impl ScalarRedaction for u8 {}
impl ScalarRedaction for u16 {}
impl ScalarRedaction for u32 {}
impl ScalarRedaction for u64 {}
impl ScalarRedaction for u128 {}
impl ScalarRedaction for usize {}
impl ScalarRedaction for f32 {}
impl ScalarRedaction for f64 {}
impl ScalarRedaction for bool {}
impl ScalarRedaction for char {
fn redact(self) -> Self {
'*'
}
}
pub fn redact<W>(value: W) -> W
where
W: RedactableWithMapper,
{
let mapper = PolicyMapper;
value.redact_with(&mapper)
}
pub fn apply_policy<P, V>(value: V) -> V
where
P: RedactionPolicy,
V: PolicyApplicable,
{
let mapper = PolicyMapper;
value.apply_policy::<P, _>(&mapper)
}
pub fn apply_policy_ref<P, V>(value: &V) -> V::Output
where
P: RedactionPolicy,
V: PolicyApplicableRef,
{
let mapper = PolicyMapper;
value.apply_policy_ref::<P, _>(&mapper)
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot have a policy applied",
label = "this type doesn't support redaction policies",
note = "policies work on String, Cow<str>, and containers of these types",
note = "for custom string-like types, implement `SensitiveWithPolicy<YourPolicy>`"
)]
#[doc(hidden)]
pub trait PolicyApplicable {
#[must_use]
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper;
}
#[doc(hidden)]
pub trait PolicyApplicableRef {
type Output;
#[must_use]
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper;
}
impl PolicyApplicable for String {
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
mapper.map_sensitive::<_, P>(self)
}
}
impl PolicyApplicable for Cow<'_, str> {
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
mapper.map_sensitive::<_, P>(self)
}
}
impl PolicyApplicableRef for String {
type Output = String;
fn apply_policy_ref<P, M>(&self, _mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
let policy = P::policy();
policy.apply_to(self.as_str())
}
}
impl PolicyApplicableRef for Cow<'_, str> {
type Output = Cow<'static, str>;
fn apply_policy_ref<P, M>(&self, _mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
let policy = P::policy();
Cow::Owned(policy.apply_to(self.as_ref()))
}
}
impl PolicyApplicableRef for &str {
type Output = String;
fn apply_policy_ref<P, M>(&self, _mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
let policy = P::policy();
policy.apply_to(self)
}
}
impl<T: PolicyApplicable> PolicyApplicable for Option<T> {
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
self.map(|v| v.apply_policy::<P, M>(mapper))
}
}
impl<T> PolicyApplicableRef for Option<T>
where
T: PolicyApplicableRef,
{
type Output = Option<T::Output>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
self.as_ref().map(|v| v.apply_policy_ref::<P, M>(mapper))
}
}
impl<T> PolicyApplicableRef for Vec<T>
where
T: PolicyApplicableRef,
{
type Output = Vec<T::Output>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
self.iter()
.map(|v| v.apply_policy_ref::<P, M>(mapper))
.collect()
}
}
impl<T> PolicyApplicableRef for Box<T>
where
T: PolicyApplicableRef,
{
type Output = Box<T::Output>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
Box::new((**self).apply_policy_ref::<P, M>(mapper))
}
}
impl<T> PolicyApplicableRef for Arc<T>
where
T: PolicyApplicableRef,
{
type Output = Arc<T::Output>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
Arc::new((**self).apply_policy_ref::<P, M>(mapper))
}
}
impl<T> PolicyApplicableRef for Rc<T>
where
T: PolicyApplicableRef,
{
type Output = Rc<T::Output>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
Rc::new((**self).apply_policy_ref::<P, M>(mapper))
}
}
impl<T> PolicyApplicableRef for RefCell<T>
where
T: PolicyApplicableRef,
{
type Output = RefCell<T::Output>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
RefCell::new(self.borrow().apply_policy_ref::<P, M>(mapper))
}
}
impl<T> PolicyApplicableRef for Cell<T>
where
T: PolicyApplicableRef + Copy,
T::Output: Copy,
{
type Output = Cell<T::Output>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
let value = self.get();
Cell::new(value.apply_policy_ref::<P, M>(mapper))
}
}
impl<T, E> PolicyApplicableRef for Result<T, E>
where
T: PolicyApplicableRef,
E: PolicyApplicableRef,
{
type Output = Result<T::Output, E::Output>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
match self {
Ok(v) => Ok(v.apply_policy_ref::<P, M>(mapper)),
Err(e) => Err(e.apply_policy_ref::<P, M>(mapper)),
}
}
}
impl<T: PolicyApplicable> PolicyApplicable for Vec<T> {
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
self.into_iter()
.map(|v| v.apply_policy::<P, M>(mapper))
.collect()
}
}
impl<T: PolicyApplicable> PolicyApplicable for Box<T> {
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
Box::new((*self).apply_policy::<P, M>(mapper))
}
}
impl<T> PolicyApplicable for Arc<T>
where
T: PolicyApplicable + Clone,
{
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
Arc::new((*self).clone().apply_policy::<P, M>(mapper))
}
}
impl<T> PolicyApplicable for Rc<T>
where
T: PolicyApplicable + Clone,
{
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
Rc::new((*self).clone().apply_policy::<P, M>(mapper))
}
}
impl<T> PolicyApplicable for RefCell<T>
where
T: PolicyApplicable,
{
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
RefCell::new(self.into_inner().apply_policy::<P, M>(mapper))
}
}
impl<T> PolicyApplicable for Cell<T>
where
T: PolicyApplicable + Copy,
{
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
Cell::new(self.get().apply_policy::<P, M>(mapper))
}
}
impl<T, E> PolicyApplicable for Result<T, E>
where
T: PolicyApplicable,
E: PolicyApplicable,
{
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
match self {
Ok(v) => Ok(v.apply_policy::<P, M>(mapper)),
Err(e) => Err(e.apply_policy::<P, M>(mapper)),
}
}
}
impl<K, V, S> PolicyApplicable for HashMap<K, V, S>
where
K: Hash + Eq,
V: PolicyApplicable,
S: BuildHasher + Clone,
{
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
let hasher = self.hasher().clone();
let mut result = HashMap::with_capacity_and_hasher(self.len(), hasher);
result.extend(
self.into_iter()
.map(|(k, v)| (k, v.apply_policy::<P, M>(mapper))),
);
result
}
}
impl<K, V> PolicyApplicable for BTreeMap<K, V>
where
K: Ord,
V: PolicyApplicable,
{
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
self.into_iter()
.map(|(k, v)| (k, v.apply_policy::<P, M>(mapper)))
.collect()
}
}
impl<K, V, S> PolicyApplicableRef for HashMap<K, V, S>
where
K: Hash + Eq + Clone,
V: PolicyApplicableRef,
S: BuildHasher + Clone,
{
type Output = HashMap<K, V::Output, S>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
let hasher = self.hasher().clone();
let mut result = HashMap::with_capacity_and_hasher(self.len(), hasher);
result.extend(
self.iter()
.map(|(k, v)| (k.clone(), v.apply_policy_ref::<P, M>(mapper))),
);
result
}
}
impl<K, V> PolicyApplicableRef for BTreeMap<K, V>
where
K: Ord + Clone,
V: PolicyApplicableRef,
{
type Output = BTreeMap<K, V::Output>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
self.iter()
.map(|(k, v)| (k.clone(), v.apply_policy_ref::<P, M>(mapper)))
.collect()
}
}
impl<T, S> PolicyApplicable for HashSet<T, S>
where
T: PolicyApplicable + Hash + Eq,
S: BuildHasher + Clone,
{
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
let hasher = self.hasher().clone();
let mut result = HashSet::with_capacity_and_hasher(self.len(), hasher);
result.extend(self.into_iter().map(|v| v.apply_policy::<P, M>(mapper)));
result
}
}
impl<T> PolicyApplicable for BTreeSet<T>
where
T: PolicyApplicable + Ord,
{
fn apply_policy<P, M>(self, mapper: &M) -> Self
where
P: RedactionPolicy,
M: RedactableMapper,
{
self.into_iter()
.map(|v| v.apply_policy::<P, M>(mapper))
.collect()
}
}
impl<T, S> PolicyApplicableRef for HashSet<T, S>
where
T: PolicyApplicableRef,
T::Output: Hash + Eq,
S: BuildHasher + Clone,
{
type Output = HashSet<T::Output, S>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
let hasher = self.hasher().clone();
let mut result = HashSet::with_capacity_and_hasher(self.len(), hasher);
result.extend(self.iter().map(|v| v.apply_policy_ref::<P, M>(mapper)));
result
}
}
impl<T> PolicyApplicableRef for BTreeSet<T>
where
T: PolicyApplicableRef,
T::Output: Ord,
{
type Output = BTreeSet<T::Output>;
fn apply_policy_ref<P, M>(&self, mapper: &M) -> Self::Output
where
P: RedactionPolicy,
M: RedactableMapper,
{
self.iter()
.map(|v| v.apply_policy_ref::<P, M>(mapper))
.collect()
}
}
#[cfg(test)]
mod tests {
use std::{collections::HashMap, sync::Arc};
use super::{apply_policy, apply_policy_ref, redact};
use crate::{Secret, Sensitive};
#[test]
fn redact_applies_policy() {
#[derive(Clone, Sensitive)]
#[cfg_attr(feature = "slog", derive(serde::Serialize))]
struct DefaultValue {
#[sensitive(Secret)]
value: String,
}
let value = DefaultValue {
value: "top_secret".to_string(),
};
let redacted = redact(value);
assert_eq!(redacted.value, "[REDACTED]");
}
#[test]
fn apply_policy_to_string() {
#[derive(Clone, Sensitive)]
#[cfg_attr(feature = "slog", derive(serde::Serialize))]
struct Simple {
#[sensitive(Secret)]
value: String,
}
let s = Simple {
value: "secret".into(),
};
let redacted = redact(s);
assert_eq!(redacted.value, "[REDACTED]");
}
#[test]
fn apply_policy_to_option_string() {
#[derive(Clone, Sensitive)]
#[cfg_attr(feature = "slog", derive(serde::Serialize))]
struct WithOption {
#[sensitive(Secret)]
value: Option<String>,
}
let s = WithOption {
value: Some("secret".into()),
};
let redacted = redact(s);
assert_eq!(redacted.value, Some("[REDACTED]".into()));
}
#[test]
fn apply_policy_to_vec_string() {
#[derive(Clone, Sensitive)]
#[cfg_attr(feature = "slog", derive(serde::Serialize))]
struct WithVec {
#[sensitive(Secret)]
values: Vec<String>,
}
let s = WithVec {
values: vec!["secret1".into(), "secret2".into()],
};
let redacted = redact(s);
assert_eq!(redacted.values, vec!["[REDACTED]", "[REDACTED]"]);
}
#[test]
fn apply_policy_to_arc_string() {
let value = Arc::new("secret".to_string());
let redacted = apply_policy::<Secret, _>(value);
assert_eq!(&*redacted, "[REDACTED]");
}
#[test]
fn apply_policy_to_nested_option_vec() {
#[derive(Clone, Sensitive)]
#[cfg_attr(feature = "slog", derive(serde::Serialize))]
struct Nested {
#[sensitive(Secret)]
values: Option<Vec<String>>,
}
let s = Nested {
values: Some(vec!["secret1".into(), "secret2".into()]),
};
let redacted = redact(s);
assert_eq!(
redacted.values,
Some(vec!["[REDACTED]".into(), "[REDACTED]".into()])
);
}
#[test]
fn apply_policy_to_nested_vec_option() {
#[derive(Clone, Sensitive)]
#[cfg_attr(feature = "slog", derive(serde::Serialize))]
struct Nested {
#[sensitive(Secret)]
values: Vec<Option<String>>,
}
let s = Nested {
values: vec![Some("secret1".into()), None, Some("secret2".into())],
};
let redacted = redact(s);
assert_eq!(
redacted.values,
vec![Some("[REDACTED]".into()), None, Some("[REDACTED]".into())]
);
}
#[test]
fn apply_policy_to_deeply_nested() {
#[derive(Clone, Sensitive)]
#[cfg_attr(feature = "slog", derive(serde::Serialize))]
struct DeepNest {
#[sensitive(Secret)]
values: Option<Vec<Option<String>>>,
}
let s = DeepNest {
values: Some(vec![Some("secret".into()), None]),
};
let redacted = redact(s);
assert_eq!(redacted.values, Some(vec![Some("[REDACTED]".into()), None]));
}
#[test]
fn apply_policy_to_hashmap_values() {
#[derive(Clone, Sensitive)]
#[cfg_attr(feature = "slog", derive(serde::Serialize))]
struct WithMap {
#[sensitive(Secret)]
data: HashMap<String, String>,
}
let mut data = HashMap::new();
data.insert("key1".into(), "secret1".into());
data.insert("key2".into(), "secret2".into());
let s = WithMap { data };
let redacted = redact(s);
assert!(redacted.data.contains_key("key1"));
assert!(redacted.data.contains_key("key2"));
assert_eq!(redacted.data.get("key1"), Some(&"[REDACTED]".to_string()));
assert_eq!(redacted.data.get("key2"), Some(&"[REDACTED]".to_string()));
}
#[test]
fn apply_policy_to_nested_map_vec() {
#[derive(Clone, Sensitive)]
#[cfg_attr(feature = "slog", derive(serde::Serialize))]
struct ComplexNest {
#[sensitive(Secret)]
data: HashMap<String, Vec<String>>,
}
let mut data = HashMap::new();
data.insert("secrets".into(), vec!["secret1".into(), "secret2".into()]);
let s = ComplexNest { data };
let redacted = redact(s);
assert_eq!(
redacted.data.get("secrets"),
Some(&vec!["[REDACTED]".to_string(), "[REDACTED]".to_string()])
);
}
#[test]
fn apply_policy_ref_to_str() {
let value = "secret";
let redacted = apply_policy_ref::<Secret, _>(&value);
assert_eq!(redacted, "[REDACTED]");
}
#[test]
fn apply_policy_ref_to_option_str() {
let value: Option<&str> = Some("secret");
let redacted = apply_policy_ref::<Secret, _>(&value);
assert_eq!(redacted, Some("[REDACTED]".to_string()));
}
}