Skip to main content

cpex_core/
factory.rs

1// Location: ./crates/cpex-core/src/factory.rs
2// Copyright 2025
3// SPDX-License-Identifier: Apache-2.0
4// Authors: Teryl Taylor
5//
6// Plugin factory registry.
7//
8// Provides a factory pattern for creating plugin instances from
9// config. The host registers factories by `kind` name before
10// loading config. When the manager processes a config file, it
11// looks up the factory for each plugin's `kind` and calls create().
12//
13// This decouples plugin instantiation from the manager — the
14// manager doesn't know how to create a "builtin" vs "wasm" vs
15// "python" plugin. The factory does.
16//
17// Mirrors the Python framework's PluginLoader in
18// cpex/framework/loader/plugin.py.
19
20use std::collections::HashMap;
21use std::sync::Arc;
22
23use crate::error::PluginError;
24use crate::plugin::{Plugin, PluginConfig};
25use crate::registry::AnyHookHandler;
26
27// ---------------------------------------------------------------------------
28// Plugin Factory Trait
29// ---------------------------------------------------------------------------
30
31/// Factory for creating plugin instances from config.
32///
33/// The host registers factories by `kind` name before loading
34/// config. When the manager processes a config file, it looks up
35/// the factory for each plugin's `kind` and calls `create()`.
36///
37/// The factory returns both the plugin and its handler because it
38/// knows the concrete types — which handler traits the plugin
39/// implements and which hooks it handles.
40///
41/// # Examples
42///
43/// ```rust,ignore
44/// struct RateLimiterFactory;
45///
46/// impl PluginFactory for RateLimiterFactory {
47///     fn create(&self, config: &PluginConfig)
48///         -> Result<PluginInstance, Box<PluginError>>
49///     {
50///         let plugin = Arc::new(RateLimiter::from_config(config)?);
51///         let handler = Arc::new(TypedHandlerAdapter::<RequestHeadersReceived, _>::new(
52///             Arc::clone(&plugin),
53///         ));
54///         Ok(PluginInstance { plugin, handler })
55///     }
56/// }
57///
58/// let mut factories = PluginFactoryRegistry::new();
59/// factories.register("security/rate_limit", Box::new(RateLimiterFactory));
60/// ```
61pub trait PluginFactory: Send + Sync {
62    /// Create a plugin instance and its handler from config.
63    ///
64    /// The `config` is the plugin's entry from the YAML file.
65    fn create(&self, config: &PluginConfig) -> Result<PluginInstance, Box<PluginError>>;
66}
67
68/// A created plugin instance — the plugin and its type-erased handlers.
69///
70/// Each handler is paired with the hook name it handles. A plugin
71/// that implements multiple hook types (e.g., `ToolPreInvoke` and
72/// `ToolPostInvoke`) returns one entry per hook.
73pub struct PluginInstance {
74    /// The plugin implementation.
75    pub plugin: Arc<dyn Plugin>,
76
77    /// Type-erased handlers paired with their hook names.
78    /// Each entry maps a hook name to the adapter for that hook type.
79    pub handlers: Vec<(&'static str, Arc<dyn AnyHookHandler>)>,
80}
81
82// ---------------------------------------------------------------------------
83// Plugin Factory Registry
84// ---------------------------------------------------------------------------
85
86/// Registry of plugin factories keyed by `kind` name.
87///
88/// The host populates this before calling `PluginManager::from_config()`.
89/// Each factory knows how to create plugins of a specific kind.
90///
91/// # Examples
92///
93/// ```rust,ignore
94/// let mut factories = PluginFactoryRegistry::new();
95/// factories.register("builtin/rate_limit", Box::new(RateLimiterFactory));
96/// factories.register("builtin/identity", Box::new(IdentityFactory));
97///
98/// let manager = PluginManager::from_config(path, &factories)?;
99/// ```
100pub struct PluginFactoryRegistry {
101    factories: HashMap<String, Box<dyn PluginFactory>>,
102}
103
104impl PluginFactoryRegistry {
105    /// Create an empty factory registry.
106    pub fn new() -> Self {
107        Self {
108            factories: HashMap::new(),
109        }
110    }
111
112    /// Register a factory for a given `kind` name.
113    ///
114    /// Registration is last-writer-wins: re-registering an existing `kind`
115    /// overrides it (this is intentional — a host can swap a builtin's impl).
116    /// Because silent override is a footgun, a warning is logged when an
117    /// existing registration is replaced.
118    pub fn register(&mut self, kind: impl Into<String>, factory: Box<dyn PluginFactory>) {
119        let kind = kind.into();
120        if self.factories.insert(kind.clone(), factory).is_some() {
121            tracing::warn!(kind = %kind, "plugin factory overrides an existing registration");
122        }
123    }
124
125    /// Look up a factory by `kind` name.
126    pub fn get(&self, kind: &str) -> Option<&dyn PluginFactory> {
127        self.factories.get(kind).map(|f| f.as_ref())
128    }
129
130    /// Whether a factory exists for the given `kind`.
131    pub fn has(&self, kind: &str) -> bool {
132        self.factories.contains_key(kind)
133    }
134
135    /// All registered kind names.
136    pub fn kinds(&self) -> Vec<&str> {
137        self.factories.keys().map(|s| s.as_str()).collect()
138    }
139}
140
141impl Default for PluginFactoryRegistry {
142    fn default() -> Self {
143        Self::new()
144    }
145}