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                unsafe {
67                    let _ = Box::from_raw(ptr as *mut $plugin_type);
68                }
69            }
70        }
71    };
72}
73
74/// Generate a plugin configuration struct
75///
76/// # Example
77///
78/// ```rust,no_run
79/// use mockforge_plugin_sdk::plugin_config;
80///
81/// plugin_config! {
82///     id = "my-plugin",
83///     version = "1.0.0",
84///     name = "My Plugin",
85///     description = "A custom plugin",
86///     capabilities = ["network:http"],
87///     author = {
88///         name = "Your Name",
89///         email = "your.email@example.com",
90///     },
91/// }
92/// ```
93#[macro_export]
94macro_rules! plugin_config {
95    (
96        id = $id:expr,
97        version = $version:expr,
98        name = $name:expr,
99        description = $desc:expr,
100        capabilities = [$($capability:expr),*],
101        author = {
102            name = $author_name:expr,
103            email = $author_email:expr $(,)?
104        } $(,)?
105    ) => {
106        /// Plugin configuration
107        pub fn plugin_config() -> mockforge_plugin_core::PluginManifest {
108            use mockforge_plugin_core::*;
109
110            let info = PluginInfo::new(
111                PluginId::new($id),
112                PluginVersion::parse($version).expect("Invalid version"),
113                $name,
114                $desc,
115                PluginAuthor::with_email($author_name, $author_email),
116            );
117
118            let mut manifest = PluginManifest::new(info);
119            $(
120                manifest.capabilities.push($capability.to_string());
121            )*
122            manifest
123        }
124    };
125}
126
127/// Quick test macro for plugin functions
128///
129/// # Example
130///
131/// ```rust,no_run
132/// # use mockforge_plugin_sdk::prelude::*;
133/// # async fn test_auth() {
134/// use axum::http::{HeaderMap, Method, Uri};
135/// use mockforge_plugin_sdk::{
136///     mock_context, plugin_test, prelude::*, Result as PluginCoreResult,
137/// };
138/// use std::collections::HashMap;
139///
140/// #[derive(Default)]
141/// struct TestPlugin;
142///
143/// #[async_trait]
144/// impl AuthPlugin for TestPlugin {
145///     fn capabilities(&self) -> PluginCapabilities {
146///         PluginCapabilities::default()
147///     }
148///
149///     async fn initialize(&self, _config: &AuthPluginConfig) -> PluginCoreResult<()> {
150///         Ok(())
151///     }
152///
153///     async fn authenticate(
154///         &self,
155///         _context: &PluginContext,
156///         _request: &AuthRequest,
157///         _config: &AuthPluginConfig,
158///     ) -> PluginCoreResult<PluginResult<AuthResponse>> {
159///         let identity = UserIdentity::new("user");
160///         let response = AuthResponse::success(identity, HashMap::new());
161///         Ok(PluginResult::success(response, 0))
162///     }
163///
164///     fn validate_config(&self, _config: &AuthPluginConfig) -> PluginCoreResult<()> {
165///         Ok(())
166///     }
167///
168///     fn supported_schemes(&self) -> Vec<String> {
169///         vec!["basic".to_string()]
170///     }
171///
172///     async fn cleanup(&self) -> PluginCoreResult<()> {
173///         Ok(())
174///     }
175/// }
176///
177/// plugin_test! {
178///     test_name: authenticate_valid_user,
179///     plugin: TestPlugin,
180///     context: mock_context! {
181///         plugin_id: "test-plugin",
182///         request_id: "req-123",
183///     },
184///     request: AuthRequest::from_axum(
185///         Method::GET,
186///         Uri::from_static("/login"),
187///         HeaderMap::new(),
188///         None
189///     ),
190///     config: AuthPluginConfig::default(),
191///     assert: |result| {
192///         assert!(result.unwrap().is_success());
193///     }
194/// }
195/// # }
196/// ```
197#[macro_export]
198macro_rules! plugin_test {
199    (
200        test_name: $name:ident,
201        plugin: $plugin:ty,
202        context: $context:expr,
203        request: $request:expr,
204        config: $config:expr,
205        assert: $assert:expr
206    ) => {
207        #[tokio::test]
208        async fn $name() {
209            let plugin = <$plugin>::default();
210            let context = $context;
211            let request = $request;
212            let config = $config;
213            let result = plugin.authenticate(&context, &request, &config).await;
214            ($assert)(result);
215        }
216    };
217}
218
219/// Create a mock plugin context for testing
220///
221/// # Example
222///
223/// ```rust,no_run
224/// # use mockforge_plugin_sdk::{mock_context, prelude::*};
225/// let context = mock_context! {
226///     plugin_id: "test-plugin",
227///     request_id: "req-123",
228/// };
229/// ```
230#[macro_export]
231macro_rules! mock_context {
232    (
233        plugin_id: $plugin_id:expr,
234        request_id: $request_id:expr $(,)?
235    ) => {{
236        use mockforge_plugin_core::{PluginContext, PluginId, PluginVersion};
237        let mut context =
238            PluginContext::new(PluginId::new($plugin_id), PluginVersion::new(0, 1, 0));
239        context.request_id = $request_id.to_string();
240        context
241    }};
242
243    () => {{
244        use mockforge_plugin_core::{PluginContext, PluginId, PluginVersion};
245        PluginContext::new(PluginId::new("mockforge-plugin"), PluginVersion::new(0, 1, 0))
246    }};
247}
248
249#[cfg(test)]
250mod tests {
251
252    #[test]
253    fn test_macro_compilation() {
254        // Just verify macros compile
255        // Test passes if compilation succeeds
256    }
257}