use core::ffi::{c_char, c_void};
use std::ffi::CString;
use crate::{COwnedIntoRust, RustOwnedIntoC};
#[doc(hidden)]
pub struct EzffiError {
code: i32,
message: String,
}
impl EzffiError {
pub fn new(code: i32, message: String) -> Self {
EzffiError { code, message }
}
}
#[doc(hidden)]
#[derive(Clone, Copy)]
#[repr(C)]
pub struct EzffiOption {
value: *mut c_void,
payload_fn: unsafe extern "C" fn(*mut c_void, *mut c_void),
}
#[doc(hidden)]
#[derive(Clone, Copy)]
#[repr(C)]
pub struct EzffiResult {
ok: *mut c_void,
message: *mut c_char,
payload_fn: unsafe extern "C" fn(*mut c_void, *mut c_void),
code: i32,
}
unsafe extern "C" fn payload_op<R, F: COwnedIntoRust<R>>(value: *mut c_void, out: *mut c_void) {
if value.is_null() {
return;
}
let ffi = unsafe { *Box::from_raw(value as *mut F) };
if out.is_null() {
let _ = unsafe { ffi.into_rust_owned() };
} else {
unsafe { (out as *mut F).write(ffi) };
}
}
unsafe fn free_message(message: *mut c_char) {
if !message.is_null() {
let _ = unsafe { CString::from_raw(message) };
}
}
impl<R> RustOwnedIntoC<()> for Option<R>
where
R: RustOwnedIntoC<()>,
{
type C = EzffiOption;
unsafe fn owned_into_c(self) -> EzffiOption {
let payload_fn = payload_op::<R, <R as RustOwnedIntoC<()>>::C>;
match self {
Some(inner) => {
let ffi = unsafe { inner.owned_into_c() };
EzffiOption {
value: Box::into_raw(Box::new(ffi)) as *mut c_void,
payload_fn,
}
}
None => EzffiOption {
value: core::ptr::null_mut(),
payload_fn,
},
}
}
}
impl<R> COwnedIntoRust<Option<R>> for EzffiOption
where
R: RustOwnedIntoC<()>,
{
unsafe fn into_rust_owned(self) -> Option<R> {
if self.value.is_null() {
return None;
}
let ffi = unsafe { *Box::from_raw(self.value as *mut <R as RustOwnedIntoC<()>>::C) };
Some(unsafe { ffi.into_rust_owned() })
}
}
impl<T, E> RustOwnedIntoC<()> for Result<T, E>
where
T: RustOwnedIntoC<()>,
E: Into<EzffiError>,
{
type C = EzffiResult;
unsafe fn owned_into_c(self) -> EzffiResult {
let payload_fn = payload_op::<T, <T as RustOwnedIntoC<()>>::C>;
match self {
Ok(inner) => {
let ffi = unsafe { inner.owned_into_c() };
EzffiResult {
ok: Box::into_raw(Box::new(ffi)) as *mut c_void,
message: core::ptr::null_mut(),
payload_fn,
code: 0,
}
}
Err(error) => {
let error: EzffiError = error.into();
let cmsg = CString::new(error.message).unwrap_or_else(|_| {
CString::new("<error message contained an interior NUL byte>")
.expect("fallback message is well-formed")
});
EzffiResult {
ok: core::ptr::null_mut(),
message: cmsg.into_raw(),
payload_fn,
code: error.code,
}
}
}
}
}
impl<T, E> COwnedIntoRust<Result<T, E>> for EzffiResult {
unsafe fn into_rust_owned(self) -> Result<T, E> {
unimplemented!("`Result` is only supported as a return value across FFI")
}
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ezffi_option_is_some(o: *const EzffiOption) -> bool {
let o = unsafe { &*o };
!o.value.is_null()
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ezffi_result_is_ok(o: *const EzffiResult) -> bool {
let o = unsafe { &*o };
!o.ok.is_null()
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ezffi_result_error_code(o: *const EzffiResult) -> i32 {
let o = unsafe { &*o };
o.code
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ezffi_result_error_message(o: *const EzffiResult) -> *const c_char {
let o = unsafe { &*o };
o.message as *const c_char
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ezffi_option_unwrap(o: *const EzffiOption, out: *mut c_void) {
let o = unsafe { &*o };
unsafe { (o.payload_fn)(o.value, out) };
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ezffi_result_unwrap(o: *const EzffiResult, out: *mut c_void) {
let o = unsafe { &*o };
unsafe { (o.payload_fn)(o.ok, out) };
unsafe { free_message(o.message) };
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ezffi_option_free(o: *const EzffiOption) {
let o = unsafe { &*o };
unsafe { (o.payload_fn)(o.value, core::ptr::null_mut()) };
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ezffi_result_free(o: *const EzffiResult) {
let o = unsafe { &*o };
unsafe { (o.payload_fn)(o.ok, core::ptr::null_mut()) };
unsafe { free_message(o.message) };
}