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)]
328#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_counter_increment() {
334 let mut state = CounterState::default();
335 state.update(CounterMessage::Increment);
336 assert_eq!(state.count, 1);
337 }
338
339 #[test]
340 fn test_counter_decrement() {
341 let mut state = CounterState { count: 5 };
342 state.update(CounterMessage::Decrement);
343 assert_eq!(state.count, 4);
344 }
345
346 #[test]
347 fn test_counter_set() {
348 let mut state = CounterState::default();
349 state.update(CounterMessage::Set(42));
350 assert_eq!(state.count, 42);
351 }
352
353 #[test]
354 fn test_counter_reset() {
355 let mut state = CounterState { count: 100 };
356 state.update(CounterMessage::Reset);
357 assert_eq!(state.count, 0);
358 }
359
360 #[test]
361 fn test_command_none() {
362 let cmd: Command<()> = Command::None;
363 assert!(cmd.is_none());
364 }
365
366 #[test]
367 fn test_command_default() {
368 let cmd: Command<()> = Command::default();
369 assert!(cmd.is_none());
370 }
371
372 #[test]
373 fn test_command_batch() {
374 let cmd: Command<i32> = Command::batch([
375 Command::Navigate {
376 route: "/a".to_string(),
377 },
378 Command::Navigate {
379 route: "/b".to_string(),
380 },
381 ]);
382 assert!(!cmd.is_none());
383 if let Command::Batch(cmds) = cmd {
384 assert_eq!(cmds.len(), 2);
385 } else {
386 panic!("Expected Batch command");
387 }
388 }
389
390 #[test]
391 fn test_command_navigate() {
392 let cmd: Command<()> = Command::Navigate {
393 route: "/home".to_string(),
394 };
395 if let Command::Navigate { route } = cmd {
396 assert_eq!(route, "/home");
397 } else {
398 panic!("Expected Navigate command");
399 }
400 }
401
402 #[test]
403 fn test_command_save_state() {
404 let cmd: Command<()> = Command::SaveState {
405 key: "app_state".to_string(),
406 };
407 if let Command::SaveState { key } = cmd {
408 assert_eq!(key, "app_state");
409 } else {
410 panic!("Expected SaveState command");
411 }
412 }
413
414 #[test]
415 fn test_counter_serialization() {
416 let state = CounterState { count: 42 };
417 let json = serde_json::to_string(&state).unwrap();
418 let loaded: CounterState = serde_json::from_str(&json).unwrap();
419 assert_eq!(loaded.count, 42);
420 }
421
422 #[test]
423 fn test_command_map() {
424 let cmd: Command<i32> = Command::Navigate {
425 route: "/test".to_string(),
426 };
427 let mapped: Command<String> = cmd.map(|_i| "mapped".to_string());
428
429 if let Command::Navigate { route } = mapped {
430 assert_eq!(route, "/test");
431 } else {
432 panic!("Expected Navigate command after map");
433 }
434 }
435
436 #[test]
437 fn test_command_map_none() {
438 let cmd: Command<i32> = Command::None;
439 let mapped: Command<String> = cmd.map(|i| i.to_string());
440 assert!(mapped.is_none());
441 }
442
443 #[test]
444 fn test_command_batch_map() {
445 let cmd: Command<i32> = Command::batch([
446 Command::SaveState {
447 key: "key1".to_string(),
448 },
449 Command::SaveState {
450 key: "key2".to_string(),
451 },
452 ]);
453
454 let mapped: Command<String> = cmd.map(|i| format!("val_{i}"));
455
456 if let Command::Batch(cmds) = mapped {
457 assert_eq!(cmds.len(), 2);
458 } else {
459 panic!("Expected Batch command after map");
460 }
461 }
462
463 #[test]
468 fn test_store_new() {
469 let store = Store::new(CounterState::default());
470 assert_eq!(store.state().count, 0);
471 }
472
473 #[test]
474 fn test_store_dispatch() {
475 let mut store = Store::new(CounterState::default());
476 store.dispatch(CounterMessage::Increment);
477 assert_eq!(store.state().count, 1);
478 }
479
480 #[test]
481 fn test_store_history() {
482 let mut store = Store::new(CounterState::default());
483
484 store.dispatch(CounterMessage::Increment);
485 store.dispatch(CounterMessage::Increment);
486 store.dispatch(CounterMessage::Increment);
487
488 assert_eq!(store.state().count, 3);
489 assert_eq!(store.history_len(), 3);
490 }
491
492 #[test]
493 fn test_store_undo() {
494 let mut store = Store::new(CounterState::default());
495
496 store.dispatch(CounterMessage::Increment);
497 store.dispatch(CounterMessage::Increment);
498 assert_eq!(store.state().count, 2);
499
500 assert!(store.can_undo());
501 assert!(store.undo());
502 assert_eq!(store.state().count, 1);
503
504 assert!(store.undo());
505 assert_eq!(store.state().count, 0);
506 }
507
508 #[test]
509 fn test_store_redo() {
510 let mut store = Store::new(CounterState::default());
511
512 store.dispatch(CounterMessage::Increment);
513 store.dispatch(CounterMessage::Increment);
514 store.undo();
515 store.undo();
516
517 assert_eq!(store.state().count, 0);
518 assert!(store.can_redo());
519
520 assert!(store.redo());
521 assert_eq!(store.state().count, 1);
522
523 assert!(store.redo());
524 assert_eq!(store.state().count, 2);
525 }
526
527 #[test]
528 fn test_store_undo_at_start() {
529 let mut store = Store::new(CounterState::default());
530 assert!(!store.can_undo());
531 assert!(!store.undo());
532 }
533
534 #[test]
535 fn test_store_redo_at_end() {
536 let mut store = Store::new(CounterState::default());
537 store.dispatch(CounterMessage::Increment);
538 assert!(!store.can_redo());
539 assert!(!store.redo());
540 }
541
542 #[test]
543 fn test_store_history_truncation() {
544 let mut store = Store::new(CounterState::default());
545
546 store.dispatch(CounterMessage::Set(1));
547 store.dispatch(CounterMessage::Set(2));
548 store.dispatch(CounterMessage::Set(3));
549
550 store.undo();
552 store.undo();
553 assert_eq!(store.state().count, 1);
554
555 store.dispatch(CounterMessage::Set(10));
557 assert_eq!(store.state().count, 10);
558
559 assert!(!store.redo());
561 }
562
563 #[test]
564 fn test_store_jump_to() {
565 let mut store = Store::new(CounterState::default());
566
567 store.dispatch(CounterMessage::Set(10));
568 store.dispatch(CounterMessage::Set(20));
569 store.dispatch(CounterMessage::Set(30));
570
571 assert!(store.jump_to(0));
572 assert_eq!(store.state().count, 0);
573
574 assert!(store.jump_to(2));
575 assert_eq!(store.state().count, 20);
576 }
577
578 #[test]
579 fn test_store_jump_invalid() {
580 let mut store = Store::new(CounterState::default());
581 store.dispatch(CounterMessage::Increment);
582
583 assert!(!store.jump_to(100));
584 }
585
586 #[test]
587 fn test_store_clear_history() {
588 let mut store = Store::new(CounterState::default());
589
590 store.dispatch(CounterMessage::Increment);
591 store.dispatch(CounterMessage::Increment);
592 assert!(store.history_len() > 0);
593
594 store.clear_history();
595 assert_eq!(store.history_len(), 0);
596 assert!(!store.can_undo());
597 }
598
599 #[test]
600 fn test_store_with_history_limit() {
601 let mut store = Store::with_history_limit(CounterState::default(), 3);
602
603 for i in 1..=10 {
604 store.dispatch(CounterMessage::Set(i));
605 }
606
607 assert!(store.history_len() <= 3);
609 }
610
611 #[test]
612 fn test_store_subscribe() {
613 use std::sync::atomic::{AtomicI32, Ordering};
614 use std::sync::Arc;
615
616 let call_count = Arc::new(AtomicI32::new(0));
617 let call_count_clone = call_count.clone();
618
619 let mut store = Store::new(CounterState::default());
620 store.subscribe(move |_| {
621 call_count_clone.fetch_add(1, Ordering::SeqCst);
622 });
623
624 store.dispatch(CounterMessage::Increment);
625 store.dispatch(CounterMessage::Increment);
626
627 assert_eq!(call_count.load(Ordering::SeqCst), 2);
628 }
629
630 #[test]
631 fn test_store_no_history() {
632 let mut store = Store::with_history_limit(CounterState::default(), 0);
633
634 store.dispatch(CounterMessage::Increment);
635 store.dispatch(CounterMessage::Increment);
636
637 assert_eq!(store.history_len(), 0);
638 assert!(!store.can_undo());
639 }
640}