crux_core/bridge/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
mod registry;
mod request_serde;

use bincode::{DefaultOptions, Options};
use erased_serde::Serialize as _;
use serde::{Deserialize, Serialize};

use crate::Effect;
use crate::{App, Core};
use registry::{EffectId, ResolveRegistry};
// ResolveByte is public to be accessible from crux_macros
#[doc(hidden)]
pub use request_serde::ResolveSerialized;

/// Request for a side-effect passed from the Core to the Shell. The `EffectId` links
/// the `Request` with the corresponding call to [`Core::resolve`] to pass the data back
/// to the [`App::update`] function (wrapped in the event provided to the capability originating the effect).
// used in docs/internals/bridge.md
// ANCHOR: request
#[derive(Debug, Serialize, Deserialize)]
pub struct Request<Eff>
where
    Eff: Serialize,
{
    pub id: EffectId,
    pub effect: Eff,
}
// ANCHOR_END: request

/// Bridge is a core wrapper presenting the same interface as the [`Core`] but in a
/// serialized form, using bincode as the serialization format.
pub struct Bridge<Eff, A>
where
    Eff: Effect,
    A: App,
{
    inner: BridgeWithSerializer<Eff, A>,
}

impl<Eff, A> Bridge<Eff, A>
where
    Eff: Effect + Send + 'static,
    A: App,
{
    /// Create a new Bridge using the provided `core`.
    pub fn new(core: Core<Eff, A>) -> Self {
        Self {
            inner: BridgeWithSerializer::new(core),
        }
    }

    /// Receive an event from the shell.
    ///
    /// The `event` is serialized and will be deserialized by the core before it's passed
    /// to your app.
    pub fn process_event(&self, event: &[u8]) -> Vec<u8>
    where
        A::Event: for<'a> Deserialize<'a>,
    {
        let options = Self::bincode_options();

        let mut deser = bincode::Deserializer::from_slice(event, options);

        let mut return_buffer = vec![];
        let mut ser = bincode::Serializer::new(&mut return_buffer, options);

        self.inner.process_event(&mut deser, &mut ser);

        return_buffer
    }

    /// Receive a response to a capability request from the shell.
    ///
    /// The `output` is serialized capability output. It will be deserialized by the core.
    /// The `id` MUST match the `id` of the effect that triggered it, else the core will panic.
    // used in docs/internals/bridge.md
    // ANCHOR: handle_response_sig
    pub fn handle_response(&self, id: u32, output: &[u8]) -> Vec<u8>
    // ANCHOR_END: handle_response_sig
    where
        A::Event: for<'a> Deserialize<'a>,
    {
        let options = Self::bincode_options();

        let mut deser = bincode::Deserializer::from_slice(output, options);

        let mut return_buffer = vec![];
        let mut ser = bincode::Serializer::new(&mut return_buffer, options);

        self.inner.handle_response(id, &mut deser, &mut ser);

        return_buffer
    }

    /// Get the current state of the app's view model (serialized).
    pub fn view(&self) -> Vec<u8> {
        let options = Self::bincode_options();

        let mut return_buffer = vec![];

        self.inner
            .view(&mut bincode::Serializer::new(&mut return_buffer, options));

        return_buffer
    }

    fn bincode_options() -> impl bincode::Options + Copy {
        DefaultOptions::new()
            .with_fixint_encoding()
            .allow_trailing_bytes()
    }
}

/// A bridge with a user supplied serializer
///
/// This is exactly the same as [`Bridge`], except instead of using the default
/// bincode serialization, you can provide your own [`Serializer`].
///
/// **Warning**: the support for custom serialization is **experimental** and
/// does not have a corresponding type generation support - you will need
/// to write deserialization code on the shell side yourself, or generate
/// it using separate tooling.
// used in docs/internals/bridge.md
// ANCHOR: bridge_with_serializer
pub struct BridgeWithSerializer<Eff, A>
where
    Eff: Effect,
    A: App,
{
    core: Core<Eff, A>,
    registry: ResolveRegistry,
}
// ANCHOR_END: bridge_with_serializer

impl<Eff, A> BridgeWithSerializer<Eff, A>
where
    Eff: Effect,
    A: App,
{
    pub fn new(core: Core<Eff, A>) -> Self {
        Self {
            core,
            registry: Default::default(),
        }
    }

    /// Receive an event from the shell.
    ///
    /// The `event` is serialized and will be deserialized by the core before it's passed
    /// to your app.
    pub fn process_event<'de, D, S>(&self, event: D, requests_out: S)
    where
        for<'a> A::Event: Deserialize<'a>,
        D: ::serde::de::Deserializer<'de> + 'de,
        S: ::serde::ser::Serializer,
    {
        let mut erased_de = <dyn erased_serde::Deserializer>::erase(event);
        self.process(
            None,
            &mut erased_de,
            &mut <dyn erased_serde::Serializer>::erase(requests_out),
        );
    }

    /// Receive a response to a capability request from the shell.
    ///
    /// The `output` is serialized capability output. It will be deserialized by the core.
    /// The `id` MUST match the `id` of the effect that triggered it, else the core will panic.
    pub fn handle_response<'de, D, S>(&self, id: u32, response: D, requests_out: S)
    where
        for<'a> A::Event: Deserialize<'a>,
        D: ::serde::de::Deserializer<'de>,
        S: ::serde::ser::Serializer,
    {
        let mut erased_response = <dyn erased_serde::Deserializer>::erase(response);
        self.process(
            Some(EffectId(id)),
            &mut erased_response,
            &mut <dyn erased_serde::Serializer>::erase(requests_out),
        );
    }

    fn process(
        &self,
        id: Option<EffectId>,
        data: &mut dyn erased_serde::Deserializer,
        requests_out: &mut dyn erased_serde::Serializer,
    ) where
        A::Event: for<'a> Deserialize<'a>,
    {
        let effects = match id {
            None => {
                let shell_event =
                    erased_serde::deserialize(data).expect("Message deserialization failed.");

                self.core.process_event(shell_event)
            }
            Some(id) => {
                self.registry.resume(id, data).expect(
                    "Response could not be handled. The request did not expect a response.",
                );

                self.core.process()
            }
        };

        let requests: Vec<_> = effects
            .into_iter()
            .map(|eff| self.registry.register(eff))
            .collect();

        requests
            .erased_serialize(requests_out)
            .expect("Request serialization failed.")
    }

    /// Get the current state of the app's view model (serialized).
    pub fn view<S>(&self, ser: S)
    where
        S: ::serde::ser::Serializer,
    {
        self.core
            .view()
            .erased_serialize(&mut <dyn erased_serde::Serializer>::erase(ser))
            .expect("View should serialize")
    }
}