reduxr 0.1.3

Redux store with full middleware support
Documentation
use crate::*;
use std::cell::RefCell;

pub struct Store<State, Action, DispatchResult = ()> {
    state: RefCell<State>,
    initial_result_factory: fn() -> DispatchResult,
    middleware: Vec<Middleware<State, Action, DispatchResult>>,
}

impl<State, Action, DispatchResult> Store<State, Action, DispatchResult>
where
    State: Clone + Reduce<Action>,
{
    pub fn new(state: State, initial_result_factory: fn() -> DispatchResult) -> Self {
        let middleware = Vec::default();
        Self {
            state: RefCell::new(state),
            initial_result_factory,
            middleware,
        }
    }

    pub fn add_middleware<Middleware>(mut self, middleware: Middleware) -> Self
    where
        Middleware:
            'static + Fn(MiddlewareContext<State, Action, DispatchResult>) -> DispatchResult,
    {
        self.middleware.push(Box::new(middleware));
        self
    }

    pub fn dispatch(&self, action: &Action) -> DispatchResult {
        self.dispatch_index(action, 0)
    }

    pub fn dispatch_index(&self, action: &Action, index: usize) -> DispatchResult {
        let middleware = self.middleware.get(index);

        match middleware {
            Option::None => {
                let state = self.get_state();
                let state = state.reduce(action);
                self.state.replace(state);
                (self.initial_result_factory)()
            }
            Option::Some(middleware) => {
                let context = MiddlewareContext::new(self, action, index);
                middleware(context)
            }
        }
    }

    pub fn get_state(&self) -> State {
        self.state.borrow().clone()
    }
}

impl<State, Action, DispatchResult> Default for Store<State, Action, DispatchResult>
where
    State: Default + Clone + Reduce<Action>,
    DispatchResult: Default,
{
    fn default() -> Self {
        Store::new(State::default(), DispatchResult::default)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    enum LampAction {
        TurnOn,
        TurnOff,
        Switch,
    }

    #[derive(Default, Clone)]
    struct LampState {
        power: bool,
    }

    impl Reduce<LampAction> for LampState {
        fn reduce(self, action: &LampAction) -> Self {
            match action {
                LampAction::TurnOn => LampState { power: true },
                LampAction::TurnOff => LampState { power: false },
                _ => self,
            }
        }
    }

    #[test]
    fn store_test() {
        let store: Store<LampState, LampAction> = Store::default();

        let state = store.get_state();
        assert_eq!(state.power, false);

        store.dispatch(&LampAction::TurnOn);
        let state = store.get_state();
        assert_eq!(state.power, true);

        store.dispatch(&LampAction::TurnOff);
        let state = store.get_state();
        assert_eq!(state.power, false);
    }

    #[test]
    fn store_middleware_test() {
        let store: Store<LampState, LampAction> =
            Store::default().add_middleware(|context: MiddlewareContext<LampState, LampAction>| {
                context.dispatch_next(context.action);

                if let LampAction::Switch = context.action {
                    let state = context.get_state();
                    if state.power {
                        context.dispatch(&LampAction::TurnOff);
                    } else {
                        context.dispatch(&LampAction::TurnOn);
                    }
                }
            });

        let state = store.get_state();
        assert_eq!(state.power, false);

        store.dispatch(&LampAction::Switch);
        let state = store.get_state();
        assert_eq!(state.power, true);

        store.dispatch(&LampAction::Switch);
        let state = store.get_state();
        assert_eq!(state.power, false);
    }

    #[test]
    fn store_result_test() {
        let store: Store<LampState, LampAction, usize> = Store::default().add_middleware(
            |context: MiddlewareContext<LampState, LampAction, usize>| {
                let count = context.dispatch_next(context.action);

                count + 1
            },
        );

        let state = store.get_state();
        assert_eq!(state.power, false);

        store.dispatch(&LampAction::TurnOn);
        let state = store.get_state();
        assert_eq!(state.power, true);

        store.dispatch(&LampAction::TurnOff);
        let state = store.get_state();
        assert_eq!(state.power, false);
    }
}