rust_sanitize/processor/
registry.rs1use super::{FileTypeProfile, Processor};
12use crate::error::Result;
13use crate::store::MappingStore;
14use std::collections::HashMap;
15use std::sync::Arc;
16
17pub struct ProcessorRegistry {
22 processors: HashMap<String, Arc<dyn Processor>>,
24}
25
26impl ProcessorRegistry {
27 #[must_use]
29 pub fn new() -> Self {
30 Self {
31 processors: HashMap::new(),
32 }
33 }
34
35 #[must_use]
37 pub fn with_builtins() -> Self {
38 let mut reg = Self::new();
39 reg.register_with_alias(Arc::new(super::key_value::KeyValueProcessor), "key-value");
42 reg.register(Arc::new(super::json_proc::JsonProcessor));
43 reg.register(Arc::new(super::jsonl_proc::JsonLinesProcessor));
44 reg.register(Arc::new(super::yaml_proc::YamlProcessor));
45 #[cfg(feature = "structured")]
46 reg.register(Arc::new(super::xml_proc::XmlProcessor));
47 #[cfg(feature = "structured")]
48 reg.register(Arc::new(super::csv_proc::CsvProcessor));
49 reg.register(Arc::new(super::toml_proc::TomlProcessor));
50 reg.register(Arc::new(super::env_proc::EnvProcessor));
51 reg.register(Arc::new(super::ini_proc::IniProcessor));
52 reg.register(Arc::new(super::log_line::LogLineProcessor::new()));
53 reg
54 }
55
56 pub fn register(&mut self, processor: Arc<dyn Processor>) {
59 self.processors
60 .insert(processor.name().to_string(), processor);
61 }
62
63 pub fn register_with_alias(&mut self, processor: Arc<dyn Processor>, alias: &str) {
65 self.processors
66 .insert(alias.to_string(), Arc::clone(&processor));
67 self.processors
68 .insert(processor.name().to_string(), processor);
69 }
70
71 pub fn get(&self, name: &str) -> Option<&Arc<dyn Processor>> {
73 self.processors.get(name)
74 }
75
76 pub fn names(&self) -> Vec<&str> {
78 self.processors.keys().map(|s| s.as_str()).collect()
79 }
80
81 #[must_use]
83 pub fn len(&self) -> usize {
84 self.processors.len()
85 }
86
87 #[must_use]
89 pub fn is_empty(&self) -> bool {
90 self.processors.is_empty()
91 }
92
93 pub fn find_processor(
102 &self,
103 content: &[u8],
104 profile: &FileTypeProfile,
105 ) -> Option<&Arc<dyn Processor>> {
106 if let Some(proc) = self.processors.get(&profile.processor) {
108 if proc.can_handle(content, profile) {
109 return Some(proc);
110 }
111 }
112
113 self.processors
115 .values()
116 .find(|proc| proc.can_handle(content, profile))
117 }
118
119 pub fn process(
129 &self,
130 content: &[u8],
131 profile: &FileTypeProfile,
132 store: &MappingStore,
133 ) -> Result<Option<Vec<u8>>> {
134 match self.find_processor(content, profile) {
135 Some(proc) => {
136 let output = proc.process(content, profile, store)?;
137 Ok(Some(output))
138 }
139 None => Ok(None),
140 }
141 }
142
143 pub fn process_to_edits(
152 &self,
153 content: &[u8],
154 profile: &FileTypeProfile,
155 store: &MappingStore,
156 ) -> Result<Option<(Vec<u8>, usize)>> {
157 match self.find_processor(content, profile) {
158 Some(proc) => match proc.process_to_edits(content, profile, store)? {
159 Some(edits) => {
160 let count = edits.len();
161 Ok(Some((super::apply_edits(content, edits), count)))
162 }
163 None => Ok(None),
164 },
165 None => Ok(None),
166 }
167 }
168}
169
170impl Default for ProcessorRegistry {
171 fn default() -> Self {
172 Self::with_builtins()
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use crate::category::Category;
180 use crate::generator::HmacGenerator;
181 use crate::processor::profile::{FieldRule, FileTypeProfile};
182 use std::sync::Arc;
183
184 fn make_store() -> MappingStore {
185 let gen = Arc::new(HmacGenerator::new([42u8; 32]));
186 MappingStore::new(gen, None)
187 }
188
189 #[test]
190 fn new_registry_is_empty() {
191 let reg = ProcessorRegistry::new();
192 assert!(reg.is_empty());
193 assert_eq!(reg.len(), 0);
194 }
195
196 #[test]
197 fn with_builtins_registers_known_processors() {
198 let reg = ProcessorRegistry::with_builtins();
199 assert!(!reg.is_empty());
200 let names = reg.names();
201 for expected in &["json", "yaml", "xml", "csv", "toml", "jsonl"] {
202 assert!(names.contains(expected), "missing processor: {expected}");
203 }
204 }
205
206 #[test]
207 fn register_and_get_roundtrip() {
208 let mut reg = ProcessorRegistry::new();
209 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
210 assert!(reg.get("json").is_some());
211 assert!(reg.get("xml").is_none());
212 }
213
214 #[test]
215 fn register_overwrites_existing() {
216 let mut reg = ProcessorRegistry::new();
217 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
218 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
219 assert_eq!(reg.len(), 1);
220 }
221
222 #[test]
223 fn names_lists_all_registered() {
224 let mut reg = ProcessorRegistry::new();
225 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
226 reg.register(Arc::new(crate::processor::yaml_proc::YamlProcessor));
227 let names = reg.names();
228 assert_eq!(names.len(), 2);
229 assert!(names.contains(&"json"));
230 assert!(names.contains(&"yaml"));
231 }
232
233 #[test]
234 fn find_processor_by_profile_name() {
235 let reg = ProcessorRegistry::with_builtins();
236 let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
237 let content = b"{}";
238 assert!(reg.find_processor(content, &profile).is_some());
239 }
240
241 #[test]
242 fn find_processor_returns_none_for_unrecognised_content() {
243 let reg = ProcessorRegistry::new(); let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
245 assert!(reg.find_processor(b"{}", &profile).is_none());
246 }
247
248 #[test]
249 fn process_returns_some_for_matching_content() {
250 let reg = ProcessorRegistry::with_builtins();
251 let store = make_store();
252 let profile = FileTypeProfile::new(
253 "json",
254 vec![FieldRule::new("*.secret").with_category(Category::Custom("s".into()))],
255 )
256 .with_extension(".json");
257 let result = reg
258 .process(br#"{"secret":"abc"}"#, &profile, &store)
259 .unwrap();
260 assert!(result.is_some());
261 }
262
263 #[test]
264 fn process_returns_none_when_no_processor_matches() {
265 let reg = ProcessorRegistry::new(); let store = make_store();
267 let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
268 let result = reg.process(b"{}", &profile, &store).unwrap();
269 assert!(result.is_none());
270 }
271
272 #[test]
273 fn default_impl_gives_builtins() {
274 let reg = ProcessorRegistry::default();
275 assert!(reg.get("json").is_some());
276 }
277}