Skip to main content

define_plugin

Macro define_plugin 

Source
macro_rules! define_plugin {
    (
        $plugin_ty:ty, {
            fn new() -> Self $new_body:block

            fn init(&mut self, $ctx_param:ident : &PluginContext $(,)?)
                -> Result<(), Box<dyn std::error::Error + Send + Sync>> $init_body:block

            fn name(&self) -> &str $name_body:block

            fn version(&self) -> &str $version_body:block

            fn process(
                &self,
                $msg_param:ident : &SpoeMessage $(,)?
            ) -> Result<ProcessingResult, Box<dyn std::error::Error + Send + Sync>> $process_body:block

            $(fn config_schema(&self) -> Option<&str> $schema_body:block)?

            $(fn validate(&self, $vctx_param:ident : &PluginContext $(,)?) -> Vec<Diagnostic> $validate_body:block)?
        }
    ) => { ... };
}
Expand description

Define a plugin and emit the FFI surface (vtable + entry symbol).

Plugin authors write a regular impl-style block; the macro emits extern "C" thunks that bridge into it, the static PluginVTable, and the get_plugin_vtable symbol the hub looks up after dlopen.

process is automatically wrapped in std::panic::catch_unwind so a panic during request handling does not abort the hub process.

§Usage

use haproxy_spoa_hub_plugin_api::{ProcessingResult, SpoeMessage, SpoeValue, TxnVariable, define_plugin};

#[derive(Debug, Default)]
struct MyPlugin;

define_plugin!(MyPlugin, {
    fn new() -> Self { MyPlugin }

    fn init(&mut self, _: &PluginContext) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        Ok(())
    }

    fn name(&self) -> &str { "my-plugin" }
    fn version(&self) -> &str { env!("CARGO_PKG_VERSION") }

    fn process(&self, _: &SpoeMessage)
        -> Result<ProcessingResult, Box<dyn std::error::Error + Send + Sync>>
    {
        Ok(ProcessingResult::single(TxnVariable::session(
            "result", SpoeValue::String("ok".into()),
        )))
    }
});

config_schema and validate are optional. Add them at the end of the block in that order if you need them. Plugins that omit them get the default no-op behavior (no schema, empty diagnostics).

§Lifetime constraints

name and version MUST return &'static str. The macro emits an FFI thunk whose return type is RStr<'static>, so a &str borrowed from &self will not compile. In practice this means returning either a string literal or env!("CARGO_PKG_VERSION"). If you need to compute the name from instance fields, store it in a &'static str (e.g. via Box::leak(...) at construction time) or pre-register the strings as consts.

§Panic safety

Every author-supplied body is wrapped in std::panic::catch_unwind inside its FFI thunk. A panic surfaces as:

  • process / init / create: RResult::RErr(PluginPanicError).
  • validate: a single Diagnostic::error entry returned to the host (not an abort — required for --validate-socket mode).
  • config_schema: treated as RNone.
  • name / version: returned as the literal "<plugin-panic>".
  • shutdown / destroy: absorbed; memory may leak but the hub stays up.