fidius-host::handle Rust¶
PluginHandle — the unified, caller-facing proxy over a loaded plugin.
A PluginHandle is backend-agnostic: callers use the same
call_method / call_method_raw API whether the plugin is a cdylib, a
Python package, or (Phase 2) a WASM component. The backend lives in the
private [Backend] enum.
Structs¶
fidius-host::handle::PluginHandle¶
pub
A handle to a loaded plugin, ready for calling methods.
Holds the active execution backend. call_method() handles serialization,
dispatch, and cleanup; concurrent calls from multiple threads are safe as
long as the underlying plugin is thread-safe (the cdylib macro enforces
&self-only methods; the Python backend serialises through the GIL).
Fields¶
| Name | Type | Description |
|---|---|---|
backend |
Backend |
Methods¶
from_loaded pub¶
Create a PluginHandle from a freshly loaded cdylib plugin.
Source
from_descriptor pub¶
Create a PluginHandle from a descriptor already registered in the current process's inventory (a #[plugin_impl] linked as a normal rlib). No dylib is loaded. Used by Client::in_process(plugin_name).
Source
configure_in_process pub¶
fn configure_in_process < C : Serialize > (desc : & 'static PluginDescriptor , config : & C ,) -> Result < Self , LoadError >
Construct a configured in-process plugin instance (FIDIUS-A-0006 / CI.2): serialize config and bind it once at construction. The plugin's #[plugin_impl(Trait, config = C)] configure constructor receives it; methods then close over it without re-passing. The config crosses the boundary exactly once, and N differently-configured instances can coexist.
Source
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)?),
})
}
configure_from_loaded pub¶
fn configure_from_loaded < C : Serialize > (plugin : crate :: loader :: LoadedPlugin , config : & C ,) -> Result < Self , LoadError >
Construct a configured plugin instance from a DYNAMICALLY loaded cdylib — the dynamic-load analogue of [Self::configure_in_process]. A LoadedPlugin from load_library / [PluginHost::load] is constructed with config bound once (the plugin's #[plugin_impl(Trait, config = C)] configure constructor receives it) instead of the singleton [Self::from_loaded] builds. This lets a host load a configured provider cdylib at runtime and bind N differently-configured instances from the same library. The config crosses the boundary exactly once, at construction.
Source
pub fn configure_from_loaded<C: Serialize>(
plugin: crate::loader::LoadedPlugin,
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_loaded_with_config(plugin, &cfg)),
})
}
find_in_process_descriptor pub¶
fn find_in_process_descriptor (plugin_name : & str ,) -> Result < & 'static PluginDescriptor , LoadError >
Look up a descriptor in the current process's inventory registry by plugin_name (the Rust struct name passed to #[plugin_impl]).
Source
from_python pub¶
Create a PluginHandle backed by a loaded Python plugin. info is built by the loader from the package manifest + interface descriptor. Only available with the python feature.
Source
from_wasm pub¶
Create a PluginHandle backed by a loaded WASM component. Only available with the wasm feature.
Source
call_method pub¶
fn call_method < I : Serialize , O : DeserializeOwned > (& self , index : usize , input : & I ,) -> Result < O , CallError >
Call a plugin method by vtable index.
Serializes the input with the backend's native wire (cdylib → bincode;
Python/WASM → [fidius_core::Value]), dispatches, and decodes the
result into O. No built-in timeout — see the fidius crate docs.
Source
pub fn call_method<I: Serialize, O: DeserializeOwned>(
&self,
index: usize,
input: &I,
) -> Result<O, CallError> {
match &self.backend {
// cdylib: serialise the concrete type with bincode directly — byte
// for byte what the plugin's shim decodes (no `Value` hop).
Backend::Cdylib(e) => e.call_method(index, input),
// python: cross via the self-describing `Value` currency.
#[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()))
}
// wasm: same self-describing `Value` currency as python.
#[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()))
}
}
}
call_streaming pub¶
async
async fn call_streaming < I : Serialize , O : DeserializeOwned + Serialize > (& self , index : usize , input : & I ,) -> Result < crate :: stream :: ChunkStream , CallError >
Start a server-streaming method call by vtable index (FIDIUS-I-0026).
Returns a [crate::stream::ChunkStream] — a futures::Stream of
Result<Value, _> the caller pulls with .next().await. Backpressure and
cancellation are structural: a slow consumer parks the producer, and
dropping the stream tears the producer down. All three backends stream:
Python and WASM cross via the self-describing [Value] currency; cdylib
crosses items as concrete bincode of the item type O and decodes them
here (FIDIUS-T-0137).
O is the stream's item type. Python/WASM ignore it (they're already
Value-native); cdylib uses it to bincode::<O>-decode each item.
Source
pub async fn call_streaming<I: Serialize, O: DeserializeOwned + Serialize>(
&self,
index: usize,
input: &I,
) -> Result<crate::stream::ChunkStream, CallError> {
match &self.backend {
// cdylib: concrete bincode of the args (no `Value` hop), then the
// iterator-handle streaming path (FIDIUS-I-0026 CS.1). Items also cross
// as concrete bincode, decoded by `cdylib_stream_decode::<O>`.
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
}
}
}
call_bidi_streaming pub¶
async
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 ,
Start a bidirectional streaming call (FIDIUS-I-0032 / ADR-0010): the host produces items (the plugin's Stream<In> argument) and consumes the plugin's Stream<Out> return as the returned [crate::stream::ChunkStream]. Pulling the output drives the plugin, which pulls the input on demand — the synchronous lazy-pull composition. args are the non-stream arguments. O is the output item type. Wired for cdylib; WASM/Python are BD.3/BD.4.
Source
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 {
// Lazy producer — items are encoded only as the plugin pulls them (T-0172).
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()))?;
// SAFETY: `handle` is a freshly-built, exclusively-owned producer.
unsafe {
e.call_bidi_streaming_raw(index, handle, &arg_bytes, cdylib_stream_decode::<O>)
}
}
#[cfg(feature = "python")]
Backend::Python(e) => {
// Python crosses via the self-describing `Value` currency, streamed lazily.
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
}
}
}
call_method_raw pub¶
Call a #[wire(raw)] method: raw bytes in, raw bytes out, no bincode.
Source
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),
}
}
call_client_streaming_raw pub¶
unsafe
unsafe fn call_client_streaming_raw (& self , index : usize , handle : * mut fidius_core :: stream_ffi :: FidiusStreamHandle , input : & [u8] ,) -> Result < Vec < u8 > , CallError >
Client-streaming raw call (FIDIUS-I-0030 CS2.2): pass the host's producer handle (built via [crate::client_stream::host_producer_handle]) and the bincode of the non-stream args; returns the bincode of the method's result. Wired for the cdylib backend; WASM/Python land in CS2.3/CS2.4. The typed call_client_streaming wrapper is CS2.5.
Safety¶
handle must be a valid, exclusively-owned producer handle (e.g. from [crate::client_stream::host_producer_handle]); it is consumed by the call.
Source
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 {
// SAFETY: forwarded per this fn's contract.
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(),
}),
}
}
call_client_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 ,
Typed client-streaming (FIDIUS-I-0030): the host produces items (the Stream<T> argument); the plugin pulls + consumes them and returns O. args are the method's non-stream arguments (a tuple). Wired for cdylib (in-process producer handle) and WASM (the fidius:stream-pull import); Python is CS2.4. The safe wrapper over the per-backend mechanisms.
Source
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 {
// cdylib: a lazy producer handle — each item is bincode-encoded only as the
// plugin pulls it, so an unbounded input stays bounded in memory (T-0172).
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()))?;
// SAFETY: `handle` is a freshly-built, exclusively-owned producer.
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()))
}
// WASM: same laziness — the boxed producer encodes on pull from the import.
#[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()))
}
// Python crosses via the self-describing `Value` currency, streamed lazily
// (FIDIUS-T-0174) — each item is converted only as the Python iterator pulls it.
#[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()))
}
}
}
bind_wasm_host_table pub¶
unsafe
unsafe fn bind_wasm_host_table (& self , table : * const fidius_core :: host_ffi :: HostFunctionTable ,) -> Result < () , LoadError >
Bind a host-function table (plugin → host callback channel) to a WASM-backed handle. The generated <Trait>Binding::bind_wasm resolves through this: table must be a process-lifetime table built by a #[host_interface]-generated binding (<Trait>Binding::table). The guest's fidius:host-call import gates every dispatch against the table's version + signature hash, so a mismatched surface fails with a typed error and can never mis-dispatch.
Returns LoadError::HostBindFailed with
[fidius_core::host_ffi::BIND_ERR_WRONG_BACKEND] for non-WASM
backends — cdylib handles bind through <Trait>Binding::bind /
bind_plugin (the dylib import registry) instead.
Safety¶
table must be null or a valid, process-lifetime [fidius_core::host_ffi::HostFunctionTable] (e.g. the leaked table a generated <Trait>Binding::table builds); the backend retains it and dispatches through it for its remaining lifetime.
Source
pub unsafe fn bind_wasm_host_table(
&self,
table: *const fidius_core::host_ffi::HostFunctionTable,
) -> Result<(), LoadError> {
match &self.backend {
// SAFETY: forwarded per this fn's contract.
Backend::Wasm(e) => unsafe { e.bind_host_table(table) },
_ => {
use fidius_core::host_ffi::{bind_status_message, BIND_ERR_WRONG_BACKEND};
let interface = if table.is_null() {
"<null>".to_string()
} else {
// SAFETY: non-null table per the bind contract.
unsafe { std::ffi::CStr::from_ptr((*table).interface_name) }
.to_str()
.unwrap_or("<invalid>")
.to_string()
};
Err(LoadError::HostBindFailed {
interface,
code: BIND_ERR_WRONG_BACKEND,
message: bind_status_message(BIND_ERR_WRONG_BACKEND).to_string(),
})
}
}
}
has_capability pub¶
Check if an optional method is supported (capability bit set). Returns false for bit >= 64 and for backends without capabilities.
Source
info pub¶
Access the plugin's owned metadata.
Source
method_metadata pub¶
Static #[method_meta(...)] key/value metadata for the given method, in declaration order. Empty for out-of-range ids, for interfaces that declared none, and for backends without descriptor metadata.
Source
pub fn method_metadata(&self, method_id: u32) -> Vec<(&str, &str)> {
match &self.backend {
Backend::Cdylib(e) => e.method_metadata(method_id),
// Python/WASM plugins carry no descriptor-level method metadata.
#[cfg(feature = "python")]
Backend::Python(_) => Vec::new(),
#[cfg(feature = "wasm")]
Backend::Wasm(_) => Vec::new(),
}
}
trait_metadata pub¶
Static #[trait_meta(...)] key/value metadata declared on the trait. Empty when none was declared or for backends without descriptor metadata.
Source
Enums¶
fidius-host::handle::Backend private¶
The execution backend behind a [PluginHandle].
One variant per runtime. The WASM variant lands in Phase 2.
Variants¶
CdylibPython-.pypackage viafidius-python's embedded interpreter. Only present when thepythonfeature is enabled.Wasm-.wasmcomponent via wasmtime. Only present when thewasmfeature is enabled.
Functions¶
fidius-host::handle::cdylib_stream_decode¶
private
fn cdylib_stream_decode < O : DeserializeOwned + Serialize > (bytes : & [u8] ,) -> Result < fidius_core :: Value , CallError >
Per-item decoder for the cdylib streaming fast path (FIDIUS-T-0137): each item crosses as concrete bincode(O) (byte-identical to the unary cdylib wire), so we wire::deserialize::<O> then lift to a Value. This is the decode_item fn pointer the typed caller hands to [CdylibExecutor::call_streaming_raw] — O is monomorphised in by call_streaming::<_, O>.
Source
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()))
}
fidius-host::handle::lazy_bincode_producer¶
private
fn lazy_bincode_producer < I : Serialize + 'static > (items : impl IntoIterator < Item = I , IntoIter : Send + 'static > ,) -> Box < dyn Iterator < Item = Vec < u8 > > + Send >
A lazy, boxed bincode producer for the WASM client/bidi streaming input path: each item is bincode-encoded only when the guest's fidius:stream-pull import pulls it (FIDIUS-T-0172), so an unbounded input stays bounded in host memory. An item that fails to encode is skipped (bincode of a Serialize type is effectively infallible, and a panic must not cross the host→guest call).
Source
fidius-host::handle::lazy_json_producer¶
private
fn lazy_json_producer < I : Serialize + 'static > (items : impl IntoIterator < Item = I , IntoIter : Send + 'static > ,) -> Box < dyn Iterator < Item = serde_json :: Value > + Send >
A lazy, boxed producer of Value-shaped JSON for the Python client/bidi streaming input path (FIDIUS-T-0174): each item is converted (I → Value → serde_json) only as the Python iterator pulls it, so an unbounded input stays bounded in host memory. An item that fails to convert is skipped (effectively infallible for real types; a panic must not cross into the interpreter).