use super::search::{find, wildcard};
use super::{Node, Path, Value};
pub trait Redactor {
fn redact(&self, path: &Path, node: &Node) -> Option<Node>;
}
impl<F: Fn(&Path, &Node) -> Option<Node>> Redactor for F {
fn redact(&self, path: &Path, node: &Node) -> Option<Node> {
self(path, node)
}
}
#[derive(Clone, Debug)]
pub struct Redaction {
patterns: Vec<String>,
mask: String,
}
pub use crate::redact::SECRET_KEYS;
impl Default for Redaction {
fn default() -> Self {
Redaction {
patterns: Vec::new(),
mask: "********".into(),
}
}
}
impl Redaction {
pub fn new() -> Self {
Self::default()
}
pub fn secrets() -> Self {
Self::new().patterns(SECRET_KEYS.iter().copied())
}
pub fn pattern(mut self, pattern: impl Into<String>) -> Self {
self.patterns.push(pattern.into());
self
}
pub fn patterns<S: Into<String>>(mut self, patterns: impl IntoIterator<Item = S>) -> Self {
self.patterns.extend(patterns.into_iter().map(Into::into));
self
}
pub fn mask(mut self, mask: impl Into<String>) -> Self {
self.mask = mask.into();
self
}
pub fn matches_key(&self, key: &str) -> bool {
let dashless = key.replace('-', "_");
self.patterns.iter().any(|pattern| {
if pattern.contains(['*', '?']) {
wildcard(pattern, key, true)
|| wildcard(&pattern.replace('-', "_"), &dashless, true)
} else {
find(&dashless, &pattern.replace('-', "_"), true).is_some()
}
})
}
fn masked(&self, node: &Node) -> Node {
let value = match &node.value {
Value::String(_)
| Value::DateTime(_)
| Value::Int(_)
| Value::UInt(_)
| Value::Float(_) => Value::String(self.mask.clone()),
Value::Seq(items) => Value::Seq(items.iter().map(|item| self.masked(item)).collect()),
Value::Map(entries) => Value::Map(
entries
.iter()
.map(|(key, value)| (key.clone(), self.masked(value)))
.collect(),
),
other => other.clone(),
};
Node::with_meta(value, node.meta.clone())
}
}
impl Redactor for Redaction {
fn redact(&self, path: &Path, node: &Node) -> Option<Node> {
path.last_key()
.is_some_and(|key| self.matches_key(key))
.then(|| self.masked(node))
}
}
pub(crate) fn apply(node: &Node, redactor: &dyn Redactor) -> Node {
fn go(node: &Node, path: &mut Path, redactor: &dyn Redactor) -> Node {
if let Some(replacement) = redactor.redact(path, node) {
return replacement;
}
let value = match &node.value {
Value::Seq(items) => Value::Seq(
items
.iter()
.enumerate()
.map(|(i, item)| {
path.push(super::PathSegment::Index(i));
let out = go(item, path, redactor);
path.0.pop();
out
})
.collect(),
),
Value::Map(entries) => Value::Map(
entries
.iter()
.map(|(key, value)| {
path.push(super::PathSegment::Key(key.clone()));
let out = go(value, path, redactor);
path.0.pop();
(key.clone(), out)
})
.collect(),
),
other => other.clone(),
};
Node::with_meta(value, node.meta.clone())
}
go(node, &mut Path::root(), redactor)
}