tui_dispatch_core/
store.rs

1//! Centralized state store with reducer pattern
2
3use crate::Action;
4use std::marker::PhantomData;
5
6/// A reducer function that handles actions and mutates state
7///
8/// Returns `true` if the state changed and a re-render is needed.
9pub type Reducer<S, A> = fn(&mut S, A) -> bool;
10
11/// Centralized state store with Redux-like reducer pattern
12///
13/// The store holds the application state and provides a single point
14/// for state mutations through the `dispatch` method.
15///
16/// # Type Parameters
17/// * `S` - The application state type
18/// * `A` - The action type (must implement `Action`)
19///
20/// # Example
21/// ```ignore
22/// #[derive(Default)]
23/// struct AppState {
24///     counter: i32,
25/// }
26///
27/// #[derive(Action, Clone, Debug)]
28/// enum MyAction {
29///     Increment,
30///     Decrement,
31/// }
32///
33/// fn reducer(state: &mut AppState, action: MyAction) -> bool {
34///     match action {
35///         MyAction::Increment => {
36///             state.counter += 1;
37///             true
38///         }
39///         MyAction::Decrement => {
40///             state.counter -= 1;
41///             true
42///         }
43///     }
44/// }
45///
46/// let mut store = Store::new(AppState::default(), reducer);
47/// store.dispatch(MyAction::Increment);
48/// assert_eq!(store.state().counter, 1);
49/// ```
50pub struct Store<S, A: Action> {
51    state: S,
52    reducer: Reducer<S, A>,
53    _marker: PhantomData<A>,
54}
55
56impl<S, A: Action> Store<S, A> {
57    /// Create a new store with initial state and reducer
58    pub fn new(state: S, reducer: Reducer<S, A>) -> Self {
59        Self {
60            state,
61            reducer,
62            _marker: PhantomData,
63        }
64    }
65
66    /// Dispatch an action to the store
67    ///
68    /// The reducer will be called with the current state and action.
69    /// Returns `true` if the state changed and a re-render is needed.
70    pub fn dispatch(&mut self, action: A) -> bool {
71        (self.reducer)(&mut self.state, action)
72    }
73
74    /// Get a reference to the current state
75    pub fn state(&self) -> &S {
76        &self.state
77    }
78
79    /// Get a mutable reference to the state
80    ///
81    /// Use this sparingly - prefer dispatching actions for state changes.
82    /// This is useful for initializing state or for cases where the
83    /// action pattern doesn't fit well.
84    pub fn state_mut(&mut self) -> &mut S {
85        &mut self.state
86    }
87}
88
89/// Store with middleware support
90///
91/// Wraps a `Store` and allows middleware to intercept actions
92/// before and after they are processed by the reducer.
93pub struct StoreWithMiddleware<S, A: Action, M: Middleware<A>> {
94    store: Store<S, A>,
95    middleware: M,
96}
97
98impl<S, A: Action, M: Middleware<A>> StoreWithMiddleware<S, A, M> {
99    /// Create a new store with middleware
100    pub fn new(state: S, reducer: Reducer<S, A>, middleware: M) -> Self {
101        Self {
102            store: Store::new(state, reducer),
103            middleware,
104        }
105    }
106
107    /// Dispatch an action through middleware and store
108    pub fn dispatch(&mut self, action: A) -> bool {
109        self.middleware.before(&action);
110        let changed = self.store.dispatch(action.clone());
111        self.middleware.after(&action, changed);
112        changed
113    }
114
115    /// Get a reference to the current state
116    pub fn state(&self) -> &S {
117        self.store.state()
118    }
119
120    /// Get a mutable reference to the state
121    pub fn state_mut(&mut self) -> &mut S {
122        self.store.state_mut()
123    }
124
125    /// Get a reference to the middleware
126    pub fn middleware(&self) -> &M {
127        &self.middleware
128    }
129
130    /// Get a mutable reference to the middleware
131    pub fn middleware_mut(&mut self) -> &mut M {
132        &mut self.middleware
133    }
134}
135
136/// Middleware trait for intercepting actions
137///
138/// Implement this trait to add logging, persistence, or other
139/// cross-cutting concerns to your store.
140pub trait Middleware<A: Action> {
141    /// Called before the action is dispatched to the reducer
142    fn before(&mut self, action: &A);
143
144    /// Called after the action is processed by the reducer
145    fn after(&mut self, action: &A, state_changed: bool);
146}
147
148/// A no-op middleware that does nothing
149#[derive(Debug, Clone, Copy, Default)]
150pub struct NoopMiddleware;
151
152impl<A: Action> Middleware<A> for NoopMiddleware {
153    fn before(&mut self, _action: &A) {}
154    fn after(&mut self, _action: &A, _state_changed: bool) {}
155}
156
157/// Middleware that logs actions (for debugging)
158#[derive(Debug, Clone, Default)]
159pub struct LoggingMiddleware {
160    /// Whether to log before dispatch
161    pub log_before: bool,
162    /// Whether to log after dispatch
163    pub log_after: bool,
164}
165
166impl LoggingMiddleware {
167    /// Create a new logging middleware with default settings (log after only)
168    pub fn new() -> Self {
169        Self {
170            log_before: false,
171            log_after: true,
172        }
173    }
174
175    /// Create a logging middleware that logs both before and after
176    pub fn verbose() -> Self {
177        Self {
178            log_before: true,
179            log_after: true,
180        }
181    }
182}
183
184impl<A: Action> Middleware<A> for LoggingMiddleware {
185    fn before(&mut self, action: &A) {
186        if self.log_before {
187            tracing::debug!(action = %action.name(), "Dispatching action");
188        }
189    }
190
191    fn after(&mut self, action: &A, state_changed: bool) {
192        if self.log_after {
193            tracing::debug!(
194                action = %action.name(),
195                state_changed = state_changed,
196                "Action processed"
197            );
198        }
199    }
200}
201
202/// Compose multiple middleware into a single middleware
203pub struct ComposedMiddleware<A: Action> {
204    middlewares: Vec<Box<dyn Middleware<A>>>,
205}
206
207impl<A: Action> std::fmt::Debug for ComposedMiddleware<A> {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        f.debug_struct("ComposedMiddleware")
210            .field("middlewares_count", &self.middlewares.len())
211            .finish()
212    }
213}
214
215impl<A: Action> Default for ComposedMiddleware<A> {
216    fn default() -> Self {
217        Self::new()
218    }
219}
220
221impl<A: Action> ComposedMiddleware<A> {
222    /// Create a new composed middleware
223    pub fn new() -> Self {
224        Self {
225            middlewares: Vec::new(),
226        }
227    }
228
229    /// Add a middleware to the composition
230    pub fn add<M: Middleware<A> + 'static>(&mut self, middleware: M) {
231        self.middlewares.push(Box::new(middleware));
232    }
233}
234
235impl<A: Action> Middleware<A> for ComposedMiddleware<A> {
236    fn before(&mut self, action: &A) {
237        for middleware in &mut self.middlewares {
238            middleware.before(action);
239        }
240    }
241
242    fn after(&mut self, action: &A, state_changed: bool) {
243        // Call in reverse order for proper nesting
244        for middleware in self.middlewares.iter_mut().rev() {
245            middleware.after(action, state_changed);
246        }
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    #[derive(Default)]
255    struct TestState {
256        counter: i32,
257    }
258
259    #[derive(Clone, Debug)]
260    enum TestAction {
261        Increment,
262        Decrement,
263        NoOp,
264    }
265
266    impl Action for TestAction {
267        fn name(&self) -> &'static str {
268            match self {
269                TestAction::Increment => "Increment",
270                TestAction::Decrement => "Decrement",
271                TestAction::NoOp => "NoOp",
272            }
273        }
274    }
275
276    fn test_reducer(state: &mut TestState, action: TestAction) -> bool {
277        match action {
278            TestAction::Increment => {
279                state.counter += 1;
280                true
281            }
282            TestAction::Decrement => {
283                state.counter -= 1;
284                true
285            }
286            TestAction::NoOp => false,
287        }
288    }
289
290    #[test]
291    fn test_store_dispatch() {
292        let mut store = Store::new(TestState::default(), test_reducer);
293
294        assert!(store.dispatch(TestAction::Increment));
295        assert_eq!(store.state().counter, 1);
296
297        assert!(store.dispatch(TestAction::Increment));
298        assert_eq!(store.state().counter, 2);
299
300        assert!(store.dispatch(TestAction::Decrement));
301        assert_eq!(store.state().counter, 1);
302    }
303
304    #[test]
305    fn test_store_noop() {
306        let mut store = Store::new(TestState::default(), test_reducer);
307
308        assert!(!store.dispatch(TestAction::NoOp));
309        assert_eq!(store.state().counter, 0);
310    }
311
312    #[test]
313    fn test_store_state_mut() {
314        let mut store = Store::new(TestState::default(), test_reducer);
315
316        store.state_mut().counter = 100;
317        assert_eq!(store.state().counter, 100);
318    }
319
320    #[derive(Default)]
321    struct CountingMiddleware {
322        before_count: usize,
323        after_count: usize,
324    }
325
326    impl<A: Action> Middleware<A> for CountingMiddleware {
327        fn before(&mut self, _action: &A) {
328            self.before_count += 1;
329        }
330
331        fn after(&mut self, _action: &A, _state_changed: bool) {
332            self.after_count += 1;
333        }
334    }
335
336    #[test]
337    fn test_store_with_middleware() {
338        let mut store = StoreWithMiddleware::new(
339            TestState::default(),
340            test_reducer,
341            CountingMiddleware::default(),
342        );
343
344        store.dispatch(TestAction::Increment);
345        store.dispatch(TestAction::Increment);
346
347        assert_eq!(store.middleware().before_count, 2);
348        assert_eq!(store.middleware().after_count, 2);
349        assert_eq!(store.state().counter, 2);
350    }
351}