1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
#[cfg(feature = "std")]
mod arc;
mod array;
#[cfg(feature = "std")]
mod boxed;
#[cfg(feature = "std")]
mod rc;
mod reference;
mod slice;
mod tuple;

/// Trait for types that represent the logical state of an application.
///
/// Perhaps a more accurate mental model for types that implement this trait is that of a
/// _state machine_, where the nodes correspond to the universe of all possible representable
/// values and the edges correspond to _actions_.
pub trait Reducer<A> {
    /// Implements the transition given the current state and an action.
    ///
    /// This method is expected to have no side effects and must never fail.
    /// In many cases, an effective way to handle illegal state transitions is to make
    /// them idempotent, that is to leave the state unchanged.
    ///
    /// # Example
    ///
    /// ```rust
    /// use reducer::Reducer;
    ///
    /// #[derive(Debug)]
    /// struct Todos(Vec<String>);
    ///
    /// // Actions
    /// struct Create(String);
    /// struct Remove(usize);
    ///
    /// impl Reducer<Create> for Todos {
    ///     fn reduce(&mut self, Create(todo): Create) {
    ///         self.0.push(todo);
    ///     }
    /// }
    ///
    /// impl Reducer<Remove> for Todos {
    ///     fn reduce(&mut self, Remove(i): Remove) {
    ///         if i < self.0.len() {
    ///             self.0.remove(i);
    ///         } else {
    ///             // Illegal transition, leave the state unchanged.
    ///         }
    ///     }
    /// }
    ///
    /// let mut todos = Todos(vec![]);
    ///
    /// todos.reduce(Create("Buy milk".to_string()));
    /// println!("{:?}", todos); // ["Buy milk"]
    ///
    /// todos.reduce(Create("Learn Reducer".to_string()));
    /// println!("{:?}", todos); // ["Buy milk", "Learn Reducer"]
    ///
    /// todos.reduce(Remove(42)); // out of bounds
    /// println!("{:?}", todos); // ["Buy milk", "Learn Reducer"]
    ///
    /// todos.reduce(Remove(0));
    /// println!("{:?}", todos); // ["Learn Reducer"]
    /// ```
    fn reduce(&mut self, action: A);
}

#[cfg(test)]
mod tests {
    use super::*;
    use mockall::{predicate::*, *};
    use proptest::prelude::*;
    use std::{boxed::Box, vec::Vec};

    mock! {
        pub(crate) Reducer<A: 'static> {
            fn id(&self) -> usize;
        }
        trait Reducer<A> {
            fn reduce(&mut self, action: A);
        }
        trait Clone {
            fn clone(&self) -> Self;
        }
    }

    proptest! {
        #[test]
        fn reduce(action: u8) {
            let mut mock = MockReducer::new();

            mock.expect_reduce()
                .with(eq(action))
                .times(1)
                .return_const(());

            let reducer: &mut dyn Reducer<_> = &mut mock;
            reducer.reduce(action);
        }
    }
}

#[cfg(test)]
pub(crate) use self::tests::MockReducer;