folk_api/factory.rs
1//! Plugin factory: how `folk-builder`'s generated `main.rs` constructs each plugin.
2//!
3//! The convention is: every plugin crate exports
4//!
5//! ```rust,ignore
6//! pub fn folk_plugin_factory() -> Box<dyn folk_api::PluginFactory> {
7//! Box::new(MyPluginFactory)
8//! }
9//! ```
10//!
11//! The builder calls `crate_name::folk_plugin_factory()` for each plugin
12//! and registers the result. There is no other naming convention; the
13//! function name is fixed.
14
15use anyhow::Result;
16use serde_json::Value as JsonValue;
17
18use crate::plugin::Plugin;
19
20/// Constructs a [`Plugin`] from runtime configuration.
21pub trait PluginFactory: Send + Sync + 'static {
22 /// Construct a plugin from its config section (JSON from `folk.toml`).
23 ///
24 /// # Contract: empty config must be accepted
25 ///
26 /// `folk-builder` compiles every selected plugin into the extension
27 /// unconditionally and passes `{}` (an empty JSON object) for any plugin
28 /// whose section is absent from `folk.toml`. Implementations **must**
29 /// succeed on `create(json!({}))` and apply sensible defaults — an absent
30 /// section is not the same as a disabled plugin.
31 ///
32 /// Every plugin crate should include a regression test:
33 /// ```rust,ignore
34 /// #[test]
35 /// fn factory_accepts_empty_config() {
36 /// assert!(folk_plugin_factory().create(serde_json::json!({})).is_ok());
37 /// }
38 /// ```
39 fn create(&self, config: JsonValue) -> Result<Box<dyn Plugin>>;
40}