1use std::collections::HashMap;
2use std::hash::Hash;
3
4pub trait RullstCollection<T> {
6 fn key_by<K, F>(self, f: F) -> HashMap<K, T>
8 where
9 F: Fn(&T) -> K,
10 K: Hash + Eq;
11
12 fn map<U, F>(self, f: F) -> Vec<U>
15 where
16 F: FnMut(T) -> U;
17
18 fn filter<F>(self, f: F) -> Vec<T>
20 where
21 F: FnMut(&T) -> bool;
22
23 fn chunk(self, size: usize) -> Vec<Vec<T>>;
24
25 fn implode<F>(&self, separator: &str, f: F) -> String
27 where
28 F: Fn(&T) -> String;
29
30 fn sum_by<N, F>(&self, f: F) -> N
32 where
33 F: Fn(&T) -> N,
34 N: std::iter::Sum;
35
36 fn max_by_key<K, F>(&self, f: F) -> Option<&T>
38 where
39 F: Fn(&T) -> K,
40 K: Ord;
41
42 fn min_by_key<K, F>(&self, f: F) -> Option<&T>
44 where
45 F: Fn(&T) -> K,
46 K: Ord;
47
48 fn collection_resource(&self) -> serde_json::Value
50 where
51 T: crate::resource::ApiResource;
52}
53
54impl<T> RullstCollection<T> for Vec<T> {
55 fn key_by<K, F>(self, f: F) -> HashMap<K, T>
56 where
57 F: Fn(&T) -> K,
58 K: Hash + Eq,
59 {
60 let mut map = HashMap::with_capacity(self.len());
61 for item in self {
62 map.insert(f(&item), item);
63 }
64 map
65 }
66
67 fn map<U, F>(self, f: F) -> Vec<U>
68 where
69 F: FnMut(T) -> U,
70 {
71 self.into_iter().map(f).collect()
72 }
73
74 fn filter<F>(self, f: F) -> Vec<T>
75 where
76 F: FnMut(&T) -> bool,
77 {
78 self.into_iter().filter(f).collect()
79 }
80
81 fn chunk(self, size: usize) -> Vec<Vec<T>> {
82 if self.is_empty() {
83 return vec![];
84 }
85 if size == 0 {
86 return vec![self];
87 }
88
89 let mut remaining = self.len();
90 let mut chunks = Vec::with_capacity(remaining.div_ceil(size));
91 let mut iter = self.into_iter();
92
93 while let Some(first) = iter.next() {
94 let chunk_cap = std::cmp::min(size, remaining);
95 let mut chunk = Vec::with_capacity(chunk_cap);
96 chunk.push(first);
97 remaining -= 1;
98 for _ in 1..size {
99 if let Some(item) = iter.next() {
100 chunk.push(item);
101 remaining -= 1;
102 } else {
103 break;
104 }
105 }
106 chunks.push(chunk);
107 }
108
109 chunks
110 }
111
112 fn implode<F>(&self, separator: &str, f: F) -> String
113 where
114 F: Fn(&T) -> String,
115 {
116 let mut result = String::with_capacity(self.len() * 16);
118 let mut iter = self.iter();
119 if let Some(first) = iter.next() {
120 result.push_str(&f(first));
121 for item in iter {
122 result.push_str(separator);
123 result.push_str(&f(item));
124 }
125 }
126 result
127 }
128
129 fn sum_by<N, F>(&self, f: F) -> N
130 where
131 F: Fn(&T) -> N,
132 N: std::iter::Sum,
133 {
134 self.iter().map(f).sum()
135 }
136
137 fn max_by_key<K, F>(&self, f: F) -> Option<&T>
138 where
139 F: Fn(&T) -> K,
140 K: Ord,
141 {
142 self.iter().max_by_key(|item| f(*item))
143 }
144
145 fn min_by_key<K, F>(&self, f: F) -> Option<&T>
146 where
147 F: Fn(&T) -> K,
148 K: Ord,
149 {
150 self.iter().min_by_key(|item| f(*item))
151 }
152
153 #[cfg_attr(test, mutants::skip)]
154 fn collection_resource(&self) -> serde_json::Value
155 where
156 T: crate::resource::ApiResource,
157 {
158 crate::resource::ResourceCollection::new(self).resolve()
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_key_by() {
168 let v = vec![(1u32, "a"), (2, "b"), (3, "c")];
169 let map = v.key_by(|(k, _)| *k);
170 assert_eq!(map[&1].1, "a");
171 assert_eq!(map[&3].1, "c");
172 }
173
174 #[test]
175 fn test_map() {
176 let v = vec![1, 2, 3];
177 let mapped = v.map(|x| x * 2);
178 assert_eq!(mapped, vec![2, 4, 6]);
179 }
180
181 #[test]
182 fn test_filter() {
183 let v = vec![1, 2, 3, 4];
184 let filtered = v.filter(|x| x % 2 == 0);
185 assert_eq!(filtered, vec![2, 4]);
186 }
187
188 #[test]
189 fn test_chunk_even() {
190 let v = vec![1, 2, 3, 4];
191 let chunks = v.chunk(2);
192 assert_eq!(chunks.len(), 2);
193 assert_eq!(chunks[0], vec![1, 2]);
194 assert_eq!(chunks[1], vec![3, 4]);
195 }
196
197 #[test]
198 fn test_chunk_with_remainder() {
199 let v = vec![1, 2, 3, 4, 5];
200 let chunks = v.chunk(2);
201 assert_eq!(chunks.len(), 3);
202 assert_eq!(chunks[2], vec![5]);
203 assert_eq!(chunks[0].capacity(), 2);
204 assert_eq!(chunks[1].capacity(), 2);
205 assert_eq!(chunks[2].capacity(), 1);
206 }
207
208 #[test]
209 fn test_chunk_zero_returns_all() {
210 let v = vec![1, 2, 3];
211 let chunks = v.chunk(0);
212 assert_eq!(chunks.len(), 1);
213 assert_eq!(chunks[0], vec![1, 2, 3]);
214 }
215
216 #[test]
217 fn test_implode() {
218 let v = vec![1, 2, 3];
219 let result = v.implode(", ", |n| n.to_string());
220 assert_eq!(result, "1, 2, 3");
221 }
222
223 #[test]
224 fn test_implode_single_element() {
225 let v = vec![42];
226 let result = v.implode(", ", |n| n.to_string());
227 assert_eq!(result, "42");
228 }
229
230 #[test]
231 fn test_sum_by() {
232 let v = vec![1, 2, 3, 4];
233 let sum: i32 = v.sum_by(|n| *n);
234 assert_eq!(sum, 10);
235 }
236
237 #[test]
238 fn test_max_by_key() {
239 let v = vec![3, 1, 4, 1, 5, 9];
240 let max = v.max_by_key(|n| *n);
241 assert_eq!(max, Some(&9));
242 }
243
244 #[test]
245 fn test_min_by_key() {
246 let v = vec![3, 1, 4, 1, 5, 9];
247 let min = v.min_by_key(|n| *n);
248 assert_eq!(min, Some(&1));
249 }
250
251 #[test]
252 fn test_empty_collection() {
253 let v: Vec<i32> = vec![];
254 assert!(v.max_by_key(|n| *n).is_none());
255 assert!(v.min_by_key(|n| *n).is_none());
256 let sum: i32 = v.sum_by(|n| *n);
257 assert_eq!(sum, 0);
258 }
259
260 #[test]
261 fn test_chunk_larger_than_len() {
262 let v = vec![1, 2];
263 let chunks = v.chunk(5);
264 assert_eq!(chunks.len(), 1);
265 assert_eq!(chunks[0], vec![1, 2]);
266 }
267
268 #[test]
269 fn test_chunk_empty() {
270 let v: Vec<i32> = vec![];
271 let chunks = v.chunk(2);
272 assert!(chunks.is_empty());
273 }
274
275 #[test]
276 fn test_chunk_usize_max() {
277 let v = vec![1, 2, 3];
278 let chunks = v.chunk(usize::MAX);
279 assert_eq!(chunks.len(), 1);
280 assert_eq!(chunks[0], vec![1, 2, 3]);
281 }
282
283 struct DummyResource {
284 id: i32,
285 }
286
287 impl crate::resource::ApiResource for DummyResource {
288 fn to_array(&self) -> serde_json::Value {
289 crate::_serde_json::json!({ "id": self.id })
290 }
291 }
292
293 #[test]
294 fn test_collection_resource() {
295 let vec = vec![DummyResource { id: 1 }, DummyResource { id: 2 }];
296 let res = vec.collection_resource();
297 assert_eq!(res, crate::_serde_json::json!([{"id": 1}, {"id": 2}]));
298 }
299}