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