Skip to main content

haproxy_spoa_hub_plugin_api/
lib.rs

1// abi_stable's proc macros generate code that triggers these warnings
2#![allow(non_camel_case_types, non_local_definitions)]
3
4pub mod plugin_trait;
5pub mod types;
6
7pub use abi_stable;
8pub use abi_stable::std_types::{RHashMap, RString, RVec, Tuple2};
9pub use plugin_trait::{PluginBox, PluginMod, PluginMod_Ref, SpoePlugin, SpoePlugin_TO};
10pub use types::{
11    ConfigValue, PluginContext, ProcessingResult, SpoeMessage, SpoeValue, TxnVariable, VarScope,
12};
13
14/// Define a plugin with automatic panic safety and module export.
15///
16/// This macro generates the `#[export_root_module]` entry point and
17/// wraps the `process` implementation in `std::panic::catch_unwind`
18/// to prevent plugin panics from aborting the hub process.
19///
20/// # Usage
21///
22/// ```rust,ignore
23/// use haproxy_spoa_hub_plugin_api::*;
24///
25/// #[derive(Debug)]
26/// struct MyPlugin;
27///
28/// define_plugin!(MyPlugin, {
29///     fn new() -> Self {
30///         MyPlugin
31///     }
32///
33///     fn init(&mut self, _context: &PluginContext)
34///         -> Result<(), Box<dyn std::error::Error + Send + Sync>>
35///     {
36///         Ok(())
37///     }
38///
39///     fn name(&self) -> &str {
40///         "my-plugin"
41///     }
42///
43///     fn version(&self) -> &str {
44///         "0.1.0"
45///     }
46///
47///     fn process(
48///         &self,
49///         message: &SpoeMessage,
50///     ) -> Result<ProcessingResult, Box<dyn std::error::Error + Send + Sync>> {
51///         Ok(ProcessingResult {
52///             variables: vec![].into(),
53///         })
54///     }
55/// });
56/// ```
57#[macro_export]
58macro_rules! define_plugin {
59    // Arm with config_schema
60    ($plugin_ty:ty, {
61        fn new() -> Self $new_body:block
62
63        fn init(&mut self, $ctx_param:ident : &PluginContext $(,)?) -> Result<(), Box<dyn std::error::Error + Send + Sync>> $init_body:block
64
65        fn name(&self) -> &str $name_body:block
66
67        fn version(&self) -> &str $version_body:block
68
69        fn process(
70            &self,
71            $msg_param:ident : &SpoeMessage $(,)?
72        ) -> Result<ProcessingResult, Box<dyn std::error::Error + Send + Sync>> $process_body:block
73
74        fn config_schema(&self) -> Option<&str> $schema_body:block
75    }) => {
76        $crate::__define_plugin_impl!($plugin_ty,
77            new: $new_body,
78            init($ctx_param): $init_body,
79            name: $name_body,
80            version: $version_body,
81            process($msg_param): $process_body,
82            schema: { $schema_body }
83        );
84    };
85
86    // Arm without config_schema (backward compatible)
87    ($plugin_ty:ty, {
88        fn new() -> Self $new_body:block
89
90        fn init(&mut self, $ctx_param:ident : &PluginContext $(,)?) -> Result<(), Box<dyn std::error::Error + Send + Sync>> $init_body:block
91
92        fn name(&self) -> &str $name_body:block
93
94        fn version(&self) -> &str $version_body:block
95
96        fn process(
97            &self,
98            $msg_param:ident : &SpoeMessage $(,)?
99        ) -> Result<ProcessingResult, Box<dyn std::error::Error + Send + Sync>> $process_body:block
100    }) => {
101        $crate::__define_plugin_impl!($plugin_ty,
102            new: $new_body,
103            init($ctx_param): $init_body,
104            name: $name_body,
105            version: $version_body,
106            process($msg_param): $process_body,
107            schema: { { None } }
108        );
109    };
110}
111
112#[doc(hidden)]
113#[macro_export]
114macro_rules! __define_plugin_impl {
115    ($plugin_ty:ty,
116        new: $new_body:block,
117        init($ctx_param:ident): $init_body:block,
118        name: $name_body:block,
119        version: $version_body:block,
120        process($msg_param:ident): $process_body:block,
121        schema: { $schema_body:block }
122    ) => {
123        mod __plugin_impl {
124            use super::*;
125            use $crate::abi_stable::std_types::{
126                RBoxError, ROption, RResult, RStr, RString,
127            };
128            use $crate::abi_stable::sabi_trait::prelude::TD_Opaque;
129
130            impl $plugin_ty {
131                pub fn __new() -> Self $new_body
132                #[allow(clippy::unnecessary_wraps)]
133                pub fn __init(
134                    &mut self,
135                    $ctx_param: &$crate::PluginContext,
136                ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> $init_body
137                pub fn __name(&self) -> &str $name_body
138                pub fn __version(&self) -> &str $version_body
139                #[allow(clippy::unnecessary_wraps)]
140                pub fn __process(
141                    &self,
142                    $msg_param: &$crate::SpoeMessage,
143                ) -> Result<$crate::ProcessingResult, Box<dyn std::error::Error + Send + Sync>> $process_body
144                #[allow(clippy::unnecessary_wraps)]
145                pub fn __config_schema(&self) -> Option<&str> $schema_body
146            }
147
148            impl $crate::SpoePlugin for $plugin_ty {
149                fn init(
150                    &mut self,
151                    context: &$crate::PluginContext,
152                ) -> RResult<(), RBoxError> {
153                    self.__init(context).map_err(RBoxError::from_box).into()
154                }
155
156                fn process(
157                    &self,
158                    message: &$crate::SpoeMessage,
159                ) -> RResult<$crate::ProcessingResult, RBoxError> {
160                    match std::panic::catch_unwind(
161                        std::panic::AssertUnwindSafe(|| self.__process(message)),
162                    ) {
163                        Ok(res) => res.map_err(RBoxError::from_box).into(),
164                        Err(_) => {
165                            RResult::RErr(RBoxError::new(
166                                $crate::PluginPanicError,
167                            ))
168                        }
169                    }
170                }
171
172                fn name(&self) -> RStr<'_> {
173                    self.__name().into()
174                }
175
176                fn version(&self) -> RStr<'_> {
177                    self.__version().into()
178                }
179
180                fn shutdown(&self) {}
181
182                fn config_schema(&self) -> ROption<RString> {
183                    self.__config_schema().map(RString::from).into()
184                }
185            }
186
187            #[$crate::abi_stable::export_root_module]
188            pub fn get_root_module() -> $crate::PluginMod_Ref {
189                use $crate::abi_stable::prefix_type::PrefixTypeTrait;
190                $crate::PluginMod {
191                    new: __create_plugin,
192                }
193                .leak_into_prefix()
194            }
195
196            extern "C" fn __create_plugin() -> RResult<$crate::PluginBox, RBoxError> {
197                let plugin = <$plugin_ty>::__new();
198                RResult::ROk($crate::SpoePlugin_TO::from_value(plugin, TD_Opaque))
199            }
200        }
201    };
202}
203
204/// Error type for plugin panics caught by the `define_plugin!` macro.
205#[derive(Debug)]
206pub struct PluginPanicError;
207
208impl std::fmt::Display for PluginPanicError {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        write!(f, "plugin panicked during message processing")
211    }
212}
213
214impl std::error::Error for PluginPanicError {}