Skip to main content

boxology_runtime/
local.rs

1//! In-process composition binding for generated typed handles.
2
3use crate::{TransportBinding, TransportHandle, TransportJoinFuture, TransportRuntime};
4use boxology_contract::{
5    CallContext, CapabilityDescriptor, CapabilityId, CapabilityShape, Detail, ErasedCallError,
6    ErasedCallTarget, ExposureLevel, SlotValue,
7};
8use std::{
9    future::Future,
10    pin::Pin,
11    sync::{Arc, Mutex, Weak},
12};
13
14/// A production in-process binding that can be passed directly to a generated handle.
15#[derive(Default)]
16pub struct LocalBinding {
17    runtime: Mutex<Option<Weak<TransportRuntime<()>>>>,
18}
19
20impl LocalBinding {
21    /// Constructs an unstarted local binding.
22    pub fn new() -> Self {
23        Self::default()
24    }
25}
26
27/// Keeps an activated local binding alive for the composition lifetime.
28#[doc(hidden)]
29pub struct LocalHandle(Arc<TransportRuntime<()>>);
30
31impl TransportHandle for LocalHandle {
32    fn stop_intake(&self) {}
33    fn cancel_tasks(&self) {}
34    fn abort_tasks(&self) {}
35    fn join_tasks(self: Box<Self>) -> TransportJoinFuture {
36        drop(self.0);
37        Box::pin(std::future::ready(Ok(())))
38    }
39}
40
41impl TransportBinding for LocalBinding {
42    type Config = ();
43    type Handle = LocalHandle;
44
45    fn config(&self) -> Arc<()> {
46        Arc::new(())
47    }
48
49    fn conform(
50        &self,
51        descriptor: &CapabilityDescriptor,
52        _level: ExposureLevel,
53    ) -> Result<(), Detail> {
54        matches!(descriptor.shape(), CapabilityShape::Unary)
55            .then_some(())
56            .ok_or_else(|| Detail::new("unsupported_interaction_shape"))
57    }
58
59    fn prepare(&self, _descriptors: &[&'static CapabilityDescriptor]) -> Result<(), Detail> {
60        Ok(())
61    }
62
63    fn start(&self, runtime: TransportRuntime<()>) -> Result<LocalHandle, Detail> {
64        let runtime = Arc::new(runtime);
65        let mut retained = self.runtime.lock().map_err(|_| Detail::new("local_lock"))?;
66        if retained.is_some() {
67            return Err(Detail::new("local_binding_already_started"));
68        }
69        *retained = Some(Arc::downgrade(&runtime));
70        Ok(LocalHandle(runtime))
71    }
72}
73
74impl ErasedCallTarget for LocalBinding {
75    fn call<'a>(
76        &'a self,
77        capability: &'a CapabilityId,
78        context: CallContext,
79        input: SlotValue,
80    ) -> Pin<Box<dyn Future<Output = Result<SlotValue, ErasedCallError>> + Send + 'a>> {
81        let exposure = self
82            .runtime
83            .lock()
84            .ok()
85            .and_then(|runtime| runtime.as_ref()?.upgrade())
86            .and_then(|runtime| {
87                runtime
88                    .exposures()
89                    .iter()
90                    .find(|exposure| exposure.descriptor().id() == capability)
91                    .cloned()
92            });
93        Box::pin(async move {
94            match exposure {
95                Some(exposure) => exposure.dispatch(context, input).await,
96                None => Err(ErasedCallError::Internal(Detail::new("local_capability"))),
97            }
98        })
99    }
100}