Skip to main content

crux_core/middleware/
mod.rs

1//! Middleware which can be wrapped around the Core to modify its behaviour.
2//!
3//! Note that this is still somewhat experimental.
4//!
5//! This is useful for changing the mechanics of the Core without modifying the actual
6//! behaviour of the app.
7//!
8//! Currently, the main use-case is processing effects requested by the app inside the core,
9//! but outside the app itself (which is side-effect free and synchronous). To do this,
10//! use [`Layer::handle_effects_using`] and provide an implementation of [`EffectMiddleware`].
11//!
12//! Note that apps using middleware must be `Send` and `Sync`, because the effect middlewares
13//! are expected to process effects asynchronously (in order not to block the caller of
14//! `process_event`). On native targets this typically means a background thread; on WASM it
15//! means an async task (e.g. `spawn_local`). See [`EffectMiddleware`] for more discussion.
16//!
17//! Note: In the documentation we refer to the directions in the middleware chain
18//! as "down" - towards the core, and "up" - away from the Core, towards the Shell.
19use crate::{App, Core, EffectFFI, Request, Resolvable, ResolveError, bridge::BridgeError};
20
21mod bridge;
22mod effect_conversion;
23mod effect_handling;
24
25pub use crate::bridge::{BincodeFfiFormat, FfiFormat, JsonFfiFormat};
26pub use bridge::Bridge;
27pub use effect_conversion::MapEffectLayer;
28pub use effect_handling::{EffectMiddleware, EffectResolver, HandleEffectLayer};
29use serde::Deserialize;
30
31/// A layer in the middleware stack.
32///
33/// This is implemented by the Core and the different types of middlewares,
34/// so that they can be composed as required.
35///
36/// This is the lower-level of the middleware traits. You might want to implement this
37/// for middleware which filters or transforms events or your view model, with awareness
38/// of your app's `Event` and `ViewModel` types.
39///
40/// If you want to build a reusable effect-handling middleware, see [`EffectMiddleware`].
41pub trait Layer: Send + Sync + Sized {
42    /// Event type expected by this layer
43    type Event;
44    /// Effect type returned by this layer
45    type Effect;
46    /// `ViewModel` returned by this layer
47    type ViewModel;
48
49    /// Process event from the Shell. Compared to [`Core::process_event`] this expects an
50    /// additional argument - a callback to be called with effects requested outside of the
51    /// initial call context.
52    ///
53    /// The callback is used in scenarios where an effect handling middleware has handled and
54    /// resolved an effect, and received follow-up effects (from the next layer down), which
55    /// it cannot process. This may happen some time after the initial `process_event`
56    /// call from the shell, and on a different thread.
57    ///
58    /// The expected behaviour of the callback is to process the effects like a shell would
59    /// and call [`Layer::resolve`] with the output of the processing.
60    fn update<F>(&self, event: Self::Event, effect_callback: F) -> Vec<Self::Effect>
61    where
62        F: Fn(Vec<Self::Effect>) + Sync + Send + 'static;
63
64    /// Resolve a requested effect. Compared to [`Core::process_event`] this expects an
65    /// additional argument - a callback to be called with effects requested outside of the
66    /// initial call context.
67    ///
68    /// The callback is used in scenarios where an effect handling middleware has handled and
69    /// resolved an effect, and received follow-up effects (from the next layer down), which
70    /// it cannot process. This may happen some time after this `resolve` call, and on a different
71    /// thread.
72    ///
73    /// The expected behaviour of the callback is to process the effects like a shell would
74    /// and call [`Layer::resolve`] with the output of the processing.
75    ///
76    /// # Errors
77    ///
78    /// Returns a `ResolveError` if the request fails to resolve due to a type mismatch, or isn't
79    /// expected to be resolved (either it was never expected to be resolved, or it has already
80    /// been resolved)
81    fn resolve<Output, F>(
82        &self,
83        request: &mut impl Resolvable<Output>,
84        output: Output,
85        effect_callback: F,
86    ) -> Result<Vec<Self::Effect>, ResolveError>
87    where
88        F: Fn(Vec<Self::Effect>) + Sync + Send + 'static;
89
90    /// Process any tasks in the effect runtime of the Core, which are able to proceed.
91    /// The tasks may produce effects which will be returned by the core and may be
92    /// processed by lower middleware layers.
93    ///
94    /// You should not need to call this method directly. Most implementations should
95    /// simply forward the call to the next `Layer`.
96    ///
97    /// This is used by the [`Bridge`], when resolving effects over FFI. It can't call
98    /// [`Core::resolve`], because the `Output` type argument is not known due to the type erasure
99    /// involved in serializing effects and storing request handles for the FFI.
100    fn process_tasks<F>(&self, effect_callback: F) -> Vec<Self::Effect>
101    where
102        F: Fn(Vec<Self::Effect>) + Sync + Send + 'static;
103
104    /// Return the current state of the view model
105    fn view(&self) -> Self::ViewModel;
106
107    /// Wrap this layer with an effect handling middleware. The `middleware` argument
108    /// must implement the [`EffectMiddleware`] trait.
109    fn handle_effects_using<EM>(self, middleware: EM) -> HandleEffectLayer<Self, EM>
110    where
111        EM: EffectMiddleware + 'static,
112        Self::Effect: TryInto<Request<EM::Op>, Error = Self::Effect>,
113    {
114        HandleEffectLayer::new(self, middleware)
115    }
116
117    /// Wrap this layer with an effect mapping middleware to change the
118    /// Effect type returned.
119    ///
120    /// This is generally used after a number of effect handling layers to "narrow"
121    /// the effect type - eliminate the variants which will never be encountered, so that
122    /// exhaustive matches don't require unused branches.
123    fn map_effect<NewEffect>(self) -> MapEffectLayer<Self, NewEffect>
124    where
125        NewEffect: From<Self::Effect> + Send + 'static,
126    {
127        MapEffectLayer::new(self)
128    }
129
130    fn bridge<Format: FfiFormat>(
131        self,
132        effect_callback: impl Fn(Result<Vec<u8>, BridgeError<Format>>) + Send + Sync + 'static,
133    ) -> Bridge<Self, Format>
134    where
135        Self::Effect: EffectFFI,
136        Self::Event: for<'a> Deserialize<'a>,
137    {
138        Bridge::new(self, effect_callback)
139    }
140}
141
142// Core is a valid Layer, but only for thread-safe Apps, because
143// middlewares need to be able to run background tasks and therefore
144// be thread-safe (they may get called from different threads)
145impl<A: App> Layer for Core<A>
146where
147    A: Send + Sync + 'static,
148    A::Model: Send + Sync + 'static,
149{
150    type Event = A::Event;
151    type Effect = A::Effect;
152    type ViewModel = A::ViewModel;
153
154    fn update<F: Fn(Vec<Self::Effect>) + Send + Sync + 'static>(
155        &self,
156        event: Self::Event,
157        _effect_callback: F,
158    ) -> Vec<Self::Effect> {
159        self.process_event(event)
160    }
161
162    fn resolve<Output, F: Fn(Vec<Self::Effect>) + Send + Sync + 'static>(
163        &self,
164        request: &mut impl Resolvable<Output>,
165        output: Output,
166        _effect_callback: F,
167    ) -> Result<Vec<Self::Effect>, ResolveError> {
168        self.resolve(request, output)
169    }
170
171    fn view(&self) -> Self::ViewModel {
172        self.view()
173    }
174
175    fn process_tasks<F>(&self, _effect_callback: F) -> Vec<Self::Effect>
176    where
177        F: Fn(Vec<Self::Effect>) + Sync + Send + 'static,
178    {
179        self.process()
180    }
181}