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