1use crate::Error;
26
27pub trait Sanitizer {
33 fn sanitize(&self, error: &Error) -> Error;
41}
42
43pub trait PatternTransform {
48 fn apply(&self, input: &str) -> String;
50}
51
52pub struct SimplePattern {
54 pub pattern: String,
56 pub replacement: String,
58}
59
60impl SimplePattern {
61 pub fn new(pattern: impl Into<String>, replacement: impl Into<String>) -> Self {
62 Self {
63 pattern: pattern.into(),
64 replacement: replacement.into(),
65 }
66 }
67}
68
69impl PatternTransform for SimplePattern {
70 fn apply(&self, input: &str) -> String {
71 input.replace(&self.pattern, &self.replacement)
72 }
73}
74
75pub struct CaseInsensitivePattern {
77 pub pattern: String,
79 pub replacement: String,
81}
82
83impl CaseInsensitivePattern {
84 pub fn new(pattern: impl Into<String>, replacement: impl Into<String>) -> Self {
85 Self {
86 pattern: pattern.into(),
87 replacement: replacement.into(),
88 }
89 }
90}
91
92impl PatternTransform for CaseInsensitivePattern {
93 fn apply(&self, input: &str) -> String {
94 let pattern_lower = self.pattern.to_lowercase();
95 let input_lower = input.to_lowercase();
96
97 if let Some(pos) = input_lower.find(&pattern_lower) {
98 let mut result = input.to_string();
99 result.replace_range(pos..pos + self.pattern.len(), &self.replacement);
100
101 if result.to_lowercase().contains(&pattern_lower) {
103 return self.apply(&result);
104 }
105 result
106 } else {
107 input.to_string()
108 }
109 }
110}
111
112pub struct CompositeTransform {
114 transforms: Vec<Box<dyn PatternTransform + Send + Sync>>,
115}
116
117impl CompositeTransform {
118 pub fn new() -> Self {
119 Self {
120 transforms: Vec::new(),
121 }
122 }
123
124 pub fn add_transform<T: PatternTransform + Send + Sync + 'static>(
125 mut self,
126 transform: T,
127 ) -> Self {
128 self.transforms.push(Box::new(transform));
129 self
130 }
131}
132
133impl Default for CompositeTransform {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139impl PatternTransform for CompositeTransform {
140 fn apply(&self, input: &str) -> String {
141 self.transforms
142 .iter()
143 .fold(input.to_string(), |acc, transform| transform.apply(&acc))
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_simple_pattern() {
153 let pattern = SimplePattern::new("password", "[REDACTED]");
154 let result = pattern.apply("The password is secret123");
155 assert!(result.contains("[REDACTED]"));
156 assert!(!result.contains("password"));
157 }
158
159 #[test]
160 fn test_case_insensitive_pattern() {
161 let pattern = CaseInsensitivePattern::new("password", "[REDACTED]");
162 let result = pattern.apply("The PASSWORD is secret123");
163 assert!(result.contains("[REDACTED]"));
164 }
165
166 #[test]
167 fn test_composite_transform() {
168 let composite = CompositeTransform::new()
169 .add_transform(SimplePattern::new("password", "[REDACTED]"))
170 .add_transform(SimplePattern::new("token", "[REDACTED]"));
171
172 let result = composite.apply("password is secret, token is abc123");
173 assert!(result.contains("[REDACTED]"));
174 assert!(!result.contains("password"));
175 assert!(!result.contains("token"));
176 }
177
178 #[test]
179 fn test_simple_pattern_no_match() {
180 let pattern = SimplePattern::new("password", "[REDACTED]");
181 let result = pattern.apply("This text has no sensitive data");
182 assert_eq!(result, "This text has no sensitive data");
183 }
184
185 #[test]
186 fn test_simple_pattern_multiple_occurrences() {
187 let pattern = SimplePattern::new("key", "[REDACTED]");
188 let result = pattern.apply("key1, key2, key3");
189 assert_eq!(result.matches("[REDACTED]").count(), 3);
190 }
191
192 #[test]
193 fn test_case_insensitive_pattern_various_cases() {
194 let pattern = CaseInsensitivePattern::new("secret", "[REDACTED]");
195
196 let result1 = pattern.apply("SECRET value");
197 assert!(result1.contains("[REDACTED]"));
198
199 let result2 = pattern.apply("Secret value");
200 assert!(result2.contains("[REDACTED]"));
201
202 let result3 = pattern.apply("secret value");
203 assert!(result3.contains("[REDACTED]"));
204 }
205
206 #[test]
207 fn test_case_insensitive_pattern_no_match() {
208 let pattern = CaseInsensitivePattern::new("password", "[REDACTED]");
209 let result = pattern.apply("No sensitive data here");
210 assert_eq!(result, "No sensitive data here");
211 }
212
213 #[test]
214 fn test_case_insensitive_pattern_multiple_occurrences() {
215 let pattern = CaseInsensitivePattern::new("pass", "[REDACTED]");
216 let result = pattern.apply("pass PASS Pass");
217 assert!(result.contains("[REDACTED]"));
219 }
220
221 #[test]
222 fn test_composite_transform_empty() {
223 let composite = CompositeTransform::new();
224 let result = composite.apply("test string");
225 assert_eq!(result, "test string");
226 }
227
228 #[test]
229 fn test_composite_transform_default() {
230 let composite = CompositeTransform::default();
231 let result = composite.apply("test");
232 assert_eq!(result, "test");
233 }
234
235 #[test]
236 fn test_composite_transform_single() {
237 let composite = CompositeTransform::new().add_transform(SimplePattern::new("test", "TEST"));
238
239 let result = composite.apply("test string");
240 assert_eq!(result, "TEST string");
241 }
242
243 #[test]
244 fn test_composite_transform_chained() {
245 let composite = CompositeTransform::new()
246 .add_transform(SimplePattern::new("a", "b"))
247 .add_transform(SimplePattern::new("b", "c"))
248 .add_transform(SimplePattern::new("c", "d"));
249
250 let result = composite.apply("a");
251 assert_eq!(result, "d");
252 }
253
254 #[test]
255 fn test_pattern_transform_trait() {
256 let pattern: Box<dyn PatternTransform> = Box::new(SimplePattern::new("test", "replaced"));
257 let result = pattern.apply("test value");
258 assert_eq!(result, "replaced value");
259 }
260}