1use serde::{Deserialize, Serialize};
44use std::future::Future;
45use std::pin::Pin;
46
47pub trait State: Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync {
51 type Message: Send;
53
54 fn update(&mut self, msg: Self::Message) -> Command<Self::Message>;
58}
59
60#[derive(Default)]
67pub enum Command<M> {
68 #[default]
70 None,
71 Batch(Vec<Self>),
73 Task(Pin<Box<dyn Future<Output = M> + Send>>),
75 Navigate {
77 route: String,
79 },
80 SaveState {
82 key: String,
84 },
85 LoadState {
87 key: String,
89 on_load: fn(Option<Vec<u8>>) -> M,
91 },
92}
93
94impl<M> Command<M> {
95 pub fn task<F>(future: F) -> Self
97 where
98 F: Future<Output = M> + Send + 'static,
99 {
100 Self::Task(Box::pin(future))
101 }
102
103 pub fn batch(commands: impl IntoIterator<Item = Self>) -> Self {
105 Self::Batch(commands.into_iter().collect())
106 }
107
108 #[must_use]
110 pub const fn is_none(&self) -> bool {
111 matches!(self, Self::None)
112 }
113
114 pub fn map<N, F>(self, f: F) -> Command<N>
116 where
117 F: Fn(M) -> N + Send + Sync + 'static,
118 M: Send + 'static,
119 N: Send + 'static,
120 {
121 let f: std::sync::Arc<dyn Fn(M) -> N + Send + Sync> = std::sync::Arc::new(f);
122 self.map_inner(&f)
123 }
124
125 fn map_inner<N>(self, f: &std::sync::Arc<dyn Fn(M) -> N + Send + Sync>) -> Command<N>
126 where
127 M: Send + 'static,
128 N: Send + 'static,
129 {
130 match self {
131 Self::None => Command::None,
132 Self::Batch(cmds) => Command::Batch(cmds.into_iter().map(|c| c.map_inner(f)).collect()),
133 Self::Task(fut) => {
134 let f = f.clone();
135 Command::Task(Box::pin(async move { f(fut.await) }))
136 }
137 Self::Navigate { route } => Command::Navigate { route },
138 Self::SaveState { key } => Command::SaveState { key },
139 Self::LoadState { .. } => {
140 Command::None
143 }
144 }
145 }
146}
147
148#[derive(Debug, Clone, Default, Serialize, Deserialize)]
150pub struct CounterState {
151 pub count: i32,
153}
154
155#[derive(Debug, Clone)]
157pub enum CounterMessage {
158 Increment,
160 Decrement,
162 Set(i32),
164 Reset,
166}
167
168impl State for CounterState {
169 type Message = CounterMessage;
170
171 fn update(&mut self, msg: Self::Message) -> Command<Self::Message> {
172 match msg {
173 CounterMessage::Increment => self.count += 1,
174 CounterMessage::Decrement => self.count -= 1,
175 CounterMessage::Set(value) => self.count = value,
176 CounterMessage::Reset => self.count = 0,
177 }
178 Command::None
179 }
180}
181
182type Subscriber<S> = Box<dyn Fn(&S) + Send + Sync>;
184
185pub struct Store<S: State> {
187 state: S,
188 history: Vec<S>,
189 history_index: usize,
190 max_history: usize,
191 subscribers: Vec<Subscriber<S>>,
192}
193
194impl<S: State> Store<S> {
195 pub fn new(initial: S) -> Self {
197 Self {
198 state: initial,
199 history: Vec::new(),
200 history_index: 0,
201 max_history: 100,
202 subscribers: Vec::new(),
203 }
204 }
205
206 pub fn with_history_limit(initial: S, max_history: usize) -> Self {
208 Self {
209 state: initial,
210 history: Vec::new(),
211 history_index: 0,
212 max_history,
213 subscribers: Vec::new(),
214 }
215 }
216
217 pub const fn state(&self) -> &S {
219 &self.state
220 }
221
222 pub fn dispatch(&mut self, msg: S::Message) -> Command<S::Message> {
224 if self.max_history > 0 {
226 if self.history_index < self.history.len() {
228 self.history.truncate(self.history_index);
229 }
230
231 self.history.push(self.state.clone());
232
233 if self.history.len() > self.max_history {
235 self.history.remove(0);
236 } else {
237 self.history_index = self.history.len();
238 }
239 }
240
241 let cmd = self.state.update(msg);
243
244 self.notify_subscribers();
246
247 cmd
248 }
249
250 pub fn subscribe<F>(&mut self, callback: F)
252 where
253 F: Fn(&S) + Send + Sync + 'static,
254 {
255 self.subscribers.push(Box::new(callback));
256 }
257
258 pub fn history_len(&self) -> usize {
260 self.history.len()
261 }
262
263 pub const fn can_undo(&self) -> bool {
265 self.history_index > 0
266 }
267
268 pub fn can_redo(&self) -> bool {
270 self.history_index < self.history.len()
271 }
272
273 pub fn undo(&mut self) -> bool {
275 if self.can_undo() {
276 if self.history_index == self.history.len() {
278 self.history.push(self.state.clone());
279 }
280
281 self.history_index -= 1;
282 self.state = self.history[self.history_index].clone();
283 self.notify_subscribers();
284 true
285 } else {
286 false
287 }
288 }
289
290 pub fn redo(&mut self) -> bool {
292 if self.history_index < self.history.len().saturating_sub(1) {
293 self.history_index += 1;
294 self.state = self.history[self.history_index].clone();
295 self.notify_subscribers();
296 true
297 } else {
298 false
299 }
300 }
301
302 pub fn jump_to(&mut self, index: usize) -> bool {
304 if index < self.history.len() {
305 self.history_index = index;
306 self.state = self.history[index].clone();
307 self.notify_subscribers();
308 true
309 } else {
310 false
311 }
312 }
313
314 pub fn clear_history(&mut self) {
316 self.history.clear();
317 self.history_index = 0;
318 }
319
320 fn notify_subscribers(&self) {
321 for subscriber in &self.subscribers {
322 subscriber(&self.state);
323 }
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn test_counter_increment() {
333 let mut state = CounterState::default();
334 state.update(CounterMessage::Increment);
335 assert_eq!(state.count, 1);
336 }
337
338 #[test]
339 fn test_counter_decrement() {
340 let mut state = CounterState { count: 5 };
341 state.update(CounterMessage::Decrement);
342 assert_eq!(state.count, 4);
343 }
344
345 #[test]
346 fn test_counter_set() {
347 let mut state = CounterState::default();
348 state.update(CounterMessage::Set(42));
349 assert_eq!(state.count, 42);
350 }
351
352 #[test]
353 fn test_counter_reset() {
354 let mut state = CounterState { count: 100 };
355 state.update(CounterMessage::Reset);
356 assert_eq!(state.count, 0);
357 }
358
359 #[test]
360 fn test_command_none() {
361 let cmd: Command<()> = Command::None;
362 assert!(cmd.is_none());
363 }
364
365 #[test]
366 fn test_command_default() {
367 let cmd: Command<()> = Command::default();
368 assert!(cmd.is_none());
369 }
370
371 #[test]
372 fn test_command_batch() {
373 let cmd: Command<i32> = Command::batch([
374 Command::Navigate {
375 route: "/a".to_string(),
376 },
377 Command::Navigate {
378 route: "/b".to_string(),
379 },
380 ]);
381 assert!(!cmd.is_none());
382 if let Command::Batch(cmds) = cmd {
383 assert_eq!(cmds.len(), 2);
384 } else {
385 panic!("Expected Batch command");
386 }
387 }
388
389 #[test]
390 fn test_command_navigate() {
391 let cmd: Command<()> = Command::Navigate {
392 route: "/home".to_string(),
393 };
394 if let Command::Navigate { route } = cmd {
395 assert_eq!(route, "/home");
396 } else {
397 panic!("Expected Navigate command");
398 }
399 }
400
401 #[test]
402 fn test_command_save_state() {
403 let cmd: Command<()> = Command::SaveState {
404 key: "app_state".to_string(),
405 };
406 if let Command::SaveState { key } = cmd {
407 assert_eq!(key, "app_state");
408 } else {
409 panic!("Expected SaveState command");
410 }
411 }
412
413 #[test]
414 fn test_counter_serialization() {
415 let state = CounterState { count: 42 };
416 let json = serde_json::to_string(&state).unwrap();
417 let loaded: CounterState = serde_json::from_str(&json).unwrap();
418 assert_eq!(loaded.count, 42);
419 }
420
421 #[test]
422 fn test_command_map() {
423 let cmd: Command<i32> = Command::Navigate {
424 route: "/test".to_string(),
425 };
426 let mapped: Command<String> = cmd.map(|_i| "mapped".to_string());
427
428 if let Command::Navigate { route } = mapped {
429 assert_eq!(route, "/test");
430 } else {
431 panic!("Expected Navigate command after map");
432 }
433 }
434
435 #[test]
436 fn test_command_map_none() {
437 let cmd: Command<i32> = Command::None;
438 let mapped: Command<String> = cmd.map(|i| i.to_string());
439 assert!(mapped.is_none());
440 }
441
442 #[test]
443 fn test_command_batch_map() {
444 let cmd: Command<i32> = Command::batch([
445 Command::SaveState {
446 key: "key1".to_string(),
447 },
448 Command::SaveState {
449 key: "key2".to_string(),
450 },
451 ]);
452
453 let mapped: Command<String> = cmd.map(|i| format!("val_{i}"));
454
455 if let Command::Batch(cmds) = mapped {
456 assert_eq!(cmds.len(), 2);
457 } else {
458 panic!("Expected Batch command after map");
459 }
460 }
461
462 #[test]
467 fn test_store_new() {
468 let store = Store::new(CounterState::default());
469 assert_eq!(store.state().count, 0);
470 }
471
472 #[test]
473 fn test_store_dispatch() {
474 let mut store = Store::new(CounterState::default());
475 store.dispatch(CounterMessage::Increment);
476 assert_eq!(store.state().count, 1);
477 }
478
479 #[test]
480 fn test_store_history() {
481 let mut store = Store::new(CounterState::default());
482
483 store.dispatch(CounterMessage::Increment);
484 store.dispatch(CounterMessage::Increment);
485 store.dispatch(CounterMessage::Increment);
486
487 assert_eq!(store.state().count, 3);
488 assert_eq!(store.history_len(), 3);
489 }
490
491 #[test]
492 fn test_store_undo() {
493 let mut store = Store::new(CounterState::default());
494
495 store.dispatch(CounterMessage::Increment);
496 store.dispatch(CounterMessage::Increment);
497 assert_eq!(store.state().count, 2);
498
499 assert!(store.can_undo());
500 assert!(store.undo());
501 assert_eq!(store.state().count, 1);
502
503 assert!(store.undo());
504 assert_eq!(store.state().count, 0);
505 }
506
507 #[test]
508 fn test_store_redo() {
509 let mut store = Store::new(CounterState::default());
510
511 store.dispatch(CounterMessage::Increment);
512 store.dispatch(CounterMessage::Increment);
513 store.undo();
514 store.undo();
515
516 assert_eq!(store.state().count, 0);
517 assert!(store.can_redo());
518
519 assert!(store.redo());
520 assert_eq!(store.state().count, 1);
521
522 assert!(store.redo());
523 assert_eq!(store.state().count, 2);
524 }
525
526 #[test]
527 fn test_store_undo_at_start() {
528 let mut store = Store::new(CounterState::default());
529 assert!(!store.can_undo());
530 assert!(!store.undo());
531 }
532
533 #[test]
534 fn test_store_redo_at_end() {
535 let mut store = Store::new(CounterState::default());
536 store.dispatch(CounterMessage::Increment);
537 assert!(!store.can_redo());
538 assert!(!store.redo());
539 }
540
541 #[test]
542 fn test_store_history_truncation() {
543 let mut store = Store::new(CounterState::default());
544
545 store.dispatch(CounterMessage::Set(1));
546 store.dispatch(CounterMessage::Set(2));
547 store.dispatch(CounterMessage::Set(3));
548
549 store.undo();
551 store.undo();
552 assert_eq!(store.state().count, 1);
553
554 store.dispatch(CounterMessage::Set(10));
556 assert_eq!(store.state().count, 10);
557
558 assert!(!store.redo());
560 }
561
562 #[test]
563 fn test_store_jump_to() {
564 let mut store = Store::new(CounterState::default());
565
566 store.dispatch(CounterMessage::Set(10));
567 store.dispatch(CounterMessage::Set(20));
568 store.dispatch(CounterMessage::Set(30));
569
570 assert!(store.jump_to(0));
571 assert_eq!(store.state().count, 0);
572
573 assert!(store.jump_to(2));
574 assert_eq!(store.state().count, 20);
575 }
576
577 #[test]
578 fn test_store_jump_invalid() {
579 let mut store = Store::new(CounterState::default());
580 store.dispatch(CounterMessage::Increment);
581
582 assert!(!store.jump_to(100));
583 }
584
585 #[test]
586 fn test_store_clear_history() {
587 let mut store = Store::new(CounterState::default());
588
589 store.dispatch(CounterMessage::Increment);
590 store.dispatch(CounterMessage::Increment);
591 assert!(store.history_len() > 0);
592
593 store.clear_history();
594 assert_eq!(store.history_len(), 0);
595 assert!(!store.can_undo());
596 }
597
598 #[test]
599 fn test_store_with_history_limit() {
600 let mut store = Store::with_history_limit(CounterState::default(), 3);
601
602 for i in 1..=10 {
603 store.dispatch(CounterMessage::Set(i));
604 }
605
606 assert!(store.history_len() <= 3);
608 }
609
610 #[test]
611 fn test_store_subscribe() {
612 use std::sync::atomic::{AtomicI32, Ordering};
613 use std::sync::Arc;
614
615 let call_count = Arc::new(AtomicI32::new(0));
616 let call_count_clone = call_count.clone();
617
618 let mut store = Store::new(CounterState::default());
619 store.subscribe(move |_| {
620 call_count_clone.fetch_add(1, Ordering::SeqCst);
621 });
622
623 store.dispatch(CounterMessage::Increment);
624 store.dispatch(CounterMessage::Increment);
625
626 assert_eq!(call_count.load(Ordering::SeqCst), 2);
627 }
628
629 #[test]
630 fn test_store_no_history() {
631 let mut store = Store::with_history_limit(CounterState::default(), 0);
632
633 store.dispatch(CounterMessage::Increment);
634 store.dispatch(CounterMessage::Increment);
635
636 assert_eq!(store.history_len(), 0);
637 assert!(!store.can_undo());
638 }
639}