Skip to main content

crawlkit_plugin_sdk/
export.rs

1//! WASM export macros for plugins.
2
3/// Export an analyzer as a WASM plugin.
4///
5/// This macro generates the required WASM export functions:
6/// - `crawlkit_plugin_init`
7/// - `crawlkit_plugin_analyze`
8/// - `crawlkit_plugin_free_string`
9///
10/// # Example
11///
12/// ```rust,no_run
13/// use crawlkit_plugin_sdk::{Analyzer, Finding, Severity, AnalysisContext};
14///
15/// pub struct MyAnalyzer;
16/// impl MyAnalyzer { pub fn new() -> Self { Self } }
17/// impl Default for MyAnalyzer { fn default() -> Self { Self::new() } }
18///
19/// impl Analyzer for MyAnalyzer {
20///     fn name(&self) -> &str { "my-analyzer" }
21///     fn analyze(&self, _ctx: &AnalysisContext) -> Vec<Finding> { vec![] }
22/// }
23///
24/// crawlkit_plugin_sdk::export_analyzer!(MyAnalyzer);
25/// ```
26#[macro_export]
27macro_rules! export_analyzer {
28    ($analyzer_type:ty) => {
29        static mut ANALYZER: Option<$analyzer_type> = None;
30
31        #[no_mangle]
32        pub extern "C" fn crawlkit_plugin_init() -> i32 {
33            unsafe {
34                ANALYZER = Some(<$analyzer_type>::new());
35            }
36            0
37        }
38
39        /// # Safety
40        ///
41        /// This function reads from raw pointers provided by the host.
42        /// The host must ensure the pointers are valid and point to valid UTF-8 data.
43        #[no_mangle]
44        pub unsafe extern "C" fn crawlkit_plugin_analyze(
45            html_ptr: *const u8,
46            html_len: usize,
47            url_ptr: *const u8,
48            url_len: usize,
49        ) -> *mut u8 {
50            let html = String::from_utf8_unchecked(
51                std::slice::from_raw_parts(html_ptr, html_len).to_vec(),
52            );
53            let url =
54                String::from_utf8_unchecked(std::slice::from_raw_parts(url_ptr, url_len).to_vec());
55
56            let ctx = $crate::AnalysisContext {
57                url,
58                html,
59                status_code: None,
60                headers: Vec::new(),
61                response_time_ms: None,
62            };
63
64            let analyzer = ANALYZER.as_ref().expect("Analyzer not initialized");
65            let findings = analyzer.analyze(&ctx);
66
67            let json = serde_json::to_string(&findings).unwrap();
68            let bytes = json.into_bytes();
69            let ptr = bytes.as_ptr();
70            std::mem::forget(bytes);
71            ptr as *mut u8
72        }
73
74        /// # Safety
75        ///
76        /// This function frees memory allocated by the plugin.
77        /// The pointer must have been returned by `crawlkit_plugin_analyze`.
78        #[no_mangle]
79        pub unsafe extern "C" fn crawlkit_plugin_free_string(ptr: *mut u8) {
80            if !ptr.is_null() {
81                let _ = Box::from_raw(ptr);
82            }
83        }
84
85        #[no_mangle]
86        pub extern "C" fn crawlkit_plugin_api_version() -> *mut u8 {
87            let version = "1.0";
88            let bytes = version.as_bytes().to_vec();
89            let ptr = bytes.as_ptr();
90            std::mem::forget(bytes);
91            ptr as *mut u8
92        }
93    };
94}