use super::{FileTypeProfile, Processor};
use crate::error::Result;
use crate::store::MappingStore;
use std::collections::HashMap;
use std::sync::Arc;
pub struct ProcessorRegistry {
processors: HashMap<String, Arc<dyn Processor>>,
}
impl ProcessorRegistry {
#[must_use]
pub fn new() -> Self {
Self {
processors: HashMap::new(),
}
}
#[must_use]
pub fn with_builtins() -> Self {
let mut reg = Self::new();
let kv: Arc<dyn Processor> = Arc::new(super::key_value::KeyValueProcessor);
reg.processors.insert("key_value".into(), Arc::clone(&kv));
reg.processors.insert("key-value".into(), kv);
reg.register(Arc::new(super::json_proc::JsonProcessor));
reg.register(Arc::new(super::jsonl_proc::JsonLinesProcessor));
reg.register(Arc::new(super::yaml_proc::YamlProcessor));
reg.register(Arc::new(super::xml_proc::XmlProcessor));
reg.register(Arc::new(super::csv_proc::CsvProcessor));
reg.register(Arc::new(super::toml_proc::TomlProcessor));
reg.register(Arc::new(super::env_proc::EnvProcessor));
reg.register(Arc::new(super::ini_proc::IniProcessor));
reg.register(Arc::new(super::log_line::LogLineProcessor::new()));
reg
}
pub fn register(&mut self, processor: Arc<dyn Processor>) {
self.processors
.insert(processor.name().to_string(), processor);
}
pub fn get(&self, name: &str) -> Option<&Arc<dyn Processor>> {
self.processors.get(name)
}
pub fn names(&self) -> Vec<&str> {
self.processors.keys().map(|s| s.as_str()).collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.processors.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.processors.is_empty()
}
pub fn find_processor(
&self,
content: &[u8],
profile: &FileTypeProfile,
) -> Option<&Arc<dyn Processor>> {
if let Some(proc) = self.processors.get(&profile.processor) {
if proc.can_handle(content, profile) {
return Some(proc);
}
}
self.processors
.values()
.find(|proc| proc.can_handle(content, profile))
}
pub fn process(
&self,
content: &[u8],
profile: &FileTypeProfile,
store: &MappingStore,
) -> Result<Option<Vec<u8>>> {
match self.find_processor(content, profile) {
Some(proc) => {
let output = proc.process(content, profile, store)?;
Ok(Some(output))
}
None => Ok(None),
}
}
}
impl Default for ProcessorRegistry {
fn default() -> Self {
Self::with_builtins()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::category::Category;
use crate::generator::HmacGenerator;
use crate::processor::profile::{FieldRule, FileTypeProfile};
use std::sync::Arc;
fn make_store() -> MappingStore {
let gen = Arc::new(HmacGenerator::new([42u8; 32]));
MappingStore::new(gen, None)
}
#[test]
fn new_registry_is_empty() {
let reg = ProcessorRegistry::new();
assert!(reg.is_empty());
assert_eq!(reg.len(), 0);
}
#[test]
fn with_builtins_registers_known_processors() {
let reg = ProcessorRegistry::with_builtins();
assert!(!reg.is_empty());
let names = reg.names();
for expected in &["json", "yaml", "xml", "csv", "toml", "jsonl"] {
assert!(names.contains(expected), "missing processor: {expected}");
}
}
#[test]
fn register_and_get_roundtrip() {
let mut reg = ProcessorRegistry::new();
reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
assert!(reg.get("json").is_some());
assert!(reg.get("xml").is_none());
}
#[test]
fn register_overwrites_existing() {
let mut reg = ProcessorRegistry::new();
reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
assert_eq!(reg.len(), 1);
}
#[test]
fn names_lists_all_registered() {
let mut reg = ProcessorRegistry::new();
reg.register(Arc::new(crate::processor::json_proc::JsonProcessor));
reg.register(Arc::new(crate::processor::yaml_proc::YamlProcessor));
let names = reg.names();
assert_eq!(names.len(), 2);
assert!(names.contains(&"json"));
assert!(names.contains(&"yaml"));
}
#[test]
fn find_processor_by_profile_name() {
let reg = ProcessorRegistry::with_builtins();
let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
let content = b"{}";
assert!(reg.find_processor(content, &profile).is_some());
}
#[test]
fn find_processor_returns_none_for_unrecognised_content() {
let reg = ProcessorRegistry::new(); let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
assert!(reg.find_processor(b"{}", &profile).is_none());
}
#[test]
fn process_returns_some_for_matching_content() {
let reg = ProcessorRegistry::with_builtins();
let store = make_store();
let profile = FileTypeProfile::new(
"json",
vec![FieldRule::new("*.secret").with_category(Category::Custom("s".into()))],
)
.with_extension(".json");
let result = reg
.process(br#"{"secret":"abc"}"#, &profile, &store)
.unwrap();
assert!(result.is_some());
}
#[test]
fn process_returns_none_when_no_processor_matches() {
let reg = ProcessorRegistry::new(); let store = make_store();
let profile = FileTypeProfile::new("json", vec![]).with_extension(".json");
let result = reg.process(b"{}", &profile, &store).unwrap();
assert!(result.is_none());
}
#[test]
fn default_impl_gives_builtins() {
let reg = ProcessorRegistry::default();
assert!(reg.get("json").is_some());
}
}