use serde::{Deserialize, Serialize};
use std::os::raw::{c_char, c_int};
pub const SDK_ABI_VERSION: u32 = 1;
#[repr(C)]
pub struct PluginManifest {
pub abi_version: u32,
pub name: *const c_char,
pub version: *const c_char,
pub description: *const c_char,
pub vtable: PluginVtable,
}
unsafe impl Send for PluginManifest {}
unsafe impl Sync for PluginManifest {}
#[repr(C)]
pub struct PluginVtable {
pub introspect:
unsafe extern "C" fn(input_json: *const c_char, out_json: *mut *mut c_char) -> c_int,
pub free_string: unsafe extern "C" fn(ptr: *mut c_char),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct IntrospectResponse {
pub schema: crate::schema::Schema,
}
#[macro_export]
macro_rules! declare_plugin {
(
name: $name:expr,
version: $version:expr,
description: $desc:expr,
introspect: $introspect:path $(,)?
) => {
const _APPCTL_PLUGIN_NAME: &[u8] = concat!($name, "\0").as_bytes();
const _APPCTL_PLUGIN_VERSION: &[u8] = concat!($version, "\0").as_bytes();
const _APPCTL_PLUGIN_DESC: &[u8] = concat!($desc, "\0").as_bytes();
#[unsafe(no_mangle)]
pub unsafe extern "C" fn appctl_plugin_register() -> *const $crate::ffi::PluginManifest {
static MANIFEST: $crate::ffi::PluginManifest = $crate::ffi::PluginManifest {
abi_version: $crate::ffi::SDK_ABI_VERSION,
name: _APPCTL_PLUGIN_NAME.as_ptr() as *const ::std::os::raw::c_char,
version: _APPCTL_PLUGIN_VERSION.as_ptr() as *const ::std::os::raw::c_char,
description: _APPCTL_PLUGIN_DESC.as_ptr() as *const ::std::os::raw::c_char,
vtable: $crate::ffi::PluginVtable {
introspect: _appctl_plugin_introspect_cabi,
free_string: _appctl_plugin_free_string_cabi,
},
};
&MANIFEST as *const _
}
unsafe extern "C" fn _appctl_plugin_introspect_cabi(
input_json: *const ::std::os::raw::c_char,
out_json: *mut *mut ::std::os::raw::c_char,
) -> ::std::os::raw::c_int {
let input_str = if input_json.is_null() {
"{}".to_string()
} else {
unsafe { ::std::ffi::CStr::from_ptr(input_json) }
.to_string_lossy()
.into_owned()
};
let res: ::std::result::Result<$crate::schema::Schema, ::anyhow::Error> = (|| {
let input: $crate::SyncInput = ::serde_json::from_str(&input_str)?;
let rt = ::tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
rt.block_on($introspect(input))
})();
let (code, payload) = match res {
Ok(schema) => {
let env = $crate::ffi::IntrospectResponse { schema };
match ::serde_json::to_string(&env) {
Ok(json) => (0, json),
Err(err) => (1, format!("{{\"error\":\"serialize failed: {}\"}}", err)),
}
}
Err(err) => (
1,
format!(
"{{\"error\":{}}}",
::serde_json::to_string(&err.to_string())
.unwrap_or_else(|_| "\"unknown\"".into())
),
),
};
match ::std::ffi::CString::new(payload) {
Ok(cstr) => unsafe {
*out_json = cstr.into_raw();
code
},
Err(_) => unsafe {
*out_json = ::std::ptr::null_mut();
2
},
}
}
unsafe extern "C" fn _appctl_plugin_free_string_cabi(ptr: *mut ::std::os::raw::c_char) {
if !ptr.is_null() {
unsafe {
drop(::std::ffi::CString::from_raw(ptr));
}
}
}
};
}
pub unsafe fn cstr_to_string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
None
} else {
Some(
unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned(),
)
}
}