httpward_core/module_export.rs
1// httpward-core/src/module_export.rs
2// Generic module export utilities for HttpWard dynamic modules
3// Provides reusable export functions to eliminate boilerplate in module implementations
4
5use std::os::raw::c_void;
6use std::boxed::Box;
7use crate::httpward_middleware::middleware_trait::HttpWardMiddleware;
8use crate::httpward_middleware::pipe::MiddlewareFatPtr;
9use crate::module_logging::ModuleLogger;
10use crate::module_logging::module_setup;
11
12/// Generic module logger setup function
13/// This can be used directly by modules or through the export_middleware_module macro
14/// Note: This function is not FFI-safe due to &str parameter, use the macro instead
15
16/// Generic middleware creation function
17/// Creates a middleware instance of type T and returns it as a fat pointer
18pub unsafe extern "C" fn generic_create_middleware<T>() -> MiddlewareFatPtr
19where
20 T: HttpWardMiddleware + Send + Sync + 'static,
21 T: Default,
22{
23 let logger = module_setup::get_logger();
24 logger.info("generic_create_middleware called");
25 logger.debug(&format!("creating new {} instance", std::any::type_name::<T>()));
26
27 let boxed: Box<dyn HttpWardMiddleware + Send + Sync> = Box::new(T::default());
28 let raw = Box::into_raw(boxed);
29 let (data, vtable) = unsafe {
30 std::mem::transmute::<*mut (dyn HttpWardMiddleware + Send + Sync), (*mut c_void, *mut c_void)>(raw)
31 };
32
33 logger.info("middleware created successfully");
34 logger.trace("middleware fat pointer created");
35 MiddlewareFatPtr { data, vtable }
36}
37
38/// Generic middleware destruction function
39/// Safely destroys a middleware instance created by generic_create_middleware
40pub unsafe extern "C" fn generic_destroy_middleware(ptr: MiddlewareFatPtr) {
41 let logger = module_setup::get_logger();
42 logger.info("generic_destroy_middleware called");
43
44 // If either part is null, nothing to do.
45 if ptr.data.is_null() || ptr.vtable.is_null() {
46 logger.warn("generic_destroy_middleware: null ptr, skipping");
47 return;
48 }
49
50 unsafe {
51 // Reconstruct a raw fat pointer *mut (dyn Trait)
52 // Transmute the tuple (data, vtable) back into a trait object pointer.
53 let raw = std::mem::transmute::<(*mut std::ffi::c_void, *mut std::ffi::c_void), *mut (dyn HttpWardMiddleware + Send + Sync)>((ptr.data, ptr.vtable));
54
55 // Recreate the Box and drop it here inside the module.
56 // This ensures free happens in the module's allocator.
57 let _boxed: Box<dyn HttpWardMiddleware + Send + Sync> = Box::from_raw(raw);
58 // when `_boxed` goes out of scope, it will be dropped here in the module
59 }
60
61 logger.info("destroyed middleware");
62}
63
64/// Macro to generate all required module export functions
65/// This eliminates boilerplate code for new modules
66///
67/// # Usage Options
68///
69/// ## 1. Automatic module name (recommended)
70/// ```rust
71/// use httpward_core::export_middleware_module;
72/// use httpward_core::httpward_middleware::middleware_trait::HttpWardMiddleware;
73/// use httpward_core::httpward_middleware::next::Next;
74/// use rama::{Context, http::{Body, Request, Response}};
75/// use async_trait::async_trait;
76/// use std::convert::Infallible;
77///
78/// #[derive(Default)]
79/// struct MyMiddleware;
80///
81/// #[async_trait]
82/// impl HttpWardMiddleware for MyMiddleware {
83/// fn name(&self) -> Option<&'static str> {
84/// Some("my_middleware")
85/// }
86///
87/// async fn handle(
88/// &self,
89/// _ctx: Context<()>,
90/// request: Request<Body>,
91/// next: Next<'_>,
92/// ) -> Result<Response<Body>, Box<dyn std::error::Error + Send + Sync>> {
93/// next.run(_ctx, request).await
94/// }
95/// }
96///
97/// export_middleware_module!(MyMiddleware); // Uses Cargo.toml name
98/// ```
99///
100/// ## 2. Custom module name
101/// ```rust
102/// use httpward_core::export_middleware_module;
103/// use httpward_core::httpward_middleware::middleware_trait::HttpWardMiddleware;
104/// use httpward_core::httpward_middleware::next::Next;
105/// use rama::{Context, http::{Body, Request, Response}};
106/// use async_trait::async_trait;
107///
108/// #[derive(Default)]
109/// struct MyMiddleware;
110///
111/// #[async_trait]
112/// impl HttpWardMiddleware for MyMiddleware {
113/// fn name(&self) -> Option<&'static str> {
114/// Some("my_middleware")
115/// }
116///
117/// async fn handle(
118/// &self,
119/// _ctx: Context<()>,
120/// request: Request<Body>,
121/// next: Next<'_>,
122/// ) -> Result<Response<Body>, Box<dyn std::error::Error + Send + Sync>> {
123/// next.run(_ctx, request).await
124/// }
125/// }
126///
127/// export_middleware_module!("custom_name", MyMiddleware);
128/// ```
129///
130/// ## 3. Environment variable name (example with literal)
131/// ```rust
132/// use httpward_core::export_middleware_module;
133/// use httpward_core::httpward_middleware::middleware_trait::HttpWardMiddleware;
134/// use httpward_core::httpward_middleware::next::Next;
135/// use rama::{Context, http::{Body, Request, Response}};
136/// use async_trait::async_trait;
137///
138/// #[derive(Default)]
139/// struct MyMiddleware;
140///
141/// #[async_trait]
142/// impl HttpWardMiddleware for MyMiddleware {
143/// fn name(&self) -> Option<&'static str> {
144/// Some("my_middleware")
145/// }
146///
147/// async fn handle(
148/// &self,
149/// _ctx: Context<()>,
150/// request: Request<Body>,
151/// next: Next<'_>,
152/// ) -> Result<Response<Body>, Box<dyn std::error::Error + Send + Sync>> {
153/// next.run(_ctx, request).await
154/// }
155/// }
156///
157/// export_middleware_module!("my_module_name", MyMiddleware);
158/// ```
159///
160/// # Generated Functions
161/// - `module_set_logger` - Sets up module logger with the given name
162/// - `create_middleware` - Creates middleware instance of type T
163/// - `destroy_middleware` - Destroys middleware instance
164#[macro_export]
165macro_rules! export_middleware_module {
166 // Case 1: Only middleware type - auto-detect name from Cargo.toml
167 ($middleware_type:ty) => {
168 $crate::export_middleware_module!(env: "CARGO_PKG_NAME", $middleware_type);
169 };
170
171 // Case 2: Environment variable + middleware type
172 (env: $env_var:literal, $middleware_type:ty) => {
173 $crate::export_middleware_module!(
174 env!($env_var),
175 $middleware_type
176 );
177 };
178
179 // Case 3: Explicit name + middleware type
180 ($module_name:expr, $middleware_type:ty) => {
181 #[unsafe(no_mangle)]
182 pub extern "C" fn module_set_logger(
183 error_fn: $crate::module_logging::HostLogErrorFn,
184 warn_fn: $crate::module_logging::HostLogWarnFn,
185 info_fn: $crate::module_logging::HostLogInfoFn,
186 debug_fn: $crate::module_logging::HostLogDebugFn,
187 trace_fn: $crate::module_logging::HostLogTraceFn,
188 ) {
189 unsafe {
190 $crate::module_logging::module_setup::setup_module_logger_with_name(
191 $module_name,
192 error_fn,
193 warn_fn,
194 info_fn,
195 debug_fn,
196 trace_fn,
197 );
198 }
199 }
200
201 #[unsafe(no_mangle)]
202 pub extern "C" fn create_middleware() -> $crate::httpward_middleware::pipe::MiddlewareFatPtr {
203 unsafe {
204 $crate::module_export::generic_create_middleware::<$middleware_type>()
205 }
206 }
207
208 #[unsafe(no_mangle)]
209 pub extern "C" fn destroy_middleware(ptr: $crate::httpward_middleware::pipe::MiddlewareFatPtr) {
210 unsafe {
211 $crate::module_export::generic_destroy_middleware(ptr)
212 }
213 }
214 };
215}
216
217/// Alternative macro for modules that need custom middleware creation logic
218/// This provides the logger setup but allows custom create/destroy functions
219///
220/// # Usage Options
221///
222/// ## 1. Automatic module name (recommended)
223/// ```rust
224/// use httpward_core::export_module_with_custom_middleware;
225///
226/// export_module_with_custom_middleware!(); // Uses Cargo.toml name
227/// ```
228///
229/// ## 2. Custom module name
230/// ```rust
231/// use httpward_core::export_module_with_custom_middleware;
232///
233/// export_module_with_custom_middleware!("custom_name");
234/// ```
235///
236/// ## 3. Environment variable name (example with literal)
237/// ```rust
238/// use httpward_core::export_module_with_custom_middleware;
239///
240/// export_module_with_custom_middleware!("my_module_name");
241/// ```
242#[macro_export]
243macro_rules! export_module_with_custom_middleware {
244 // Case 1: No arguments - auto-detect name from Cargo.toml
245 () => {
246 $crate::export_module_with_custom_middleware!(env: "CARGO_PKG_NAME");
247 };
248
249 // Case 2: Environment variable
250 (env: $env_var:literal) => {
251 $crate::export_module_with_custom_middleware!(
252 env!($env_var)
253 );
254 };
255
256 // Case 3: Explicit name
257 ($module_name:expr) => {
258 #[unsafe(no_mangle)]
259 pub extern "C" fn module_set_logger(
260 error_fn: $crate::module_logging::HostLogErrorFn,
261 warn_fn: $crate::module_logging::HostLogWarnFn,
262 info_fn: $crate::module_logging::HostLogInfoFn,
263 debug_fn: $crate::module_logging::HostLogDebugFn,
264 trace_fn: $crate::module_logging::HostLogTraceFn,
265 ) {
266 unsafe {
267 $crate::module_logging::module_setup::setup_module_logger_with_name(
268 $module_name,
269 error_fn,
270 warn_fn,
271 info_fn,
272 debug_fn,
273 trace_fn,
274 );
275 }
276 }
277
278 // Note: You must provide your own create_middleware and destroy_middleware functions
279 };
280}
281
282/// Helper trait for middleware that can be created with default constructor
283/// This is used by the generic_create_middleware function
284pub trait DefaultMiddleware: HttpWardMiddleware + Send + Sync + 'static {
285 fn create_default() -> Self where Self: Sized;
286}
287
288impl<T> DefaultMiddleware for T
289where
290 T: HttpWardMiddleware + Send + Sync + 'static + Default,
291{
292 fn create_default() -> Self where Self: Sized {
293 T::default()
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use crate::httpward_middleware::middleware_trait::HttpWardMiddleware;
301 use std::pin::Pin;
302 use rama::http::{Body, Request, Response};
303 use rama::Context;
304 use crate::httpward_middleware::BoxError;
305 use crate::httpward_middleware::next::Next;
306
307 // Test middleware for testing purposes
308 #[derive(Debug, Default)]
309 struct TestMiddleware;
310
311 #[async_trait::async_trait]
312 impl HttpWardMiddleware for TestMiddleware {
313 fn name(&self) -> Option<&'static str> {
314 Some("test_middleware")
315 }
316
317 async fn handle(
318 &self,
319 ctx: Context<()>,
320 request: Request<Body>,
321 next: Next<'_>,
322 ) -> Result<Response<Body>, BoxError> {
323 next.run(ctx, request).await
324 }
325 }
326
327 #[test]
328 fn test_generic_create_destroy_middleware() {
329 // Test that we can create and destroy middleware safely
330 let ptr = unsafe { generic_create_middleware::<TestMiddleware>() };
331
332 assert!(!ptr.data.is_null(), "Data pointer should not be null");
333 assert!(!ptr.vtable.is_null(), "VTable pointer should not be null");
334
335 unsafe { generic_destroy_middleware(ptr) };
336 }
337
338 #[test]
339 fn test_export_macro_compilation() {
340 // Test that the macro compiles correctly
341 // This is a compile-time test
342 export_middleware_module!("test_module", TestMiddleware);
343 }
344}