Skip to main content

crux_core/bridge/
mod.rs

1mod formats;
2mod registry;
3mod request_serde;
4
5use facet::Facet;
6use serde::{Deserialize, Serialize};
7use std::fmt::Debug;
8use thiserror::Error;
9
10use crate::{App, Core, core::ResolveError};
11pub use formats::{BincodeFfiFormat, JsonFfiFormat};
12pub use registry::EffectId;
13pub(crate) use registry::ResolveRegistry;
14// ResolveByte is public to be accessible from crux_macros
15#[doc(hidden)]
16pub use request_serde::ResolveSerialized;
17
18/// A serialization format for the bridge FFI.
19///
20/// **Note**: While you can implement your own format for use with the [`BridgeWithSerializer`],
21/// the type generation system doesn't yet support automatically generating the shell-side support
22/// for different formats, and you'll need to bring your own solution for this.
23pub trait FfiFormat: Debug + 'static {
24    type Error: std::error::Error;
25
26    /// Serialize an instance of `T` into the provided growable byte buffer.
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if serialization fails.
31    fn serialize<T: Serialize>(buffer: &mut Vec<u8>, value: &T) -> Result<(), Self::Error>;
32
33    /// Deserialize an instance of `T` from the provided byte slice.
34    ///
35    /// # Errors
36    ///
37    /// Returns an error if deserialization fails.
38    fn deserialize<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> Result<T, Self::Error>;
39}
40
41/// Request for a side-effect passed from the Core to the Shell. The `EffectId` links
42/// the `Request` with the corresponding call to [`Core::resolve`] to pass the data back
43/// to the [`App::update`] function (wrapped in the event provided to the capability originating the effect).
44// used in docs/internals/bridge.md
45// ANCHOR: request
46#[derive(Facet, Debug, Serialize, Deserialize)]
47pub struct Request<Eff>
48where
49    Eff: Serialize,
50{
51    pub id: EffectId,
52    pub effect: Eff,
53}
54// ANCHOR_END: request
55
56/// A batch of effect requests from the Core to the Shell, as serialised by
57/// [`Bridge::update`] and [`Bridge::resolve`].
58///
59/// The wire format is identical to `Vec<Request<Eff>>` (the newtype is
60/// `serde(transparent)`), so existing shell code that already deserialises
61/// a `Vec<Request>` remains binary-compatible.
62///
63/// Registering this type with the type-generation system causes the code
64/// generators to emit a `Requests` type (with a `value` field containing the
65/// list) together with a top-level `bincodeDeserialize` / `BincodeDeserialize`
66/// helper, replacing the hand-written extension files that were previously
67/// appended by `add_extensions()`.
68#[derive(Facet, Debug, Serialize, Deserialize)]
69#[serde(transparent)]
70pub struct Requests<Eff>(pub Vec<Request<Eff>>)
71where
72    Eff: Serialize;
73
74/// Bridge is a core wrapper presenting the same interface as the [`Core`] but in a
75/// serialized form, using bincode as the serialization format.
76pub struct Bridge<A, F = BincodeFfiFormat>
77where
78    A: App,
79    F: FfiFormat,
80{
81    core: Core<A>,
82    registry: ResolveRegistry<F>,
83}
84
85#[derive(Debug, Error)]
86pub enum BridgeError<F: FfiFormat = BincodeFfiFormat> {
87    #[error("could not deserialize event: {0}")]
88    DeserializeEvent(F::Error),
89    #[error("could not deserialize provided effect output: {0}")]
90    DeserializeOutput(F::Error),
91    #[error("could not process response: {0}")]
92    ProcessResponse(#[from] ResolveError),
93    #[error("could not serialize effect requests: {0}")]
94    SerializeRequests(F::Error),
95    #[error("could not serialize view model: {0}")]
96    SerializeView(F::Error),
97}
98
99impl<A, Format> Bridge<A, Format>
100where
101    A: App,
102    Format: FfiFormat,
103{
104    /// Create a new Bridge using the provided `core`.
105    pub fn new(core: Core<A>) -> Self {
106        Self {
107            core,
108            registry: ResolveRegistry::default(),
109        }
110    }
111
112    /// Receive an event from the shell.
113    ///
114    /// The `event` is serialized and will be deserialized by the core before it's passed
115    /// to your app.
116    ///
117    /// # Errors
118    ///
119    /// Returns an error if the event could not be deserialized.
120    #[deprecated(
121        since = "0.17.0",
122        note = "Bridge API returning vectors has been deprecated. Please use the 'update' method."
123    )]
124    pub fn process_event(&self, event: &[u8]) -> Result<Vec<u8>, BridgeError<Format>>
125    where
126        A::Event: for<'a> Deserialize<'a>,
127        A::Effect: crate::core::EffectFFI,
128    {
129        let mut return_buffer = vec![];
130
131        self.update(event, &mut return_buffer)?;
132
133        Ok(return_buffer)
134    }
135
136    /// Send an event from the shell.
137    ///
138    /// The `event` is serialized and will be deserialized by the core before it's passed
139    /// to your app.
140    ///
141    /// # Errors
142    ///
143    /// Returns an error if the event could not be deserialized.
144    pub fn update<'a>(
145        &self,
146        event: &'a [u8],
147        requests_out: &mut Vec<u8>,
148    ) -> Result<(), BridgeError<Format>>
149    where
150        A::Event: Deserialize<'a>,
151        A::Effect: crate::core::EffectFFI,
152    {
153        self.process(None, event, requests_out)
154    }
155
156    /// Receive a response to a capability request from the shell.
157    ///
158    /// The `output` is serialized capability output. It will be deserialized by the core.
159    ///
160    /// # Errors
161    ///
162    /// Returns an error if the response could not be deserialized.
163    ///
164    /// # Panics
165    ///
166    /// The `id` MUST match the `id` of the effect that triggered it, else the core will panic.
167    // used in docs/internals/bridge.md
168    // ANCHOR: handle_response_sig
169    #[deprecated(
170        since = "0.17.0",
171        note = "Bridge API returning vectors has been deprecated. Please use the 'resolve' method."
172    )]
173    pub fn handle_response(&self, id: u32, output: &[u8]) -> Result<Vec<u8>, BridgeError<Format>>
174    // ANCHOR_END: handle_response_sig
175    where
176        A::Event: for<'a> Deserialize<'a>,
177        A::Effect: crate::core::EffectFFI,
178    {
179        let mut return_buffer = vec![];
180
181        self.resolve(EffectId(id), output, &mut return_buffer)?;
182
183        Ok(return_buffer)
184    }
185
186    /// Provide a response to a capability request to resolve it and continue the corresponding command.
187    ///
188    /// The `output` is serialized capability output. It will be deserialized by the core.
189    ///
190    /// # Errors
191    ///
192    /// Returns an error if the response could not be deserialized.
193    ///
194    /// # Panics
195    ///
196    /// The `id` MUST match the `id` of the effect that triggered it, else the core will panic.
197    pub fn resolve<'a>(
198        &self,
199        id: EffectId,
200        response: &'a [u8],
201        requests_out: &mut Vec<u8>,
202    ) -> Result<(), BridgeError<Format>>
203    where
204        A::Event: Deserialize<'a>,
205        A::Effect: crate::core::EffectFFI,
206    {
207        self.process(Some(id), response, requests_out)
208    }
209
210    fn process<'a>(
211        &self,
212        id: Option<EffectId>,
213        data: &'a [u8],
214        requests_out: &mut Vec<u8>,
215    ) -> Result<(), BridgeError<Format>>
216    where
217        A::Event: Deserialize<'a>,
218        A::Effect: crate::core::EffectFFI,
219    {
220        let effects = match id {
221            None => {
222                let shell_event =
223                    Format::deserialize(data).map_err(BridgeError::DeserializeEvent)?;
224
225                self.core.process_event(shell_event)
226            }
227            Some(id) => {
228                self.registry.resume(id, data)?;
229
230                self.core.process()
231            }
232        };
233
234        self.process_effects(effects, requests_out)
235    }
236
237    fn process_effects(
238        &self,
239        effects: Vec<A::Effect>,
240        requests_out: &mut Vec<u8>,
241    ) -> Result<(), BridgeError<Format>>
242    where
243        A::Effect: crate::core::EffectFFI,
244    {
245        let requests: Vec<_> = effects
246            .into_iter()
247            .map(|eff| self.registry.register(eff))
248            .collect();
249
250        Format::serialize(requests_out, &requests).map_err(BridgeError::SerializeRequests)?;
251
252        Ok(())
253    }
254
255    /// Get the current state of the app's view model (serialized).
256    ///
257    /// # Errors
258    ///
259    /// Returns an error if the view model could not be serialized.
260    pub fn view(&self, view_out: &mut Vec<u8>) -> Result<(), BridgeError<Format>>
261    where
262        A::ViewModel: Serialize,
263    {
264        Format::serialize(view_out, &self.core.view()).map_err(BridgeError::SerializeView)
265    }
266}