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}