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 chunks = Vec::with_capacity(self.len().div_ceil(size));
90 let mut vec = self;
91
92 while !vec.is_empty() {
93 let rem = vec.len() % size;
94 let take = if rem == 0 { size } else { rem };
95 let start = vec.len() - take;
96 let chunk = vec.split_off(start);
97 chunks.push(chunk);
98 }
99 chunks.reverse();
100
101 chunks
102 }
103
104 fn implode<F>(&self, separator: &str, f: F) -> String
105 where
106 F: Fn(&T) -> String,
107 {
108 let mut result = String::with_capacity(self.len() * 16);
110 let mut iter = self.iter();
111 if let Some(first) = iter.next() {
112 result.push_str(&f(first));
113 for item in iter {
114 result.push_str(separator);
115 result.push_str(&f(item));
116 }
117 }
118 result
119 }
120
121 fn sum_by<N, F>(&self, f: F) -> N
122 where
123 F: Fn(&T) -> N,
124 N: std::iter::Sum,
125 {
126 self.iter().map(f).sum()
127 }
128
129 fn max_by_key<K, F>(&self, f: F) -> Option<&T>
130 where
131 F: Fn(&T) -> K,
132 K: Ord,
133 {
134 self.iter().max_by_key(|item| f(*item))
135 }
136
137 fn min_by_key<K, F>(&self, f: F) -> Option<&T>
138 where
139 F: Fn(&T) -> K,
140 K: Ord,
141 {
142 self.iter().min_by_key(|item| f(*item))
143 }
144
145 #[cfg_attr(test, mutants::skip)]
146 fn collection_resource(&self) -> serde_json::Value
147 where
148 T: crate::resource::ApiResource,
149 {
150 crate::resource::ResourceCollection::new(self).resolve()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_key_by() {
160 let v = vec![(1u32, "a"), (2, "b"), (3, "c")];
161 let map = v.key_by(|(k, _)| *k);
162 assert_eq!(map[&1].1, "a");
163 assert_eq!(map[&3].1, "c");
164 }
165
166 #[test]
167 fn test_map() {
168 let v = vec![1, 2, 3];
169 let mapped = v.map(|x| x * 2);
170 assert_eq!(mapped, vec![2, 4, 6]);
171 }
172
173 #[test]
174 fn test_filter() {
175 let v = vec![1, 2, 3, 4];
176 let filtered = v.filter(|x| x % 2 == 0);
177 assert_eq!(filtered, vec![2, 4]);
178 }
179
180 #[test]
181 fn test_chunk_even() {
182 let v = vec![1, 2, 3, 4];
183 let chunks = v.chunk(2);
184 assert_eq!(chunks.len(), 2);
185 assert_eq!(chunks[0], vec![1, 2]);
186 assert_eq!(chunks[1], vec![3, 4]);
187 }
188
189 #[test]
190 fn test_chunk_with_remainder() {
191 let v = vec![1, 2, 3, 4, 5];
192 let chunks = v.chunk(2);
193 assert_eq!(chunks.len(), 3);
194 assert_eq!(chunks[2], vec![5]);
195 }
196
197 #[test]
198 fn test_chunk_zero_returns_all() {
199 let v = vec![1, 2, 3];
200 let chunks = v.chunk(0);
201 assert_eq!(chunks.len(), 1);
202 assert_eq!(chunks[0], vec![1, 2, 3]);
203 }
204
205 #[test]
206 fn test_implode() {
207 let v = vec![1, 2, 3];
208 let result = v.implode(", ", |n| n.to_string());
209 assert_eq!(result, "1, 2, 3");
210 }
211
212 #[test]
213 fn test_implode_single_element() {
214 let v = vec![42];
215 let result = v.implode(", ", |n| n.to_string());
216 assert_eq!(result, "42");
217 }
218
219 #[test]
220 fn test_sum_by() {
221 let v = vec![1, 2, 3, 4];
222 let sum: i32 = v.sum_by(|n| *n);
223 assert_eq!(sum, 10);
224 }
225
226 #[test]
227 fn test_max_by_key() {
228 let v = vec![3, 1, 4, 1, 5, 9];
229 let max = v.max_by_key(|n| *n);
230 assert_eq!(max, Some(&9));
231 }
232
233 #[test]
234 fn test_min_by_key() {
235 let v = vec![3, 1, 4, 1, 5, 9];
236 let min = v.min_by_key(|n| *n);
237 assert_eq!(min, Some(&1));
238 }
239
240 #[test]
241 fn test_empty_collection() {
242 let v: Vec<i32> = vec![];
243 assert!(v.max_by_key(|n| *n).is_none());
244 assert!(v.min_by_key(|n| *n).is_none());
245 let sum: i32 = v.sum_by(|n| *n);
246 assert_eq!(sum, 0);
247 }
248
249 #[test]
250 fn test_chunk_larger_than_len() {
251 let v = vec![1, 2];
252 let chunks = v.chunk(5);
253 assert_eq!(chunks.len(), 1);
254 assert_eq!(chunks[0], vec![1, 2]);
255 }
256
257 #[test]
258 fn test_chunk_empty() {
259 let v: Vec<i32> = vec![];
260 let chunks = v.chunk(2);
261 assert!(chunks.is_empty());
262 }
263
264 #[test]
265 fn test_chunk_usize_max() {
266 let v = vec![1, 2, 3];
267 let chunks = v.chunk(usize::MAX);
268 assert_eq!(chunks.len(), 1);
269 assert_eq!(chunks[0], vec![1, 2, 3]);
270 }
271}