1use crate::effect::Effect;
2use std::sync::Arc;
3
4pub enum DispatchOp<State, Action> {
6 Dispatch(State, Vec<Effect<Action>>),
9 Keep(State, Vec<Effect<Action>>),
11}
12
13pub trait Reducer<State, Action>
22where
23 State: Send + Sync + Clone,
24 Action: Send + Sync + Clone + 'static,
25{
26 fn reduce(&self, state: &State, action: &Action) -> DispatchOp<State, Action>;
27}
28
29pub struct ReducerChain<State, Action>
33where
34 State: Send + Sync + Clone,
35 Action: Send + Sync + 'static,
36{
37 reducer: Arc<dyn Reducer<State, Action> + Send + Sync>,
38 next: Option<Box<ReducerChain<State, Action>>>,
39}
40
41impl<State, Action> ReducerChain<State, Action>
42where
43 State: Send + Sync + Clone,
44 Action: Send + Sync + 'static,
45{
46 pub fn new(reducer: Arc<dyn Reducer<State, Action> + Send + Sync>) -> Self {
48 Self {
49 reducer,
50 next: None,
51 }
52 }
53
54 pub fn chain(mut self, reducer: Arc<dyn Reducer<State, Action> + Send + Sync>) -> Self {
56 if let Some(ref mut next) = self.next {
57 *next = Box::new(next.as_ref().clone().chain(reducer));
59 } else {
60 self.next = Some(Box::new(ReducerChain::new(reducer)));
62 }
63 self
64 }
65
66 pub fn from_vec(reducers: Vec<Box<dyn Reducer<State, Action> + Send + Sync>>) -> Option<Self> {
69 if reducers.is_empty() {
70 return None;
71 }
72
73 let mut iter = reducers.into_iter();
74 let mut tail = ReducerChain::new(Arc::from(iter.next()?));
75
76 for reducer in iter {
77 tail = tail.chain(Arc::from(reducer));
78 }
79
80 Some(tail)
81 }
82}
83
84impl<State, Action> Clone for ReducerChain<State, Action>
86where
87 State: Send + Sync + Clone,
88 Action: Send + Sync + 'static,
89{
90 fn clone(&self) -> Self {
91 Self {
92 reducer: self.reducer.clone(),
93 next: self.next.as_ref().map(|n| Box::new(n.as_ref().clone())),
94 }
95 }
96}
97
98impl<State, Action> Reducer<State, Action> for ReducerChain<State, Action>
99where
100 State: Send + Sync + Clone,
101 Action: Send + Sync + Clone + 'static,
102{
103 fn reduce(&self, state: &State, action: &Action) -> DispatchOp<State, Action> {
104 let mut result = self.reducer.reduce(state, action);
106
107 if let Some(ref next) = self.next {
109 match result {
110 DispatchOp::Dispatch(current_state, current_effects) => {
111 result = next.reduce(¤t_state, action);
112 match result {
114 DispatchOp::Dispatch(next_state, mut next_effects) => {
115 next_effects.extend(current_effects);
116 DispatchOp::Dispatch(next_state, next_effects)
117 }
118 DispatchOp::Keep(next_state, mut next_effects) => {
119 next_effects.extend(current_effects);
120 DispatchOp::Keep(next_state, next_effects)
121 }
122 }
123 }
124 DispatchOp::Keep(current_state, current_effects) => {
125 result = next.reduce(¤t_state, action);
126 match result {
128 DispatchOp::Dispatch(next_state, mut next_effects) => {
129 next_effects.extend(current_effects);
130 DispatchOp::Dispatch(next_state, next_effects)
131 }
132 DispatchOp::Keep(next_state, mut next_effects) => {
133 next_effects.extend(current_effects);
134 DispatchOp::Keep(next_state, next_effects)
135 }
136 }
137 }
138 }
139 } else {
140 result
141 }
142 }
143}
144
145pub struct FnReducer<F, State, Action>
149where
150 F: Fn(&State, &Action) -> DispatchOp<State, Action>,
151 State: Send + Sync + Clone,
152 Action: Send + Sync + Clone + 'static,
153{
154 func: F,
155 _marker: std::marker::PhantomData<(State, Action)>,
156}
157
158impl<F, State, Action> Reducer<State, Action> for FnReducer<F, State, Action>
159where
160 F: Fn(&State, &Action) -> DispatchOp<State, Action>,
161 State: Send + Sync + Clone,
162 Action: Send + Sync + Clone + 'static,
163{
164 fn reduce(&self, state: &State, action: &Action) -> DispatchOp<State, Action> {
165 (self.func)(state, action)
166 }
167}
168
169impl<F, State, Action> From<F> for FnReducer<F, State, Action>
170where
171 F: Fn(&State, &Action) -> DispatchOp<State, Action>,
172 State: Send + Sync + Clone,
173 Action: Send + Sync + Clone + 'static,
174{
175 fn from(func: F) -> Self {
176 Self {
177 func,
178 _marker: std::marker::PhantomData,
179 }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::subscriber::Subscriber;
187 use crate::StoreBuilder;
188 use std::sync::{Arc, Mutex};
189 use std::thread;
190
191 struct TestSubscriber {
192 state_changes: Arc<Mutex<Vec<i32>>>,
193 }
194
195 impl Subscriber<i32, i32> for TestSubscriber {
196 fn on_notify(&self, state: &i32, _action: &i32) {
197 self.state_changes.lock().unwrap().push(*state);
198 }
199 }
200
201 #[test]
202 fn test_store_continues_after_reducer_panic() {
203 struct PanicOnValueReducer {
207 panic_on: i32,
208 }
209
210 impl Reducer<i32, i32> for PanicOnValueReducer {
211 fn reduce(&self, state: &i32, action: &i32) -> DispatchOp<i32, i32> {
212 if *action == self.panic_on {
213 let result = std::panic::catch_unwind(|| {
215 panic!("Intentional panic on action {}", action);
216 });
217 if result.is_err() {
219 return DispatchOp::Keep(state.clone(), vec![]);
220 }
221 }
222 DispatchOp::Dispatch(state + action, vec![])
224 }
225 }
226
227 let reducer = Box::new(PanicOnValueReducer { panic_on: 42 });
229 let store = StoreBuilder::new_with_reducer(0, reducer).build().unwrap();
230
231 let state_changes = Arc::new(Mutex::new(Vec::new()));
233 let state_changes_clone = state_changes.clone();
234
235 let subscriber = Arc::new(TestSubscriber {
236 state_changes: state_changes_clone,
237 });
238 store.add_subscriber(subscriber).unwrap();
239
240 store.dispatch(1).unwrap(); store.dispatch(42).unwrap(); store.dispatch(2).unwrap(); match store.stop() {
248 Ok(_) => println!("store stopped"),
249 Err(e) => {
250 panic!("store stop failed : {:?}", e);
251 }
252 }
253
254 assert_eq!(store.get_state(), 3);
257 let changes = state_changes.lock().unwrap();
259 assert_eq!(&*changes, &vec![1, 3]); }
261
262 #[test]
263 fn test_multiple_reducers_continue_after_panic() {
264 struct PanicReducer;
266 struct NormalReducer;
267
268 impl Reducer<i32, i32> for PanicReducer {
269 fn reduce(&self, state: &i32, action: &i32) -> DispatchOp<i32, i32> {
270 let result = std::panic::catch_unwind(|| {
271 panic!("Always panic!");
272 });
273 if result.is_err() {
275 return DispatchOp::Keep(state.clone(), vec![]);
276 }
277 DispatchOp::Dispatch(state + action, vec![])
278 }
279 }
280
281 impl Reducer<i32, i32> for NormalReducer {
282 fn reduce(&self, state: &i32, action: &i32) -> DispatchOp<i32, i32> {
283 DispatchOp::Dispatch(state + action, vec![])
284 }
285 }
286
287 let store = StoreBuilder::new(0)
289 .with_reducer(Box::new(PanicReducer))
290 .add_reducer(Box::new(NormalReducer))
291 .build()
292 .unwrap();
293
294 store.dispatch(1).unwrap();
297 store.dispatch(2).unwrap();
298
299 match store.stop() {
300 Ok(_) => println!("store stopped"),
301 Err(e) => {
302 panic!("store stop failed : {:?}", e);
303 }
304 }
305
306 assert_eq!(store.get_state(), 3);
309 }
310
311 #[test]
312 fn test_fn_reducer_basic() {
313 let reducer = FnReducer::from(|state: &i32, action: &i32| {
315 DispatchOp::Dispatch(state + action, vec![])
316 });
317 let store = StoreBuilder::new_with_reducer(0, Box::new(reducer)).build().unwrap();
318
319 store.dispatch(5).unwrap();
321 store.dispatch(3).unwrap();
322 match store.stop() {
323 Ok(_) => println!("store stopped"),
324 Err(e) => {
325 panic!("store stop failed : {:?}", e);
326 }
327 }
328
329 assert_eq!(store.get_state(), 8); }
332
333 #[test]
334 fn test_fn_reducer_with_effect() {
335 #[derive(Clone, Debug)]
337 enum Action {
338 AddWithEffect(i32),
339 Add(i32),
340 }
341
342 let reducer = FnReducer::from(|state: &i32, action: &Action| {
343 match action {
344 Action::AddWithEffect(i) => {
345 let new_state = state + i;
346 let effect = Effect::Action(Action::Add(40)); DispatchOp::Dispatch(new_state, vec![effect])
348 }
349 Action::Add(i) => {
350 let new_state = state + i;
351 DispatchOp::Dispatch(new_state, vec![])
352 }
353 }
354 });
355 let store = StoreBuilder::new_with_reducer(0, Box::new(reducer)).build().unwrap();
356
357 store.dispatch(Action::AddWithEffect(2)).unwrap();
359 thread::sleep(std::time::Duration::from_millis(1000)); match store.stop() {
361 Ok(_) => println!("store stopped"),
362 Err(e) => {
363 panic!("store stop failed : {:?}", e);
364 }
365 }
366
367 assert_eq!(store.get_state(), 42);
370 }
371
372 #[test]
373 fn test_fn_reducer_keep_state() {
374 let reducer = FnReducer::from(|state: &i32, action: &i32| {
376 if *action < 0 {
377 DispatchOp::Keep(state.clone(), vec![])
379 } else {
380 DispatchOp::Dispatch(state + action, vec![])
381 }
382 });
383 let store = StoreBuilder::new_with_reducer(0, Box::new(reducer)).build().unwrap();
384
385 let state_changes = Arc::new(Mutex::new(Vec::new()));
387 let state_changes_clone = state_changes.clone();
388
389 let subscriber = Arc::new(TestSubscriber {
390 state_changes: state_changes_clone,
391 });
392 store.add_subscriber(subscriber).unwrap();
393
394 store.dispatch(5).unwrap(); store.dispatch(-3).unwrap(); store.dispatch(2).unwrap(); match store.stop() {
399 Ok(_) => println!("store stopped"),
400 Err(e) => {
401 panic!("store stop failed : {:?}", e);
402 }
403 }
404
405 assert_eq!(store.get_state(), 7); let changes = state_changes.lock().unwrap();
408 assert_eq!(&*changes, &vec![5, 7]); }
410
411 #[test]
412 fn test_multiple_fn_reducers() {
413 let add_reducer = FnReducer::from(|state: &i32, action: &i32| {
415 DispatchOp::Dispatch(state + action, vec![])
416 });
417 let double_reducer =
418 FnReducer::from(|state: &i32, _action: &i32| DispatchOp::Dispatch(state * 2, vec![]));
419
420 let store = StoreBuilder::new(0)
421 .with_reducer(Box::new(add_reducer))
422 .add_reducer(Box::new(double_reducer))
423 .build()
424 .unwrap();
425
426 store.dispatch(3).unwrap(); store.dispatch(15).unwrap(); match store.stop() {
430 Ok(_) => println!("store stopped"),
431 Err(e) => {
432 panic!("store stop failed : {:?}", e);
433 }
434 }
435
436 assert_eq!(store.get_state(), 42);
438 }
439}