1use std::collections::HashMap;
7use std::sync::Mutex;
8use std::sync::OnceLock;
9use std::os::raw::c_char;
10use std::ffi::{CStr, CString};
11
12pub use serde_json::Value;
13
14pub type NativeFn = fn(HashMap<String, Value>) -> Result<Value, String>;
17
18static REGISTRY: OnceLock<Mutex<HashMap<String, NativeFn>>> = OnceLock::new();
19
20fn get_registry() -> &'static Mutex<HashMap<String, NativeFn>> {
21 REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
22}
23
24pub fn register(name: &str, func: NativeFn) {
38 if let Ok(mut reg) = get_registry().lock() {
39 reg.insert(name.to_string(), func);
40 }
41}
42
43#[no_mangle]
46pub unsafe extern "C" fn void_malloc(size: usize) -> *mut u8 {
47 let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
48 std::alloc::alloc(layout)
49}
50
51#[no_mangle]
54pub unsafe extern "C" fn void_free(ptr: *mut u8, size: usize) {
55 let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
56 std::alloc::dealloc(ptr, layout)
57}
58
59extern "C" {
60 fn void_init();
62}
63
64static INIT: std::sync::Once = std::sync::Once::new();
65
66pub fn ensure_registered() {
68 INIT.call_once(|| unsafe {
69 void_init();
70 });
71}
72
73#[no_mangle]
76pub extern "C" fn void_invoke(input: *const c_char) -> *mut c_char {
77 ensure_registered();
78
79 if input.is_null() {
80 return to_c_string_err("null input");
81 }
82
83 let c_str = unsafe { CStr::from_ptr(input) };
84 let str_slice = match c_str.to_str() {
85 Ok(s) => s,
86 Err(e) => return to_c_string_err(&format!("invalid utf-8 string: {}", e)),
87 };
88
89 let payload: serde_json::Value = match serde_json::from_str(str_slice) {
90 Ok(p) => p,
91 Err(e) => return to_c_string_err(&format!("invalid JSON payload: {}", e)),
92 };
93
94 let fn_name = match payload.get("fn").and_then(|v| v.as_str()) {
95 Some(name) => name,
96 None => return to_c_string_err("invalid or missing fn"),
97 };
98
99 if fn_name == "__list_functions__" {
101 if let Ok(reg) = get_registry().lock() {
102 let keys: Vec<String> = reg.keys().cloned().collect();
103 return to_c_string_ok(&serde_json::Value::from(keys));
104 } else {
105 return to_c_string_err("failed to lock registry");
106 }
107 }
108
109 let data = payload.get("data")
110 .and_then(|v| v.as_object())
111 .map(|o| {
112 o.iter()
113 .map(|(k, v)| (k.clone(), v.clone()))
114 .collect::<HashMap<String, serde_json::Value>>()
115 })
116 .unwrap_or_default();
117
118 let reg = match get_registry().lock() {
119 Ok(r) => r,
120 Err(_) => return to_c_string_err("failed to lock registry"),
121 };
122
123 let func = match reg.get(fn_name) {
124 Some(f) => f,
125 None => return to_c_string_err(&format!("function '{}' not found", fn_name)),
126 };
127
128 match func(data) {
129 Ok(val) => to_c_string_ok(&val),
130 Err(e) => to_c_string_err(&e),
131 }
132}
133
134#[no_mangle]
137pub unsafe extern "C" fn void_free_string(ptr: *mut c_char) {
138 if !ptr.is_null() {
139 let _ = CString::from_raw(ptr);
140 }
141}
142
143fn to_c_string_ok(val: &serde_json::Value) -> *mut c_char {
144 let response = serde_json::json!({
145 "ok": true,
146 "value": val
147 });
148 let s = serde_json::to_string(&response).unwrap();
149 CString::new(s).unwrap().into_raw()
150}
151
152fn to_c_string_err(err_msg: &str) -> *mut c_char {
153 let response = serde_json::json!({
154 "ok": false,
155 "error": err_msg
156 });
157 let s = serde_json::to_string(&response).unwrap();
158 CString::new(s).unwrap().into_raw()
159}
160
161pub fn get_string(m: &HashMap<String, Value>, key: &str) -> Result<String, String> {
165 match m.get(key) {
166 Some(Value::String(s)) => Ok(s.clone()),
167 Some(_) => Err(format!("invalid type for field '{}', expected String", key)),
168 None => Err(format!("missing required field '{}'", key)),
169 }
170}
171
172pub fn get_bool(m: &HashMap<String, Value>, key: &str, def: bool) -> bool {
176 match m.get(key) {
177 Some(Value::Bool(b)) => *b,
178 _ => def,
179 }
180}
181
182pub fn get_int(m: &HashMap<String, Value>, key: &str) -> Result<i64, String> {
186 match m.get(key) {
187 Some(Value::Number(n)) => {
188 if let Some(i) = n.as_i64() {
189 Ok(i)
190 } else if let Some(f) = n.as_f64() {
191 Ok(f as i64)
192 } else {
193 Err(format!("invalid number format for field '{}'", key))
194 }
195 }
196 Some(_) => Err(format!("invalid type for field '{}', expected Integer", key)),
197 None => Err(format!("missing required field '{}'", key)),
198 }
199}
200
201pub fn get_float(m: &HashMap<String, Value>, key: &str) -> Result<f64, String> {
205 match m.get(key) {
206 Some(Value::Number(n)) => {
207 if let Some(f) = n.as_f64() {
208 Ok(f)
209 } else {
210 Err(format!("invalid number format for field '{}'", key))
211 }
212 }
213 Some(_) => Err(format!("invalid type for field '{}', expected Float", key)),
214 None => Err(format!("missing required field '{}'", key)),
215 }
216}
217
218pub fn get_array(m: &HashMap<String, Value>, key: &str) -> Result<Vec<Value>, String> {
222 match m.get(key) {
223 Some(Value::Array(a)) => Ok(a.clone()),
224 Some(_) => Err(format!("invalid type for field '{}', expected Array", key)),
225 None => Err(format!("missing required field '{}'", key)),
226 }
227}
228
229pub fn get_object(m: &HashMap<String, Value>, key: &str) -> Result<serde_json::Map<String, Value>, String> {
233 match m.get(key) {
234 Some(Value::Object(o)) => Ok(o.clone()),
235 Some(_) => Err(format!("invalid type for field '{}', expected Object", key)),
236 None => Err(format!("missing required field '{}'", key)),
237 }
238}
239
240pub fn get_value(m: &HashMap<String, Value>, key: &str) -> Result<Value, String> {
244 match m.get(key) {
245 Some(v) => Ok(v.clone()),
246 None => Err(format!("missing required field '{}'", key)),
247 }
248}
249
250pub fn get_null(m: &HashMap<String, Value>, key: &str) -> Result<(), String> {
254 match m.get(key) {
255 Some(Value::Null) => Ok(()),
256 Some(_) => Err(format!("invalid type for field '{}', expected Null", key)),
257 None => Err(format!("missing required field '{}'", key)),
258 }
259}
260
261#[macro_export]
275macro_rules! void_plugin {
276 ($init_fn:ident) => {
277 #[no_mangle]
278 pub extern "C" fn void_init() {
279 $init_fn();
280 }
281 };
282}