- Plugin features now use type_ prefix (meta_magic, filter_grep, etc.) - Added meta_all_musl and filter_all_musl for MUSL-compatible builds - grep filter plugin made optional via filter_grep feature flag - Removed regex crate from grep-related code, uses strip_prefix instead - Updated CHANGELOG.md with breaking change documentation
656 lines
19 KiB
Rust
656 lines
19 KiB
Rust
use log::{debug, warn};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
pub mod cwd;
|
|
pub mod digest;
|
|
pub mod env;
|
|
pub mod exec;
|
|
pub mod hostname;
|
|
#[cfg(feature = "meta_infer")]
|
|
pub mod infer_plugin;
|
|
pub mod keep_pid;
|
|
pub mod magic_file;
|
|
pub mod read_rate;
|
|
pub mod read_time;
|
|
pub mod shell;
|
|
pub mod shell_pid;
|
|
pub mod text;
|
|
#[cfg(feature = "meta_tokens")]
|
|
pub mod tokens;
|
|
#[cfg(feature = "meta_tree_magic_mini")]
|
|
pub mod tree_magic_mini;
|
|
pub mod user;
|
|
|
|
pub use digest::DigestMetaPlugin;
|
|
pub use exec::MetaPluginExec;
|
|
#[cfg(feature = "meta_magic")]
|
|
pub use magic_file::MagicFileMetaPlugin;
|
|
// pub use text::TextMetaPlugin; // Removed duplicate
|
|
pub use cwd::CwdMetaPlugin;
|
|
pub use env::EnvMetaPlugin;
|
|
pub use hostname::HostnameMetaPlugin;
|
|
#[cfg(feature = "meta_infer")]
|
|
pub use infer_plugin::InferMetaPlugin;
|
|
pub use keep_pid::KeepPidMetaPlugin;
|
|
pub use read_rate::ReadRateMetaPlugin;
|
|
pub use read_time::ReadTimeMetaPlugin;
|
|
pub use shell::ShellMetaPlugin;
|
|
pub use shell_pid::ShellPidMetaPlugin;
|
|
#[cfg(feature = "meta_tree_magic_mini")]
|
|
pub use tree_magic_mini::TreeMagicMiniMetaPlugin;
|
|
pub use user::UserMetaPlugin;
|
|
|
|
#[cfg(not(feature = "meta_magic"))]
|
|
pub use magic_file::FallbackMagicFileMetaPlugin as MagicFileMetaPlugin;
|
|
|
|
type PluginConstructor = fn(
|
|
Option<HashMap<String, serde_yaml::Value>>,
|
|
Option<HashMap<String, serde_yaml::Value>>,
|
|
) -> Box<dyn MetaPlugin>;
|
|
|
|
/// Represents metadata to be stored.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MetaData {
|
|
/// The name of the metadata field.
|
|
pub name: String,
|
|
/// The value of the metadata field.
|
|
pub value: String,
|
|
}
|
|
|
|
/// Response from meta plugin operations.
|
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
|
pub struct MetaPluginResponse {
|
|
/// The generated metadata items.
|
|
pub metadata: Vec<MetaData>,
|
|
/// Indicates if the plugin has finished processing.
|
|
pub is_finalized: bool,
|
|
}
|
|
|
|
/// Type alias for the save_meta callback shared by all plugins.
|
|
pub type SaveMetaFn = Arc<Mutex<dyn FnMut(&str, &str) + Send>>;
|
|
|
|
/// Creates a no-op save_meta for plugins not wired through MetaService.
|
|
pub fn noop_save_meta() -> SaveMetaFn {
|
|
Arc::new(Mutex::new(|_: &str, _: &str| {}))
|
|
}
|
|
|
|
/// Base implementation for meta plugins to reduce boilerplate.
|
|
#[derive(Clone)]
|
|
pub struct BaseMetaPlugin {
|
|
/// Output mappings for metadata.
|
|
pub outputs: std::collections::HashMap<String, serde_yaml::Value>,
|
|
/// Configuration options for the plugin.
|
|
pub options: std::collections::HashMap<String, serde_yaml::Value>,
|
|
/// Whether the plugin is finalized.
|
|
pub is_finalized: bool,
|
|
/// Callback to store metadata. Called directly by plugins.
|
|
pub save_meta: SaveMetaFn,
|
|
}
|
|
|
|
impl std::fmt::Debug for BaseMetaPlugin {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("BaseMetaPlugin")
|
|
.field("outputs", &self.outputs)
|
|
.field("options", &self.options)
|
|
.field("is_finalized", &self.is_finalized)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
impl Default for BaseMetaPlugin {
|
|
fn default() -> Self {
|
|
Self {
|
|
outputs: HashMap::new(),
|
|
options: HashMap::new(),
|
|
is_finalized: false,
|
|
save_meta: noop_save_meta(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl BaseMetaPlugin {
|
|
/// Creates a new `BaseMetaPlugin`.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new instance of `BaseMetaPlugin`.
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Returns a reference to the outputs mapping.
|
|
pub fn outputs(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
|
&self.outputs
|
|
}
|
|
|
|
/// Returns a mutable reference to the outputs mapping.
|
|
pub fn outputs_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
|
|
&mut self.outputs
|
|
}
|
|
|
|
/// Returns a reference to the options mapping.
|
|
pub fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
|
&self.options
|
|
}
|
|
|
|
/// Returns a mutable reference to the options mapping.
|
|
pub fn options_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
|
|
&mut self.options
|
|
}
|
|
|
|
/// Sets the save_meta callback on the base plugin.
|
|
pub fn set_save_meta(&mut self, save_meta: SaveMetaFn) {
|
|
self.save_meta = save_meta;
|
|
}
|
|
|
|
/// Saves a metadata entry via the save_meta callback.
|
|
pub fn save_meta(&self, name: &str, value: &str) {
|
|
if let Ok(mut f) = self.save_meta.lock() {
|
|
f(name, value);
|
|
} else {
|
|
warn!("META_PLUGIN: save_meta lock poisoned, dropping metadata: {name}={value}");
|
|
}
|
|
}
|
|
|
|
/// Helper function to initialize plugin options and outputs.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `default_outputs` - Slice of default output names.
|
|
/// * `options` - Optional user-provided options.
|
|
/// * `outputs` - Optional user-provided outputs.
|
|
pub fn initialize_plugin(
|
|
&mut self,
|
|
default_outputs: &[&str],
|
|
options: &Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
|
outputs: &Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
|
) {
|
|
// Set default outputs
|
|
for output_name in default_outputs {
|
|
self.outputs.insert(
|
|
output_name.to_string(),
|
|
serde_yaml::Value::String(output_name.to_string()),
|
|
);
|
|
}
|
|
|
|
// Apply provided options and outputs
|
|
if let Some(opts) = options {
|
|
for (key, value) in opts {
|
|
self.options.insert(key.clone(), value.clone());
|
|
}
|
|
}
|
|
if let Some(outs) = outputs {
|
|
for (key, value) in outs {
|
|
self.outputs.insert(key.clone(), value.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MetaPlugin for BaseMetaPlugin {
|
|
/// Returns the type of this meta plugin.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `MetaPluginType::Text` (default for base).
|
|
fn meta_type(&self) -> MetaPluginType {
|
|
// This is a base implementation, so we need to return something
|
|
// This might not be used, but we need to satisfy the trait
|
|
MetaPluginType::Text
|
|
}
|
|
|
|
/// Returns a reference to the outputs mapping.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A reference to the `HashMap` of outputs.
|
|
fn outputs(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
|
&self.outputs
|
|
}
|
|
|
|
/// Returns a mutable reference to the outputs mapping.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A mutable reference to the `HashMap` of outputs.
|
|
fn outputs_mut(
|
|
&mut self,
|
|
) -> anyhow::Result<&mut std::collections::HashMap<String, serde_yaml::Value>> {
|
|
Ok(&mut self.outputs)
|
|
}
|
|
|
|
/// Returns a reference to the options mapping.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A reference to the `HashMap` of options.
|
|
fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
|
&self.options
|
|
}
|
|
|
|
/// Returns a mutable reference to the options mapping.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A mutable reference to the `HashMap` of options.
|
|
fn options_mut(
|
|
&mut self,
|
|
) -> anyhow::Result<&mut std::collections::HashMap<String, serde_yaml::Value>> {
|
|
Ok(&mut self.options)
|
|
}
|
|
}
|
|
|
|
#[derive(
|
|
Debug,
|
|
Eq,
|
|
PartialEq,
|
|
Clone,
|
|
Hash,
|
|
strum::EnumIter,
|
|
strum::Display,
|
|
strum::EnumString,
|
|
Serialize,
|
|
Deserialize,
|
|
)]
|
|
#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
|
|
pub enum MetaPluginType {
|
|
MagicFile,
|
|
Cwd,
|
|
Text,
|
|
User,
|
|
Shell,
|
|
ShellPid,
|
|
KeepPid,
|
|
Digest,
|
|
ReadTime,
|
|
ReadRate,
|
|
Hostname,
|
|
Exec,
|
|
Env,
|
|
Tokens,
|
|
TreeMagicMini,
|
|
Infer,
|
|
}
|
|
|
|
/// Central function to handle metadata output with name mapping.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `internal_name` - The internal name of the metadata.
|
|
/// * `value` - The value to process.
|
|
/// * `outputs` - The outputs mapping.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// An optional `MetaData` if the output is enabled, `None` if disabled.
|
|
pub fn process_metadata_outputs(
|
|
internal_name: &str,
|
|
value: serde_yaml::Value,
|
|
outputs: &std::collections::HashMap<String, serde_yaml::Value>,
|
|
) -> Option<MetaData> {
|
|
// Check if this output is disabled
|
|
if let Some(mapping) = outputs.get(internal_name) {
|
|
// Check for null to disable the output
|
|
if mapping.is_null() {
|
|
debug!("META: Skipping disabled output (null): {internal_name}");
|
|
return None;
|
|
}
|
|
// Check for boolean false to disable the output
|
|
if let Some(false_val) = mapping.as_bool()
|
|
&& !false_val
|
|
{
|
|
debug!("META: Skipping disabled output: {internal_name}");
|
|
return None;
|
|
}
|
|
if let Some(custom_name) = mapping.as_str() {
|
|
let value_str = yaml_value_to_string(&value);
|
|
debug!(
|
|
"META: Processing metadata: internal_name={internal_name}, custom_name={custom_name}, value={value_str}"
|
|
);
|
|
return Some(MetaData {
|
|
name: custom_name.to_string(),
|
|
value: value_str,
|
|
});
|
|
}
|
|
}
|
|
|
|
let value_str = yaml_value_to_string(&value);
|
|
|
|
// Default: use internal name as output name
|
|
debug!("META: Processing metadata: name={internal_name}, value={value_str}");
|
|
Some(MetaData {
|
|
name: internal_name.to_string(),
|
|
value: value_str,
|
|
})
|
|
}
|
|
|
|
fn yaml_value_to_string(value: &serde_yaml::Value) -> String {
|
|
match value {
|
|
serde_yaml::Value::Null => "null".to_string(),
|
|
serde_yaml::Value::Bool(b) => b.to_string(),
|
|
serde_yaml::Value::Number(n) => n.to_string(),
|
|
serde_yaml::Value::String(s) => s.clone(),
|
|
serde_yaml::Value::Sequence(_)
|
|
| serde_yaml::Value::Mapping(_)
|
|
| serde_yaml::Value::Tagged(_) => {
|
|
serde_yaml::to_string(value).unwrap_or_else(|_| "".to_string())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait MetaPlugin: Send
|
|
where
|
|
Self: 'static,
|
|
{
|
|
/// Returns the type of this meta plugin.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The `MetaPluginType` enum variant for this plugin.
|
|
fn meta_type(&self) -> MetaPluginType;
|
|
|
|
/// Checks if the plugin is supported on the current system.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `true` if supported, `false` otherwise.
|
|
fn is_supported(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
/// Checks if the plugin is internal (built-in).
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `true` if internal, `false` otherwise.
|
|
fn is_internal(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
/// Checks if the plugin is already finalized.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `true` if finalized, `false` otherwise.
|
|
fn is_finalized(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
/// Sets the finalized state (only for plugins that can track this).
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `_finalized` - The new finalized state (unused in default).
|
|
fn set_finalized(&mut self, _finalized: bool) {}
|
|
|
|
/// Updates the meta plugin with new data.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `_data` - The data chunk to process (unused in default).
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A `MetaPluginResponse` with empty metadata and `is_finalized` set to `false`.
|
|
fn update(&mut self, _data: &[u8]) -> MetaPluginResponse {
|
|
// Default implementation does nothing
|
|
MetaPluginResponse {
|
|
metadata: Vec::new(),
|
|
is_finalized: false,
|
|
}
|
|
}
|
|
|
|
/// Finalizes the plugin.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A `MetaPluginResponse` with empty metadata and `is_finalized` set to `true`.
|
|
fn finalize(&mut self) -> MetaPluginResponse {
|
|
// Default implementation does nothing
|
|
MetaPluginResponse {
|
|
metadata: Vec::new(),
|
|
is_finalized: true,
|
|
}
|
|
}
|
|
|
|
/// Gets program information for display in status.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// An optional tuple of program name and arguments, or `None`.
|
|
fn program_info(&self) -> Option<(&str, Vec<&str>)> {
|
|
None
|
|
}
|
|
|
|
/// Initializes the plugin.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A `MetaPluginResponse` with empty metadata and `is_finalized` set to `false`.
|
|
fn initialize(&mut self) -> MetaPluginResponse {
|
|
// Default implementation does nothing
|
|
MetaPluginResponse {
|
|
metadata: Vec::new(),
|
|
is_finalized: false,
|
|
}
|
|
}
|
|
|
|
/// Returns a reference to the outputs mapping.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// An empty `HashMap` (default implementation).
|
|
fn outputs(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
|
use std::sync::LazyLock;
|
|
static EMPTY: LazyLock<std::collections::HashMap<String, serde_yaml::Value>> =
|
|
LazyLock::new(std::collections::HashMap::new);
|
|
&EMPTY
|
|
}
|
|
|
|
/// Returns a mutable reference to the outputs mapping.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A mutable reference to the outputs `HashMap`.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error if the plugin does not support mutable outputs.
|
|
fn outputs_mut(
|
|
&mut self,
|
|
) -> anyhow::Result<&mut std::collections::HashMap<String, serde_yaml::Value>> {
|
|
anyhow::bail!("outputs_mut() not supported by this plugin")
|
|
}
|
|
|
|
/// Returns a reference to the options mapping.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// An empty `HashMap` (default implementation).
|
|
fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
|
use std::sync::LazyLock;
|
|
static EMPTY: LazyLock<std::collections::HashMap<String, serde_yaml::Value>> =
|
|
LazyLock::new(std::collections::HashMap::new);
|
|
&EMPTY
|
|
}
|
|
|
|
/// Returns a mutable reference to the options mapping.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A mutable reference to the options `HashMap`.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns an error if the plugin does not support mutable options.
|
|
fn options_mut(
|
|
&mut self,
|
|
) -> anyhow::Result<&mut std::collections::HashMap<String, serde_yaml::Value>> {
|
|
anyhow::bail!("options_mut() not supported by this plugin")
|
|
}
|
|
|
|
/// Gets the default output names this plugin can produce.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A vector containing the meta type as a string (default).
|
|
fn default_outputs(&self) -> Vec<String> {
|
|
// Default implementation returns the meta type as a string
|
|
vec![self.meta_type().to_string()]
|
|
}
|
|
|
|
/// Returns a description of this plugin for display in config templates.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A description string (empty by default).
|
|
fn description(&self) -> &str {
|
|
""
|
|
}
|
|
|
|
/// Returns true if this plugin can execute concurrently with other
|
|
/// parallel-safe plugins.
|
|
///
|
|
/// Plugins that do significant per-chunk work (hashing, tokenization,
|
|
/// piping to child processes) should return true. The MetaService will
|
|
/// run all parallel-safe plugins in separate threads per phase, then
|
|
/// process results sequentially.
|
|
fn parallel_safe(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
/// Builds the schema for this plugin from its options and outputs.
|
|
///
|
|
/// Default implementation infers option types from YAML values and
|
|
/// collects enabled outputs.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A `PluginSchema` describing this plugin's configuration.
|
|
fn schema(&self) -> crate::common::schema::PluginSchema {
|
|
use crate::common::schema::{OptionSchema, OptionType, OutputSchema, PluginSchema};
|
|
|
|
let options: Vec<OptionSchema> = self
|
|
.options()
|
|
.iter()
|
|
.map(|(key, value)| {
|
|
let option_type = OptionType::from_yaml_value(value);
|
|
let (default, required) = if value.is_null() {
|
|
(None, true)
|
|
} else {
|
|
(Some(value.clone()), false)
|
|
};
|
|
OptionSchema {
|
|
name: key.clone(),
|
|
option_type,
|
|
default,
|
|
required,
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let mut outputs: Vec<OutputSchema> = Vec::new();
|
|
for (key, value) in self.outputs() {
|
|
if !value.is_null() {
|
|
outputs.push(OutputSchema {
|
|
name: key.clone(),
|
|
description: key.clone(),
|
|
});
|
|
}
|
|
}
|
|
if outputs.is_empty() {
|
|
for output_name in self.default_outputs() {
|
|
outputs.push(OutputSchema {
|
|
name: output_name.clone(),
|
|
description: output_name,
|
|
});
|
|
}
|
|
}
|
|
|
|
PluginSchema {
|
|
name: self.meta_type().to_string(),
|
|
description: self.description().to_string(),
|
|
options,
|
|
outputs,
|
|
}
|
|
}
|
|
|
|
/// Method to downcast to concrete type (for checking finalization state).
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A mutable reference to `self` as `dyn Any`.
|
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any
|
|
where
|
|
Self: Sized,
|
|
{
|
|
self
|
|
}
|
|
|
|
/// Sets the save_meta callback for this plugin.
|
|
///
|
|
/// Called by MetaService to wire the plugin to the metadata storage.
|
|
fn set_save_meta(&mut self, _save_meta: SaveMetaFn) {}
|
|
|
|
/// Saves a metadata entry via the save_meta callback.
|
|
///
|
|
/// Plugins call this during initialize/update/finalize to persist metadata.
|
|
fn save_meta(&self, _name: &str, _value: &str) {}
|
|
}
|
|
|
|
/// Global registry for meta plugins.
|
|
static META_PLUGIN_REGISTRY: std::sync::LazyLock<
|
|
Mutex<HashMap<MetaPluginType, PluginConstructor>>,
|
|
> = std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
|
|
|
|
/// Register a meta plugin with the global registry.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `meta_plugin_type` - The type of the meta plugin to register.
|
|
/// * `constructor` - The constructor function for creating plugin instances.
|
|
pub fn register_meta_plugin(
|
|
meta_plugin_type: MetaPluginType,
|
|
constructor: PluginConstructor,
|
|
) -> anyhow::Result<()> {
|
|
META_PLUGIN_REGISTRY
|
|
.lock()
|
|
.map_err(|e| anyhow::anyhow!("plugin registry poisoned: {e}"))?
|
|
.insert(meta_plugin_type, constructor);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_meta_plugin(
|
|
meta_plugin_type: MetaPluginType,
|
|
options: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
|
outputs: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
|
) -> anyhow::Result<Box<dyn MetaPlugin>> {
|
|
get_meta_plugin_with_save(meta_plugin_type, options, outputs, None)
|
|
}
|
|
|
|
/// Creates a meta plugin instance with an optional save_meta callback.
|
|
///
|
|
/// If `save_meta` is provided, it is wired to the plugin so it can
|
|
/// store metadata directly during initialize/update/finalize.
|
|
pub fn get_meta_plugin_with_save(
|
|
meta_plugin_type: MetaPluginType,
|
|
options: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
|
outputs: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
|
save_meta: Option<SaveMetaFn>,
|
|
) -> anyhow::Result<Box<dyn MetaPlugin>> {
|
|
let registry = META_PLUGIN_REGISTRY
|
|
.lock()
|
|
.map_err(|e| anyhow::anyhow!("plugin registry poisoned: {e}"))?;
|
|
if let Some(constructor) = registry.get(&meta_plugin_type) {
|
|
let mut plugin = constructor(options, outputs);
|
|
if let Some(sm) = save_meta {
|
|
plugin.set_save_meta(sm);
|
|
}
|
|
return Ok(plugin);
|
|
}
|
|
|
|
anyhow::bail!("Meta plugin {meta_plugin_type:?} not registered")
|
|
}
|