folk-api 0.1.1

Plugin contract for the Folk PHP application server
Documentation

folk-api

Plugin contract for the Folk PHP application server.

Status: in active development. See folk-spec for the roadmap.

Requirements

  • Rust 1.85+
  • Tokio async runtime

Installation

# Cargo.toml
folk-api = "0.1"

Quick start

A minimal plugin that logs on boot and shutdown (~25 lines):

use anyhow::Result;
use async_trait::async_trait;
use folk_api::{
    Plugin, PluginContext, PluginFactory, ServerPlugin, ServerPluginWrapper,
    RpcMethodDef,
};
use serde::Deserialize;
use serde_json::Value;

#[derive(Debug, Deserialize, Default)]
struct GreetConfig {
    message: Option<String>,
}

struct GreetPlugin {
    message: String,
}

#[async_trait]
impl ServerPlugin for GreetPlugin {
    fn name(&self) -> &'static str {
        "greet"
    }

    async fn run(&self, ctx: PluginContext) -> Result<()> {
        tracing::info!(msg = %self.message, "greet plugin running");
        let mut shutdown = ctx.shutdown;
        let _ = shutdown.changed().await;
        tracing::info!("greet plugin stopping");
        Ok(())
    }
}

struct GreetFactory;

impl PluginFactory for GreetFactory {
    fn create(&self, config: Value) -> Result<Box<dyn Plugin>> {
        let cfg: GreetConfig = serde_json::from_value(config).unwrap_or_default();
        let message = cfg.message.unwrap_or_else(|| "Hello, Folk!".into());
        Ok(Box::new(ServerPluginWrapper::new(GreetPlugin { message })))
    }
}

/// Required entry point — the builder calls this by name.
pub fn folk_plugin_factory() -> Box<dyn PluginFactory> {
    Box::new(GreetFactory)
}

Register it in folk.build.toml:

[[plugin]]
crate_name = "my_greet_plugin"
path = "../my-greet-plugin"
config_key = "greet"

And configure it in folk.toml:

[greet]
message = "Howdy"

Configuration

Plugins receive their config section as an opaque serde_json::Value. Each plugin deserializes it into its own struct. There is no global schema — the plugin owns its config shape.

How it works

Plugin lifecycle

  1. Factory — The builder calls folk_plugin_factory() once per plugin crate. The returned PluginFactory receives the plugin's config as JSON and constructs a Box<dyn Plugin>.
  2. Bootplugin.boot(ctx) is called in registration order at server startup. Returning Err is fatal: already-booted plugins are shut down in reverse order and the server exits.
  3. Run — For ServerPlugin implementations, run() executes as a long-lived task. Watch ctx.shutdown to know when to stop.
  4. Shutdownplugin.shutdown() is called in reverse registration order. Errors are logged but do not block the shutdown sequence.

PluginContext

Every plugin receives a PluginContext at boot:

Field Type Description
executor Arc<dyn Executor> Send work to the PHP worker pool
shutdown watch::Receiver<bool> Fires when the server is shutting down
rpc_registrar Option<Arc<dyn RpcRegistrar>> Register admin RPC methods
health_registry Option<Arc<dyn HealthRegistry>> Register health checks
metrics_registry Option<Arc<dyn MetricsRegistry>> Register Prometheus metrics

Optional registries are None when the corresponding plugin (metrics, etc.) is not loaded. Check before use.

Key traits

  • Plugin — 3 required methods: name(), boot(), shutdown(). Optional: rpc_methods().
  • ServerPlugin — Convenience trait for the common "spawn a task, wait for shutdown" pattern. Wrap with ServerPluginWrapper to get a Plugin.
  • PluginFactory — Single method create(config: Value) -> Result<Box<dyn Plugin>>.
  • Executorasync fn execute(&self, payload: Bytes) -> Result<Bytes>. Sends MessagePack-encoded payloads to the PHP worker pool and returns the response.

RPC methods

Advertise methods from rpc_methods() and register handlers via rpc_registrar.register_raw(name, handler). Handlers receive and return Bytes (typically MessagePack-encoded).

Health checks

Register via health_registry.register(name, check_fn). Return HealthStatus::ok() or HealthStatus::degraded(msg).

Metrics

Create metric families via metrics_registry.counter_vec(name, help, &labels), then .with_labels(&values) to get a handle. Supports counters, gauges, and histograms. All metrics render in Prometheus text format.

See ADR 0006 — Plugin API shape for the full design rationale.

License

MIT