#![allow(unsafe_op_in_unsafe_fn)]
use core::ptr::{self, NonNull};
use std::ffi::{CStr, CString, c_void};
use crate::ffi::{
ValueConverterVTable, noesis_box_bool, noesis_box_double, noesis_box_int32, noesis_box_string,
noesis_unbox_bool, noesis_unbox_double, noesis_unbox_int32, noesis_unbox_string,
noesis_value_converter_create, noesis_value_converter_destroy,
};
pub struct ConvertArg(Option<NonNull<c_void>>);
impl ConvertArg {
pub(crate) fn new(raw: *mut c_void) -> Self {
Self(NonNull::new(raw))
}
#[must_use]
pub fn is_none(&self) -> bool {
self.0.is_none()
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.0.map_or(ptr::null_mut(), NonNull::as_ptr)
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
let p = self.0?;
let mut out = false;
let ok = unsafe { noesis_unbox_bool(p.as_ptr(), &mut out) };
ok.then_some(out)
}
#[must_use]
pub fn as_i32(&self) -> Option<i32> {
let p = self.0?;
let mut out = 0i32;
let ok = unsafe { noesis_unbox_int32(p.as_ptr(), &mut out) };
ok.then_some(out)
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
let p = self.0?;
let mut out = 0.0f64;
let ok = unsafe { noesis_unbox_double(p.as_ptr(), &mut out) };
ok.then_some(out)
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
let p = self.0?;
let s = unsafe { noesis_unbox_string(p.as_ptr()) };
if s.is_null() {
return None;
}
unsafe { CStr::from_ptr(s) }.to_str().ok()
}
}
#[derive(Debug, Clone)]
pub enum Converted {
Bool(bool),
Int32(i32),
Double(f64),
String(String),
Null,
}
impl Converted {
pub(crate) fn into_boxed(self) -> *mut c_void {
match self {
Converted::Bool(b) => unsafe { noesis_box_bool(b) },
Converted::Int32(i) => unsafe { noesis_box_int32(i) },
Converted::Double(d) => unsafe { noesis_box_double(d) },
Converted::String(s) => {
let cs = CString::new(s).expect("converted string contained NUL");
unsafe { noesis_box_string(cs.as_ptr()) }
}
Converted::Null => ptr::null_mut(),
}
}
}
pub trait ValueConverter: Send + 'static {
fn convert(&self, value: &ConvertArg, param: &ConvertArg) -> Option<Converted>;
fn convert_back(&self, _value: &ConvertArg, _param: &ConvertArg) -> Option<Converted> {
None
}
}
impl<F> ValueConverter for F
where
F: Fn(&ConvertArg, &ConvertArg) -> Option<Converted> + Send + 'static,
{
fn convert(&self, value: &ConvertArg, param: &ConvertArg) -> Option<Converted> {
self(value, param)
}
}
static CONVERTER_VTABLE: ValueConverterVTable = ValueConverterVTable {
convert: convert_trampoline,
convert_back: convert_back_trampoline,
};
unsafe extern "C" fn convert_trampoline(
userdata: *mut c_void,
value: *mut c_void,
_target_type: *const c_void,
parameter: *mut c_void,
out_result: *mut *mut c_void,
) -> bool {
crate::panic_guard::guard(|| {
let handler = &*userdata.cast::<Box<dyn ValueConverter>>();
let v = ConvertArg::new(value);
let p = ConvertArg::new(parameter);
match handler.convert(&v, &p) {
Some(result) => {
if !out_result.is_null() {
*out_result = result.into_boxed();
}
true
}
None => false,
}
})
}
unsafe extern "C" fn convert_back_trampoline(
userdata: *mut c_void,
value: *mut c_void,
_target_type: *const c_void,
parameter: *mut c_void,
out_result: *mut *mut c_void,
) -> bool {
crate::panic_guard::guard(|| {
let handler = &*userdata.cast::<Box<dyn ValueConverter>>();
let v = ConvertArg::new(value);
let p = ConvertArg::new(parameter);
match handler.convert_back(&v, &p) {
Some(result) => {
if !out_result.is_null() {
*out_result = result.into_boxed();
}
true
}
None => false,
}
})
}
unsafe extern "C" fn converter_free_trampoline(userdata: *mut c_void) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
drop(Box::from_raw(userdata.cast::<Box<dyn ValueConverter>>()));
})
}
pub struct Converter {
ptr: NonNull<c_void>,
}
unsafe impl Send for Converter {}
impl Converter {
#[must_use]
pub fn new<C: ValueConverter>(converter: C) -> Self {
let boxed: Box<Box<dyn ValueConverter>> = Box::new(Box::new(converter));
let userdata = Box::into_raw(boxed);
let ptr = unsafe {
noesis_value_converter_create(
&CONVERTER_VTABLE,
userdata.cast(),
converter_free_trampoline,
)
};
match NonNull::new(ptr) {
Some(ptr) => Converter { ptr },
None => {
unsafe { drop(Box::from_raw(userdata)) };
unreachable!("noesis_value_converter_create returned null for a non-null vtable");
}
}
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for Converter {
fn drop(&mut self) {
unsafe { noesis_value_converter_destroy(self.ptr.as_ptr()) }
}
}