use crate::ast::Value;
use crate::error::{Error, Result};
use rustc_hash::FxHashMap;
use std::any::Any;
use std::sync::{Arc, RwLock};
pub trait ParserPlugin: Send + Sync {
fn name(&self) -> &str;
fn on_parse_start(&mut self, _input: &str) -> Result<()> {
Ok(())
}
fn on_parse_end(&mut self, _value: &Value) -> Result<()> {
Ok(())
}
fn transform_value(&mut self, _value: &mut Value, _path: &str) -> Result<()> {
Ok(())
}
fn validate(&self, _value: &Value, _path: &str) -> Result<()> {
Ok(())
}
fn on_object_key(&mut self, _key: &str, _path: &str) -> Result<()> {
Ok(())
}
fn on_string(&mut self, value: &str, _path: &str) -> Result<String> {
Ok(value.to_string())
}
fn on_number(&mut self, value: &str, _path: &str) -> Result<Value> {
Ok(Value::Number(crate::ast::Number::Float(
value.parse().map_err(|_| Error::InvalidNumber(0))?,
)))
}
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PluginHook {
BeforeParse,
AfterParse,
OnValue,
OnObjectKey,
OnString,
OnNumber,
OnValidate,
}
pub struct PluginRegistry {
plugins: Vec<Box<dyn ParserPlugin>>,
hooks: FxHashMap<PluginHook, Vec<usize>>,
plugin_map: FxHashMap<String, usize>,
}
impl PluginRegistry {
pub fn new() -> Self {
PluginRegistry {
plugins: Vec::new(),
hooks: FxHashMap::default(),
plugin_map: FxHashMap::default(),
}
}
pub fn register(&mut self, plugin: Box<dyn ParserPlugin>) -> Result<()> {
let name = plugin.name().to_string();
if self.plugin_map.contains_key(&name) {
return Err(Error::Custom(format!("Plugin '{name}' already registered")));
}
let index = self.plugins.len();
self.plugins.push(plugin);
self.plugin_map.insert(name, index);
self.register_hooks(index);
Ok(())
}
fn register_hooks(&mut self, plugin_index: usize) {
let hooks = vec![
PluginHook::BeforeParse,
PluginHook::AfterParse,
PluginHook::OnValue,
PluginHook::OnValidate,
];
for hook in hooks {
self.hooks
.entry(hook)
.or_default()
.push(plugin_index);
}
}
pub fn get(&self, name: &str) -> Option<&dyn ParserPlugin> {
self.plugin_map.get(name).map(|&idx| &*self.plugins[idx])
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut dyn ParserPlugin> {
if let Some(&idx) = self.plugin_map.get(name) {
Some(&mut *self.plugins[idx])
} else {
None
}
}
pub fn execute_hook<F>(&mut self, hook: PluginHook, mut f: F) -> Result<()>
where
F: FnMut(&mut dyn ParserPlugin) -> Result<()>,
{
if let Some(indices) = self.hooks.get(&hook).cloned() {
for idx in indices {
f(&mut *self.plugins[idx])?;
}
}
Ok(())
}
pub fn transform_value(&mut self, value: &mut Value, path: &str) -> Result<()> {
for plugin in &mut self.plugins {
plugin.transform_value(value, path)?;
}
Ok(())
}
pub fn validate(&self, value: &Value, path: &str) -> Result<()> {
for plugin in &self.plugins {
plugin.validate(value, path)?;
}
Ok(())
}
}
impl Default for PluginRegistry {
fn default() -> Self {
Self::new()
}
}
pub type SharedPluginRegistry = Arc<RwLock<PluginRegistry>>;
pub fn create_shared_registry() -> SharedPluginRegistry {
Arc::new(RwLock::new(PluginRegistry::new()))
}
pub mod plugins;
pub use plugins::{
CommentPreservationPlugin, CustomNumberFormatPlugin, DateTimePlugin, SchemaValidationPlugin,
};
#[cfg(test)]
mod tests {
use super::*;
struct TestPlugin {
name: String,
transform_count: usize,
}
impl TestPlugin {
fn new(name: &str) -> Self {
TestPlugin {
name: name.to_string(),
transform_count: 0,
}
}
}
impl ParserPlugin for TestPlugin {
fn name(&self) -> &str {
&self.name
}
fn transform_value(&mut self, _value: &mut Value, _path: &str) -> Result<()> {
self.transform_count += 1;
Ok(())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[test]
fn test_plugin_registry() {
let mut registry = PluginRegistry::new();
let plugin = Box::new(TestPlugin::new("test"));
registry.register(plugin).unwrap();
assert!(registry.get("test").is_some());
assert!(registry.get("nonexistent").is_none());
}
#[test]
fn test_duplicate_plugin() {
let mut registry = PluginRegistry::new();
registry
.register(Box::new(TestPlugin::new("test")))
.unwrap();
let result = registry.register(Box::new(TestPlugin::new("test")));
assert!(result.is_err());
}
#[test]
fn test_plugin_hooks() {
let mut registry = PluginRegistry::new();
registry
.register(Box::new(TestPlugin::new("test")))
.unwrap();
let mut count = 0;
registry
.execute_hook(PluginHook::BeforeParse, |plugin| {
count += 1;
plugin.on_parse_start("test")
})
.unwrap();
assert_eq!(count, 1);
}
}