hash_map_id/
lib.rs

1use std::{any::type_name, collections::HashMap, fmt::Debug};
2
3/// HashMap wrapper with incremental ID (u64) assignment.
4pub struct HashMapId<T> {
5    id_seed: u64,
6    store: HashMap<u64, T>,
7}
8
9impl<T> HashMapId<T>
10where
11    T: Send + Sync,
12{
13    pub fn new() -> Self {
14        Self {
15            id_seed: 0,
16            store: HashMap::new(),
17        }
18    }
19
20    pub fn add(&mut self, item: T) -> u64 {
21        let id = self.id_seed;
22        self.store.insert(id, item);
23        self.id_seed += 1;
24        id
25    }
26
27    pub fn remove(&mut self, id: u64) -> Option<T> {
28        self.store.remove(&id)
29    }
30
31    pub fn get_mut(&mut self, id: u64) -> Option<&mut T> {
32        self.store.get_mut(&id)
33    }
34
35    pub fn get(&self, id: u64) -> Option<&T> {
36        self.store.get(&id)
37    }
38}
39
40impl<T> Default for HashMapId<T>
41where
42    T: Send + Sync,
43{
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49impl<T> Debug for HashMapId<T> {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        f.debug_struct("HashMapId")
52            .field("id_seed", &self.id_seed)
53            .field("type", &type_name::<T>())
54            .finish()
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    // get
63
64    #[test]
65    fn get_returns_none_for_non_existent_item() {
66        let hash: HashMapId<i32> = HashMapId::new();
67        let item = hash.get(10);
68        assert!(item.is_none());
69    }
70
71    #[test]
72    fn get_returns_reference_to_item() {
73        let mut hash: HashMapId<i32> = HashMapId::new();
74        let value = 10;
75        let id = hash.add(value);
76        let item = hash.get(id);
77        assert_eq!(item, Some(&value));
78    }
79
80    // get_mut
81
82    #[test]
83    fn get_mut_returns_mutable_reference() {
84        let mut hash: HashMapId<i32> = HashMapId::new();
85        let value = 10;
86        let id = hash.add(value);
87        let item = hash.get_mut(id);
88        assert_eq!(item, Some(&mut value.clone()));
89    }
90
91    #[test]
92    fn get_mut_returns_none_for_non_existent_item() {
93        let mut hash: HashMapId<i32> = HashMapId::new();
94        let item = hash.get_mut(0);
95        assert!(item.is_none());
96    }
97
98    // add
99
100    #[test]
101    fn add_adds_item_to_store() {
102        let mut hash: HashMapId<i32> = HashMapId::new();
103        let item = 10;
104        let id = hash.add(item);
105        assert_eq!(hash.get(id), Some(&item));
106    }
107
108    #[test]
109    fn add_can_add_multiple_items() {
110        let mut hash: HashMapId<i32> = HashMapId::new();
111        let item1 = 10;
112        let item2 = 20;
113        let id1 = hash.add(item1);
114        let id2 = hash.add(item2);
115        assert_ne!(id1, id2);
116        assert_eq!(hash.get(id1), Some(&item1));
117        assert_eq!(hash.get(id2), Some(&item2));
118    }
119
120    #[test]
121    fn add_increments_id() {
122        let mut hash: HashMapId<i32> = HashMapId::new();
123        let id1 = hash.add(10);
124        let id2 = hash.add(20);
125        assert_eq!(id2, id1 + 1);
126    }
127
128    #[test]
129    #[should_panic]
130    fn add_panics_on_integer_overflow() {
131        let mut hash: HashMapId<i32> = HashMapId::new();
132        let item = 10;
133        hash.id_seed = std::u64::MAX;
134        hash.add(item);
135    }
136
137    // remove
138
139    #[test]
140    fn remove_handles_non_existent_id() {
141        let mut hash: HashMapId<i32> = HashMapId::new();
142        let result = hash.remove(1);
143        assert!(result.is_none());
144    }
145
146    #[test]
147    fn remove_returns_removed_value() {
148        let mut hash: HashMapId<i32> = HashMapId::new();
149        let value = 10;
150        let id = hash.add(value);
151        let removed_value = hash.remove(id);
152        assert_eq!(removed_value, Some(value));
153    }
154
155    #[test]
156    fn remove_removes_id() {
157        let mut hash: HashMapId<i32> = HashMapId::new();
158        let id = hash.add(10);
159        hash.remove(id);
160        assert_eq!(hash.get(id), None);
161    }
162
163    #[test]
164    fn remove_does_not_affect_other_items() {
165        let mut hash: HashMapId<i32> = HashMapId::new();
166
167        let value1 = 10;
168        let value2 = 20;
169        let value3 = 30;
170
171        let id1 = hash.add(value1);
172        let id2 = hash.add(value2);
173        let id3 = hash.add(value3);
174
175        hash.remove(id2);
176
177        assert_eq!(hash.get(id1), Some(&value1));
178        assert!(hash.get(id2).is_none());
179        assert_eq!(hash.get(id3), Some(&value3));
180    }
181
182    // impl
183
184    #[test]
185    fn default_creates_new_hashmapid() {
186        let hash: HashMapId<i32> = HashMapId::default();
187        assert_eq!(hash.id_seed, 0);
188        assert!(hash.store.is_empty());
189    }
190
191    // fmt
192
193    #[test]
194    fn fmt_formats_the_hashmapid() {
195        let mut hash: HashMapId<i32> = HashMapId::default();
196        hash.add(10);
197
198        let expected = format!("HashMapId {{ id_seed: {}, type: \"i32\" }}", hash.id_seed);
199        let result = format!("{:?}", hash);
200
201        assert_eq!(result, expected);
202    }
203}