use anyhow::Result;
use serde::Serialize;
use serde::de::DeserializeOwned;
use crate::processors::{Processor, ProcessorType};
pub struct ProcessorPlugin {
pub name: &'static str,
pub processor_type: ProcessorType,
pub version: u32,
pub create: fn(&toml::Value) -> Result<Box<dyn Processor>>,
pub fields: &'static [crate::config::FieldSpec],
pub omit_standard_fields: &'static [&'static str],
pub scan_defaults: Option<crate::config::ScanDefaultsData>,
pub defaults: Option<crate::config::ProcessorDefaults>,
pub defconfig_json: fn(&str) -> Option<String>,
pub keywords: &'static [&'static str],
pub description: &'static str,
pub is_native: bool,
pub can_fix: bool,
pub supports_batch: bool,
pub max_jobs_cap: Option<usize>,
}
unsafe impl Sync for ProcessorPlugin {}
inventory::collect!(ProcessorPlugin);
pub fn all_plugins() -> impl Iterator<Item = &'static ProcessorPlugin> {
inventory::iter::<ProcessorPlugin>.into_iter()
}
pub fn find_plugin(name: &str) -> Option<&'static ProcessorPlugin> {
let type_name = name.split('.').next().unwrap_or(name);
all_plugins().find(|p| p.name == type_name)
}
pub fn description_of(name: &str) -> &'static str {
find_plugin(name).map_or("", |p| p.description)
}
pub fn processor_type_of(name: &str) -> crate::processors::ProcessorType {
find_plugin(name).map_or(crate::processors::ProcessorType::Checker, |p| {
p.processor_type
})
}
pub fn is_native(name: &str) -> bool {
find_plugin(name).is_some_and(|p| p.is_native)
}
pub fn can_fix(name: &str) -> bool {
find_plugin(name).is_some_and(|p| p.can_fix)
}
pub fn processor_version(name: &str) -> Option<u32> {
find_plugin(name).map(|p| p.version)
}
pub fn processor_name_parser() -> clap::builder::PossibleValuesParser {
let mut names: Vec<&'static str> = all_plugins().map(|p| p.name).collect();
names.sort_unstable();
clap::builder::PossibleValuesParser::new(names)
}
pub fn apply_all_defaults(
name: &str,
value: &mut toml::Value,
provenance: &mut crate::config::ProvenanceMap,
) {
crate::config::apply_processor_defaults(name, value, provenance);
crate::config::apply_scan_defaults(name, value, provenance);
}
pub fn deserialize_and_create<C: Default + DeserializeOwned>(
config_toml: &toml::Value,
ctor: fn(C) -> Box<dyn Processor>,
) -> Result<Box<dyn Processor>> {
let cfg: C = toml::from_str(&toml::to_string(config_toml)?)?;
Ok(ctor(cfg))
}
pub fn deserialize_and_try_create<C: Default + DeserializeOwned>(
config_toml: &toml::Value,
ctor: fn(C) -> Result<Box<dyn Processor>>,
) -> Result<Box<dyn Processor>> {
let cfg: C = toml::from_str(&toml::to_string(config_toml)?)?;
ctor(cfg)
}
pub fn default_config_json<C: Default + DeserializeOwned + Serialize>(
name: &str,
) -> Option<String> {
let mut val = toml::Value::Table(toml::map::Map::new());
let mut prov = crate::config::ProvenanceMap::new();
apply_all_defaults(name, &mut val, &mut prov);
let cfg: C = toml::from_str(&toml::to_string(&val).ok()?).ok()?;
let json_val = serde_json::to_value(&cfg).ok()?;
if cfg!(debug_assertions)
&& let Some(obj) = json_val.as_object()
{
use crate::config::KnownFields as _;
let known: std::collections::HashSet<&str> =
crate::config::ProcessorConfig::known_fields_for(name)
.unwrap_or_default()
.into_iter()
.chain(
crate::config::StandardConfig::known_fields()
.iter()
.copied(),
)
.chain(crate::config::SCAN_CONFIG_FIELDS.iter().copied())
.chain(crate::config::STANDARD_EXTRA_FIELDS.iter().copied())
.collect();
for key in obj.keys() {
debug_assert!(
known.contains(key.as_str()),
"Processor '{name}': default config field '{key}' is serialized but not declared in its FieldSpec list or scan fields"
);
}
}
serde_json::to_string_pretty(&json_val).ok()
}
pub fn typed_known_fields<C: crate::config::KnownFields>() -> &'static [&'static str] {
C::known_fields()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accessors_resolve_instance_names_like_type_names() {
for plugin in all_plugins() {
let instance = format!("{}.someinst", plugin.name);
assert_eq!(
processor_version(&instance),
Some(plugin.version),
"processor_version must strip the instance suffix for '{instance}'"
);
assert_eq!(processor_version(plugin.name), Some(plugin.version));
assert_eq!(
is_native(&instance),
is_native(plugin.name),
"is_native must strip the instance suffix for '{instance}'"
);
assert_eq!(
can_fix(&instance),
can_fix(plugin.name),
"can_fix must strip the instance suffix for '{instance}'"
);
assert_eq!(
description_of(&instance),
description_of(plugin.name),
"description_of must strip the instance suffix for '{instance}'"
);
}
}
}