1use crate::HasManyModel;
2use core::ops::Index;
3use std::hash::{Hash, Hasher};
4use std::slice;
5
6use super::super::EntityTrait;
7
8#[derive(Debug, Default, Clone)]
9pub enum HasMany<E: EntityTrait> {
10 #[default]
11 Unloaded,
12 Loaded(Vec<E::ModelEx>),
13}
14
15impl<E> PartialEq for HasMany<E>
16where
17 E: EntityTrait,
18 E::ModelEx: PartialEq,
19{
20 fn eq(&self, other: &Self) -> bool {
21 match (self, other) {
22 (HasMany::Unloaded, HasMany::Unloaded) => true,
23 (HasMany::Loaded(a), HasMany::Loaded(b)) => a == b,
24 _ => false,
25 }
26 }
27}
28
29impl<E> Eq for HasMany<E>
30where
31 E: EntityTrait,
32 E::ModelEx: Eq,
33{
34}
35
36impl<E: EntityTrait> HasMany<E> {
37 pub fn is_loaded(&self) -> bool {
39 matches!(self, HasMany::Loaded(_))
40 }
41
42 pub fn is_unloaded(&self) -> bool {
44 matches!(self, HasMany::Unloaded)
45 }
46
47 pub fn is_empty(&self) -> bool {
49 match self {
50 HasMany::Unloaded => true,
51 HasMany::Loaded(models) => models.is_empty(),
52 }
53 }
54
55 pub fn get(&self, index: usize) -> Option<&E::ModelEx> {
57 match self {
58 HasMany::Loaded(models) => models.get(index),
59 HasMany::Unloaded => None,
60 }
61 }
62
63 pub fn len(&self) -> usize {
65 match self {
66 HasMany::Loaded(models) => models.len(),
67 HasMany::Unloaded => 0,
68 }
69 }
70}
71
72impl<E> HasMany<E>
73where
74 E: EntityTrait,
75 E::ActiveModelEx: From<E::ModelEx>,
76{
77 pub fn into_active_model(self) -> HasManyModel<E> {
78 match self {
79 HasMany::Loaded(models) => {
80 HasManyModel::Append(models.into_iter().map(Into::into).collect())
81 }
82 HasMany::Unloaded => HasManyModel::NotSet,
83 }
84 }
85}
86
87impl<E: EntityTrait> From<HasMany<E>> for Option<Vec<E::ModelEx>> {
88 fn from(value: HasMany<E>) -> Self {
89 match value {
90 HasMany::Loaded(models) => Some(models),
91 HasMany::Unloaded => None,
92 }
93 }
94}
95
96impl<E: EntityTrait> Index<usize> for HasMany<E> {
97 type Output = E::ModelEx;
98
99 fn index(&self, index: usize) -> &Self::Output {
100 match self {
101 HasMany::Unloaded => {
102 panic!("index out of bounds: the HasMany is Unloaded (index: {index})")
103 }
104 HasMany::Loaded(items) => items.index(index),
105 }
106 }
107}
108
109impl<E: EntityTrait> IntoIterator for HasMany<E> {
110 type Item = E::ModelEx;
111 type IntoIter = std::vec::IntoIter<E::ModelEx>;
112
113 fn into_iter(self) -> Self::IntoIter {
114 match self {
115 HasMany::Loaded(models) => models.into_iter(),
116 HasMany::Unloaded => Vec::new().into_iter(),
117 }
118 }
119}
120
121impl<E: EntityTrait> From<Vec<E::ModelEx>> for HasMany<E> {
122 fn from(value: Vec<E::ModelEx>) -> Self {
123 HasMany::Loaded(value)
124 }
125}
126
127impl<E, const N: usize> PartialEq<[<E as EntityTrait>::Model; N]> for HasMany<E>
128where
129 E: EntityTrait,
130 E::ModelEx: PartialEq<<E as EntityTrait>::Model>,
131 <E as EntityTrait>::Model: PartialEq<E::ModelEx>,
132{
133 fn eq(&self, other: &[<E as EntityTrait>::Model; N]) -> bool {
134 match self {
135 HasMany::Loaded(models) => models.as_slice() == other.as_slice(),
136 HasMany::Unloaded => false,
137 }
138 }
139}
140
141impl<E, const N: usize> PartialEq<HasMany<E>> for [<E as EntityTrait>::Model; N]
142where
143 E: EntityTrait,
144 E::ModelEx: PartialEq<<E as EntityTrait>::Model>,
145 <E as EntityTrait>::Model: PartialEq<E::ModelEx>,
146{
147 fn eq(&self, other: &HasMany<E>) -> bool {
148 other == self
149 }
150}
151
152impl<E> PartialEq<[<E as EntityTrait>::Model]> for HasMany<E>
153where
154 E: EntityTrait,
155 E::ModelEx: PartialEq<<E as EntityTrait>::Model>,
156 <E as EntityTrait>::Model: PartialEq<E::ModelEx>,
157{
158 fn eq(&self, other: &[<E as EntityTrait>::Model]) -> bool {
159 match self {
160 HasMany::Loaded(models) => models.as_slice() == other,
161 HasMany::Unloaded => false,
162 }
163 }
164}
165
166impl<E> PartialEq<HasMany<E>> for [<E as EntityTrait>::Model]
167where
168 E: EntityTrait,
169 E::ModelEx: PartialEq<<E as EntityTrait>::Model>,
170 <E as EntityTrait>::Model: PartialEq<E::ModelEx>,
171{
172 fn eq(&self, other: &HasMany<E>) -> bool {
173 other == self
174 }
175}
176
177#[derive(Debug, Clone)]
178pub struct Iter<'a, E: EntityTrait> {
179 inner: Option<slice::Iter<'a, E::ModelEx>>,
180}
181
182impl<E: EntityTrait> HasMany<E> {
183 pub fn iter(&self) -> Iter<'_, E> {
184 Iter {
185 inner: match self {
186 HasMany::Loaded(models) => Some(models.iter()),
187 HasMany::Unloaded => None,
188 },
189 }
190 }
191}
192
193impl<'a, E: EntityTrait> Iterator for Iter<'a, E> {
194 type Item = &'a E::ModelEx;
195
196 fn next(&mut self) -> Option<Self::Item> {
197 self.inner.as_mut().and_then(|iter| iter.next())
198 }
199
200 fn size_hint(&self) -> (usize, Option<usize>) {
201 self.inner
202 .as_ref()
203 .map(|iter| iter.size_hint())
204 .unwrap_or((0, Some(0)))
205 }
206
207 fn count(self) -> usize
208 where
209 Self: Sized,
210 {
211 self.inner.map(|iter| iter.count()).unwrap_or(0)
212 }
213
214 fn last(self) -> Option<Self::Item>
215 where
216 Self: Sized,
217 {
218 self.inner.and_then(|iter| iter.last())
219 }
220}
221
222impl<'a, E: EntityTrait> ExactSizeIterator for Iter<'a, E> {
223 fn len(&self) -> usize {
224 self.inner.as_ref().map(|iter| iter.len()).unwrap_or(0)
225 }
226}
227
228impl<'a, E: EntityTrait> DoubleEndedIterator for Iter<'a, E> {
229 fn next_back(&mut self) -> Option<Self::Item> {
230 self.inner.as_mut().and_then(|iter| iter.next_back())
231 }
232}
233
234impl<'a, E: EntityTrait> IntoIterator for &'a HasMany<E> {
235 type Item = &'a E::ModelEx;
236 type IntoIter = Iter<'a, E>;
237
238 fn into_iter(self) -> Self::IntoIter {
239 Iter {
240 inner: match self {
241 HasMany::Loaded(models) => Some(models.iter()),
242 HasMany::Unloaded => None,
243 },
244 }
245 }
246}
247
248impl<E> Hash for HasMany<E>
249where
250 E: EntityTrait,
251 E::ModelEx: Hash,
252{
253 fn hash<H: Hasher>(&self, state: &mut H) {
254 std::mem::discriminant(self).hash(state);
255 match self {
256 HasMany::Loaded(model) => model.hash(state),
257 HasMany::Unloaded => {}
258 }
259 }
260}
261
262#[cfg(feature = "with-json")]
263impl<E> serde::Serialize for HasMany<E>
264where
265 E: EntityTrait,
266 E::ModelEx: serde::Serialize,
267{
268 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
269 where
270 S: serde::Serializer,
271 {
272 match self {
273 HasMany::Unloaded => None,
274 HasMany::Loaded(models) => Some(models.as_slice()),
275 }
276 .serialize(serializer)
277 }
278}
279
280#[cfg(feature = "with-json")]
281impl<'de, E> serde::Deserialize<'de> for HasMany<E>
282where
283 E: EntityTrait,
284 E::ModelEx: serde::Deserialize<'de>,
285{
286 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
287 where
288 D: serde::Deserializer<'de>,
289 {
290 match <Option<Vec<E::ModelEx>>>::deserialize(deserializer)? {
291 Some(models) => Ok(HasMany::Loaded(models)),
292 None => Ok(HasMany::Unloaded),
293 }
294 }
295}
296
297#[cfg(test)]
298mod test {
299 use crate::compound::{HasMany, HasOne};
300 use crate::tests_cfg::{cake, filling, fruit};
301
302 #[test]
303 fn test_serde_compound() {
304 let cake = cake::ModelEx {
305 id: 1,
306 name: "A".into(),
307 fruit: Default::default(),
308 fillings: Default::default(),
309 };
310
311 assert_eq!(
312 filling::Model {
313 id: 2,
314 name: "C".into(),
315 vendor_id: None,
316 ignored_attr: 3,
317 }
318 .into_ex(),
319 filling::ModelEx {
320 id: 2,
321 name: "C".into(),
322 vendor_id: None,
323 ignored_attr: 3,
324 ingredients: HasMany::Unloaded,
325 }
326 );
327
328 assert_eq!(
329 serde_json::to_string(&cake).unwrap(),
330 r#"{"id":1,"name":"A","fruit":null,"fillings":null}"#
331 );
332 assert_eq!(
333 serde_json::from_str::<cake::ModelEx>(&serde_json::to_string(&cake).unwrap()).unwrap(),
334 cake
335 );
336
337 let cake = cake::ModelEx {
338 id: 1,
339 name: "A".into(),
340 fruit: Default::default(),
341 fillings: HasMany::Loaded(vec![]),
342 };
343
344 assert_eq!(
345 serde_json::to_string(&cake).unwrap(),
346 r#"{"id":1,"name":"A","fruit":null,"fillings":[]}"#
347 );
348 assert_eq!(
349 serde_json::from_str::<cake::ModelEx>(&serde_json::to_string(&cake).unwrap()).unwrap(),
350 cake
351 );
352
353 let mut cake = cake::ModelEx {
354 id: 1,
355 name: "A".into(),
356 fruit: HasOne::Loaded(
357 fruit::ModelEx {
358 id: 2,
359 name: "B".into(),
360 cake_id: None,
361 }
362 .into(),
363 ),
364 fillings: HasMany::Unloaded,
365 };
366
367 assert_eq!(
368 serde_json::to_string(&cake).unwrap(),
369 r#"{"id":1,"name":"A","fruit":{"id":2,"name":"B","cake_id":null},"fillings":null}"#
370 );
371 cake.fruit.as_mut().unwrap().id = 0;
373 assert_eq!(
374 serde_json::from_str::<cake::ModelEx>(&serde_json::to_string(&cake).unwrap()).unwrap(),
375 cake
376 );
377
378 let cake = cake::ModelEx {
379 id: 1,
380 name: "A".into(),
381 fruit: HasOne::Loaded(
382 fruit::ModelEx {
383 id: 0,
384 name: "B".into(),
385 cake_id: None,
386 }
387 .into(),
388 ),
389 fillings: HasMany::Loaded(vec![
390 filling::Model {
391 id: 2,
392 name: "C".into(),
393 vendor_id: None,
394 ignored_attr: 3,
395 }
396 .into_ex(),
397 ]),
398 };
399
400 assert_eq!(
401 serde_json::to_string(&cake).unwrap(),
402 r#"{"id":1,"name":"A","fruit":{"id":0,"name":"B","cake_id":null},"fillings":[{"id":2,"name":"C","vendor_id":null,"ignored_attr":3,"ingredients":null}]}"#
403 );
404 assert_eq!(
405 serde_json::from_str::<cake::ModelEx>(&serde_json::to_string(&cake).unwrap()).unwrap(),
406 cake
407 );
408 }
409}