use crate::effect::Effect;
use std::sync::Arc;
pub enum DispatchOp<State, Action> {
Dispatch(State, Vec<Effect<Action>>),
Keep(State, Vec<Effect<Action>>),
}
pub trait Reducer<State, Action>
where
State: Send + Sync + Clone,
Action: Send + Sync + Clone + 'static,
{
fn reduce(&self, state: &State, action: &Action) -> DispatchOp<State, Action>;
}
pub struct ReducerChain<State, Action>
where
State: Send + Sync + Clone,
Action: Send + Sync + 'static,
{
reducer: Arc<dyn Reducer<State, Action> + Send + Sync>,
next: Option<Box<ReducerChain<State, Action>>>,
}
impl<State, Action> ReducerChain<State, Action>
where
State: Send + Sync + Clone,
Action: Send + Sync + 'static,
{
pub fn new(reducer: Arc<dyn Reducer<State, Action> + Send + Sync>) -> Self {
Self {
reducer,
next: None,
}
}
pub fn chain(mut self, reducer: Arc<dyn Reducer<State, Action> + Send + Sync>) -> Self {
if let Some(ref mut next) = self.next {
*next = Box::new(next.as_ref().clone().chain(reducer));
} else {
self.next = Some(Box::new(ReducerChain::new(reducer)));
}
self
}
pub fn from_vec(reducers: Vec<Box<dyn Reducer<State, Action> + Send + Sync>>) -> Option<Self> {
if reducers.is_empty() {
return None;
}
let mut iter = reducers.into_iter();
let mut tail = ReducerChain::new(Arc::from(iter.next()?));
for reducer in iter {
tail = tail.chain(Arc::from(reducer));
}
Some(tail)
}
}
impl<State, Action> Clone for ReducerChain<State, Action>
where
State: Send + Sync + Clone,
Action: Send + Sync + 'static,
{
fn clone(&self) -> Self {
Self {
reducer: self.reducer.clone(),
next: self.next.as_ref().map(|n| Box::new(n.as_ref().clone())),
}
}
}
impl<State, Action> Reducer<State, Action> for ReducerChain<State, Action>
where
State: Send + Sync + Clone,
Action: Send + Sync + Clone + 'static,
{
fn reduce(&self, state: &State, action: &Action) -> DispatchOp<State, Action> {
let mut result = self.reducer.reduce(state, action);
if let Some(ref next) = self.next {
match result {
DispatchOp::Dispatch(current_state, current_effects) => {
result = next.reduce(¤t_state, action);
match result {
DispatchOp::Dispatch(next_state, mut next_effects) => {
next_effects.extend(current_effects);
DispatchOp::Dispatch(next_state, next_effects)
}
DispatchOp::Keep(next_state, mut next_effects) => {
next_effects.extend(current_effects);
DispatchOp::Keep(next_state, next_effects)
}
}
}
DispatchOp::Keep(current_state, current_effects) => {
result = next.reduce(¤t_state, action);
match result {
DispatchOp::Dispatch(next_state, mut next_effects) => {
next_effects.extend(current_effects);
DispatchOp::Dispatch(next_state, next_effects)
}
DispatchOp::Keep(next_state, mut next_effects) => {
next_effects.extend(current_effects);
DispatchOp::Keep(next_state, next_effects)
}
}
}
}
} else {
result
}
}
}
pub struct FnReducer<F, State, Action>
where
F: Fn(&State, &Action) -> DispatchOp<State, Action>,
State: Send + Sync + Clone,
Action: Send + Sync + Clone + 'static,
{
func: F,
_marker: std::marker::PhantomData<(State, Action)>,
}
impl<F, State, Action> Reducer<State, Action> for FnReducer<F, State, Action>
where
F: Fn(&State, &Action) -> DispatchOp<State, Action>,
State: Send + Sync + Clone,
Action: Send + Sync + Clone + 'static,
{
fn reduce(&self, state: &State, action: &Action) -> DispatchOp<State, Action> {
(self.func)(state, action)
}
}
impl<F, State, Action> From<F> for FnReducer<F, State, Action>
where
F: Fn(&State, &Action) -> DispatchOp<State, Action>,
State: Send + Sync + Clone,
Action: Send + Sync + Clone + 'static,
{
fn from(func: F) -> Self {
Self {
func,
_marker: std::marker::PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::subscriber::Subscriber;
use crate::StoreBuilder;
use std::sync::{Arc, Mutex};
use std::thread;
struct TestSubscriber {
state_changes: Arc<Mutex<Vec<i32>>>,
}
impl Subscriber<i32, i32> for TestSubscriber {
fn on_notify(&self, state: &i32, _action: &i32) {
self.state_changes.lock().unwrap().push(*state);
}
}
#[test]
fn test_store_continues_after_reducer_panic() {
struct PanicOnValueReducer {
panic_on: i32,
}
impl Reducer<i32, i32> for PanicOnValueReducer {
fn reduce(&self, state: &i32, action: &i32) -> DispatchOp<i32, i32> {
if *action == self.panic_on {
let result = std::panic::catch_unwind(|| {
panic!("Intentional panic on action {}", action);
});
if result.is_err() {
return DispatchOp::Keep(state.clone(), vec![]);
}
}
DispatchOp::Dispatch(state + action, vec![])
}
}
let reducer = Box::new(PanicOnValueReducer { panic_on: 42 });
let store = StoreBuilder::new_with_reducer(0, reducer).build().unwrap();
let state_changes = Arc::new(Mutex::new(Vec::new()));
let state_changes_clone = state_changes.clone();
let subscriber = Arc::new(TestSubscriber {
state_changes: state_changes_clone,
});
store.add_subscriber(subscriber).unwrap();
store.dispatch(1).unwrap(); store.dispatch(42).unwrap(); store.dispatch(2).unwrap();
match store.stop() {
Ok(_) => println!("store stopped"),
Err(e) => {
panic!("store stop failed : {:?}", e);
}
}
assert_eq!(store.get_state(), 3);
let changes = state_changes.lock().unwrap();
assert_eq!(&*changes, &vec![1, 3]); }
#[test]
fn test_multiple_reducers_continue_after_panic() {
struct PanicReducer;
struct NormalReducer;
impl Reducer<i32, i32> for PanicReducer {
fn reduce(&self, state: &i32, action: &i32) -> DispatchOp<i32, i32> {
let result = std::panic::catch_unwind(|| {
panic!("Always panic!");
});
if result.is_err() {
return DispatchOp::Keep(state.clone(), vec![]);
}
DispatchOp::Dispatch(state + action, vec![])
}
}
impl Reducer<i32, i32> for NormalReducer {
fn reduce(&self, state: &i32, action: &i32) -> DispatchOp<i32, i32> {
DispatchOp::Dispatch(state + action, vec![])
}
}
let store = StoreBuilder::new(0)
.with_reducer(Box::new(PanicReducer))
.add_reducer(Box::new(NormalReducer))
.build()
.unwrap();
store.dispatch(1).unwrap();
store.dispatch(2).unwrap();
match store.stop() {
Ok(_) => println!("store stopped"),
Err(e) => {
panic!("store stop failed : {:?}", e);
}
}
assert_eq!(store.get_state(), 3);
}
#[test]
fn test_fn_reducer_basic() {
let reducer = FnReducer::from(|state: &i32, action: &i32| {
DispatchOp::Dispatch(state + action, vec![])
});
let store = StoreBuilder::new_with_reducer(0, Box::new(reducer)).build().unwrap();
store.dispatch(5).unwrap();
store.dispatch(3).unwrap();
match store.stop() {
Ok(_) => println!("store stopped"),
Err(e) => {
panic!("store stop failed : {:?}", e);
}
}
assert_eq!(store.get_state(), 8); }
#[test]
fn test_fn_reducer_with_effect() {
#[derive(Clone, Debug)]
enum Action {
AddWithEffect(i32),
Add(i32),
}
let reducer = FnReducer::from(|state: &i32, action: &Action| {
match action {
Action::AddWithEffect(i) => {
let new_state = state + i;
let effect = Effect::Action(Action::Add(40)); DispatchOp::Dispatch(new_state, vec![effect])
}
Action::Add(i) => {
let new_state = state + i;
DispatchOp::Dispatch(new_state, vec![])
}
}
});
let store = StoreBuilder::new_with_reducer(0, Box::new(reducer)).build().unwrap();
store.dispatch(Action::AddWithEffect(2)).unwrap();
thread::sleep(std::time::Duration::from_millis(1000)); match store.stop() {
Ok(_) => println!("store stopped"),
Err(e) => {
panic!("store stop failed : {:?}", e);
}
}
assert_eq!(store.get_state(), 42);
}
#[test]
fn test_fn_reducer_keep_state() {
let reducer = FnReducer::from(|state: &i32, action: &i32| {
if *action < 0 {
DispatchOp::Keep(state.clone(), vec![])
} else {
DispatchOp::Dispatch(state + action, vec![])
}
});
let store = StoreBuilder::new_with_reducer(0, Box::new(reducer)).build().unwrap();
let state_changes = Arc::new(Mutex::new(Vec::new()));
let state_changes_clone = state_changes.clone();
let subscriber = Arc::new(TestSubscriber {
state_changes: state_changes_clone,
});
store.add_subscriber(subscriber).unwrap();
store.dispatch(5).unwrap(); store.dispatch(-3).unwrap(); store.dispatch(2).unwrap(); match store.stop() {
Ok(_) => println!("store stopped"),
Err(e) => {
panic!("store stop failed : {:?}", e);
}
}
assert_eq!(store.get_state(), 7); let changes = state_changes.lock().unwrap();
assert_eq!(&*changes, &vec![5, 7]); }
#[test]
fn test_multiple_fn_reducers() {
let add_reducer = FnReducer::from(|state: &i32, action: &i32| {
DispatchOp::Dispatch(state + action, vec![])
});
let double_reducer =
FnReducer::from(|state: &i32, _action: &i32| DispatchOp::Dispatch(state * 2, vec![]));
let store = StoreBuilder::new(0)
.with_reducer(Box::new(add_reducer))
.add_reducer(Box::new(double_reducer))
.build()
.unwrap();
store.dispatch(3).unwrap(); store.dispatch(15).unwrap(); match store.stop() {
Ok(_) => println!("store stopped"),
Err(e) => {
panic!("store stop failed : {:?}", e);
}
}
assert_eq!(store.get_state(), 42);
}
}