1use std::marker::PhantomData;
79
80use crate::action::Action;
81use crate::store::{
82 debug_assert_valid_dispatch_limits, run_iterative_middleware_dispatch, DispatchError,
83 DispatchLimits, Middleware, MiddlewareDispatchDriver,
84};
85
86#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct ReducerResult<E> {
91 pub changed: bool,
93 pub effects: Vec<E>,
95}
96
97impl<E> Default for ReducerResult<E> {
98 fn default() -> Self {
99 Self::unchanged()
100 }
101}
102
103impl<E> ReducerResult<E> {
104 #[inline]
106 pub fn unchanged() -> Self {
107 Self {
108 changed: false,
109 effects: vec![],
110 }
111 }
112
113 #[inline]
115 pub fn changed() -> Self {
116 Self {
117 changed: true,
118 effects: vec![],
119 }
120 }
121
122 #[inline]
124 pub fn effect(effect: E) -> Self {
125 Self {
126 changed: false,
127 effects: vec![effect],
128 }
129 }
130
131 #[inline]
133 pub fn effects(effects: Vec<E>) -> Self {
134 Self {
135 changed: false,
136 effects,
137 }
138 }
139
140 #[inline]
142 pub fn changed_with(effect: E) -> Self {
143 Self {
144 changed: true,
145 effects: vec![effect],
146 }
147 }
148
149 #[inline]
151 pub fn changed_with_many(effects: Vec<E>) -> Self {
152 Self {
153 changed: true,
154 effects,
155 }
156 }
157
158 #[inline]
160 pub fn with(mut self, effect: E) -> Self {
161 self.effects.push(effect);
162 self
163 }
164
165 #[inline]
167 pub fn mark_changed(mut self) -> Self {
168 self.changed = true;
169 self
170 }
171
172 #[inline]
174 pub fn has_effects(&self) -> bool {
175 !self.effects.is_empty()
176 }
177}
178
179pub type EffectReducer<S, A, E> = fn(&mut S, A) -> ReducerResult<E>;
184
185pub struct EffectStore<S, A, E> {
221 state: S,
222 reducer: EffectReducer<S, A, E>,
223 _marker: PhantomData<(A, E)>,
224}
225
226impl<S, A, E> EffectStore<S, A, E>
227where
228 A: Action,
229{
230 pub fn new(state: S, reducer: EffectReducer<S, A, E>) -> Self {
232 Self {
233 state,
234 reducer,
235 _marker: PhantomData,
236 }
237 }
238
239 #[inline]
241 pub fn state(&self) -> &S {
242 &self.state
243 }
244
245 #[inline]
250 pub fn state_mut(&mut self) -> &mut S {
251 &mut self.state
252 }
253
254 #[inline]
259 pub fn dispatch(&mut self, action: A) -> ReducerResult<E> {
260 (self.reducer)(&mut self.state, action)
261 }
262}
263
264pub struct EffectStoreWithMiddleware<S, A, E, M>
288where
289 A: Action,
290 M: Middleware<S, A>,
291{
292 store: EffectStore<S, A, E>,
293 middleware: M,
294 dispatch_limits: DispatchLimits,
295}
296
297impl<S, A, E, M> EffectStoreWithMiddleware<S, A, E, M>
298where
299 A: Action,
300 M: Middleware<S, A>,
301{
302 pub fn new(state: S, reducer: EffectReducer<S, A, E>, middleware: M) -> Self {
304 Self {
305 store: EffectStore::new(state, reducer),
306 middleware,
307 dispatch_limits: DispatchLimits::default(),
308 }
309 }
310
311 pub fn with_dispatch_limits(mut self, limits: DispatchLimits) -> Self {
313 debug_assert_valid_dispatch_limits(limits);
314 self.dispatch_limits = limits;
315 self
316 }
317
318 pub fn dispatch_limits(&self) -> DispatchLimits {
320 self.dispatch_limits
321 }
322
323 #[inline]
325 pub fn state(&self) -> &S {
326 self.store.state()
327 }
328
329 #[inline]
331 pub fn state_mut(&mut self) -> &mut S {
332 self.store.state_mut()
333 }
334
335 #[inline]
337 pub fn middleware(&self) -> &M {
338 &self.middleware
339 }
340
341 #[inline]
343 pub fn middleware_mut(&mut self) -> &mut M {
344 &mut self.middleware
345 }
346
347 pub fn dispatch(&mut self, action: A) -> ReducerResult<E> {
352 self.try_dispatch(action)
353 .unwrap_or_else(|error| panic!("middleware dispatch failed: {error}"))
354 }
355
356 pub fn try_dispatch(&mut self, action: A) -> Result<ReducerResult<E>, DispatchError> {
371 let mut driver = EffectDispatchDriver {
372 store: &mut self.store,
373 middleware: &mut self.middleware,
374 };
375 run_iterative_middleware_dispatch(self.dispatch_limits, action, &mut driver)
376 }
377}
378
379struct EffectDispatchDriver<'a, S, A, E, M>
380where
381 A: Action,
382 M: Middleware<S, A>,
383{
384 store: &'a mut EffectStore<S, A, E>,
385 middleware: &'a mut M,
386}
387
388impl<S, A, E, M> MiddlewareDispatchDriver<A> for EffectDispatchDriver<'_, S, A, E, M>
389where
390 A: Action,
391 M: Middleware<S, A>,
392{
393 type Output = ReducerResult<E>;
394
395 fn before(&mut self, action: &A) -> bool {
396 self.middleware.before(action, &self.store.state)
397 }
398
399 fn reduce(&mut self, action: A) -> Self::Output {
400 self.store.dispatch(action)
401 }
402
403 fn cancelled_output(&mut self) -> Self::Output {
404 ReducerResult::unchanged()
405 }
406
407 fn after(&mut self, action: &A, result: &Self::Output) -> Vec<A> {
408 self.middleware
409 .after(action, result.changed, &self.store.state)
410 }
411
412 fn merge_child(&mut self, parent: &mut Self::Output, child: Self::Output) {
413 parent.changed |= child.changed;
414 parent.effects.extend(child.effects);
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[derive(Clone, Debug)]
423 enum TestAction {
424 Increment,
425 Decrement,
426 NoOp,
427 TriggerEffect,
428 }
429
430 impl Action for TestAction {
431 fn name(&self) -> &'static str {
432 match self {
433 TestAction::Increment => "Increment",
434 TestAction::Decrement => "Decrement",
435 TestAction::NoOp => "NoOp",
436 TestAction::TriggerEffect => "TriggerEffect",
437 }
438 }
439 }
440
441 #[derive(Debug, Clone, PartialEq)]
442 enum TestEffect {
443 Log(String),
444 Save,
445 }
446
447 #[derive(Default)]
448 struct TestState {
449 count: i32,
450 }
451
452 fn test_reducer(state: &mut TestState, action: TestAction) -> ReducerResult<TestEffect> {
453 match action {
454 TestAction::Increment => {
455 state.count += 1;
456 ReducerResult::changed()
457 }
458 TestAction::Decrement => {
459 state.count -= 1;
460 ReducerResult::changed_with(TestEffect::Log(format!("count: {}", state.count)))
461 }
462 TestAction::NoOp => ReducerResult::unchanged(),
463 TestAction::TriggerEffect => {
464 ReducerResult::effects(vec![TestEffect::Log("triggered".into()), TestEffect::Save])
465 }
466 }
467 }
468
469 #[test]
470 fn test_dispatch_result_builders() {
471 let r: ReducerResult<TestEffect> = ReducerResult::unchanged();
472 assert!(!r.changed);
473 assert!(r.effects.is_empty());
474
475 let r: ReducerResult<TestEffect> = ReducerResult::changed();
476 assert!(r.changed);
477 assert!(r.effects.is_empty());
478
479 let r = ReducerResult::effect(TestEffect::Save);
480 assert!(!r.changed);
481 assert_eq!(r.effects, vec![TestEffect::Save]);
482
483 let r = ReducerResult::changed_with(TestEffect::Save);
484 assert!(r.changed);
485 assert_eq!(r.effects, vec![TestEffect::Save]);
486
487 let r =
488 ReducerResult::changed_with_many(vec![TestEffect::Save, TestEffect::Log("x".into())]);
489 assert!(r.changed);
490 assert_eq!(r.effects.len(), 2);
491 }
492
493 #[test]
494 fn test_dispatch_result_chaining() {
495 let r: ReducerResult<TestEffect> = ReducerResult::unchanged()
496 .with(TestEffect::Save)
497 .mark_changed();
498 assert!(r.changed);
499 assert_eq!(r.effects, vec![TestEffect::Save]);
500 }
501
502 #[test]
503 fn test_effect_store_basic() {
504 let mut store = EffectStore::new(TestState::default(), test_reducer);
505
506 assert_eq!(store.state().count, 0);
507
508 let result = store.dispatch(TestAction::Increment);
509 assert!(result.changed);
510 assert!(result.effects.is_empty());
511 assert_eq!(store.state().count, 1);
512
513 let result = store.dispatch(TestAction::NoOp);
514 assert!(!result.changed);
515 assert_eq!(store.state().count, 1);
516 }
517
518 #[test]
519 fn test_effect_store_with_effects() {
520 let mut store = EffectStore::new(TestState::default(), test_reducer);
521
522 let result = store.dispatch(TestAction::Decrement);
523 assert!(result.changed);
524 assert_eq!(result.effects.len(), 1);
525 assert!(matches!(&result.effects[0], TestEffect::Log(s) if s == "count: -1"));
526
527 let result = store.dispatch(TestAction::TriggerEffect);
528 assert!(!result.changed);
529 assert_eq!(result.effects.len(), 2);
530 }
531
532 #[test]
533 fn test_effect_store_state_mut() {
534 let mut store = EffectStore::new(TestState::default(), test_reducer);
535 store.state_mut().count = 100;
536 assert_eq!(store.state().count, 100);
537 }
538
539 #[test]
540 fn test_has_effects() {
541 let r: ReducerResult<TestEffect> = ReducerResult::unchanged();
542 assert!(!r.has_effects());
543
544 let r = ReducerResult::effect(TestEffect::Save);
545 assert!(r.has_effects());
546 }
547
548 struct SelfInjectingMiddleware;
549
550 impl Middleware<TestState, TestAction> for SelfInjectingMiddleware {
551 fn before(&mut self, _action: &TestAction, _state: &TestState) -> bool {
552 true
553 }
554
555 fn after(
556 &mut self,
557 action: &TestAction,
558 _state_changed: bool,
559 _state: &TestState,
560 ) -> Vec<TestAction> {
561 vec![action.clone()]
562 }
563 }
564
565 #[test]
566 fn test_effect_try_dispatch_depth_exceeded() {
567 let mut store = EffectStoreWithMiddleware::new(
568 TestState::default(),
569 test_reducer,
570 SelfInjectingMiddleware,
571 )
572 .with_dispatch_limits(DispatchLimits {
573 max_depth: 2,
574 max_actions: 100,
575 });
576
577 let err = store.try_dispatch(TestAction::Increment).unwrap_err();
578 assert_eq!(
579 err,
580 DispatchError::DepthExceeded {
581 max_depth: 2,
582 action: "Increment",
583 }
584 );
585 assert_eq!(store.state().count, 2);
586 }
587
588 #[test]
589 fn test_effect_try_dispatch_action_budget_exceeded() {
590 let mut store = EffectStoreWithMiddleware::new(
591 TestState::default(),
592 test_reducer,
593 SelfInjectingMiddleware,
594 )
595 .with_dispatch_limits(DispatchLimits {
596 max_depth: 32,
597 max_actions: 2,
598 });
599
600 let err = store.try_dispatch(TestAction::Increment).unwrap_err();
601 assert_eq!(
602 err,
603 DispatchError::ActionBudgetExceeded {
604 max_actions: 2,
605 processed: 2,
606 action: "Increment",
607 }
608 );
609 assert_eq!(store.state().count, 2);
610 }
611
612 struct FiniteCascadeMiddleware {
613 target: i32,
614 }
615
616 impl Middleware<TestState, TestAction> for FiniteCascadeMiddleware {
617 fn before(&mut self, _action: &TestAction, _state: &TestState) -> bool {
618 true
619 }
620
621 fn after(
622 &mut self,
623 action: &TestAction,
624 _state_changed: bool,
625 state: &TestState,
626 ) -> Vec<TestAction> {
627 if matches!(action, TestAction::Increment) && state.count < self.target {
628 vec![TestAction::Increment]
629 } else {
630 vec![]
631 }
632 }
633 }
634
635 #[test]
636 fn test_effect_try_dispatch_deep_finite_chain_succeeds() {
637 let target = 512usize;
638 let mut store = EffectStoreWithMiddleware::new(
639 TestState::default(),
640 test_reducer,
641 FiniteCascadeMiddleware {
642 target: target as i32,
643 },
644 )
645 .with_dispatch_limits(DispatchLimits {
646 max_depth: target + 1,
647 max_actions: target + 1,
648 });
649
650 let result = store.try_dispatch(TestAction::Increment).unwrap();
651 assert!(result.changed);
652 assert!(result.effects.is_empty());
653 assert_eq!(store.state().count, target as i32);
654 }
655
656 #[derive(Clone, Debug)]
657 enum OrderingAction {
658 Root,
659 Left,
660 Right,
661 Leaf,
662 }
663
664 impl Action for OrderingAction {
665 fn name(&self) -> &'static str {
666 match self {
667 OrderingAction::Root => "Root",
668 OrderingAction::Left => "Left",
669 OrderingAction::Right => "Right",
670 OrderingAction::Leaf => "Leaf",
671 }
672 }
673 }
674
675 #[derive(Debug, Clone, PartialEq, Eq)]
676 enum OrderingEffect {
677 Seen(&'static str),
678 }
679
680 #[derive(Default)]
681 struct OrderingState {
682 actions_seen: usize,
683 }
684
685 fn ordering_reducer(
686 state: &mut OrderingState,
687 action: OrderingAction,
688 ) -> ReducerResult<OrderingEffect> {
689 state.actions_seen += 1;
690 ReducerResult::changed_with(OrderingEffect::Seen(action.name()))
691 }
692
693 struct OrderingMiddleware;
694
695 impl Middleware<OrderingState, OrderingAction> for OrderingMiddleware {
696 fn before(&mut self, _action: &OrderingAction, _state: &OrderingState) -> bool {
697 true
698 }
699
700 fn after(
701 &mut self,
702 action: &OrderingAction,
703 _state_changed: bool,
704 _state: &OrderingState,
705 ) -> Vec<OrderingAction> {
706 match action {
707 OrderingAction::Root => vec![OrderingAction::Left, OrderingAction::Right],
708 OrderingAction::Left => vec![OrderingAction::Leaf],
709 OrderingAction::Right | OrderingAction::Leaf => vec![],
710 }
711 }
712 }
713
714 #[test]
715 fn test_effect_try_dispatch_merges_effects_in_depth_first_order() {
716 let mut store = EffectStoreWithMiddleware::new(
717 OrderingState::default(),
718 ordering_reducer,
719 OrderingMiddleware,
720 )
721 .with_dispatch_limits(DispatchLimits {
722 max_depth: 8,
723 max_actions: 8,
724 });
725
726 let result = store.try_dispatch(OrderingAction::Root).unwrap();
727 assert!(result.changed);
728 assert_eq!(
729 result.effects,
730 vec![
731 OrderingEffect::Seen("Root"),
732 OrderingEffect::Seen("Left"),
733 OrderingEffect::Seen("Leaf"),
734 OrderingEffect::Seen("Right"),
735 ]
736 );
737 assert_eq!(store.state().actions_seen, 4);
738 }
739}