Skip to main content

rullst_orm/
collection.rs

1use std::collections::HashMap;
2use std::hash::Hash;
3
4/// An extension trait that brings Laravel-style Collection methods natively to Rust's Vec<T>.
5pub trait RullstCollection<T> {
6    /// Keys the collection by the given closure's return value
7    fn key_by<K, F>(self, f: F) -> HashMap<K, T>
8    where
9        F: Fn(&T) -> K,
10        K: Hash + Eq;
11
12    /// Splits the collection into chunks of the given size
13    fn chunk(self, size: usize) -> Vec<Vec<T>>;
14
15    /// Joins the items into a single string using the given separator and closure
16    fn implode<F>(&self, separator: &str, f: F) -> String
17    where
18        F: Fn(&T) -> String;
19
20    /// Sums up the values returned by the closure
21    fn sum_by<N, F>(&self, f: F) -> N
22    where
23        F: Fn(&T) -> N,
24        N: std::iter::Sum;
25
26    /// Finds the maximum value returned by the closure
27    fn max_by_key<K, F>(&self, f: F) -> Option<&T>
28    where
29        F: Fn(&T) -> K,
30        K: Ord;
31
32    /// Finds the minimum value returned by the closure
33    fn min_by_key<K, F>(&self, f: F) -> Option<&T>
34    where
35        F: Fn(&T) -> K,
36        K: Ord;
37
38    /// Serializes the entire collection using an ApiResource transformer
39    fn collection_resource(&self) -> serde_json::Value
40    where
41        T: crate::resource::ApiResource;
42}
43
44impl<T> RullstCollection<T> for Vec<T> {
45    fn key_by<K, F>(self, f: F) -> HashMap<K, T>
46    where
47        F: Fn(&T) -> K,
48        K: Hash + Eq,
49    {
50        let mut map = HashMap::with_capacity(self.len());
51        for item in self {
52            map.insert(f(&item), item);
53        }
54        map
55    }
56
57    fn chunk(self, size: usize) -> Vec<Vec<T>> {
58        if size == 0 {
59            return vec![self];
60        }
61
62        let mut chunks = Vec::with_capacity(self.len().div_ceil(size));
63        let mut current_chunk = Vec::with_capacity(size);
64
65        for item in self {
66            current_chunk.push(item);
67            if current_chunk.len() == size {
68                chunks.push(current_chunk);
69                current_chunk = Vec::with_capacity(size);
70            }
71        }
72
73        if !current_chunk.is_empty() {
74            chunks.push(current_chunk);
75        }
76
77        chunks
78    }
79
80    fn implode<F>(&self, separator: &str, f: F) -> String
81    where
82        F: Fn(&T) -> String,
83    {
84        let mut result = String::new();
85        let mut iter = self.iter();
86        if let Some(first) = iter.next() {
87            result.push_str(&f(first));
88            for item in iter {
89                result.push_str(separator);
90                result.push_str(&f(item));
91            }
92        }
93        result
94    }
95
96    fn sum_by<N, F>(&self, f: F) -> N
97    where
98        F: Fn(&T) -> N,
99        N: std::iter::Sum,
100    {
101        self.iter().map(f).sum()
102    }
103
104    fn max_by_key<K, F>(&self, f: F) -> Option<&T>
105    where
106        F: Fn(&T) -> K,
107        K: Ord,
108    {
109        self.iter().max_by_key(|item| f(*item))
110    }
111
112    fn min_by_key<K, F>(&self, f: F) -> Option<&T>
113    where
114        F: Fn(&T) -> K,
115        K: Ord,
116    {
117        self.iter().min_by_key(|item| f(*item))
118    }
119
120    fn collection_resource(&self) -> serde_json::Value
121    where
122        T: crate::resource::ApiResource,
123    {
124        crate::resource::ResourceCollection::new(self).resolve()
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_key_by() {
134        let v = vec![(1u32, "a"), (2, "b"), (3, "c")];
135        let map = v.key_by(|(k, _)| *k);
136        assert_eq!(map[&1].1, "a");
137        assert_eq!(map[&3].1, "c");
138    }
139
140    #[test]
141    fn test_chunk_even() {
142        let v = vec![1, 2, 3, 4];
143        let chunks = v.chunk(2);
144        assert_eq!(chunks.len(), 2);
145        assert_eq!(chunks[0], vec![1, 2]);
146        assert_eq!(chunks[1], vec![3, 4]);
147    }
148
149    #[test]
150    fn test_chunk_with_remainder() {
151        let v = vec![1, 2, 3, 4, 5];
152        let chunks = v.chunk(2);
153        assert_eq!(chunks.len(), 3);
154        assert_eq!(chunks[2], vec![5]);
155    }
156
157    #[test]
158    fn test_chunk_zero_returns_all() {
159        let v = vec![1, 2, 3];
160        let chunks = v.chunk(0);
161        assert_eq!(chunks.len(), 1);
162        assert_eq!(chunks[0], vec![1, 2, 3]);
163    }
164
165    #[test]
166    fn test_implode() {
167        let v = vec![1, 2, 3];
168        let result = v.implode(", ", |n| n.to_string());
169        assert_eq!(result, "1, 2, 3");
170    }
171
172    #[test]
173    fn test_implode_single_element() {
174        let v = vec![42];
175        let result = v.implode(", ", |n| n.to_string());
176        assert_eq!(result, "42");
177    }
178
179    #[test]
180    fn test_sum_by() {
181        let v = vec![1, 2, 3, 4];
182        let sum: i32 = v.sum_by(|n| *n);
183        assert_eq!(sum, 10);
184    }
185
186    #[test]
187    fn test_max_by_key() {
188        let v = vec![3, 1, 4, 1, 5, 9];
189        let max = v.max_by_key(|n| *n);
190        assert_eq!(max, Some(&9));
191    }
192
193    #[test]
194    fn test_min_by_key() {
195        let v = vec![3, 1, 4, 1, 5, 9];
196        let min = v.min_by_key(|n| *n);
197        assert_eq!(min, Some(&1));
198    }
199
200    #[test]
201    fn test_empty_collection() {
202        let v: Vec<i32> = vec![];
203        assert!(v.max_by_key(|n| *n).is_none());
204        assert!(v.min_by_key(|n| *n).is_none());
205        let sum: i32 = v.sum_by(|n| *n);
206        assert_eq!(sum, 0);
207    }
208}