1use std::{cell::OnceCell, marker::PhantomData, ops::Deref, rc::Rc};
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub struct CapabilityReference<C> {
11 capability_id: &'static str,
12 descriptor_version: &'static str,
13 descriptor_digest: &'static str,
14 client: PhantomData<fn() -> C>,
15}
16
17impl<C> CapabilityReference<C> {
18 #[must_use]
19 pub const fn new(
20 capability_id: &'static str,
21 descriptor_version: &'static str,
22 descriptor_digest: &'static str,
23 ) -> Self {
24 Self {
25 capability_id,
26 descriptor_version,
27 descriptor_digest,
28 client: PhantomData,
29 }
30 }
31
32 #[must_use]
33 pub const fn capability_id(&self) -> &'static str {
34 self.capability_id
35 }
36
37 #[must_use]
38 pub const fn descriptor_version(&self) -> &'static str {
39 self.descriptor_version
40 }
41
42 #[must_use]
43 pub const fn descriptor_digest(&self) -> &'static str {
44 self.descriptor_digest
45 }
46}
47
48#[derive(Clone, Debug, PartialEq)]
54pub enum PluginError<DomainError, RuntimeError> {
55 Domain(DomainError),
57 Runtime(RuntimeError),
59}
60
61impl<DomainError, RuntimeError> PluginError<DomainError, RuntimeError> {
62 pub const fn domain(error: DomainError) -> Self {
64 Self::Domain(error)
65 }
66
67 pub const fn runtime(error: RuntimeError) -> Self {
69 Self::Runtime(error)
70 }
71
72 pub fn map_domain<Other>(
74 self,
75 map: impl FnOnce(DomainError) -> Other,
76 ) -> PluginError<Other, RuntimeError> {
77 match self {
78 Self::Domain(error) => PluginError::Domain(map(error)),
79 Self::Runtime(error) => PluginError::Runtime(error),
80 }
81 }
82}
83
84pub trait CapabilityClient: Sized + 'static {
91 type Dependencies: ?Sized;
93 type Error;
95
96 const CAPABILITY_ID: &'static str;
98 const DESCRIPTOR_VERSION: &'static str;
100
101 fn from_dependencies(dependencies: &Self::Dependencies) -> Result<Self, Self::Error>;
103
104 fn from_requirement(
109 dependencies: &Self::Dependencies,
110 _requirement_id: &str,
111 ) -> Result<Self, Self::Error> {
112 Self::from_dependencies(dependencies)
113 }
114
115 fn already_connected() -> Self::Error;
117}
118
119pub trait CapabilityClientMany: CapabilityClient {
122 fn many_from_dependencies(
124 dependencies: &Self::Dependencies,
125 ) -> Result<Vec<BoundCapabilityClient<Self>>, Self::Error>;
126
127 fn many_from_requirement(
129 dependencies: &Self::Dependencies,
130 _requirement_id: &str,
131 ) -> Result<Vec<BoundCapabilityClient<Self>>, Self::Error> {
132 Self::many_from_dependencies(dependencies)
133 }
134}
135
136#[derive(Debug)]
138pub struct BoundCapabilityClient<C> {
139 provider_instance: String,
140 client: C,
141}
142
143impl<C> BoundCapabilityClient<C> {
144 #[must_use]
146 pub fn new(provider_instance: impl Into<String>, client: C) -> Self {
147 Self {
148 provider_instance: provider_instance.into(),
149 client,
150 }
151 }
152
153 #[must_use]
155 pub fn provider_instance(&self) -> &str {
156 &self.provider_instance
157 }
158
159 #[must_use]
161 pub const fn client(&self) -> &C {
162 &self.client
163 }
164}
165
166impl<C> Deref for BoundCapabilityClient<C> {
167 type Target = C;
168
169 fn deref(&self) -> &Self::Target {
170 &self.client
171 }
172}
173
174pub struct Port<C: CapabilityClient> {
181 client: Rc<OnceCell<C>>,
182}
183
184impl<C: CapabilityClient> Port<C> {
185 #[must_use]
187 pub fn new() -> Self {
188 Self {
189 client: Rc::new(OnceCell::new()),
190 }
191 }
192
193 pub fn connect(&self, dependencies: &C::Dependencies) -> Result<(), C::Error> {
195 let client = C::from_dependencies(dependencies)?;
196 self.client.set(client).map_err(|_| C::already_connected())
197 }
198
199 pub fn connect_requirement(
201 &self,
202 dependencies: &C::Dependencies,
203 requirement_id: &str,
204 ) -> Result<(), C::Error> {
205 let client = C::from_requirement(dependencies, requirement_id)?;
206 self.client.set(client).map_err(|_| C::already_connected())
207 }
208
209 #[must_use]
211 pub fn is_connected(&self) -> bool {
212 self.client.get().is_some()
213 }
214}
215
216impl<C: CapabilityClient> Clone for Port<C> {
217 fn clone(&self) -> Self {
218 Self {
219 client: Rc::clone(&self.client),
220 }
221 }
222}
223
224impl<C: CapabilityClient> std::fmt::Debug for Port<C> {
225 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226 formatter
227 .debug_struct("Port")
228 .field("capability_id", &C::CAPABILITY_ID)
229 .field("descriptor_version", &C::DESCRIPTOR_VERSION)
230 .field("connected", &self.is_connected())
231 .finish()
232 }
233}
234
235impl<C: CapabilityClient> Default for Port<C> {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241impl<C: CapabilityClient> Deref for Port<C> {
242 type Target = C;
243
244 fn deref(&self) -> &Self::Target {
245 self.client.get().unwrap_or_else(|| {
246 panic!(
247 "Capability Port {} was used before Plugin activation",
248 C::CAPABILITY_ID
249 )
250 })
251 }
252}
253
254pub struct ManyPort<C: CapabilityClientMany> {
259 clients: Rc<OnceCell<Vec<BoundCapabilityClient<C>>>>,
260}
261
262impl<C: CapabilityClientMany> ManyPort<C> {
263 #[must_use]
265 pub fn new() -> Self {
266 Self {
267 clients: Rc::new(OnceCell::new()),
268 }
269 }
270
271 pub fn connect(&self, dependencies: &C::Dependencies) -> Result<(), C::Error> {
273 let clients = C::many_from_dependencies(dependencies)?;
274 self.clients
275 .set(clients)
276 .map_err(|_| C::already_connected())
277 }
278
279 pub fn connect_requirement(
281 &self,
282 dependencies: &C::Dependencies,
283 requirement_id: &str,
284 ) -> Result<(), C::Error> {
285 let clients = C::many_from_requirement(dependencies, requirement_id)?;
286 self.clients
287 .set(clients)
288 .map_err(|_| C::already_connected())
289 }
290
291 #[must_use]
293 pub fn is_connected(&self) -> bool {
294 self.clients.get().is_some()
295 }
296}
297
298impl<C: CapabilityClientMany> Clone for ManyPort<C> {
299 fn clone(&self) -> Self {
300 Self {
301 clients: Rc::clone(&self.clients),
302 }
303 }
304}
305
306impl<C: CapabilityClientMany> std::fmt::Debug for ManyPort<C> {
307 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308 formatter
309 .debug_struct("ManyPort")
310 .field("capability_id", &C::CAPABILITY_ID)
311 .field("descriptor_version", &C::DESCRIPTOR_VERSION)
312 .field("connected", &self.is_connected())
313 .field("provider_count", &self.clients.get().map(Vec::len))
314 .finish()
315 }
316}
317
318impl<C: CapabilityClientMany> Default for ManyPort<C> {
319 fn default() -> Self {
320 Self::new()
321 }
322}
323
324impl<C: CapabilityClientMany> Deref for ManyPort<C> {
325 type Target = [BoundCapabilityClient<C>];
326
327 fn deref(&self) -> &Self::Target {
328 self.clients.get().map_or_else(
329 || {
330 panic!(
331 "Capability ManyPort {} was used before Plugin activation",
332 C::CAPABILITY_ID
333 )
334 },
335 Vec::as_slice,
336 )
337 }
338}
339
340pub mod prelude {
342 pub use crate::{
343 BoundCapabilityClient, CapabilityClient, CapabilityClientMany, CapabilityReference,
344 ManyPort, PluginError, Port,
345 };
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[derive(Debug, Eq, PartialEq)]
353 struct ExampleClient(u64);
354
355 #[derive(Debug, Eq, PartialEq)]
356 enum ExampleError {
357 AlreadyConnected,
358 }
359
360 impl CapabilityClient for ExampleClient {
361 type Dependencies = ();
362 type Error = ExampleError;
363
364 const CAPABILITY_ID: &'static str = "example.echo@1";
365 const DESCRIPTOR_VERSION: &'static str = "1.0.0";
366
367 fn from_dependencies(_dependencies: &Self::Dependencies) -> Result<Self, Self::Error> {
368 Ok(Self(42))
369 }
370
371 fn already_connected() -> Self::Error {
372 ExampleError::AlreadyConnected
373 }
374 }
375
376 impl CapabilityClientMany for ExampleClient {
377 fn many_from_dependencies(
378 _dependencies: &Self::Dependencies,
379 ) -> Result<Vec<BoundCapabilityClient<Self>>, Self::Error> {
380 Ok(vec![
381 BoundCapabilityClient::new("alpha", Self(1)),
382 BoundCapabilityClient::new("beta", Self(2)),
383 ])
384 }
385 }
386
387 #[test]
388 fn port_connects_once_and_is_shared_by_plugin_clones() {
389 let port = Port::<ExampleClient>::new();
390 let plugin_clone = port.clone();
391 assert!(!port.is_connected());
392
393 port.connect(&())
394 .expect("the generated client should connect");
395
396 assert!(plugin_clone.is_connected());
397 assert_eq!(plugin_clone.0, 42);
398 assert_eq!(port.connect(&()), Err(ExampleError::AlreadyConnected));
399 }
400
401 #[test]
402 fn many_port_preserves_provider_identity_and_resolved_order() {
403 let port = ManyPort::<ExampleClient>::new();
404 let plugin_clone = port.clone();
405 assert!(!port.is_connected());
406
407 port.connect(&())
408 .expect("the generated clients should connect");
409
410 assert!(plugin_clone.is_connected());
411 assert_eq!(plugin_clone[0].provider_instance(), "alpha");
412 assert_eq!(plugin_clone[0].client().0, 1);
413 assert_eq!(plugin_clone[1].provider_instance(), "beta");
414 assert_eq!(plugin_clone[1].client().0, 2);
415 assert_eq!(port.connect(&()), Err(ExampleError::AlreadyConnected));
416 }
417
418 #[test]
419 fn plugin_error_preserves_runtime_failures_while_mapping_domain_errors() {
420 let domain = PluginError::<_, &str>::domain("missing").map_domain(str::len);
421 assert_eq!(domain, PluginError::Domain(7));
422
423 let runtime = PluginError::<&str, _>::runtime("cancelled").map_domain(str::len);
424 assert_eq!(runtime, PluginError::Runtime("cancelled"));
425 }
426
427 #[derive(Debug, Eq, PartialEq)]
428 struct NamedClient(String);
429
430 impl CapabilityClient for NamedClient {
431 type Dependencies = ();
432 type Error = ExampleError;
433
434 const CAPABILITY_ID: &'static str = "example.store@1";
435 const DESCRIPTOR_VERSION: &'static str = "1.0.0";
436
437 fn from_dependencies(_dependencies: &Self::Dependencies) -> Result<Self, Self::Error> {
438 Ok(Self("legacy".to_owned()))
439 }
440
441 fn from_requirement(
442 _dependencies: &Self::Dependencies,
443 requirement_id: &str,
444 ) -> Result<Self, Self::Error> {
445 Ok(Self(requirement_id.to_owned()))
446 }
447
448 fn already_connected() -> Self::Error {
449 ExampleError::AlreadyConnected
450 }
451 }
452
453 impl CapabilityClientMany for NamedClient {
454 fn many_from_dependencies(
455 _dependencies: &Self::Dependencies,
456 ) -> Result<Vec<BoundCapabilityClient<Self>>, Self::Error> {
457 Ok(Vec::new())
458 }
459
460 fn many_from_requirement(
461 _dependencies: &Self::Dependencies,
462 requirement_id: &str,
463 ) -> Result<Vec<BoundCapabilityClient<Self>>, Self::Error> {
464 Ok(vec![BoundCapabilityClient::new(
465 format!("{requirement_id}-provider"),
466 Self(requirement_id.to_owned()),
467 )])
468 }
469 }
470
471 #[test]
472 fn named_ports_pass_the_exact_source_requirement_to_generated_clients() {
473 let source = Port::<NamedClient>::new();
474 let destination = Port::<NamedClient>::new();
475 source.connect_requirement(&(), "source").unwrap();
476 destination.connect_requirement(&(), "destination").unwrap();
477 assert_eq!(source.0, "source");
478 assert_eq!(destination.0, "destination");
479
480 let stores = ManyPort::<NamedClient>::new();
481 stores.connect_requirement(&(), "stores").unwrap();
482 assert_eq!(stores[0].provider_instance(), "stores-provider");
483 assert_eq!(stores[0].0, "stores");
484 }
485
486 #[test]
487 fn generated_capability_reference_is_typed_and_provider_free() {
488 let reference = CapabilityReference::<NamedClient>::new(
489 "example.store@1",
490 "1.0.0",
491 "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
492 );
493 assert_eq!(reference.capability_id(), "example.store@1");
494 assert_eq!(reference.descriptor_version(), "1.0.0");
495 assert_eq!(
496 reference.descriptor_digest(),
497 "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
498 );
499 }
500}