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}