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}