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 singleDiagnostic::errorentry returned to the host (not an abort — required for--validate-socketmode).config_schema: treated asRNone.name/version: returned as the literal"<plugin-panic>".shutdown/destroy: absorbed; memory may leak but the hub stays up.