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 reg.register(Arc::new(super::xml_proc::XmlProcessor));
46 reg.register(Arc::new(super::csv_proc::CsvProcessor));
47 reg.register(Arc::new(super::toml_proc::TomlProcessor));
48 reg.register(Arc::new(super::env_proc::EnvProcessor));
49 reg.register(Arc::new(super::ini_proc::IniProcessor));
50 reg.register(Arc::new(super::log_line::LogLineProcessor::new()));
51 reg
52 }
53
54 pub fn register(&mut self, processor: Arc<dyn Processor>) {
57 self.processors
58 .insert(processor.name().to_string(), processor);
59 }
60
61 pub fn register_with_alias(&mut self, processor: Arc<dyn Processor>, alias: &str) {
63 self.processors
64 .insert(alias.to_string(), Arc::clone(&processor));
65 self.processors
66 .insert(processor.name().to_string(), processor);
67 }
68
69 pub fn get(&self, name: &str) -> Option<&Arc<dyn Processor>> {
71 self.processors.get(name)
72 }
73
74 pub fn names(&self) -> Vec<&str> {
76 self.processors.keys().map(|s| s.as_str()).collect()
77 }
78
79 #[must_use]
81 pub fn len(&self) -> usize {
82 self.processors.len()
83 }
84
85 #[must_use]
87 pub fn is_empty(&self) -> bool {
88 self.processors.is_empty()
89 }
90
91 pub fn find_processor(
100 &self,
101 content: &[u8],
102 profile: &FileTypeProfile,
103 ) -> Option<&Arc<dyn Processor>> {
104 if let Some(proc) = self.processors.get(&profile.processor) {
106 if proc.can_handle(content, profile) {
107 return Some(proc);
108 }
109 }
110
111 self.processors
113 .values()
114 .find(|proc| proc.can_handle(content, profile))
115 }
116
117 pub fn process(
127 &self,
128 content: &[u8],
129 profile: &FileTypeProfile,
130 store: &MappingStore,
131 ) -> Result<Option<Vec<u8>>> {
132 match self.find_processor(content, profile) {
133 Some(proc) => {
134 let output = proc.process(content, profile, store)?;
135 Ok(Some(output))
136 }
137 None => Ok(None),
138 }
139 }
140}
141
142impl Default for ProcessorRegistry {
143 fn default() -> Self {
144 Self::with_builtins()
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::category::Category;
152 use crate::generator::HmacGenerator;
153 use crate::processor::profile::{FieldRule, FileTypeProfile};
154 use std::sync::Arc;
155
156 fn make_store() -> MappingStore {
157 let gen = Arc::new(HmacGenerator::new([42u8; 32]));
158 MappingStore::new(gen, None)
159 }
160
161 #[test]
162 fn new_registry_is_empty() {
163 let reg = ProcessorRegistry::new();
164 assert!(reg.is_empty());
165 assert_eq!(reg.len(), 0);
166 }
167
168 #[test]
169 fn with_builtins_registers_known_processors() {
170 let reg = ProcessorRegistry::with_builtins();
171 assert!(!reg.is_empty());
172 let names = reg.names();
173 for expected in &["json", "yaml", "xml", "csv", "toml", "jsonl"] {
174 assert!(names.contains(expected), "missing processor: {expected}");
175 }
176 }
177
178 #[test]
179 fn register_and_get_roundtrip() {
180 let mut reg = ProcessorRegistry::new();
181 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
182 assert!(reg.get("json").is_some());
183 assert!(reg.get("xml").is_none());
184 }
185
186 #[test]
187 fn register_overwrites_existing() {
188 let mut reg = ProcessorRegistry::new();
189 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
190 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
191 assert_eq!(reg.len(), 1);
192 }
193
194 #[test]
195 fn names_lists_all_registered() {
196 let mut reg = ProcessorRegistry::new();
197 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
198 reg.register(Arc::new(crate::processor::yaml_proc::YamlProcessor));
199 let names = reg.names();
200 assert_eq!(names.len(), 2);
201 assert!(names.contains(&"json"));
202 assert!(names.contains(&"yaml"));
203 }
204
205 #[test]
206 fn find_processor_by_profile_name() {
207 let reg = ProcessorRegistry::with_builtins();
208 let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
209 let content = b"{}";
210 assert!(reg.find_processor(content, &profile).is_some());
211 }
212
213 #[test]
214 fn find_processor_returns_none_for_unrecognised_content() {
215 let reg = ProcessorRegistry::new(); let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
217 assert!(reg.find_processor(b"{}", &profile).is_none());
218 }
219
220 #[test]
221 fn process_returns_some_for_matching_content() {
222 let reg = ProcessorRegistry::with_builtins();
223 let store = make_store();
224 let profile = FileTypeProfile::new(
225 "json",
226 vec![FieldRule::new("*.secret").with_category(Category::Custom("s".into()))],
227 )
228 .with_extension(".json");
229 let result = reg
230 .process(br#"{"secret":"abc"}"#, &profile, &store)
231 .unwrap();
232 assert!(result.is_some());
233 }
234
235 #[test]
236 fn process_returns_none_when_no_processor_matches() {
237 let reg = ProcessorRegistry::new(); let store = make_store();
239 let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
240 let result = reg.process(b"{}", &profile, &store).unwrap();
241 assert!(result.is_none());
242 }
243
244 #[test]
245 fn default_impl_gives_builtins() {
246 let reg = ProcessorRegistry::default();
247 assert!(reg.get("json").is_some());
248 }
249}