1use dashmap::{mapref::entry::Entry, DashMap};
2use std::{
3 panic::AssertUnwindSafe,
4 sync::{Arc, Mutex, MutexGuard},
5 time::{SystemTime, UNIX_EPOCH},
6};
7
8use super::{
9 limits::{
10 invalid_config, validate_identifier, validate_text, PluginConfig, MAX_PLUGIN_ID_BYTES,
11 },
12 metadata::{PluginDescriptor, PluginMetadata},
13 Plugin, PluginCategory, PluginError, PLUGIN_API_VERSION,
14};
15
16pub(super) struct PluginStats {
17 state: Mutex<PluginStatsState>,
18}
19
20#[derive(Default)]
21struct PluginStatsState {
22 execution_count: u64,
23 success_count: u64,
24 error_count: u64,
25 active_invocations: u64,
26}
27
28impl PluginStats {
29 pub(super) fn acquire_invocation(
30 self: &Arc<Self>,
31 ) -> Result<PluginInvocationLease, PluginError> {
32 let mut state = lock_stats(&self.state);
33 state.active_invocations = state
34 .active_invocations
35 .checked_add(1)
36 .ok_or(PluginError::HostStateUnavailable)?;
37 Ok(PluginInvocationLease {
38 stats: self.clone(),
39 })
40 }
41
42 fn release_invocation(&self) {
43 let mut state = lock_stats(&self.state);
44 state.active_invocations = state.active_invocations.saturating_sub(1);
45 }
46
47 fn has_active_invocation(&self) -> bool {
48 lock_stats(&self.state).active_invocations != 0
49 }
50
51 pub(super) fn record_execution(&self) {
52 let mut state = lock_stats(&self.state);
53 state.execution_count = state.execution_count.saturating_add(1);
54 }
55
56 pub(super) fn record_success(&self) {
57 let mut state = lock_stats(&self.state);
58 state.success_count = state.success_count.saturating_add(1);
59 }
60
61 pub(super) fn record_error(&self) {
62 let mut state = lock_stats(&self.state);
63 state.error_count = state.error_count.saturating_add(1);
64 }
65
66 fn snapshot(&self) -> (u64, u64, u64) {
67 let state = lock_stats(&self.state);
68 (
69 state.execution_count,
70 state.success_count,
71 state.error_count,
72 )
73 }
74}
75
76pub(super) struct PluginInvocationLease {
77 stats: Arc<PluginStats>,
78}
79
80impl Drop for PluginInvocationLease {
81 fn drop(&mut self) {
82 self.stats.release_invocation();
83 }
84}
85
86fn lock_stats(state: &Mutex<PluginStatsState>) -> MutexGuard<'_, PluginStatsState> {
87 match state.lock() {
88 Ok(guard) => guard,
89 Err(poisoned) => poisoned.into_inner(),
90 }
91}
92
93pub(super) struct PluginEntry {
94 pub(super) plugin: Arc<dyn Plugin>,
95 pub(super) config: PluginConfig,
96 descriptor: PluginDescriptor,
97 pub(super) stats: Arc<PluginStats>,
98}
99
100#[derive(Default)]
101pub struct PluginRegistry {
102 pub(super) entries: DashMap<String, PluginEntry>,
103}
104
105impl PluginRegistry {
106 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn register(
113 &self,
114 plugin: Arc<dyn Plugin>,
115 config: PluginConfig,
116 ) -> Result<(), PluginError> {
117 self.register_at(plugin, config, SystemTime::now())
118 }
119
120 pub(super) fn register_at(
121 &self,
122 plugin: Arc<dyn Plugin>,
123 config: PluginConfig,
124 now: SystemTime,
125 ) -> Result<(), PluginError> {
126 let descriptor = std::panic::catch_unwind(AssertUnwindSafe(|| {
127 let descriptor = plugin_descriptor(plugin.as_ref(), now)?;
128 validate_plugin_descriptor(&descriptor)?;
129 validate_api_version(&descriptor.api_version)?;
130 plugin
131 .validate()
132 .map_err(|_| invalid_config("plugin validation failed"))?;
133 Ok(descriptor)
134 }))
135 .map_err(|_| PluginError::Panicked)??;
136 let id = descriptor.id.clone();
137 let entry = PluginEntry {
138 plugin,
139 config,
140 descriptor,
141 stats: Arc::new(PluginStats {
142 state: Mutex::new(PluginStatsState::default()),
143 }),
144 };
145 match self.entries.entry(id) {
146 Entry::Vacant(slot) => {
147 slot.insert(entry);
148 Ok(())
149 },
150 Entry::Occupied(_) => Err(PluginError::DuplicateId),
151 }
152 }
153
154 pub fn unregister(&self, plugin_id: &str) -> Result<(), PluginError> {
156 if plugin_id.is_empty() || plugin_id.len() > MAX_PLUGIN_ID_BYTES {
157 return Err(PluginError::NotFound);
158 }
159 match self.entries.entry(plugin_id.to_owned()) {
160 Entry::Occupied(entry) if entry.get().stats.has_active_invocation() => {
161 Err(PluginError::InUse)
162 },
163 Entry::Occupied(entry) => {
164 entry.remove();
165 Ok(())
166 },
167 Entry::Vacant(_) => Err(PluginError::NotFound),
168 }
169 }
170
171 pub fn get(&self, plugin_id: &str) -> Option<Arc<dyn Plugin>> {
173 self.entries
174 .get(plugin_id)
175 .map(|entry| entry.plugin.clone())
176 }
177
178 pub fn get_metadata(&self, plugin_id: &str) -> Option<PluginMetadata> {
180 self.entries
181 .get(plugin_id)
182 .map(|entry| metadata_snapshot(&entry))
183 }
184
185 pub fn get_config(&self, plugin_id: &str) -> Option<PluginConfig> {
187 self.entries
188 .get(plugin_id)
189 .map(|entry| entry.config.clone())
190 }
191
192 pub fn update_config(&self, plugin_id: &str, config: PluginConfig) -> Result<(), PluginError> {
194 let mut entry = self
195 .entries
196 .get_mut(plugin_id)
197 .ok_or(PluginError::NotFound)?;
198 entry.config = config;
199 Ok(())
200 }
201}
202
203impl PluginRegistry {
204 pub fn list_all(&self) -> Vec<PluginMetadata> {
206 let mut metadata: Vec<_> = self
207 .entries
208 .iter()
209 .map(|entry| metadata_snapshot(&entry))
210 .collect();
211 metadata.sort_by(|left, right| left.id.cmp(&right.id));
212 metadata
213 }
214
215 pub fn list_by_category(&self, category: PluginCategory) -> Vec<PluginMetadata> {
217 self.list_all()
218 .into_iter()
219 .filter(|metadata| metadata.category == category)
220 .collect()
221 }
222
223 pub fn count(&self) -> usize {
225 self.entries.len()
226 }
227}
228
229fn metadata_snapshot(entry: &PluginEntry) -> PluginMetadata {
230 let (execution_count, success_count, error_count) = entry.stats.snapshot();
231 PluginMetadata {
232 id: entry.descriptor.id.clone(),
233 name: entry.descriptor.name.clone(),
234 version: entry.descriptor.version.clone(),
235 description: entry.descriptor.description.clone(),
236 author: entry.descriptor.author.clone(),
237 category: entry.descriptor.category,
238 api_version: entry.descriptor.api_version.clone(),
239 enabled: entry.config.enabled,
240 loaded_at: entry.descriptor.loaded_at,
241 execution_count,
242 success_count,
243 error_count,
244 }
245}
246
247fn plugin_descriptor(
248 plugin: &dyn Plugin,
249 now: SystemTime,
250) -> Result<PluginDescriptor, PluginError> {
251 let loaded_at = now
252 .duration_since(UNIX_EPOCH)
253 .map_err(|_| PluginError::ClockBeforeUnixEpoch)?
254 .as_secs();
255 Ok(PluginDescriptor {
256 id: plugin.id().to_owned(),
257 name: plugin.name().to_owned(),
258 version: plugin.version().to_owned(),
259 description: plugin.description().to_owned(),
260 author: plugin.author().to_owned(),
261 category: plugin.category(),
262 api_version: plugin.api_version().to_owned(),
263 loaded_at,
264 })
265}
266
267fn validate_plugin_descriptor(descriptor: &PluginDescriptor) -> Result<(), PluginError> {
268 validate_identifier(&descriptor.id, "plugin id", MAX_PLUGIN_ID_BYTES)?;
269 validate_text(&descriptor.name, "plugin name")?;
270 validate_identifier(&descriptor.version, "plugin version", MAX_PLUGIN_ID_BYTES)?;
271 validate_text(&descriptor.description, "plugin description")?;
272 validate_text(&descriptor.author, "plugin author")?;
273 Ok(())
274}
275
276pub(super) fn validate_api_version(actual: &str) -> Result<(), PluginError> {
277 fn line(version: &str) -> Option<(u64, u64)> {
278 let mut parts = version.split('.');
279 let major = parts.next()?.parse().ok()?;
280 let minor = parts.next()?.parse().ok()?;
281 let _patch: u64 = parts.next()?.parse().ok()?;
282 if parts.next().is_some() {
283 return None;
284 }
285 Some((major, minor))
286 }
287 let actual_line = line(actual);
288 if actual_line.is_some() && actual_line == line(PLUGIN_API_VERSION) {
289 Ok(())
290 } else {
291 Err(PluginError::IncompatibleApiVersion {
292 expected: PLUGIN_API_VERSION.to_owned(),
293 actual: if actual_line.is_some() && actual.len() <= 32 {
294 actual.to_owned()
295 } else {
296 "[invalid]".to_owned()
297 },
298 })
299 }
300}