Skip to main content

wasm_link/
cardinality.rs

1//! Cardinality wrappers for plugin collections.
2
3use std::collections::HashMap ;
4use std::future::Future ;
5use std::hash::Hash ;
6
7use futures::future::{ BoxFuture, join_all };
8use nonempty_collections::{ NEMap, NonEmptyIterator, IntoNonEmptyIterator };
9use wasmtime::component::Val ;
10
11
12
13/// Cardinality behavior for plugin containers.
14///
15/// Implementations preserve the container shape while allowing the inner value
16/// type to be transformed.
17pub trait Cardinality<Id, T>: Sized {
18	/// Same cardinality with a different inner type.
19	type Rebind<U>;
20
21	/// Maps values by reference while preserving cardinality.
22	fn map<N>( &self, map: impl FnMut( &Id, &T ) -> N ) -> Self::Rebind<N>
23	where
24		Id: Clone;
25
26	/// Maps values by value while preserving cardinality.
27	fn map_mut<N>( self, map: impl FnMut( T ) -> N ) -> Self::Rebind<N> ;
28
29	/// Maps cloned values asynchronously while preserving cardinality.
30	///
31	/// Calls for collection cardinalities may run concurrently.
32	///
33	/// # Example
34	///
35	/// ```
36	/// use wasm_link::cardinality::{ Cardinality, ExactlyOne };
37	///
38	/// # futures::executor::block_on( async {
39	/// let values = ExactlyOne( "plugin", 41_u32 );
40	/// let mapped = values.map_async(| _id, value | async move { value + 1 }).await;
41	/// assert_eq!( mapped.1, 42 );
42	/// # });
43	/// ```
44	fn map_async<'a, N, F, Fut>( &'a self, map: F ) -> BoxFuture<'a, Self::Rebind<N>>
45	where
46		Id: Clone + Send + 'a,
47		T: Clone + Send + 'a,
48		N: Send + 'a,
49		F: Fn( Id, T ) -> Fut + Send + 'a,
50		Fut: Future<Output = N> + Send + 'a;
51
52	/// Returns the value associated with `id`, if present.
53	fn get( &self, id: &Id ) -> Option<&T>
54	where
55		Id: Hash + Eq ;
56}
57
58/// Exactly one value with ID, guaranteed present.
59#[derive( Debug, Clone )]
60pub struct ExactlyOne<Id, T>(
61	/// The plugin identifier.
62	pub Id,
63	/// The associated value.
64	pub T
65);
66
67/// Zero or one value with ID.
68#[derive( Debug, Clone )]
69pub struct AtMostOne<Id, T>(
70	/// Optional `(id, value)` pair.
71	pub Option<( Id, T )>
72);
73
74/// One or more values keyed by ID.
75#[derive( Debug, Clone )]
76pub struct AtLeastOne<Id, T>(
77	/// Non-empty map keyed by plugin id.
78	pub NEMap<Id, T>
79);
80
81/// Zero or more values keyed by ID.
82#[derive( Debug, Clone )]
83pub struct Any<Id, T>(
84	/// Map keyed by plugin id.
85	pub HashMap<Id, T>
86);
87
88impl<Id, T> Cardinality<Id, T> for ExactlyOne<Id, T> {
89	type Rebind<U> = ExactlyOne<Id, U>;
90
91	fn map<N>( &self, mut map: impl FnMut( &Id, &T ) -> N ) -> Self::Rebind<N>
92	where
93		Id: Clone,
94	{
95		ExactlyOne( self.0.clone(), map( &self.0, &self.1 ))
96	}
97
98	fn map_mut<N>( self, mut map: impl FnMut( T ) -> N ) -> Self::Rebind<N> {
99		ExactlyOne( self.0, map( self.1 ))
100	}
101
102	fn map_async<'a, N, F, Fut>( &'a self, map: F ) -> BoxFuture<'a, Self::Rebind<N>>
103	where
104		Id: Clone + Send + 'a,
105		T: Clone + Send + 'a,
106		N: Send + 'a,
107		F: Fn( Id, T ) -> Fut + Send + 'a,
108		Fut: Future<Output = N> + Send + 'a,
109	{
110		let id = self.0.clone();
111		let value = self.1.clone();
112		Box::pin( async move {
113			let mapped = map( id.clone(), value ).await;
114			ExactlyOne( id, mapped )
115		})
116	}
117
118	fn get( &self, id: &Id ) -> Option<&T>
119	where
120		Id: Hash + Eq,
121	{
122		// In a singleton wrapper, mismatched ids indicate a logic bug upstream.
123		// We still return the only value in release builds to avoid masking state.
124		debug_assert!( &self.0 == id, "singleton cardinality id mismatch" );
125		Some( &self.1 )
126	}
127}
128
129impl<Id, T> Cardinality<Id, T> for AtMostOne<Id, T> {
130	type Rebind<U> = AtMostOne<Id, U>;
131
132	fn map<N>( &self, mut map: impl FnMut( &Id, &T ) -> N ) -> Self::Rebind<N>
133	where
134		Id: Clone,
135	{
136		match &self.0 {
137			None => AtMostOne( None ),
138			Some(( id, value )) => AtMostOne( Some(( id.clone(), map( id, value )))),
139		}
140	}
141
142	fn map_mut<N>( self, mut map: impl FnMut( T ) -> N ) -> Self::Rebind<N> {
143		match self.0 {
144			None => AtMostOne( None ),
145			Some(( id, value )) => AtMostOne( Some(( id, map( value )))),
146		}
147	}
148
149	fn map_async<'a, N, F, Fut>( &'a self, map: F ) -> BoxFuture<'a, Self::Rebind<N>>
150	where
151		Id: Clone + Send + 'a,
152		T: Clone + Send + 'a,
153		N: Send + 'a,
154		F: Fn( Id, T ) -> Fut + Send + 'a,
155		Fut: Future<Output = N> + Send + 'a,
156	{
157		let value = self.0.clone();
158		Box::pin( async move { match value {
159			None => AtMostOne( None ),
160			Some(( id, value )) => {
161				let mapped = map( id.clone(), value ).await;
162				AtMostOne( Some(( id, mapped )))
163			}
164		}})
165	}
166
167	fn get( &self, id: &Id ) -> Option<&T>
168	where
169		Id: Hash + Eq,
170	{
171		match self.0.as_ref() {
172			None => None,
173			Some(( stored_id, value )) => {
174				// In a singleton wrapper, mismatched ids indicate a logic bug upstream.
175				// We still return the only value in release builds to avoid masking state.
176				debug_assert!( stored_id == id, "singleton cardinality id mismatch" );
177				Some( value )
178			}
179		}
180	}
181}
182
183impl<Id: Hash + Eq, T> Cardinality<Id, T> for AtLeastOne<Id, T> {
184	type Rebind<U> = AtLeastOne<Id, U>;
185
186	fn map<N>( &self, mut map: impl FnMut( &Id, &T ) -> N ) -> Self::Rebind<N>
187	where
188		Id: Clone,
189	{
190		AtLeastOne(
191			self.0.nonempty_iter()
192				.map(|( id, value )| ( id.clone(), map( id, value )))
193				.collect()
194		)
195	}
196
197	fn map_mut<N>( self, mut map: impl FnMut( T ) -> N ) -> Self::Rebind<N> {
198		AtLeastOne(
199			self.0.into_nonempty_iter()
200				.map(|( id, value )| ( id, map( value )))
201				.collect()
202		)
203	}
204
205	fn map_async<'a, N, F, Fut>( &'a self, map: F ) -> BoxFuture<'a, Self::Rebind<N>>
206	where
207		Id: Clone + Send + 'a,
208		T: Clone + Send + 'a,
209		N: Send + 'a,
210		F: Fn( Id, T ) -> Fut + Send + 'a,
211		Fut: Future<Output = N> + Send + 'a,
212	{
213		let entries = self.0.nonempty_iter()
214			.map(|( id, value )| ( id.clone(), value.clone() ))
215			.collect::<Vec<_>>();
216		Box::pin( async move {
217			let mut mapped_values = join_all( entries.into_iter().map(|( id, value )| {
218				let future = map( id.clone(), value );
219				async move { ( id, future.await ) }
220			})).await.into_iter();
221			let Some(( first_id, first_mapped )) = mapped_values.next() else { unreachable!() };
222			let mut mapped = NEMap::new( first_id, first_mapped );
223			mapped.extend( mapped_values );
224			AtLeastOne( mapped )
225		})
226	}
227
228	fn get( &self, id: &Id ) -> Option<&T>
229	where
230		Id: Hash + Eq,
231	{
232		self.0.get( id )
233	}
234}
235
236impl<Id: Hash + Eq, T> Cardinality<Id, T> for Any<Id, T> {
237	type Rebind<U> = Any<Id, U>;
238
239	fn map<N>( &self, mut map: impl FnMut( &Id, &T ) -> N ) -> Self::Rebind<N>
240	where
241		Id: Clone,
242	{
243		Any( self.0.iter().map(|( id, value )| ( id.clone(), map( id, value ))).collect() )
244	}
245
246	fn map_mut<N>( self, mut map: impl FnMut( T ) -> N ) -> Self::Rebind<N> {
247		Any( self.0.into_iter().map(|( id, value )| ( id, map( value ))).collect() )
248	}
249
250	fn map_async<'a, N, F, Fut>( &'a self, map: F ) -> BoxFuture<'a, Self::Rebind<N>>
251	where
252		Id: Clone + Send + 'a,
253		T: Clone + Send + 'a,
254		N: Send + 'a,
255		F: Fn( Id, T ) -> Fut + Send + 'a,
256		Fut: Future<Output = N> + Send + 'a,
257	{
258		let entries = self.0.iter().map(|( id, value )| ( id.clone(), value.clone() )).collect::<Vec<_>>();
259		Box::pin( async move {
260			Any( join_all( entries.into_iter().map(|( id, value )| {
261				let future = map( id.clone(), value );
262				async move { ( id, future.await ) }
263			})).await.into_iter().collect() )
264		})
265	}
266
267	fn get( &self, id: &Id ) -> Option<&T>
268	where
269		Id: Hash + Eq,
270	{
271		self.0.get( id )
272	}
273}
274
275impl<Id: Hash + Eq + Into<Val>> From<ExactlyOne<Id, Val>> for Val {
276	fn from( socket: ExactlyOne<Id, Val> ) -> Self {
277		Val::Tuple( vec![ socket.0.into(), socket.1 ])
278	}
279}
280
281impl<Id: Hash + Eq + Into<Val>> From<AtMostOne<Id, Val>> for Val {
282	fn from( socket: AtMostOne<Id, Val> ) -> Self {
283		match socket.0 {
284			None => Val::Option( None ),
285			Some(( id, val )) => Val::Option( Some( Box::new( Val::Tuple( vec![ id.into(), val ] )))),
286		}
287	}
288}
289
290impl<Id: Hash + Eq + Into<Val>> From<AtLeastOne<Id, Val>> for Val {
291	fn from( socket: AtLeastOne<Id, Val> ) -> Self {
292		Val::Map(
293			socket.0.into_iter()
294				.map(|( id, val )| ( id.into(), val ))
295				.collect()
296		)
297	}
298}
299
300impl<Id: Hash + Eq + Into<Val>> From<Any<Id, Val>> for Val {
301	fn from( socket: Any<Id, Val> ) -> Self {
302		Val::Map(
303			socket.0.into_iter()
304				.map(|( id, val )| ( id.into(), val ))
305				.collect()
306		)
307	}
308}