dwara_core/plugins/registry.rs
1//! The native filter registry (DW-119).
2//!
3//! [`NativeRegistry`] maps a registered implementation name to a
4//! [`NativeFilterFactory`]. Compiled-in filters register themselves at
5//! startup (the binary or test calls [`NativeRegistry::register`]); the
6//! registry is dependency-free -- no inventory/linkme, just a function
7//! the caller invokes. The unified [`super::PluginChain`] looks up a
8//! plugin's `native` name in the registry to build the per-request
9//! filter list.
10//!
11//! The registry is `Send + Sync` (an `RwLock<HashMap<...>>`) so it can
12//! be shared across the dataplane's worker tasks.
13
14use std::collections::HashMap;
15use std::sync::RwLock;
16
17use super::filter::NativeFilter;
18
19/// A factory function: given the plugin's opaque `config` string (from
20/// [`crate::config::PluginConfig::config`]), produce a boxed native
21/// filter. The config is the same blob a WASM plugin receives via
22/// `proxy_on_configure`; a native filter parses it itself (typically
23/// JSON or YAML).
24pub type NativeFilterFactory =
25 Box<dyn Fn(&Option<String>) -> Result<Box<dyn NativeFilter>, String> + Send + Sync>;
26
27/// An error from the registry.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum RegistryError {
30 /// No factory registered under this name.
31 NotFound { name: String },
32 /// The factory returned an error constructing the filter.
33 Construction { name: String, error: String },
34 /// A factory is already registered under this name.
35 Duplicate { name: String },
36}
37
38impl std::fmt::Display for RegistryError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 RegistryError::NotFound { name } => {
42 write!(f, "native filter '{name}' is not registered")
43 }
44 RegistryError::Construction { name, error } => {
45 write!(f, "native filter '{name}' construction failed: {error}")
46 }
47 RegistryError::Duplicate { name } => {
48 write!(f, "native filter '{name}' is already registered")
49 }
50 }
51 }
52}
53
54impl std::error::Error for RegistryError {}
55
56/// The native filter registry: implementation name -> factory.
57///
58/// Created once at startup and shared (via `Arc<NativeRegistry>` or a
59/// clone, which shares the inner map through `Arc<RwLock<...>>`).
60/// Compiled-in filters register themselves by name; config selects one
61/// with `native: <name>` on a [`crate::config::PluginConfig`].
62#[derive(Clone)]
63pub struct NativeRegistry {
64 factories: std::sync::Arc<RwLock<HashMap<String, NativeFilterFactory>>>,
65}
66
67impl NativeRegistry {
68 /// Create an empty registry.
69 pub fn new() -> Self {
70 Self {
71 factories: std::sync::Arc::new(RwLock::new(HashMap::new())),
72 }
73 }
74
75 /// Register a factory under a name. Returns
76 /// [`RegistryError::Duplicate`] if the name is already taken.
77 pub fn register(
78 &self,
79 name: impl Into<String>,
80 factory: NativeFilterFactory,
81 ) -> Result<(), RegistryError> {
82 let name = name.into();
83 let mut factories = self.factories.write().unwrap();
84 if factories.contains_key(&name) {
85 return Err(RegistryError::Duplicate { name });
86 }
87 factories.insert(name, factory);
88 Ok(())
89 }
90
91 /// Whether a factory is registered under this name.
92 pub fn contains(&self, name: &str) -> bool {
93 self.factories.read().unwrap().contains_key(name)
94 }
95
96 /// The number of registered factories.
97 pub fn len(&self) -> usize {
98 self.factories.read().unwrap().len()
99 }
100
101 /// Whether the registry is empty.
102 pub fn is_empty(&self) -> bool {
103 self.factories.read().unwrap().is_empty()
104 }
105
106 /// The registered implementation names (sorted for determinism).
107 pub fn names(&self) -> Vec<String> {
108 let mut names: Vec<String> = self.factories.read().unwrap().keys().cloned().collect();
109 names.sort();
110 names
111 }
112
113 /// Construct a native filter by name, passing the plugin's config
114 /// string. Returns [`RegistryError::NotFound`] if no factory is
115 /// registered, or [`RegistryError::Construction`] if the factory
116 /// errors.
117 pub fn create(
118 &self,
119 name: &str,
120 config: &Option<String>,
121 ) -> Result<Box<dyn NativeFilter>, RegistryError> {
122 let factories = self.factories.read().unwrap();
123 let factory = factories.get(name).ok_or_else(|| RegistryError::NotFound {
124 name: name.to_string(),
125 })?;
126 // Clone the name out so the read guard can drop before calling
127 // the factory (the factory may itself call register/lookup).
128 let factory_ref: &NativeFilterFactory = factory;
129 // The factory borrows the map's value; to avoid holding the
130 // read lock across a potentially slow construction, we cannot
131 // clone a Box<dyn Fn>. Instead we accept the lock duration --
132 // construction is expected to be cheap (parse a config string).
133 // The RwLock read guard does not block other readers, only
134 // writers, and register happens only at startup.
135 factory_ref(config).map_err(|e| RegistryError::Construction {
136 name: name.to_string(),
137 error: e,
138 })
139 }
140}
141
142impl Default for NativeRegistry {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148impl std::fmt::Debug for NativeRegistry {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 let names = self.names();
151 f.debug_struct("NativeRegistry")
152 .field("registered", &names)
153 .finish()
154 }
155}