sea_orm/entity/compound/
has_many.rs

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    /// Return true if variant is `Loaded`
38    pub fn is_loaded(&self) -> bool {
39        matches!(self, HasMany::Loaded(_))
40    }
41
42    /// Return true if variant is `Unloaded`
43    pub fn is_unloaded(&self) -> bool {
44        matches!(self, HasMany::Unloaded)
45    }
46
47    /// Return true if variant is `Unloaded` or underlying vector is empty
48    pub fn is_empty(&self) -> bool {
49        match self {
50            HasMany::Unloaded => true,
51            HasMany::Loaded(models) => models.is_empty(),
52        }
53    }
54
55    /// Like `Vec::get`
56    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    /// Length of underlying vector
64    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        // fruit has skip_deserializing on id
372        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}