Skip to main content

reifydb_sdk/procedure/
wrapper.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{
5	ffi::c_void,
6	panic::{AssertUnwindSafe, catch_unwind},
7	process::abort,
8	slice,
9};
10
11use postcard::from_bytes;
12use reifydb_abi::{context::context::ContextFFI, procedure::vtable::ProcedureVTableFFI};
13use reifydb_type::params::Params;
14use tracing::error;
15
16use crate::procedure::{FFIProcedure, FFIProcedureContext};
17
18pub struct ProcedureWrapper<T: FFIProcedure> {
19	procedure: T,
20}
21
22impl<T: FFIProcedure> ProcedureWrapper<T> {
23	pub fn new(procedure: T) -> Self {
24		Self {
25			procedure,
26		}
27	}
28
29	pub fn from_ptr(ptr: *mut c_void) -> &'static mut Self {
30		unsafe { &mut *(ptr as *mut Self) }
31	}
32}
33
34/// # Safety
35///
36/// - `instance` must be a valid pointer to a `ProcedureWrapper<T>`.
37/// - `ctx` must point to a valid `ContextFFI` for the duration of the call.
38pub unsafe extern "C" fn ffi_procedure_call<T: FFIProcedure>(
39	instance: *mut c_void,
40	ctx: *mut ContextFFI,
41	params_ptr: *const u8,
42	params_len: usize,
43) -> i32 {
44	let result = catch_unwind(AssertUnwindSafe(|| {
45		let wrapper = ProcedureWrapper::<T>::from_ptr(instance);
46
47		let params: Params = if params_ptr.is_null() || params_len == 0 {
48			Params::None
49		} else {
50			let bytes = unsafe { slice::from_raw_parts(params_ptr, params_len) };
51			match from_bytes(bytes) {
52				Ok(p) => p,
53				Err(e) => {
54					error!(?e, "Failed to deserialize procedure params");
55					return -2;
56				}
57			}
58		};
59
60		let mut pctx = FFIProcedureContext::new(ctx);
61
62		match wrapper.procedure.call(&mut pctx, params) {
63			Ok(()) => 0,
64			Err(e) => {
65				error!(?e, "Procedure call failed");
66				-2
67			}
68		}
69	}));
70
71	let code = result.unwrap_or_else(|e| {
72		error!(?e, "Panic in ffi_procedure_call");
73		-99
74	});
75	if code < 0 {
76		error!(code, "ffi_procedure_call failed - aborting");
77		abort();
78	}
79	code
80}
81
82/// # Safety
83///
84/// - `instance` must be a valid pointer to a `ProcedureWrapper<T>`, or null.
85pub unsafe extern "C" fn ffi_procedure_destroy<T: FFIProcedure>(instance: *mut c_void) {
86	if instance.is_null() {
87		return;
88	}
89
90	let result = catch_unwind(AssertUnwindSafe(|| unsafe {
91		let _wrapper = Box::from_raw(instance as *mut ProcedureWrapper<T>);
92	}));
93
94	if let Err(e) = result {
95		error!(?e, "Panic in ffi_procedure_destroy - aborting");
96		abort();
97	}
98}
99
100pub fn create_procedure_vtable<T: FFIProcedure>() -> ProcedureVTableFFI {
101	ProcedureVTableFFI {
102		call: ffi_procedure_call::<T>,
103		destroy: ffi_procedure_destroy::<T>,
104	}
105}