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}