mockforge_plugin_sdk/macros.rs
1//! Helper macros for plugin development
2
3/// Export a plugin with boilerplate
4///
5/// This macro generates the necessary WASM exports for your plugin.
6///
7/// # Example
8///
9/// ```rust,no_run
10/// use mockforge_plugin_sdk::{export_plugin, prelude::*, Result as PluginCoreResult};
11/// use std::collections::HashMap;
12///
13/// #[derive(Debug, Default)]
14/// pub struct MyPlugin;
15///
16/// #[async_trait]
17/// impl AuthPlugin for MyPlugin {
18/// fn capabilities(&self) -> PluginCapabilities {
19/// PluginCapabilities::default()
20/// }
21///
22/// async fn initialize(&self, _config: &AuthPluginConfig) -> PluginCoreResult<()> {
23/// Ok(())
24/// }
25///
26/// async fn authenticate(
27/// &self,
28/// _context: &PluginContext,
29/// _request: &AuthRequest,
30/// _config: &AuthPluginConfig,
31/// ) -> PluginCoreResult<PluginResult<AuthResponse>> {
32/// let identity = UserIdentity::new("user123");
33/// let response = AuthResponse::success(identity, HashMap::new());
34/// Ok(PluginResult::success(response, 0))
35/// }
36///
37/// fn validate_config(&self, _config: &AuthPluginConfig) -> PluginCoreResult<()> {
38/// Ok(())
39/// }
40///
41/// fn supported_schemes(&self) -> Vec<String> {
42/// vec!["basic".to_string()]
43/// }
44///
45/// async fn cleanup(&self) -> PluginCoreResult<()> {
46/// Ok(())
47/// }
48/// }
49///
50/// export_plugin!(MyPlugin);
51/// ```
52#[macro_export]
53macro_rules! export_plugin {
54 ($plugin_type:ty) => {
55 /// Create plugin instance
56 #[no_mangle]
57 pub extern "C" fn create_plugin() -> *mut std::ffi::c_void {
58 let plugin = Box::new(<$plugin_type>::default());
59 Box::into_raw(plugin) as *mut std::ffi::c_void
60 }
61
62 /// Destroy plugin instance
63 #[no_mangle]
64 pub extern "C" fn destroy_plugin(ptr: *mut std::ffi::c_void) {
65 if !ptr.is_null() {
66 // SAFETY: This is WASM plugin cleanup code. The pointer comes from
67 // the WASM runtime and is guaranteed to be valid for the plugin type.
68 // We take ownership via Box::from_raw to ensure proper cleanup when
69 // the plugin is dropped. The WASM runtime ensures memory safety.
70 unsafe {
71 let _ = Box::from_raw(ptr as *mut $plugin_type);
72 }
73 }
74 }
75 };
76}
77
78/// Generate a plugin configuration struct
79///
80/// # Example
81///
82/// ```rust,no_run
83/// use mockforge_plugin_sdk::plugin_config;
84///
85/// plugin_config! {
86/// id = "my-plugin",
87/// version = "1.0.0",
88/// name = "My Plugin",
89/// description = "A custom plugin",
90/// capabilities = ["network:http"],
91/// author = {
92/// name = "Your Name",
93/// email = "your.email@example.com",
94/// },
95/// }
96/// ```
97#[macro_export]
98macro_rules! plugin_config {
99 (
100 id = $id:expr,
101 version = $version:expr,
102 name = $name:expr,
103 description = $desc:expr,
104 capabilities = [$($capability:expr),*],
105 author = {
106 name = $author_name:expr,
107 email = $author_email:expr $(,)?
108 } $(,)?
109 ) => {
110 /// Plugin configuration
111 pub fn plugin_config() -> mockforge_plugin_core::PluginManifest {
112 use mockforge_plugin_core::*;
113
114 let info = PluginInfo::new(
115 PluginId::new($id),
116 PluginVersion::parse($version).expect("Invalid version"),
117 $name,
118 $desc,
119 PluginAuthor::with_email($author_name, $author_email),
120 );
121
122 let mut manifest = PluginManifest::new(info);
123 $(
124 manifest.capabilities.push($capability.to_string());
125 )*
126 manifest
127 }
128 };
129}
130
131/// Quick test macro for plugin functions
132///
133/// # Example
134///
135/// ```rust,no_run
136/// # use mockforge_plugin_sdk::prelude::*;
137/// # async fn test_auth() {
138/// use axum::http::{HeaderMap, Method, Uri};
139/// use mockforge_plugin_sdk::{
140/// mock_context, plugin_test, prelude::*, Result as PluginCoreResult,
141/// };
142/// use std::collections::HashMap;
143///
144/// #[derive(Default)]
145/// struct TestPlugin;
146///
147/// #[async_trait]
148/// impl AuthPlugin for TestPlugin {
149/// fn capabilities(&self) -> PluginCapabilities {
150/// PluginCapabilities::default()
151/// }
152///
153/// async fn initialize(&self, _config: &AuthPluginConfig) -> PluginCoreResult<()> {
154/// Ok(())
155/// }
156///
157/// async fn authenticate(
158/// &self,
159/// _context: &PluginContext,
160/// _request: &AuthRequest,
161/// _config: &AuthPluginConfig,
162/// ) -> PluginCoreResult<PluginResult<AuthResponse>> {
163/// let identity = UserIdentity::new("user");
164/// let response = AuthResponse::success(identity, HashMap::new());
165/// Ok(PluginResult::success(response, 0))
166/// }
167///
168/// fn validate_config(&self, _config: &AuthPluginConfig) -> PluginCoreResult<()> {
169/// Ok(())
170/// }
171///
172/// fn supported_schemes(&self) -> Vec<String> {
173/// vec!["basic".to_string()]
174/// }
175///
176/// async fn cleanup(&self) -> PluginCoreResult<()> {
177/// Ok(())
178/// }
179/// }
180///
181/// plugin_test! {
182/// test_name: authenticate_valid_user,
183/// plugin: TestPlugin,
184/// context: mock_context! {
185/// plugin_id: "test-plugin",
186/// request_id: "req-123",
187/// },
188/// request: AuthRequest::from_axum(
189/// Method::GET,
190/// Uri::from_static("/login"),
191/// HeaderMap::new(),
192/// None
193/// ),
194/// config: AuthPluginConfig::default(),
195/// assert: |result| {
196/// assert!(result.unwrap().is_success());
197/// }
198/// }
199/// # }
200/// ```
201#[macro_export]
202macro_rules! plugin_test {
203 (
204 test_name: $name:ident,
205 plugin: $plugin:ty,
206 context: $context:expr,
207 request: $request:expr,
208 config: $config:expr,
209 assert: $assert:expr
210 ) => {
211 #[tokio::test]
212 async fn $name() {
213 let plugin = <$plugin>::default();
214 let context = $context;
215 let request = $request;
216 let config = $config;
217 let result = plugin.authenticate(&context, &request, &config).await;
218 ($assert)(result);
219 }
220 };
221}
222
223/// Create a mock plugin context for testing
224///
225/// # Example
226///
227/// ```rust,no_run
228/// # use mockforge_plugin_sdk::{mock_context, prelude::*};
229/// let context = mock_context! {
230/// plugin_id: "test-plugin",
231/// request_id: "req-123",
232/// };
233/// ```
234#[macro_export]
235macro_rules! mock_context {
236 (
237 plugin_id: $plugin_id:expr,
238 request_id: $request_id:expr $(,)?
239 ) => {{
240 use mockforge_plugin_core::{PluginContext, PluginId, PluginVersion};
241 let mut context =
242 PluginContext::new(PluginId::new($plugin_id), PluginVersion::new(0, 1, 0));
243 context.request_id = $request_id.to_string();
244 context
245 }};
246
247 () => {{
248 use mockforge_plugin_core::{PluginContext, PluginId, PluginVersion};
249 PluginContext::new(PluginId::new("mockforge-plugin"), PluginVersion::new(0, 1, 0))
250 }};
251}
252
253#[cfg(test)]
254mod tests {
255
256 #[test]
257 fn test_macro_compilation() {
258 // Just verify macros compile
259 // Test passes if compilation succeeds
260 }
261}