use std::borrow::Cow;
use regex::Regex;
pub struct RedactionRule<'a> {
pub pattern: &'a str,
pub limit: usize,
pub replacement: &'a str,
}
impl<'a> RedactionRule<'a> {
pub fn new(pattern: &'a str, replacement: &'a str, limit: usize) -> Self {
Self {
pattern,
limit,
replacement,
}
}
}
#[track_caller]
pub(crate) fn apply_redactions<'a>(
data: &'a str,
redaction_rules: &[RedactionRule],
) -> Cow<'a, str> {
let mut result = Cow::from(data);
for RedactionRule {
pattern,
limit,
replacement,
} in redaction_rules
{
let regex = Regex::new(pattern).expect("regex parse error in redaction rule");
let redacted = regex.replacen(&result, *limit, *replacement);
if let Cow::Owned(modified) = redacted {
result = Cow::from(modified);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apply_redactions_empty_rules() {
let data = "hello world";
let rules = [];
let result = apply_redactions(data, &rules);
assert_eq!(result, "hello world");
assert!(matches!(result, Cow::Borrowed(_)));
}
#[test]
fn test_apply_redactions_no_match() {
let data = "hello world";
let rules = [RedactionRule {
pattern: "foo",
limit: 0,
replacement: "bar",
}];
let result = apply_redactions(data, &rules);
assert_eq!(result, "hello world");
assert!(matches!(result, Cow::Borrowed(_)));
}
#[test]
fn test_apply_redactions_single_replacement() {
let data = "hello world hello";
let rules = [RedactionRule {
pattern: "hello",
limit: 1,
replacement: "hi",
}];
let result = apply_redactions(data, &rules);
assert_eq!(result, "hi world hello");
assert!(matches!(result, Cow::Owned(_)));
}
#[test]
fn test_apply_redactions_limit_zero_replaces_all() {
let data = "hello world hello";
let rules = [RedactionRule {
pattern: "hello",
limit: 0,
replacement: "hi",
}];
let result = apply_redactions(data, &rules);
assert_eq!(result, "hi world hi");
}
#[test]
fn test_apply_redactions_multiple_rules() {
let data = "hello world";
let rules = [
RedactionRule {
pattern: "hello",
limit: 1,
replacement: "hi",
},
RedactionRule {
pattern: "world",
limit: 1,
replacement: "earth",
},
];
let result = apply_redactions(data, &rules);
assert_eq!(result, "hi earth");
}
#[test]
fn test_apply_redactions_capture_groups() {
let data = "hello world";
let rules = [RedactionRule {
pattern: r"hello (\w+)",
limit: 0,
replacement: "hi $1",
}];
let result = apply_redactions(data, &rules);
assert_eq!(result, "hi world");
}
#[test]
#[should_panic(expected = "regex parse error")]
fn test_apply_redactions_invalid_regex() {
let data = "hello world";
let rules = [RedactionRule {
pattern: "(invalid",
limit: 0,
replacement: "hi",
}];
let _result = apply_redactions(data, &rules);
}
}