Skip to main content

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}