Skip to main content

xtask_todo_lib/
store.rs

1//! Storage abstraction for todo items.
2
3use std::collections::HashMap;
4
5use crate::{Todo, TodoId};
6
7/// Backing store for todo items. Implementations may be in-memory or persistent.
8pub trait Store {
9    /// Returns the next available id and advances the counter.
10    fn next_id(&mut self) -> TodoId;
11    /// Inserts a todo; the id must have been obtained from `next_id`.
12    fn insert(&mut self, todo: Todo);
13    /// Returns the todo with the given id, if any.
14    fn get(&self, id: TodoId) -> Option<Todo>;
15    /// Returns all todos in creation order (by `created_at`).
16    fn list(&self) -> Vec<Todo>;
17    /// Updates an existing todo (e.g. after marking completed).
18    fn update(&mut self, todo: Todo);
19    /// Removes the todo with the given id.
20    fn remove(&mut self, id: TodoId);
21}
22
23/// In-memory store using a map and a monotonic id counter.
24#[derive(Default)]
25pub struct InMemoryStore {
26    next_id: u64,
27    items: HashMap<TodoId, Todo>,
28}
29
30impl InMemoryStore {
31    #[must_use]
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Builds a store from existing todos (e.g. after loading from file). Next id will be max(existing ids) + 1.
37    #[must_use]
38    pub fn from_todos(todos: Vec<Todo>) -> Self {
39        let next_id = todos.iter().map(|t| t.id.as_u64()).max().unwrap_or(0);
40        let items = todos.into_iter().map(|t| (t.id, t)).collect();
41        Self { next_id, items }
42    }
43}
44
45impl Store for InMemoryStore {
46    fn next_id(&mut self) -> TodoId {
47        self.next_id += 1;
48        TodoId::from_raw(self.next_id).expect("id overflow")
49    }
50
51    fn insert(&mut self, todo: Todo) {
52        self.items.insert(todo.id, todo);
53    }
54
55    fn get(&self, id: TodoId) -> Option<Todo> {
56        self.items.get(&id).cloned()
57    }
58
59    fn list(&self) -> Vec<Todo> {
60        let mut out: Vec<Todo> = self.items.values().cloned().collect();
61        out.sort_by_key(|t| t.id);
62        out
63    }
64
65    fn update(&mut self, todo: Todo) {
66        if self.items.contains_key(&todo.id) {
67            self.items.insert(todo.id, todo);
68        }
69    }
70
71    fn remove(&mut self, id: TodoId) {
72        self.items.remove(&id);
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::TodoList;
80    use std::time::SystemTime;
81
82    #[test]
83    fn from_todos_empty() {
84        let store = InMemoryStore::from_todos(vec![]);
85        let mut list = TodoList::with_store(store);
86        let id = list.create("first").unwrap();
87        assert_eq!(id.as_u64(), 1);
88        assert_eq!(list.list().len(), 1);
89    }
90
91    #[test]
92    fn from_todos_with_existing() {
93        let created_at = SystemTime::now();
94        let id1 = TodoId::from_raw(1).unwrap();
95        let id2 = TodoId::from_raw(2).unwrap();
96        let todos = vec![
97            Todo {
98                id: id1,
99                title: "a".into(),
100                completed: false,
101                created_at,
102                completed_at: None,
103                description: None,
104                due_date: None,
105                priority: None,
106                tags: Vec::new(),
107                repeat_rule: None,
108                repeat_until: None,
109                repeat_count: None,
110            },
111            Todo {
112                id: id2,
113                title: "b".into(),
114                completed: true,
115                created_at,
116                completed_at: Some(created_at),
117                description: None,
118                due_date: None,
119                priority: None,
120                tags: Vec::new(),
121                repeat_rule: None,
122                repeat_until: None,
123                repeat_count: None,
124            },
125        ];
126        let store = InMemoryStore::from_todos(todos);
127        let mut list = TodoList::with_store(store);
128        let id3 = list.create("c").unwrap();
129        assert_eq!(id3.as_u64(), 3);
130        let items = list.list();
131        assert_eq!(items.len(), 3);
132        assert_eq!(items[0].title, "a");
133        assert_eq!(items[1].title, "b");
134        assert_eq!(items[2].title, "c");
135    }
136}