steckrs/
lib.rs

1//! # steckrs
2//!
3//! A lightweight, trait-based plugin system for Rust applications and libraries.
4//!
5//! ## What is steckrs?
6//!
7//! "steckrs" is a wordplay combining the German word "Stecker" (meaning "plug" or "connector") and
8//! the Rust file extension (.rs). The library provides a flexible, type-safe plugin architecture
9//! for Rust applications, allowing developers to:
10//!
11//! - Define extension points in their applications
12//! - Create plugins that integrate with these extension points
13//! - Dynamically manage plugins (loading, enabling, disabling, unloading)
14//! - Register and invoke hooks with proper type safety
15//!
16//! ## Core Concepts
17//!
18//! ### Extension Points
19//!
20//! [Extension points](crate::hook::ExtensionPoint) define interfaces where plugins can add functionality. Each extension point:
21//! - Is defined as a trait that plugins implement
22//! - Specifies the contract that plugins must fulfill
23//! - Provides type-safe interaction between the core application and plugins
24//!
25//! ### Plugins
26//!
27//! [Plugins](Plugin) are self-contained modules that implement functionality for extension points.
28//! Each plugin:
29//! - Has a unique identifier
30//! - Can be enabled or disabled at runtime
31//! - Can register multiple hooks to different extension points
32//! - Has lifecycle methods ([`on_load`](Plugin::on_load), [`on_unload`](Plugin::on_unload))
33//!
34//! ### Hooks
35//!
36//! [Hooks](crate::hook::Hook) are implementations of extension points that plugins register. They:
37//! - Implement the trait defined by an extension point
38//! - Are invoked when the application calls that extension point
39//! - Can be uniquely identified by their plugin ID, extension point, and optional discriminator
40//!
41//! ## Logs
42//!
43//! This library logs certain events with the [`tracing`] library.
44//!
45//! ## Usage Example
46//!
47//! Here's a simple example of how to use steckrs to create a plugin-enabled application:
48//!
49//! ```rust
50//! use steckrs::{extension_point, simple_plugin, PluginManager};
51//!
52//! // Define an extension point
53//! extension_point!(
54//!     GreeterExtension: GreeterTrait;
55//!     fn greet(&self, name: &str) -> String;
56//! );
57//!
58//! // Create a plugin
59//! simple_plugin!(
60//!     HelloPlugin,
61//!     "hello_plugin",
62//!     "A simple greeting plugin",
63//!     hooks: [(GreeterExtension, EnglishGreeter)]
64//! );
65//!
66//! // Implement a hook
67//! struct EnglishGreeter;
68//! impl GreeterTrait for EnglishGreeter {
69//!     fn greet(&self, name: &str) -> String {
70//!         format!("Hello, {}!", name)
71//!     }
72//! }
73//!
74//! // Create plugin manager
75//! let mut plugin_manager = PluginManager::new();
76//!
77//! // Load and enable the plugin
78//! plugin_manager.load_plugin(Box::new(HelloPlugin::new())).unwrap();
79//! plugin_manager.enable_plugin(HelloPlugin::ID).unwrap();
80//!
81//! // Get all enabled hooks (plugins could be disabled)
82//! let hooks = plugin_manager.get_enabled_hooks_by_ep::<GreeterExtension>();
83//!
84//! // execute all hooks relevant for this extension point
85//! for (_id, hook) in hooks {
86//!     println!("{}", hook.inner().greet("World"));
87//! }
88//! ```
89//!
90//! ## Macros
91//!
92//! steckrs provides several convenience macros to reduce boilerplate:
93//!
94//! - [`extension_point!`] - Defines an extension point and its associated trait
95//! - [`simple_plugin!`] - Creates a simple plugin with minimal boilerplate
96//! - [`register_hook!`] - Registers a hook with the hook registry
97//!
98//! Note that [`register_hook!`] is not needed if you generate your plugin with [`simple_plugin!`].
99//!
100//! ## Advanced Usage
101//!
102//! For more complex scenarios, you can implement the [`Plugin`] trait directly,
103//! allowing for more customized plugin behavior and state management.
104
105#![warn(missing_docs)]
106#![warn(clippy::missing_errors_doc)]
107#![warn(clippy::missing_panics_doc)]
108#![warn(clippy::missing_safety_doc)]
109#![warn(clippy::panic)]
110#![warn(clippy::todo)]
111#![warn(clippy::pedantic)]
112#![warn(clippy::all)]
113#![warn(clippy::empty_docs)]
114
115use std::any::Any;
116use std::collections::HashMap;
117use std::fmt::Debug;
118
119pub mod error;
120pub mod hook;
121pub mod macros;
122
123use tracing::{error, warn};
124
125use self::error::{PluginError, PluginResult};
126use self::hook::{ExtensionPoint, HookRegistry};
127
128/// Plugin identifier type.
129///
130/// Every plugin must have a unique identifier. This type is used to identify plugins within
131/// the [steckrs](crate) system. It's implemented as a static string reference for efficiency and
132/// simplicity.
133///
134/// See also [`PluginIDOwned`], which can be owned and provides serialization support, if you need
135/// that.
136///
137/// # Examples
138///
139/// ```
140/// let id: steckrs::PluginID = "hello_world_plugin";
141/// ```
142pub type PluginID = &'static str;
143
144/// An owned version of [`PluginID`] that can be owned and has optional serialization support with
145/// [serde].
146///
147/// This type wraps a static string reference and provides implementations for
148/// conversion to/from [`PluginID`], as well as serialization support when the
149/// [`serde`] feature is enabled.
150///
151/// This type is particularly useful when working with serialization frameworks,
152/// as it allows plugin identifiers to be properly serialized and deserialized.
153///
154/// If you deserialize a string into this datastructure, please note that this uses an internal
155/// leak mechanism ([`String::leak`]) to make sure that the actual data of the plugin id will always exist (making it
156/// `'static`). That means that you may take up more memory than expected if you deserialize huge
157/// amoungs of plugin ids.
158///
159/// # Examples
160///
161/// ```
162/// use steckrs::{PluginIDOwned, PluginID};
163///
164/// let plugin_id: PluginID = "my_plugin";
165///
166/// // Create from owned id
167/// let id = PluginIDOwned::from(plugin_id);
168///
169/// // Convert back to a PluginID
170/// let plugin_id2: PluginID = id.into();
171///
172/// assert_eq!(plugin_id, plugin_id2);
173/// ```
174///
175/// # Serialization
176///
177/// When the `serde` feature is enabled, this type implements [`Serialize`](serde::Serialize)
178/// and [`Deserialize`](serde::Deserialize). Note that deserialization involves a memory leak,
179/// as the string is converted to a `&'static str` by leaking memory (to make sure it is always
180/// existing in memory).
181///
182/// The leaking is using safe rust with [`String::leak`].
183#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize))]
185pub struct PluginIDOwned {
186    inner: &'static str,
187}
188
189impl From<PluginID> for PluginIDOwned {
190    fn from(value: PluginID) -> Self {
191        Self { inner: value }
192    }
193}
194
195impl From<PluginIDOwned> for PluginID {
196    fn from(value: PluginIDOwned) -> Self {
197        value.inner
198    }
199}
200
201impl std::fmt::Display for PluginIDOwned {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        std::fmt::Display::fmt(&self.inner, f)
204    }
205}
206
207#[cfg(feature = "serde")]
208impl<'de> serde::Deserialize<'de> for PluginIDOwned {
209    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
210    where
211        D: serde::Deserializer<'de>,
212    {
213        struct PluginIDVisitor;
214
215        impl serde::de::Visitor<'_> for PluginIDVisitor {
216            type Value = PluginIDOwned;
217
218            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
219                formatter.write_str("a string")
220            }
221
222            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
223            where
224                E: serde::de::Error,
225            {
226                Ok(PluginIDOwned {
227                    // Convert the string to a 'static str by leaking memory
228                    inner: value.to_string().leak(),
229                })
230            }
231        }
232
233        deserializer.deserialize_str(PluginIDVisitor)
234    }
235}
236
237/// Plugin trait that must be implemented by all plugins.
238///
239/// This trait defines the interface for plugin lifecycle management,
240/// including loading, enabling, disabling, and unloading operations.
241///
242/// # Macros
243///
244/// Most users will find the [`simple_plugin!`] macro sufficient.
245///
246///
247/// ```
248/// # use steckrs::{extension_point, simple_plugin, PluginManager};
249/// # extension_point!(
250/// #     GreeterExtension: GreeterTrait;
251/// #     fn greet(&self, name: &str) -> String;
252/// # );
253/// #
254/// # struct EnglishGreeter;
255/// # impl GreeterTrait for EnglishGreeter {
256/// #     fn greet(&self, name: &str) -> String {
257/// #         format!("Hello, {}!", name)
258/// #     }
259/// # }
260/// #
261/// simple_plugin!(
262///     HelloPlugin,
263///     "hello_plugin",
264///     "A simple greeting plugin",
265///     hooks: [(GreeterExtension, EnglishGreeter)]
266/// );
267/// ```
268///
269/// # Examples
270///
271/// ```
272/// use steckrs::{Plugin, error::PluginResult, hook::HookRegistry};
273///
274/// #[derive(Debug)]
275/// struct MyPlugin {
276///     enabled: bool,
277/// }
278///
279/// impl Plugin for MyPlugin {
280///     fn id(&self) -> steckrs::PluginID {
281///         // recommendation: add an associated constant for the ID
282///         "my_plugin"
283///     }
284///
285///     fn description(&self) -> &str {
286///         // recommendation: add an associated constant for the DESCRIPTION
287///         "A custom plugin implementation"
288///     }
289///
290///     fn is_enabled(&self) -> bool {
291///         self.enabled
292///     }
293///
294///     fn enable(&mut self) {
295///         self.enabled = true;
296///     }
297///
298///     fn disable(&mut self) {
299///         self.enabled = false;
300///     }
301///
302///     fn register_hooks(&self, registry: &mut HookRegistry) -> PluginResult<()> {
303///         // Register hooks here
304///         Ok(())
305///     }
306///
307///     // optionally define on_load and on_unload
308/// }
309/// ```
310pub trait Plugin: Any + Send + Sync + Debug {
311    /// Returns the unique identifier for this plugin.
312    ///
313    /// The ID must be unique across all loaded plugins.
314    fn id(&self) -> PluginID;
315
316    /// Returns a human-readable description of the plugin.
317    fn description(&self) -> &str;
318
319    /// Returns whether the plugin is currently enabled.
320    fn is_enabled(&self) -> bool;
321
322    /// Enables the plugin, allowing its hooks to be used.
323    fn enable(&mut self);
324
325    /// Disables the plugin, preventing its hooks from being used.
326    fn disable(&mut self);
327
328    /// Registers this plugin's [Hooks](crate::hook::Hook) with the [`HookRegistry`].
329    ///
330    /// This method is called during plugin loading, and should register
331    /// all hooks that the plugin provides.
332    ///
333    /// # Errors
334    ///
335    /// Returns a `PluginError` if hook registration fails./
336    fn register_hooks(&self, registry: &mut HookRegistry) -> PluginResult<()>;
337
338    /// Called when the plugin is loaded.
339    ///
340    /// Provides an opportunity to perform initialization that should happen
341    /// when the plugin is first loaded, before hooks are used.
342    ///
343    /// This function is always called after [`register_hooks`](Plugin::register_hooks).
344    ///
345    /// # Errors
346    ///
347    /// Returns a [`PluginError`] if loading fails.
348    fn on_load(&mut self) -> PluginResult<()> {
349        Ok(())
350    }
351
352    /// Called when the plugin is unloaded.
353    ///
354    /// Provides an opportunity to perform cleanup before the plugin is removed.
355    ///
356    /// # Errors
357    ///
358    /// Returns a [`PluginError`] if the unloading cleanup fails.
359    fn on_unload(&mut self) -> PluginResult<()> {
360        Ok(())
361    }
362}
363
364/// Manages plugin loading, execution, and lifecycle.
365///
366/// The [`PluginManager`] is the core component of the steckrs plugin system,
367/// responsible for:
368/// - Loading and unloading plugins
369/// - Enabling and disabling plugins
370/// - Maintaining the hook registry
371/// - Tracking loaded plugins
372///
373/// # Examples
374///
375/// ```
376/// use steckrs::{PluginManager, simple_plugin, extension_point};
377///
378/// // Define extension point
379/// extension_point!(
380///     ExampleExt: ExampleTrait;
381///     fn do_something(&self) -> &'static str;
382/// );
383///
384/// // Define plugin
385/// simple_plugin!(
386///     ExamplePlugin,
387///     "example_plugin",
388///     "An example plugin",
389///     hooks: [(ExampleExt, ExampleHook)]
390/// );
391///
392/// // Hook implementation
393/// struct ExampleHook;
394/// impl ExampleTrait for ExampleHook {
395///     fn do_something(&self) -> &'static str {
396///         "I did something!"
397///     }
398/// }
399///
400/// // Plugin management
401/// let mut manager = PluginManager::new();
402/// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
403/// manager.enable_plugin(ExamplePlugin::ID).unwrap();
404///
405/// // Use plugin hooks
406/// // Get all enabled hooks (plugins could be disabled)
407/// let hooks = manager.get_enabled_hooks_by_ep::<ExampleExt>();
408/// for (_id, hook) in hooks {
409///     assert_eq!(hook.inner().do_something(), "I did something!");
410/// }
411/// ```
412#[derive(Debug, Default)]
413pub struct PluginManager {
414    plugins: HashMap<PluginID, Box<dyn Plugin>>,
415    hook_registry: HookRegistry,
416}
417
418impl PluginManager {
419    /// Creates a new empty plugin manager.
420    ///
421    /// # Examples
422    ///
423    /// ```
424    /// use steckrs::PluginManager;
425    ///
426    /// let manager = PluginManager::new();
427    /// assert_eq!(manager.plugin_ids().len(), 0);
428    /// ```
429    #[must_use]
430    pub fn new() -> Self {
431        Self {
432            plugins: HashMap::new(),
433            hook_registry: HookRegistry::new(),
434        }
435    }
436
437    /// Creates a new plugin manager with an existing hook registry.
438    ///
439    /// This allows sharing a hook registry between multiple plugin managers,
440    /// which can be useful for complex applications.
441    ///
442    /// # Examples
443    ///
444    /// ```
445    /// use steckrs::{PluginManager, hook::HookRegistry};
446    ///
447    /// let registry = HookRegistry::new();
448    /// let manager = PluginManager::with_registry(registry);
449    /// ```
450    #[must_use]
451    pub fn with_registry(hook_registry: HookRegistry) -> Self {
452        Self {
453            plugins: HashMap::new(),
454            hook_registry,
455        }
456    }
457
458    /// Returns a reference to the hook registry.
459    ///
460    /// The hook registry contains all registered hooks from loaded plugins.
461    #[must_use]
462    pub fn hook_registry(&self) -> &HookRegistry {
463        &self.hook_registry
464    }
465
466    /// Returns a mutable reference to the hook registry.
467    ///
468    /// This can be used to directly manipulate the hook registry if needed.
469    #[must_use]
470    pub fn hook_registry_mut(&mut self) -> &mut HookRegistry {
471        &mut self.hook_registry
472    }
473
474    /// Loads a plugin into the plugin manager.
475    ///
476    /// This will:
477    /// 1. Register the plugin's hooks in the hook registry
478    /// 2. Call the plugin's `on_load` method
479    /// 3. Store the plugin in the manager
480    ///
481    /// # Errors
482    ///
483    /// Returns a `PluginError` if:
484    /// - A plugin with the same ID is already loaded
485    /// - The plugin's [`on_load`](Plugin::register_hooks) method fails
486    /// - The plugin's [`on_load`](Plugin::on_load) method fails
487    ///
488    /// If any of the steps fail, this function will try to unload the half-loaded plugin again,
489    /// using [`unload_plugin`](Self::unload_plugin).
490    ///
491    /// # Panics
492    ///
493    /// If loading of the plugin and then unloading the half-loaded plugin both fail, this function
494    /// will panic.
495    ///
496    /// # Examples
497    ///
498    /// ```
499    /// use steckrs::{PluginManager, simple_plugin};
500    ///
501    /// simple_plugin!(
502    ///     ExamplePlugin,
503    ///     "example_plugin",
504    ///     "An example plugin",
505    ///     hooks: []
506    /// );
507    ///
508    /// let mut manager = PluginManager::new();
509    /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
510    /// assert!(manager.get_plugin("example_plugin").is_some());
511    /// ```
512    pub fn load_plugin(&mut self, mut plugin: Box<dyn Plugin>) -> PluginResult<()> {
513        let id = plugin.id();
514        if self.plugins.contains_key(id) {
515            return Err(error::PluginError::AlreadyLoaded(id.into()));
516        }
517
518        // register the hooks
519        if let Err(e) = plugin.register_hooks(self.hook_registry_mut()) {
520            self.handle_error_during_load(&e, id);
521            return Err(e);
522        }
523        // Load the plugin
524        if let Err(e) = plugin.on_load() {
525            self.handle_error_during_load(&e, id);
526            return Err(e);
527        }
528
529        // Store the plugin
530        self.plugins.insert(id, plugin);
531
532        Ok(())
533    }
534
535    /// Internal helper to handle errors during plugin loading.
536    ///
537    /// If a plugin fails during loading, this will attempt to clean up
538    /// by unloading the plugin.
539    fn handle_error_during_load(&mut self, e: &PluginError, plugin_id: PluginID) {
540        error!("Could not register hooks of plugin {plugin_id}: {e}");
541        warn!("Trying to unload the plugin again... Will crash if this fails");
542        self.unload_plugin(plugin_id)
543            .expect("Could not unload bad plugin again");
544    }
545
546    /// Unloads a plugin by ID.
547    ///
548    /// This will:
549    /// 1. Call the plugin's `on_unload` method for cleanup
550    /// 2. Remove all hooks registered by the plugin
551    /// 3. Remove the plugin from the manager
552    ///
553    /// # Errors
554    ///
555    /// Returns a [`PluginError`] if:
556    /// - The plugin's [`on_unload`](Plugin::on_unload) method fails
557    ///
558    /// # Examples
559    ///
560    /// ```
561    /// use steckrs::{PluginManager, simple_plugin};
562    ///
563    /// simple_plugin!(
564    ///     ExamplePlugin,
565    ///     "example_plugin",
566    ///     "An example plugin",
567    ///     hooks: []
568    /// );
569    ///
570    /// let mut manager = PluginManager::new();
571    /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
572    /// manager.unload_plugin("example_plugin").unwrap();
573    /// assert!(manager.get_plugin("example_plugin").is_none());
574    /// ```
575    pub fn unload_plugin(&mut self, id: PluginID) -> PluginResult<()> {
576        if let Some(mut plugin) = self.plugins.remove(id) {
577            // Call on_unload for cleanup
578            plugin.on_unload()?;
579
580            // Remove all hooks registered by this plugin
581            self.hook_registry.deregister_hooks_for_plugin(id);
582        }
583        Ok(())
584    }
585
586    /// Gets a reference to a plugin by ID.
587    ///
588    /// # Examples
589    ///
590    /// ```
591    /// use steckrs::{PluginManager, simple_plugin};
592    ///
593    /// simple_plugin!(
594    ///     ExamplePlugin,
595    ///     "example_plugin",
596    ///     "An example plugin",
597    ///     hooks: []
598    /// );
599    ///
600    /// let mut manager = PluginManager::new();
601    /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
602    ///
603    /// let plugin = manager.get_plugin("example_plugin");
604    /// assert!(plugin.is_some());
605    /// assert_eq!(plugin.unwrap().id(), "example_plugin");
606    /// ```
607    #[must_use]
608    pub fn get_plugin(&self, id: PluginID) -> Option<&dyn Plugin> {
609        self.plugins.get(id).map(std::convert::AsRef::as_ref)
610    }
611
612    /// Gets a mutable reference to a plugin by ID.
613    ///
614    /// This can be used to modify a plugin's state after it's been loaded.
615    ///
616    /// # Examples
617    ///
618    /// ```
619    /// use steckrs::{PluginManager, simple_plugin};
620    ///
621    /// simple_plugin!(
622    ///     ExamplePlugin,
623    ///     "example_plugin",
624    ///     "An example plugin",
625    ///     hooks: []
626    /// );
627    ///
628    /// let mut manager = PluginManager::new();
629    /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
630    ///
631    /// let plugin = manager.get_plugin_mut("example_plugin");
632    /// assert!(plugin.is_some());
633    /// ```
634    #[must_use]
635    pub fn get_plugin_mut(&mut self, id: PluginID) -> Option<&mut dyn Plugin> {
636        self.plugins.get_mut(id).map(std::convert::AsMut::as_mut)
637    }
638
639    /// Gets all plugin IDs.
640    ///
641    /// # Examples
642    ///
643    /// ```
644    /// use steckrs::{PluginManager, simple_plugin};
645    ///
646    /// simple_plugin!(
647    ///     Plugin1,
648    ///     "plugin1",
649    ///     "First plugin",
650    ///     hooks: []
651    /// );
652    ///
653    /// simple_plugin!(
654    ///     Plugin2,
655    ///     "plugin2",
656    ///     "Second plugin",
657    ///     hooks: []
658    /// );
659    ///
660    /// let mut manager = PluginManager::new();
661    /// manager.load_plugin(Box::new(Plugin1::new())).unwrap();
662    /// manager.load_plugin(Box::new(Plugin2::new())).unwrap();
663    ///
664    /// let ids = manager.plugin_ids();
665    /// assert_eq!(ids.len(), 2);
666    /// assert!(ids.contains(&"plugin1"));
667    /// assert!(ids.contains(&"plugin2"));
668    /// ```
669    #[must_use]
670    pub fn plugin_ids(&self) -> Vec<PluginID> {
671        self.plugins.keys().copied().collect()
672    }
673
674    /// Gets all plugins.
675    ///
676    /// # Examples
677    ///
678    /// ```
679    /// use steckrs::{PluginManager, simple_plugin};
680    ///
681    /// simple_plugin!(
682    ///     Plugin1,
683    ///     "plugin1",
684    ///     "First plugin",
685    ///     hooks: []
686    /// );
687    ///
688    /// let mut manager = PluginManager::new();
689    /// manager.load_plugin(Box::new(Plugin1::new())).unwrap();
690    ///
691    /// let plugins = manager.plugins();
692    /// assert_eq!(plugins.len(), 1);
693    /// assert_eq!(plugins[0].id(), "plugin1");
694    /// ```
695    #[must_use]
696    pub fn plugins(&self) -> Vec<&dyn Plugin> {
697        self.plugins
698            .values()
699            .map(std::convert::AsRef::as_ref)
700            .collect()
701    }
702
703    /// Gets all enabled plugins.
704    ///
705    /// # Examples
706    ///
707    /// ```
708    /// use steckrs::{PluginManager, simple_plugin};
709    ///
710    /// simple_plugin!(
711    ///     Plugin1,
712    ///     "plugin1",
713    ///     "First plugin",
714    ///     hooks: []
715    /// );
716    ///
717    /// simple_plugin!(
718    ///     Plugin2,
719    ///     "plugin2",
720    ///     "Second plugin",
721    ///     hooks: []
722    /// );
723    ///
724    /// let mut manager = PluginManager::new();
725    /// manager.load_plugin(Box::new(Plugin1::new())).unwrap();
726    /// manager.load_plugin(Box::new(Plugin2::new())).unwrap();
727    /// manager.enable_plugin("plugin1").unwrap();
728    ///
729    /// let enabled = manager.enabled_plugins();
730    /// assert_eq!(enabled.len(), 1);
731    /// assert_eq!(enabled[0].id(), "plugin1");
732    /// ```
733    #[must_use]
734    pub fn enabled_plugins(&self) -> Vec<&dyn Plugin> {
735        self.plugins
736            .values()
737            .filter(|p| p.is_enabled())
738            .map(std::convert::AsRef::as_ref)
739            .collect()
740    }
741
742    /// Quickly check if a [`Plugin`] with a specific [`PluginID`] is enabled.
743    ///
744    /// This will return [`None`] if the [`Plugin`] with that [`PluginID`] was not found, otherwise
745    /// `Some(enabled)`, where `enabled` is gotten with [`Plugin::is_enabled`].
746    ///
747    /// # Examples
748    ///
749    /// ```
750    /// use steckrs::{PluginManager, simple_plugin};
751    ///
752    /// simple_plugin!(
753    ///     Plugin,
754    ///     "plugin",
755    ///     "Some plugin",
756    ///     hooks: []
757    /// );
758    ///
759    /// let mut manager = PluginManager::new();
760    /// manager.load_plugin(Box::new(Plugin::new())).unwrap();
761    /// manager.enable_plugin("plugin").unwrap();
762    ///
763    /// assert_eq!(manager.plugin_is_enabled("plugin"), Some(true));
764    /// assert_eq!(manager.plugin_is_enabled("nope"), None);
765    ///
766    /// manager.disable_plugin("plugin").unwrap();
767    ///
768    /// assert_eq!(manager.plugin_is_enabled("plugin"), Some(false));
769    /// ```
770    #[inline]
771    #[must_use]
772    pub fn plugin_is_enabled(&self, id: PluginID) -> Option<bool> {
773        Some(self.plugins.get(id)?.is_enabled())
774    }
775
776    /// Enables a plugin by ID.
777    ///
778    /// Note that plugins are disabled by default
779    ///
780    /// # Errors
781    ///
782    /// Returns a [`PluginError::NotFound`] if no plugin with the given ID is loaded.
783    ///
784    /// # Examples
785    ///
786    /// ```
787    /// use steckrs::{PluginManager, simple_plugin};
788    ///
789    /// simple_plugin!(
790    ///     ExamplePlugin,
791    ///     "example_plugin",
792    ///     "An example plugin",
793    ///     hooks: []
794    /// );
795    ///
796    /// let mut manager = PluginManager::new();
797    /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
798    /// manager.enable_plugin("example_plugin").unwrap();
799    ///
800    /// let plugin = manager.get_plugin("example_plugin").unwrap();
801    /// assert!(plugin.is_enabled());
802    /// ```
803    pub fn enable_plugin(&mut self, id: PluginID) -> PluginResult<()> {
804        match self.plugins.get_mut(id) {
805            Some(plugin) => {
806                plugin.enable();
807                Ok(())
808            }
809            None => Err(error::PluginError::NotFound(id.into())),
810        }
811    }
812
813    /// Disables a plugin by ID.
814    ///
815    /// # Errors
816    ///
817    /// Returns a [`PluginError::NotFound`] if no plugin with the given ID is loaded.
818    ///
819    /// # Examples
820    ///
821    /// ```
822    /// use steckrs::{PluginManager, simple_plugin};
823    ///
824    /// simple_plugin!(
825    ///     ExamplePlugin,
826    ///     "example_plugin",
827    ///     "An example plugin",
828    ///     hooks: []
829    /// );
830    ///
831    /// let mut manager = PluginManager::new();
832    /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
833    /// manager.enable_plugin("example_plugin").unwrap();
834    /// manager.disable_plugin("example_plugin").unwrap();
835    ///
836    /// let plugin = manager.get_plugin("example_plugin").unwrap();
837    /// assert!(!plugin.is_enabled());
838    /// ```
839    pub fn disable_plugin(&mut self, id: PluginID) -> PluginResult<()> {
840        match self.plugins.get_mut(id) {
841            Some(plugin) => {
842                plugin.disable();
843                Ok(())
844            }
845            None => Err(error::PluginError::NotFound(id.into())),
846        }
847    }
848
849    /// Gets all hooks of enabled [Plugins](Plugin) for a specific [`ExtensionPoint`] type.
850    ///
851    /// This method filters hooks by both extension point type and plugin enabled status,
852    /// returning only hooks from enabled plugins.
853    ///
854    /// # Type Parameters
855    ///
856    /// - `E`: The [`ExtensionPoint`] type
857    ///
858    /// # Returns
859    ///
860    /// A vector of tuples containing references to [`HookID`](crate::hook::HookID)s and hooks registered for the [`ExtensionPoint`]
861    /// from enabled plugins.
862    ///
863    /// # Examples
864    ///
865    /// ```
866    /// use steckrs::{extension_point, simple_plugin, PluginManager};
867    ///
868    /// extension_point!(
869    ///     Logger: LoggerTrait;
870    ///     fn log(&self, message: &str);
871    /// );
872    ///
873    /// struct ConsoleLogger;
874    /// impl LoggerTrait for ConsoleLogger {
875    ///     fn log(&self, message: &str) {
876    ///         // In a real implementation, this would print to console
877    ///     }
878    /// }
879    ///
880    /// simple_plugin!(
881    ///     LoggerPlugin,
882    ///     "logger_plugin",
883    ///     "Basic logging plugin",
884    ///     hooks: [(Logger, ConsoleLogger)]
885    /// );
886    ///
887    /// let mut manager = PluginManager::new();
888    /// manager.load_plugin(Box::new(LoggerPlugin::new())).unwrap();
889    /// manager.enable_plugin(LoggerPlugin::ID).unwrap();
890    ///
891    /// // Get all enabled hooks for the Logger extension point
892    /// let hooks = manager.get_enabled_hooks_by_ep::<Logger>();
893    /// assert_eq!(hooks.len(), 1);
894    ///
895    /// // Use the hook
896    /// for (id, hook) in hooks {
897    ///     assert_eq!(id.plugin_id, "logger_plugin");
898    ///     hook.inner().log("Hello from logger!");
899    /// }
900    /// ```
901    #[must_use]
902    pub fn get_enabled_hooks_by_ep<E: ExtensionPoint>(
903        &self,
904    ) -> Vec<(&hook::HookID, &hook::Hook<E>)> {
905        self.hook_registry()
906            .get_by_extension_point()
907            .into_iter()
908            .filter(|(id, _hook)| {
909                if let Some(plugin) = self.plugins.get(id.plugin_id) {
910                    plugin.is_enabled()
911                } else {
912                    false
913                }
914            })
915            .collect()
916    }
917
918    /// Gets all mutable hooks of enabled [Plugins](Plugin) for a specific [`ExtensionPoint`] type.
919    ///
920    /// This method filters hooks by both extension point type and plugin enabled status,
921    /// returning only hooks from enabled plugins.
922    ///
923    /// # Type Parameters
924    ///
925    /// - `E`: The [`ExtensionPoint`] type
926    ///
927    /// # Returns
928    ///
929    /// A vector of tuples containing mutable references to [`HookID`](crate::hook::HookID)s and hooks registered for the [`ExtensionPoint`]
930    /// from enabled plugins.
931    ///
932    /// # Examples
933    ///
934    /// ```
935    /// use steckrs::{extension_point, simple_plugin, PluginManager};
936    ///
937    /// extension_point!(
938    ///     Logger: LoggerTrait;
939    ///     fn log(&self, message: &str);
940    /// );
941    ///
942    /// struct ConsoleLogger;
943    /// impl LoggerTrait for ConsoleLogger {
944    ///     fn log(&self, message: &str) {
945    ///         // In a real implementation, this would print to console
946    ///     }
947    /// }
948    ///
949    /// simple_plugin!(
950    ///     LoggerPlugin,
951    ///     "logger_plugin",
952    ///     "Basic logging plugin",
953    ///     hooks: [(Logger, ConsoleLogger)]
954    /// );
955    ///
956    /// let mut manager = PluginManager::new();
957    /// manager.load_plugin(Box::new(LoggerPlugin::new())).unwrap();
958    /// manager.enable_plugin(LoggerPlugin::ID).unwrap();
959    ///
960    /// // Get all enabled hooks for the Logger extension point
961    /// let hooks = manager.get_enabled_hooks_by_ep::<Logger>();
962    /// assert_eq!(hooks.len(), 1);
963    ///
964    /// // Use the hook
965    /// for (id, hook) in hooks {
966    ///     assert_eq!(id.plugin_id, "logger_plugin");
967    ///     hook.inner().log("Hello from logger!");
968    /// }
969    /// ```
970    #[must_use]
971    pub fn get_enabled_hooks_by_ep_mut<E: ExtensionPoint>(
972        &mut self,
973    ) -> Vec<(&hook::HookID, &mut hook::Hook<E>)> {
974        let enabled_ids: Vec<PluginID> = self
975            .plugins
976            .iter()
977            .filter_map(|(id, plug)| if plug.is_enabled() { Some(*id) } else { None })
978            .collect();
979        self.hook_registry_mut()
980            .get_by_extension_point_mut()
981            .into_iter()
982            .filter(|(id, _hook)| enabled_ids.contains(&id.plugin_id))
983            .collect()
984    }
985}