Skip to main content

traits_plugin_api/
plugin_api.rs

1/// Generate C ABI exports (`trait_call`, `trait_free`) for a trait function.
2///
3/// The target function must have signature:
4///   `fn(&[serde_json::Value]) -> serde_json::Value`
5///
6/// # Usage
7/// ```ignore
8/// mod my_trait;
9/// plugin_api::export_trait!(my_trait::handler);
10/// ```
11///
12/// This generates:
13/// - `trait_call(json_ptr, json_len, out_len) -> *mut u8`
14/// - `trait_free(ptr, len)`
15///
16/// The caller (kernel's dylib_loader) passes JSON-serialized args as bytes,
17/// receives JSON-serialized result as bytes, and frees the buffer afterward.
18#[macro_export]
19macro_rules! export_trait {
20    ($func:path) => {
21        /// C ABI entry point: receives JSON args, calls trait function, returns JSON result.
22        ///
23        /// # Safety
24        /// - `json_ptr` must point to `json_len` valid bytes of JSON
25        /// - `out_len` must be a valid pointer to write the result length
26        /// - Caller must free the returned buffer via `trait_free`
27        #[no_mangle]
28        pub unsafe extern "C" fn trait_call(
29            json_ptr: *const u8,
30            json_len: usize,
31            out_len: *mut usize,
32        ) -> *mut u8 {
33            *out_len = 0;
34
35            if json_ptr.is_null() || json_len == 0 {
36                // Empty args → call with empty slice
37                let result = $func(&[]);
38                let result_bytes = serde_json::to_vec(&result).unwrap_or_default();
39                let len = result_bytes.len();
40                let ptr = result_bytes.as_ptr() as *mut u8;
41                std::mem::forget(result_bytes);
42                *out_len = len;
43                return ptr;
44            }
45
46            let bytes = std::slice::from_raw_parts(json_ptr, json_len);
47            let args: Vec<serde_json::Value> = match serde_json::from_slice(bytes) {
48                Ok(v) => v,
49                Err(_) => {
50                    let err = serde_json::json!({"error": "invalid JSON args"});
51                    let err_bytes = serde_json::to_vec(&err).unwrap_or_default();
52                    let len = err_bytes.len();
53                    let ptr = err_bytes.as_ptr() as *mut u8;
54                    std::mem::forget(err_bytes);
55                    *out_len = len;
56                    return ptr;
57                }
58            };
59
60            let result = $func(&args);
61            let result_bytes = serde_json::to_vec(&result).unwrap_or_default();
62            let len = result_bytes.len();
63            let ptr = result_bytes.as_ptr() as *mut u8;
64            std::mem::forget(result_bytes);
65            *out_len = len;
66            ptr
67        }
68
69        /// C ABI: free a buffer previously returned by `trait_call`.
70        ///
71        /// # Safety
72        /// - `ptr` must have been returned by `trait_call`
73        /// - `len` must match the `out_len` value set by `trait_call`
74        /// - Must be called exactly once per `trait_call` return
75        #[no_mangle]
76        pub unsafe extern "C" fn trait_free(ptr: *mut u8, len: usize) {
77            if !ptr.is_null() && len > 0 {
78                drop(Vec::from_raw_parts(ptr, len, len));
79            }
80        }
81    };
82}
83
84// ── Trait dispatch entry point (only in the main binary) ──
85
86/// kernel.plugin_api introspection: returns plugin ABI contract and installed plugins.
87#[cfg(kernel)]
88pub fn plugin_api(args: &[serde_json::Value]) -> serde_json::Value {
89    let _ = args;
90
91    // Query the global dylib loader for installed plugins
92    let plugins = match crate::dylib_loader::LOADER.get() {
93        Some(loader) => {
94            let list = loader.list();
95            serde_json::json!(list)
96        }
97        None => serde_json::json!([]),
98    };
99
100    serde_json::json!({
101        "abi": {
102            "version": 1,
103            "entry": "trait_call(json_ptr: *const u8, json_len: usize, out_len: *mut usize) -> *mut u8",
104            "free": "trait_free(ptr: *mut u8, len: usize)",
105            "convention": "C",
106            "format": "JSON bytes in, JSON bytes out"
107        },
108        "installed_plugins": plugins,
109        "plugin_count": plugins.as_array().map(|a| a.len()).unwrap_or(0)
110    })
111}