scour_secrets/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.register(Arc::new(super::command_output::CommandOutputProcessor));
54 reg.register(Arc::new(super::columns::ColumnsProcessor));
55 reg
56 }
57
58 pub fn register(&mut self, processor: Arc<dyn Processor>) {
61 self.processors
62 .insert(processor.name().to_string(), processor);
63 }
64
65 pub fn register_with_alias(&mut self, processor: Arc<dyn Processor>, alias: &str) {
67 self.processors
68 .insert(alias.to_string(), Arc::clone(&processor));
69 self.processors
70 .insert(processor.name().to_string(), processor);
71 }
72
73 pub fn get(&self, name: &str) -> Option<&Arc<dyn Processor>> {
75 self.processors.get(name)
76 }
77
78 pub fn names(&self) -> Vec<&str> {
80 self.processors.keys().map(|s| s.as_str()).collect()
81 }
82
83 #[must_use]
85 pub fn len(&self) -> usize {
86 self.processors.len()
87 }
88
89 #[must_use]
91 pub fn is_empty(&self) -> bool {
92 self.processors.is_empty()
93 }
94
95 pub fn find_processor(
104 &self,
105 content: &[u8],
106 profile: &FileTypeProfile,
107 ) -> Option<&Arc<dyn Processor>> {
108 if let Some(proc) = self.processors.get(&profile.processor) {
110 if proc.can_handle(content, profile) {
111 return Some(proc);
112 }
113 }
114
115 self.processors
117 .values()
118 .find(|proc| proc.can_handle(content, profile))
119 }
120
121 pub fn process(
131 &self,
132 content: &[u8],
133 profile: &FileTypeProfile,
134 store: &MappingStore,
135 ) -> Result<Option<Vec<u8>>> {
136 match self.find_processor(content, profile) {
137 Some(proc) => {
138 let output = proc.process(content, profile, store)?;
139 Ok(Some(output))
140 }
141 None => Ok(None),
142 }
143 }
144
145 pub fn process_to_edits(
154 &self,
155 content: &[u8],
156 profile: &FileTypeProfile,
157 store: &MappingStore,
158 ) -> Result<Option<(Vec<u8>, usize)>> {
159 match self.find_processor(content, profile) {
160 Some(proc) => match proc.process_to_edits(content, profile, store)? {
161 Some(edits) => {
162 let count = edits.len();
163 Ok(Some((super::apply_edits(content, edits), count)))
164 }
165 None => Ok(None),
166 },
167 None => Ok(None),
168 }
169 }
170}
171
172impl Default for ProcessorRegistry {
173 fn default() -> Self {
174 Self::with_builtins()
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::category::Category;
182 use crate::generator::HmacGenerator;
183 use crate::processor::profile::{FieldRule, FileTypeProfile};
184 use std::sync::Arc;
185
186 fn make_store() -> MappingStore {
187 let gen = Arc::new(HmacGenerator::new([42u8; 32]));
188 MappingStore::new(gen, None)
189 }
190
191 #[test]
192 fn new_registry_is_empty() {
193 let reg = ProcessorRegistry::new();
194 assert!(reg.is_empty());
195 assert_eq!(reg.len(), 0);
196 }
197
198 #[test]
199 fn with_builtins_registers_known_processors() {
200 let reg = ProcessorRegistry::with_builtins();
201 assert!(!reg.is_empty());
202 let names = reg.names();
203 for expected in &["json", "yaml", "xml", "csv", "toml", "jsonl"] {
204 assert!(names.contains(expected), "missing processor: {expected}");
205 }
206 }
207
208 #[test]
209 fn register_and_get_roundtrip() {
210 let mut reg = ProcessorRegistry::new();
211 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
212 assert!(reg.get("json").is_some());
213 assert!(reg.get("xml").is_none());
214 }
215
216 #[test]
217 fn register_overwrites_existing() {
218 let mut reg = ProcessorRegistry::new();
219 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
220 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
221 assert_eq!(reg.len(), 1);
222 }
223
224 #[test]
225 fn names_lists_all_registered() {
226 let mut reg = ProcessorRegistry::new();
227 reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
228 reg.register(Arc::new(crate::processor::yaml_proc::YamlProcessor));
229 let names = reg.names();
230 assert_eq!(names.len(), 2);
231 assert!(names.contains(&"json"));
232 assert!(names.contains(&"yaml"));
233 }
234
235 #[test]
236 fn find_processor_by_profile_name() {
237 let reg = ProcessorRegistry::with_builtins();
238 let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
239 let content = b"{}";
240 assert!(reg.find_processor(content, &profile).is_some());
241 }
242
243 #[test]
244 fn find_processor_returns_none_for_unrecognised_content() {
245 let reg = ProcessorRegistry::new(); let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
247 assert!(reg.find_processor(b"{}", &profile).is_none());
248 }
249
250 #[test]
251 fn process_returns_some_for_matching_content() {
252 let reg = ProcessorRegistry::with_builtins();
253 let store = make_store();
254 let profile = FileTypeProfile::new(
255 "json",
256 vec![FieldRule::new("*.secret").with_category(Category::Custom("s".into()))],
257 )
258 .with_extension(".json");
259 let result = reg
260 .process(br#"{"secret":"abc"}"#, &profile, &store)
261 .unwrap();
262 assert!(result.is_some());
263 }
264
265 #[test]
266 fn process_returns_none_when_no_processor_matches() {
267 let reg = ProcessorRegistry::new(); let store = make_store();
269 let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
270 let result = reg.process(b"{}", &profile, &store).unwrap();
271 assert!(result.is_none());
272 }
273
274 #[test]
275 fn default_impl_gives_builtins() {
276 let reg = ProcessorRegistry::default();
277 assert!(reg.get("json").is_some());
278 }
279}