1use std::borrow::Cow;
2
3use regex::Regex;
4
5pub struct RedactionRule<'a> {
7 pub(crate) pattern: &'a str,
9 pub(crate) limit: usize,
11 pub(crate) replacement: &'a str,
13}
14
15impl<'a> RedactionRule<'a> {
16 pub fn new(pattern: &'a str, replacement: &'a str, limit: usize) -> Self {
28 Self {
29 pattern,
30 limit,
31 replacement,
32 }
33 }
34}
35#[track_caller]
45pub(crate) fn apply_redactions<'a>(
46 data: &'a str,
47 redaction_rules: &[RedactionRule],
48) -> Cow<'a, str> {
49 let mut result = Cow::from(data);
50 for RedactionRule {
51 pattern,
52 limit,
53 replacement,
54 } in redaction_rules
55 {
56 let regex = Regex::new(pattern).unwrap();
57 let redacted = regex.replacen(&result, *limit, *replacement);
58 if let Cow::Owned(modified) = redacted {
59 result = Cow::from(modified);
60 }
61 }
62 result
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn test_apply_redactions_empty_rules() {
71 let data = "hello world";
72 let rules = [];
73 let result = apply_redactions(data, &rules);
74 assert_eq!(result, "hello world");
75 assert!(matches!(result, Cow::Borrowed(_)));
76 }
77
78 #[test]
79 fn test_apply_redactions_no_match() {
80 let data = "hello world";
81 let rules = [RedactionRule {
82 pattern: "foo",
83 limit: 0,
84 replacement: "bar",
85 }];
86 let result = apply_redactions(data, &rules);
87 assert_eq!(result, "hello world");
88 assert!(matches!(result, Cow::Borrowed(_)));
89 }
90
91 #[test]
92 fn test_apply_redactions_single_replacement() {
93 let data = "hello world hello";
94 let rules = [RedactionRule {
95 pattern: "hello",
96 limit: 1,
97 replacement: "hi",
98 }];
99 let result = apply_redactions(data, &rules);
100 assert_eq!(result, "hi world hello");
101 assert!(matches!(result, Cow::Owned(_)));
102 }
103
104 #[test]
105 fn test_apply_redactions_limit_zero_replaces_all() {
106 let data = "hello world hello";
107 let rules = [RedactionRule {
108 pattern: "hello",
109 limit: 0,
110 replacement: "hi",
111 }];
112 let result = apply_redactions(data, &rules);
113 assert_eq!(result, "hi world hi");
114 }
115
116 #[test]
117 fn test_apply_redactions_multiple_rules() {
118 let data = "hello world";
119 let rules = [
120 RedactionRule {
121 pattern: "hello",
122 limit: 1,
123 replacement: "hi",
124 },
125 RedactionRule {
126 pattern: "world",
127 limit: 1,
128 replacement: "earth",
129 },
130 ];
131 let result = apply_redactions(data, &rules);
132 assert_eq!(result, "hi earth");
133 }
134
135 #[test]
136 fn test_apply_redactions_capture_groups() {
137 let data = "hello world";
138 let rules = [RedactionRule {
139 pattern: r"hello (\w+)",
140 limit: 0,
141 replacement: "hi $1",
142 }];
143 let result = apply_redactions(data, &rules);
144 assert_eq!(result, "hi world");
145 }
146
147 #[test]
148 #[should_panic(expected = "regex parse error")]
149 fn test_apply_redactions_invalid_regex() {
150 let data = "hello world";
151 let rules = [RedactionRule {
152 pattern: "(invalid",
153 limit: 0,
154 replacement: "hi",
155 }];
156 let _result = apply_redactions(data, &rules);
157 }
158}