Skip to main content

cdm_plugin_interface/
ffi.rs

1//! FFI helpers for WASM plugins
2//!
3//! This module provides utilities for plugin authors to implement the WASM FFI layer
4//! without having to deal with raw pointers and memory management.
5
6use crate::*;
7use std::slice;
8
9/// Read bytes from WASM memory using a pointer and length
10///
11/// # Safety
12/// This function is unsafe because it dereferences raw pointers.
13/// The caller must ensure the pointer and length are valid.
14unsafe fn read_bytes(ptr: *const u8, len: usize) -> Vec<u8> {
15    if ptr.is_null() || len == 0 {
16        return Vec::new();
17    }
18    slice::from_raw_parts(ptr, len).to_vec()
19}
20
21/// Allocate memory and write bytes, returning a pointer with length prefix
22///
23/// Format: [4-byte length (little-endian)][data bytes]
24fn write_result(data: &[u8]) -> *mut u8 {
25    let total_len = 4 + data.len();
26    let mut buffer = Vec::with_capacity(total_len);
27
28    // Write length prefix (4 bytes, little-endian)
29    let len_bytes = (data.len() as u32).to_le_bytes();
30    buffer.extend_from_slice(&len_bytes);
31
32    // Write actual data
33    buffer.extend_from_slice(data);
34
35    // Convert to raw pointer and forget the Vec so it's not deallocated
36    let ptr = buffer.as_mut_ptr();
37    std::mem::forget(buffer);
38    ptr
39}
40
41/// Helper for implementing _schema WASM export
42///
43/// This function is safe because it doesn't work with raw pointers (other than the return value).
44pub fn ffi_schema<F>(schema_fn: F) -> *mut u8
45where
46    F: Fn() -> String,
47{
48    // Call the actual schema function
49    let schema_content = schema_fn();
50
51    // Write result to WASM memory and return pointer
52    write_result(schema_content.as_bytes())
53}
54
55/// Helper for implementing _validate_config WASM export
56///
57/// # Safety
58/// This function is unsafe because it works with raw pointers from WASM.
59pub unsafe fn ffi_validate_config<F>(
60    level_ptr: *const u8,
61    level_len: usize,
62    config_ptr: *const u8,
63    config_len: usize,
64    validate_fn: F,
65) -> *mut u8
66where
67    F: Fn(ConfigLevel, JSON, &Utils) -> Vec<ValidationError>,
68{
69    // Read inputs from WASM memory
70    let level_bytes = read_bytes(level_ptr, level_len);
71    let config_bytes = read_bytes(config_ptr, config_len);
72
73    // Deserialize inputs
74    let level: ConfigLevel = match serde_json::from_slice(&level_bytes) {
75        Ok(l) => l,
76        Err(e) => {
77            eprintln!("Failed to deserialize ConfigLevel: {}", e);
78            return write_result(b"[]");
79        }
80    };
81
82    let config: JSON = match serde_json::from_slice(&config_bytes) {
83        Ok(c) => c,
84        Err(e) => {
85            eprintln!("Failed to deserialize config: {}", e);
86            return write_result(b"[]");
87        }
88    };
89
90    // Call the actual validation function
91    let utils = Utils;
92    let errors = validate_fn(level, config, &utils);
93
94    // Serialize the result
95    let result_json = match serde_json::to_vec(&errors) {
96        Ok(json) => json,
97        Err(e) => {
98            eprintln!("Failed to serialize validation errors: {}", e);
99            return write_result(b"[]");
100        }
101    };
102
103    // Write result to WASM memory and return pointer
104    write_result(&result_json)
105}
106
107/// Helper for implementing _build WASM export
108///
109/// # Safety
110/// This function is unsafe because it works with raw pointers from WASM.
111pub unsafe fn ffi_build<F>(
112    schema_ptr: *const u8,
113    schema_len: usize,
114    config_ptr: *const u8,
115    config_len: usize,
116    build_fn: F,
117) -> *mut u8
118where
119    F: Fn(Schema, JSON, &Utils) -> Vec<OutputFile>,
120{
121    // Read inputs from WASM memory
122    let schema_bytes = read_bytes(schema_ptr, schema_len);
123    let config_bytes = read_bytes(config_ptr, config_len);
124
125    // Deserialize inputs
126    let schema: Schema = match serde_json::from_slice(&schema_bytes) {
127        Ok(s) => s,
128        Err(e) => {
129            eprintln!("Failed to deserialize Schema: {}", e);
130            return write_result(b"[]");
131        }
132    };
133
134    let config: JSON = match serde_json::from_slice(&config_bytes) {
135        Ok(c) => c,
136        Err(e) => {
137            eprintln!("Failed to deserialize config: {}", e);
138            return write_result(b"[]");
139        }
140    };
141
142    // Call the actual build function
143    let utils = Utils;
144    let files = build_fn(schema, config, &utils);
145
146    // Serialize the result
147    let result_json = match serde_json::to_vec(&files) {
148        Ok(json) => json,
149        Err(e) => {
150            eprintln!("Failed to serialize output files: {}", e);
151            return write_result(b"[]");
152        }
153    };
154
155    // Write result to WASM memory and return pointer
156    write_result(&result_json)
157}
158
159/// Helper for implementing _migrate WASM export
160///
161/// # Safety
162/// This function is unsafe because it works with raw pointers from WASM.
163pub unsafe fn ffi_migrate<F>(
164    schema_ptr: *const u8,
165    schema_len: usize,
166    deltas_ptr: *const u8,
167    deltas_len: usize,
168    config_ptr: *const u8,
169    config_len: usize,
170    migrate_fn: F,
171) -> *mut u8
172where
173    F: Fn(Schema, Vec<Delta>, JSON, &Utils) -> Vec<OutputFile>,
174{
175    // Read inputs from WASM memory
176    let schema_bytes = read_bytes(schema_ptr, schema_len);
177    let deltas_bytes = read_bytes(deltas_ptr, deltas_len);
178    let config_bytes = read_bytes(config_ptr, config_len);
179
180    // Deserialize inputs
181    let schema: Schema = match serde_json::from_slice(&schema_bytes) {
182        Ok(s) => s,
183        Err(e) => {
184            eprintln!("Failed to deserialize Schema: {}", e);
185            return write_result(b"[]");
186        }
187    };
188
189    let deltas: Vec<Delta> = match serde_json::from_slice(&deltas_bytes) {
190        Ok(d) => d,
191        Err(e) => {
192            eprintln!("Failed to deserialize deltas: {}", e);
193            return write_result(b"[]");
194        }
195    };
196
197    let config: JSON = match serde_json::from_slice(&config_bytes) {
198        Ok(c) => c,
199        Err(e) => {
200            eprintln!("Failed to deserialize config: {}", e);
201            return write_result(b"[]");
202        }
203    };
204
205    // Call the actual migrate function
206    let utils = Utils;
207    let files = migrate_fn(schema, deltas, config, &utils);
208
209    // Serialize the result
210    let result_json = match serde_json::to_vec(&files) {
211        Ok(json) => json,
212        Err(e) => {
213            eprintln!("Failed to serialize migration files: {}", e);
214            return write_result(b"[]");
215        }
216    };
217
218    // Write result to WASM memory and return pointer
219    write_result(&result_json)
220}
221
222/// Standard WASM memory allocation function
223///
224/// This should be exported as `_alloc` by all plugins.
225#[no_mangle]
226pub extern "C" fn _alloc(size: usize) -> *mut u8 {
227    let mut buf = Vec::with_capacity(size);
228    let ptr = buf.as_mut_ptr();
229    std::mem::forget(buf);
230    ptr
231}
232
233/// Standard WASM memory deallocation function
234///
235/// This should be exported as `_dealloc` by all plugins.
236///
237/// # Safety
238/// This function is unsafe because it deallocates raw pointers.
239#[no_mangle]
240pub unsafe extern "C" fn _dealloc(ptr: *mut u8, size: usize) {
241    if !ptr.is_null() && size > 0 {
242        let _ = Vec::from_raw_parts(ptr, 0, size);
243    }
244}
245
246/// Macro to export schema function with proper FFI wrapper
247#[macro_export]
248macro_rules! export_schema {
249    ($func:expr) => {
250        #[no_mangle]
251        pub extern "C" fn _schema() -> *mut u8 {
252            $crate::ffi::ffi_schema($func)
253        }
254    };
255}
256
257/// Macro to export validate_config function with proper FFI wrapper
258#[macro_export]
259macro_rules! export_validate_config {
260    ($func:expr) => {
261        #[no_mangle]
262        pub extern "C" fn _validate_config(
263            level_ptr: *const u8,
264            level_len: usize,
265            config_ptr: *const u8,
266            config_len: usize,
267        ) -> *mut u8 {
268            unsafe {
269                $crate::ffi::ffi_validate_config(
270                    level_ptr,
271                    level_len,
272                    config_ptr,
273                    config_len,
274                    $func,
275                )
276            }
277        }
278    };
279}
280
281/// Macro to export build function with proper FFI wrapper
282#[macro_export]
283macro_rules! export_build {
284    ($func:expr) => {
285        #[no_mangle]
286        pub extern "C" fn _build(
287            schema_ptr: *const u8,
288            schema_len: usize,
289            config_ptr: *const u8,
290            config_len: usize,
291        ) -> *mut u8 {
292            unsafe {
293                $crate::ffi::ffi_build(
294                    schema_ptr,
295                    schema_len,
296                    config_ptr,
297                    config_len,
298                    $func,
299                )
300            }
301        }
302    };
303}
304
305/// Macro to export migrate function with proper FFI wrapper
306#[macro_export]
307macro_rules! export_migrate {
308    ($func:expr) => {
309        #[no_mangle]
310        pub extern "C" fn _migrate(
311            schema_ptr: *const u8,
312            schema_len: usize,
313            deltas_ptr: *const u8,
314            deltas_len: usize,
315            config_ptr: *const u8,
316            config_len: usize,
317        ) -> *mut u8 {
318            unsafe {
319                $crate::ffi::ffi_migrate(
320                    schema_ptr,
321                    schema_len,
322                    deltas_ptr,
323                    deltas_len,
324                    config_ptr,
325                    config_len,
326                    $func,
327                )
328            }
329        }
330    };
331}