Skip to main content

wasm_link/
binding.rs

1//! Binding specification and metadata types.
2//!
3//! A [`Binding`] defines an abstract contract specifying what plugins must implement
4//! (via plugs) or what they could depend on (via sockets). It bundles one or more WIT
5//! [`Interface`]s under a single identifier.
6
7use std::sync::{ Arc, Mutex };
8use std::collections::HashMap ;
9use wasmtime::component::{ Linker, Val };
10
11use crate::{ Interface, PluginContext };
12use crate::cardinality::{ Any, AtLeastOne, AtMostOne, Cardinality, ExactlyOne };
13use crate::plugin_instance::PluginInstance ;
14
15
16
17type PluginSockets<PluginId, Ctx, Plugins> =
18	<Plugins as Cardinality<PluginId, PluginInstance<Ctx>>>::Rebind<Mutex<PluginInstance<Ctx>>> ;
19
20type DispatchResults<PluginId, Ctx, Plugins> =
21	<PluginSockets<PluginId, Ctx, Plugins> as Cardinality<PluginId, Mutex<PluginInstance<Ctx>>>>::Rebind<
22		Result<wasmtime::component::Val, crate::DispatchError>
23	>;
24
25type DispatchVals<PluginId, Ctx, Plugins> =
26	<PluginSockets<PluginId, Ctx, Plugins> as Cardinality<PluginId, Mutex<PluginInstance<Ctx>>>>::Rebind<
27		wasmtime::component::Val
28	>;
29
30struct BindingData<PluginId, Ctx, Plugins>
31where
32	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
33	Ctx: PluginContext + 'static,
34	Plugins: Cardinality<PluginId, PluginInstance<Ctx>>,
35	PluginSockets<PluginId, Ctx, Plugins>: Send + Sync,
36{
37	package_name: String,
38	interfaces: HashMap<String, Interface>,
39	plugins: PluginSockets<PluginId, Ctx, Plugins>,
40}
41
42/// An abstract contract specifying what plugins must implement (via plugs) or what
43/// they could depend on (via sockets). It bundles one or more WIT [`Interface`]s
44/// under a single package name.
45///
46/// `Binding` is a handle to shared state. Cloning a `Binding` creates another handle
47/// to the same underlying binding, enabling shared dependencies where multiple
48/// plugins depend on the same binding.
49///
50/// ```
51/// # use std::collections::{ HashMap, HashSet };
52/// # use wasm_link::{ Binding, Interface, Function, FunctionKind, ReturnKind, Plugin, Engine, Component, Linker, ResourceTable };
53/// # use wasm_link::cardinality::ExactlyOne ;
54/// # struct Ctx { resource_table: ResourceTable }
55/// # impl wasm_link::PluginContext for Ctx {
56/// # 	fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
57/// # }
58/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
59/// # let engine = Engine::default();
60/// # let linker = Linker::new( &engine );
61/// # let plugin = Plugin::new( Component::new( &engine, "(component)" )?, Ctx { resource_table: ResourceTable::new() }).instantiate( &engine, &linker )?;
62/// let binding: Binding<String, Ctx> = Binding::new(
63/// 	"my:package",
64/// 	HashMap::from([
65/// 		( "api".to_string(), Interface::new(
66/// 			HashMap::from([( "get-value".into(), Function::new(
67/// 				FunctionKind::Freestanding,
68/// 				ReturnKind::MayContainResources,
69/// 			))]),
70/// 			HashSet::from([ "my-resource".to_string() ]),
71/// 		)),
72/// 	]),
73/// 	ExactlyOne( "my-plugin".to_string(), plugin ),
74/// );
75///
76/// // Clone for shared dependencies - both refer to the same binding
77/// let binding_clone = binding.clone();
78/// # let binding_any_clone = binding.into_any().clone();
79/// # Ok(())
80/// # }
81/// ```
82///
83/// # Type Parameters
84/// - `PluginId`: Unique identifier type for plugins (e.g., `String`, `UUID`)
85pub struct Binding<PluginId, Ctx, Plugins = ExactlyOne<PluginId, PluginInstance<Ctx>>>(Arc<BindingData<PluginId, Ctx, Plugins>>)
86where
87	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
88	Ctx: PluginContext + 'static,
89	Plugins: Cardinality<PluginId, PluginInstance<Ctx>> + 'static,
90	PluginSockets<PluginId, Ctx, Plugins>: Send + Sync;
91
92impl<PluginId, Ctx, Plugins> Clone for Binding<PluginId, Ctx, Plugins>
93where
94	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
95	Ctx: PluginContext + 'static,
96	Plugins: Cardinality<PluginId, PluginInstance<Ctx>> + 'static,
97	PluginSockets<PluginId, Ctx, Plugins>: Send + Sync,
98{
99	/// Creates another handle to the same underlying binding, enabling shared dependencies where
100	/// multiple plugins depend on the same binding.
101	fn clone( &self ) -> Self {
102		Self( Arc::clone( &self.0 ))
103	}
104}
105
106impl<PluginId, Ctx, Plugins> std::fmt::Debug for Binding<PluginId, Ctx, Plugins>
107where
108	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + std::fmt::Debug + 'static,
109	Ctx: PluginContext + std::fmt::Debug + 'static,
110	Plugins: Cardinality<PluginId, PluginInstance<Ctx>> + 'static,
111	PluginSockets<PluginId, Ctx, Plugins>: Send + Sync,
112	PluginSockets<PluginId, Ctx, Plugins>: std::fmt::Debug,
113{
114	fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result {
115		f.debug_struct( "Binding" )
116			.field( "package_name", &self.0.package_name )
117			.field( "interfaces", &self.0.interfaces )
118			.field( "plugins", &self.0.plugins )
119			.finish()
120	}
121}
122
123impl<PluginId, Ctx, Plugins> Binding<PluginId, Ctx, Plugins>
124where
125	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
126	Ctx: PluginContext + 'static,
127	Plugins: Cardinality<PluginId, PluginInstance<Ctx>> + 'static,
128	PluginSockets<PluginId, Ctx, Plugins>: Cardinality<PluginId, Mutex<PluginInstance<Ctx>>>,
129	PluginSockets<PluginId, Ctx, Plugins>: Send + Sync,
130{
131
132	/// Creates a new binding specification.
133	pub fn new(
134		package_name: impl Into<String>,
135		interfaces: HashMap<String, Interface>,
136		plugins: Plugins
137	) -> Self {
138		Self( Arc::new( BindingData {
139			package_name: package_name.into(),
140			interfaces,
141			plugins: plugins.map_mut( Mutex::new ),
142		}))
143	}
144
145	pub(crate) fn add_to_linker( binding: &Binding<PluginId, Ctx, Plugins>, linker: &mut Linker<Ctx> ) -> Result<(), wasmtime::Error>
146	where
147		PluginId: Into<Val>,
148		DispatchVals<PluginId, Ctx, Plugins>: Into<Val>,
149	{
150		binding.0.interfaces.iter().try_for_each(|( name, interface )| {
151			let interface_ident = format!( "{}/{}", binding.0.package_name, name );
152			interface.add_to_linker( linker, &binding.0.package_name, &interface_ident, name, binding )
153		})
154	}
155
156	pub(crate) fn plugins( &self ) -> &PluginSockets<PluginId, Ctx, Plugins> {
157		&self.0.plugins
158	}
159
160	/// Dispatches a function call to all plugins implementing this binding.
161	///
162	/// This is used for external dispatch (calling into the plugin graph from outside).
163	/// The result is wrapped in a type matching the binding's cardinality.
164	///
165	/// # Arguments
166	/// * `interface_name` - The interface name within this binding (e.g., "example")
167	/// * `function_name` - The function name within the interface (e.g., "get-value")
168	/// * `args` - Arguments to pass to the function
169	///
170	/// # Returns
171	/// A cardinality wrapper containing `Result<Val, DispatchError>` for each plugin.
172	/// For [`ReturnKind::Void`]( crate::ReturnKind::Void ), the value is an empty tuple
173	/// (`Val::Option( None )`) placeholder.
174	///
175	/// # Errors
176	/// Returns an error if the interface or function is not found in this binding.
177	pub fn dispatch(
178		&self,
179		interface_name: &str,
180		function_name: &str,
181		args: &[wasmtime::component::Val],
182	) -> Result<DispatchResults<PluginId, Ctx, Plugins>, crate::DispatchError> {
183
184		let interface = self.0.interfaces.get( interface_name )
185			.ok_or_else(|| crate::DispatchError::InvalidInterfacePath( format!( "{}/{}", self.0.package_name, interface_name )))?;
186
187		let function = interface.function( function_name )
188			.ok_or_else(|| crate::DispatchError::InvalidFunction( function_name.to_string() ))?;
189
190		Ok( self.0.plugins.map(| _, plugin | plugin
191			.lock().map_err(|_| crate::DispatchError::LockRejected )
192			.and_then(| mut lock | lock.dispatch(
193				&self.0.package_name,
194				interface_name,
195				function_name,
196				function,
197				args,
198			))
199		))
200
201	}
202
203}
204
205/// Type-erased binding wrapper for heterogeneous socket lists.
206///
207/// Use when a plugin's sockets include bindings with different cardinalities.
208#[derive( Debug )]
209pub enum BindingAny<PluginId, Ctx>
210where
211	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
212	Ctx: PluginContext + 'static,
213{
214	/// Exactly one plugin implementation.
215	ExactlyOne( Binding<PluginId, Ctx, ExactlyOne<PluginId, PluginInstance<Ctx>>> ),
216	/// Zero or one plugin implementation.
217	AtMostOne( Binding<PluginId, Ctx, AtMostOne<PluginId, PluginInstance<Ctx>>> ),
218	/// One or more plugin implementations.
219	AtLeastOne( Binding<PluginId, Ctx, AtLeastOne<PluginId, PluginInstance<Ctx>>> ),
220	/// Zero or more plugin implementations.
221	Any( Binding<PluginId, Ctx, Any<PluginId, PluginInstance<Ctx>>> ),
222}
223
224impl<PluginId, Ctx> BindingAny<PluginId, Ctx>
225where
226	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + Into<Val> + 'static,
227	Ctx: PluginContext + 'static,
228{
229	pub(crate) fn add_to_linker( &self, linker: &mut Linker<Ctx> ) -> Result<(), wasmtime::Error> {
230		match self {
231			Self::ExactlyOne( binding ) => Binding::add_to_linker( binding, linker ),
232			Self::AtMostOne( binding ) => Binding::add_to_linker( binding, linker ),
233			Self::AtLeastOne( binding ) => Binding::add_to_linker( binding, linker ),
234			Self::Any( binding ) => Binding::add_to_linker( binding, linker ),
235		}
236	}
237}
238
239impl<PluginId, Ctx> From<Binding<PluginId, Ctx, ExactlyOne<PluginId, PluginInstance<Ctx>>>> for BindingAny<PluginId, Ctx>
240where
241	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
242	Ctx: PluginContext + 'static,
243{
244	fn from( binding: Binding<PluginId, Ctx, ExactlyOne<PluginId, PluginInstance<Ctx>>> ) -> Self {
245		Self::ExactlyOne( binding )
246	}
247}
248
249impl<PluginId, Ctx> From<Binding<PluginId, Ctx, AtMostOne<PluginId, PluginInstance<Ctx>>>> for BindingAny<PluginId, Ctx>
250where
251	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
252	Ctx: PluginContext + 'static,
253{
254	fn from( binding: Binding<PluginId, Ctx, AtMostOne<PluginId, PluginInstance<Ctx>>> ) -> Self {
255		Self::AtMostOne( binding )
256	}
257}
258
259impl<PluginId, Ctx> From<Binding<PluginId, Ctx, AtLeastOne<PluginId, PluginInstance<Ctx>>>> for BindingAny<PluginId, Ctx>
260where
261	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
262	Ctx: PluginContext + 'static,
263{
264	fn from( binding: Binding<PluginId, Ctx, AtLeastOne<PluginId, PluginInstance<Ctx>>> ) -> Self {
265		Self::AtLeastOne( binding )
266	}
267}
268
269impl<PluginId, Ctx> From<Binding<PluginId, Ctx, Any<PluginId, PluginInstance<Ctx>>>> for BindingAny<PluginId, Ctx>
270where
271	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
272	Ctx: PluginContext + 'static,
273{
274	fn from( binding: Binding<PluginId, Ctx, Any<PluginId, PluginInstance<Ctx>>> ) -> Self {
275		Self::Any( binding )
276	}
277}
278
279impl<PluginId, Ctx, Plugins> Binding<PluginId, Ctx, Plugins>
280where
281	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
282	Ctx: PluginContext + 'static,
283	Plugins: Cardinality<PluginId, PluginInstance<Ctx>>,
284	PluginSockets<PluginId, Ctx, Plugins>: Send + Sync,
285	BindingAny<PluginId, Ctx>: From<Binding<PluginId, Ctx, Plugins>>,
286{
287	/// Converts this binding into a type-erased [`BindingAny`] for heterogeneous socket lists.
288	pub fn into_any( self ) -> BindingAny<PluginId, Ctx> {
289		self.into()
290	}
291}
292
293impl<PluginId, Ctx> Clone for BindingAny<PluginId, Ctx>
294where
295	PluginId: std::hash::Hash + Eq + Clone + Send + Sync + 'static,
296	Ctx: PluginContext + 'static,
297{
298	/// Creates another handle to the same underlying binding, enabling shared dependencies where
299	/// multiple plugins depend on the same binding.
300	fn clone( &self ) -> Self {
301		match self {
302			Self::ExactlyOne( binding ) => Self::ExactlyOne( binding.clone() ),
303			Self::AtMostOne( binding ) => Self::AtMostOne( binding.clone() ),
304			Self::AtLeastOne( binding ) => Self::AtLeastOne( binding.clone() ),
305			Self::Any( binding ) => Self::Any( binding.clone() ),
306		}
307	}
308}