reducer/
reducer.rs

1#[cfg(feature = "alloc")]
2mod arc;
3#[cfg(feature = "alloc")]
4mod boxed;
5#[cfg(feature = "alloc")]
6mod rc;
7mod tuple;
8
9/// Trait for types that represent the logical state of an application.
10///
11/// Perhaps a more accurate mental model for types that implement this trait is that of a
12/// _state machine_, where the nodes correspond to the universe of all possible representable
13/// values and the edges correspond to _actions_.
14pub trait Reducer<A> {
15    /// Implements the transition given the current state and an action.
16    ///
17    /// This method is expected to have no side effects and must never fail.
18    /// In many cases, an effective way to handle illegal state transitions is to make
19    /// them idempotent, that is to leave the state unchanged.
20    ///
21    /// # Example
22    ///
23    /// ```rust
24    /// use reducer::Reducer;
25    ///
26    /// #[derive(Debug)]
27    /// struct Todos(Vec<String>);
28    ///
29    /// // Actions
30    /// struct Create(String);
31    /// struct Remove(usize);
32    ///
33    /// impl Reducer<Create> for Todos {
34    ///     fn reduce(&mut self, Create(todo): Create) {
35    ///         self.0.push(todo);
36    ///     }
37    /// }
38    ///
39    /// impl Reducer<Remove> for Todos {
40    ///     fn reduce(&mut self, Remove(i): Remove) {
41    ///         if i < self.0.len() {
42    ///             self.0.remove(i);
43    ///         } else {
44    ///             // Illegal transition, leave the state unchanged.
45    ///         }
46    ///     }
47    /// }
48    ///
49    /// let mut todos = Todos(vec![]);
50    ///
51    /// todos.reduce(Create("Buy milk".to_string()));
52    /// println!("{:?}", todos); // ["Buy milk"]
53    ///
54    /// todos.reduce(Create("Learn Reducer".to_string()));
55    /// println!("{:?}", todos); // ["Buy milk", "Learn Reducer"]
56    ///
57    /// todos.reduce(Remove(42)); // out of bounds
58    /// println!("{:?}", todos); // ["Buy milk", "Learn Reducer"]
59    ///
60    /// todos.reduce(Remove(0));
61    /// println!("{:?}", todos); // ["Learn Reducer"]
62    /// ```
63    fn reduce(&mut self, action: A);
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use mockall::{predicate::*, *};
70    use test_strategy::proptest;
71
72    mock! {
73        pub Reducer<A: 'static> {
74            pub fn id(&self) -> usize;
75        }
76
77        impl<A: 'static> Reducer<A> for Reducer<A> {
78            fn reduce(&mut self, action: A);
79        }
80
81        impl<A: 'static> Clone for Reducer<A> {
82            fn clone(&self) -> Self;
83        }
84    }
85
86    #[proptest]
87    fn reduce(action: u8) {
88        let mut mock = MockReducer::new();
89
90        mock.expect_reduce()
91            .with(eq(action))
92            .once()
93            .return_const(());
94
95        let reducer: &mut dyn Reducer<_> = &mut mock;
96        reducer.reduce(action);
97    }
98}
99
100#[cfg(test)]
101pub(crate) use self::tests::MockReducer;