use serde::de::DeserializeOwned;
use serde::Serialize;
use fidius_core::descriptor::PluginDescriptor;
use crate::error::{CallError, LoadError};
use crate::executor::cdylib::CdylibExecutor;
#[cfg(feature = "python")]
use crate::executor::python::Pyo3Executor;
#[cfg(feature = "wasm")]
use crate::executor::wasm::WasmComponentExecutor;
#[cfg(any(feature = "python", feature = "wasm"))]
use crate::executor::{PluginExecutor, ValueExecutor};
use crate::types::PluginInfo;
enum Backend {
Cdylib(CdylibExecutor),
#[cfg(feature = "python")]
Python(Pyo3Executor),
#[cfg(feature = "wasm")]
Wasm(WasmComponentExecutor),
}
pub struct PluginHandle {
backend: Backend,
}
impl PluginHandle {
pub fn from_loaded(plugin: crate::loader::LoadedPlugin) -> Self {
Self {
backend: Backend::Cdylib(CdylibExecutor::from_loaded(plugin)),
}
}
pub fn from_descriptor(desc: &'static PluginDescriptor) -> Result<Self, LoadError> {
Ok(Self {
backend: Backend::Cdylib(CdylibExecutor::from_descriptor(desc)?),
})
}
pub fn configure_in_process<C: Serialize>(
desc: &'static PluginDescriptor,
config: &C,
) -> Result<Self, LoadError> {
let cfg = fidius_core::wire::serialize(config)
.map_err(|e| LoadError::ConfigSerialization(e.to_string()))?;
Ok(Self {
backend: Backend::Cdylib(CdylibExecutor::from_descriptor_with_config(desc, &cfg)?),
})
}
pub fn find_in_process_descriptor(
plugin_name: &str,
) -> Result<&'static PluginDescriptor, LoadError> {
CdylibExecutor::find_in_process_descriptor(plugin_name)
}
#[cfg(feature = "python")]
pub fn from_python(py: fidius_python::PythonPluginHandle, info: PluginInfo) -> Self {
Self {
backend: Backend::Python(Pyo3Executor::new(py, info)),
}
}
#[cfg(feature = "wasm")]
pub fn from_wasm(executor: WasmComponentExecutor) -> Self {
Self {
backend: Backend::Wasm(executor),
}
}
pub fn call_method<I: Serialize, O: DeserializeOwned>(
&self,
index: usize,
input: &I,
) -> Result<O, CallError> {
match &self.backend {
Backend::Cdylib(e) => e.call_method(index, input),
#[cfg(feature = "python")]
Backend::Python(e) => {
let args = fidius_core::to_value(input)
.map_err(|err| CallError::Serialization(err.to_string()))?;
let out = ValueExecutor::call(e, index, args)?;
fidius_core::from_value(out)
.map_err(|err| CallError::Deserialization(err.to_string()))
}
#[cfg(feature = "wasm")]
Backend::Wasm(e) => {
let args = fidius_core::to_value(input)
.map_err(|err| CallError::Serialization(err.to_string()))?;
let out = ValueExecutor::call(e, index, args)?;
fidius_core::from_value(out)
.map_err(|err| CallError::Deserialization(err.to_string()))
}
}
}
#[cfg(feature = "streaming")]
pub async fn call_streaming<I: Serialize, O: DeserializeOwned + Serialize>(
&self,
index: usize,
input: &I,
) -> Result<crate::stream::ChunkStream, CallError> {
match &self.backend {
Backend::Cdylib(e) => {
let input_bytes = fidius_core::wire::serialize(input)
.map_err(|err| CallError::Serialization(err.to_string()))?;
e.call_streaming_raw(index, &input_bytes, cdylib_stream_decode::<O>)
}
#[cfg(feature = "python")]
Backend::Python(e) => {
let args = fidius_core::to_value(input)
.map_err(|err| CallError::Serialization(err.to_string()))?;
crate::stream::StreamExecutor::call_streaming(e, index, args).await
}
#[cfg(feature = "wasm")]
Backend::Wasm(e) => {
let args = fidius_core::to_value(input)
.map_err(|err| CallError::Serialization(err.to_string()))?;
crate::stream::StreamExecutor::call_streaming(e, index, args).await
}
}
}
#[cfg(feature = "streaming")]
pub async fn call_bidi_streaming<I, A, O>(
&self,
index: usize,
items: impl IntoIterator<Item = I, IntoIter: Send + 'static>,
args: &A,
) -> Result<crate::stream::ChunkStream, CallError>
where
I: Serialize + 'static,
A: Serialize,
O: DeserializeOwned + Serialize,
{
match &self.backend {
Backend::Cdylib(e) => {
let handle = crate::client_stream::host_producer_handle_typed(items.into_iter());
let arg_bytes = fidius_core::wire::serialize(args)
.map_err(|err| CallError::Serialization(err.to_string()))?;
unsafe {
e.call_bidi_streaming_raw(index, handle, &arg_bytes, cdylib_stream_decode::<O>)
}
}
#[cfg(feature = "python")]
Backend::Python(e) => {
let producer = lazy_json_producer(items);
let arg_value = fidius_core::to_value(args)
.map_err(|err| CallError::Serialization(err.to_string()))?;
e.call_bidi_streaming(index, producer, arg_value)
}
#[cfg(feature = "wasm")]
Backend::Wasm(e) => {
let producer = lazy_bincode_producer(items);
let arg_value = fidius_core::to_value(args)
.map_err(|err| CallError::Serialization(err.to_string()))?;
e.call_bidi_streaming(index, producer, arg_value).await
}
}
}
pub fn call_method_raw(&self, index: usize, input: &[u8]) -> Result<Vec<u8>, CallError> {
match &self.backend {
Backend::Cdylib(e) => e.call_method_raw(index, input),
#[cfg(feature = "python")]
Backend::Python(e) => PluginExecutor::call_raw(e, index, input),
#[cfg(feature = "wasm")]
Backend::Wasm(e) => PluginExecutor::call_raw(e, index, input),
}
}
#[cfg(feature = "streaming")]
pub unsafe fn call_client_streaming_raw(
&self,
index: usize,
handle: *mut fidius_core::stream_ffi::FidiusStreamHandle,
input: &[u8],
) -> Result<Vec<u8>, CallError> {
match &self.backend {
Backend::Cdylib(e) => unsafe { e.call_client_streaming_raw(index, handle, input) },
#[cfg(feature = "python")]
Backend::Python(_) => Err(CallError::Backend {
runtime: "python".into(),
message: "client-streaming is not yet wired for Python (FIDIUS-I-0030 CS2.4)"
.into(),
}),
#[cfg(feature = "wasm")]
Backend::Wasm(_) => Err(CallError::Backend {
runtime: "wasm".into(),
message: "use the typed `call_client_streaming` for the WASM backend".into(),
}),
}
}
#[cfg(feature = "streaming")]
pub fn call_client_streaming<I, A, O>(
&self,
method: usize,
items: impl IntoIterator<Item = I, IntoIter: Send + 'static>,
args: &A,
) -> Result<O, CallError>
where
I: Serialize + 'static,
A: Serialize,
O: DeserializeOwned,
{
match &self.backend {
Backend::Cdylib(e) => {
let handle = crate::client_stream::host_producer_handle_typed(items.into_iter());
let arg_bytes = fidius_core::wire::serialize(args)
.map_err(|e| CallError::Serialization(e.to_string()))?;
let out = unsafe { e.call_client_streaming_raw(method, handle, &arg_bytes) }?;
fidius_core::wire::deserialize(&out)
.map_err(|e| CallError::Deserialization(e.to_string()))
}
#[cfg(feature = "wasm")]
Backend::Wasm(e) => {
let producer = lazy_bincode_producer(items);
let arg_value = fidius_core::to_value(args)
.map_err(|err| CallError::Serialization(err.to_string()))?;
let out = e.call_client_streaming(method, producer, arg_value)?;
fidius_core::from_value(out)
.map_err(|err| CallError::Deserialization(err.to_string()))
}
#[cfg(feature = "python")]
Backend::Python(e) => {
let producer = lazy_json_producer(items);
let arg_value = fidius_core::to_value(args)
.map_err(|err| CallError::Serialization(err.to_string()))?;
let out = e.call_client_streaming(method, producer, arg_value)?;
fidius_core::from_value(out)
.map_err(|err| CallError::Deserialization(err.to_string()))
}
}
}
pub fn has_capability(&self, bit: u32) -> bool {
if bit >= 64 {
return false;
}
self.info().capabilities & (1u64 << bit) != 0
}
pub fn info(&self) -> &PluginInfo {
match &self.backend {
Backend::Cdylib(e) => e.info(),
#[cfg(feature = "python")]
Backend::Python(e) => PluginExecutor::info(e),
#[cfg(feature = "wasm")]
Backend::Wasm(e) => PluginExecutor::info(e),
}
}
pub fn method_metadata(&self, method_id: u32) -> Vec<(&str, &str)> {
match &self.backend {
Backend::Cdylib(e) => e.method_metadata(method_id),
#[cfg(feature = "python")]
Backend::Python(_) => Vec::new(),
#[cfg(feature = "wasm")]
Backend::Wasm(_) => Vec::new(),
}
}
pub fn trait_metadata(&self) -> Vec<(&str, &str)> {
match &self.backend {
Backend::Cdylib(e) => e.trait_metadata(),
#[cfg(feature = "python")]
Backend::Python(_) => Vec::new(),
#[cfg(feature = "wasm")]
Backend::Wasm(_) => Vec::new(),
}
}
}
#[cfg(feature = "streaming")]
fn cdylib_stream_decode<O: DeserializeOwned + Serialize>(
bytes: &[u8],
) -> Result<fidius_core::Value, CallError> {
let item: O = fidius_core::wire::deserialize(bytes)
.map_err(|e| CallError::Deserialization(e.to_string()))?;
fidius_core::to_value(&item).map_err(|e| CallError::Serialization(e.to_string()))
}
#[cfg(all(feature = "streaming", feature = "wasm"))]
fn lazy_bincode_producer<I: Serialize + 'static>(
items: impl IntoIterator<Item = I, IntoIter: Send + 'static>,
) -> Box<dyn Iterator<Item = Vec<u8>> + Send> {
Box::new(
items
.into_iter()
.filter_map(|i| fidius_core::wire::serialize(&i).ok()),
)
}
#[cfg(all(feature = "streaming", feature = "python"))]
fn lazy_json_producer<I: Serialize + 'static>(
items: impl IntoIterator<Item = I, IntoIter: Send + 'static>,
) -> Box<dyn Iterator<Item = serde_json::Value> + Send> {
Box::new(items.into_iter().filter_map(|i| {
fidius_core::to_value(&i)
.ok()
.and_then(|v| serde_json::to_value(v).ok())
}))
}