sea_orm/entity/compound/
has_one.rs

1use crate::{EntityTrait, HasOneModel};
2use std::hash::{Hash, Hasher};
3
4#[derive(Debug, Default, Clone)]
5pub enum HasOne<E: EntityTrait> {
6    #[default]
7    Unloaded,
8    NotFound,
9    Loaded(Box<E::ModelEx>),
10}
11
12impl<E: EntityTrait> HasOne<E> {
13    /// Construct a `HasOne::Loaded` value
14    pub fn loaded<M: Into<E::ModelEx>>(model: M) -> Self {
15        Self::Loaded(Box::new(model.into()))
16    }
17
18    /// Return true if variant is `Unloaded`
19    pub fn is_unloaded(&self) -> bool {
20        matches!(self, HasOne::Unloaded)
21    }
22
23    /// Return true if variant is `NotFound`
24    pub fn is_not_found(&self) -> bool {
25        matches!(self, HasOne::NotFound)
26    }
27
28    /// Return true if variant is `Loaded`
29    pub fn is_loaded(&self) -> bool {
30        matches!(self, HasOne::Loaded(_))
31    }
32
33    /// True if variant is `Unloaded` or `NotFound`
34    pub fn is_none(&self) -> bool {
35        matches!(self, HasOne::Unloaded | HasOne::NotFound)
36    }
37
38    /// Get a reference, if loaded
39    pub fn as_ref(&self) -> Option<&E::ModelEx> {
40        match self {
41            HasOne::Loaded(model) => Some(model.as_ref()),
42            HasOne::Unloaded | HasOne::NotFound => None,
43        }
44    }
45
46    /// Get a mutable reference, if loaded
47    pub fn as_mut(&mut self) -> Option<&mut E::ModelEx> {
48        match self {
49            HasOne::Loaded(model) => Some(model),
50            HasOne::Unloaded | HasOne::NotFound => None,
51        }
52    }
53
54    /// Convert into an `Option<ModelEx>`
55    pub fn into_option(self) -> Option<E::ModelEx> {
56        match self {
57            HasOne::Loaded(model) => Some(*model),
58            HasOne::Unloaded | HasOne::NotFound => None,
59        }
60    }
61
62    /// Take ownership of the contained Model, leaving `Unloaded` in place.
63    pub fn take(&mut self) -> Option<E::ModelEx> {
64        std::mem::take(self).into_option()
65    }
66
67    /// # Panics
68    ///
69    /// Panics if called on `Unloaded` or `NotFound` values.
70    pub fn unwrap(self) -> E::ModelEx {
71        match self {
72            HasOne::Loaded(model) => *model,
73            HasOne::Unloaded => panic!("called `HasOne::unwrap()` on an `Unloaded` value"),
74            HasOne::NotFound => panic!("called `HasOne::unwrap()` on a `NotFound` value"),
75        }
76    }
77}
78
79impl<E> HasOne<E>
80where
81    E: EntityTrait,
82    E::ActiveModelEx: From<E::ModelEx>,
83{
84    pub fn into_active_model(self) -> HasOneModel<E> {
85        match self {
86            HasOne::Loaded(_) => {
87                let model = self.unwrap();
88                let active_model: E::ActiveModelEx = model.into();
89                HasOneModel::Set(active_model.into())
90            }
91            HasOne::Unloaded => HasOneModel::NotSet,
92            HasOne::NotFound => HasOneModel::NotSet,
93        }
94    }
95}
96
97impl<E> PartialEq for HasOne<E>
98where
99    E: EntityTrait,
100    E::ModelEx: PartialEq,
101{
102    fn eq(&self, other: &Self) -> bool {
103        match (self, other) {
104            (HasOne::Unloaded, HasOne::Unloaded) => true,
105            (HasOne::NotFound, HasOne::NotFound) => true,
106            (HasOne::Loaded(a), HasOne::Loaded(b)) => a == b,
107            _ => false,
108        }
109    }
110}
111
112impl<E> Eq for HasOne<E>
113where
114    E: EntityTrait,
115    E::ModelEx: Eq,
116{
117}
118
119// Option<Box<ModelEx<E>>> <-> HasOne<E> conversions and comparisons
120impl<E: EntityTrait> From<HasOne<E>> for Option<Box<E::ModelEx>> {
121    fn from(value: HasOne<E>) -> Self {
122        match value {
123            HasOne::Loaded(model) => Some(model),
124            HasOne::Unloaded | HasOne::NotFound => None,
125        }
126    }
127}
128
129impl<E: EntityTrait> From<Option<Box<E::ModelEx>>> for HasOne<E> {
130    fn from(value: Option<Box<E::ModelEx>>) -> Self {
131        match value {
132            Some(model) => HasOne::Loaded(model),
133            None => HasOne::NotFound,
134        }
135    }
136}
137
138impl<E> PartialEq<Option<Box<E::ModelEx>>> for HasOne<E>
139where
140    E: EntityTrait,
141    E::ModelEx: PartialEq,
142{
143    fn eq(&self, other: &Option<Box<E::ModelEx>>) -> bool {
144        match (self, other) {
145            (HasOne::Loaded(a), Some(b)) => a.as_ref() == b.as_ref(),
146            (HasOne::Unloaded | HasOne::NotFound, None) => true,
147            _ => false,
148        }
149    }
150}
151
152impl<E> PartialEq<HasOne<E>> for Option<Box<E::ModelEx>>
153where
154    E: EntityTrait,
155    E::ModelEx: PartialEq,
156{
157    fn eq(&self, other: &HasOne<E>) -> bool {
158        other == self
159    }
160}
161
162impl<E> Hash for HasOne<E>
163where
164    E: EntityTrait,
165    E::ModelEx: Hash,
166{
167    fn hash<H: Hasher>(&self, state: &mut H) {
168        std::mem::discriminant(self).hash(state);
169        match self {
170            Self::Loaded(model) => model.hash(state),
171            Self::Unloaded => {}
172            Self::NotFound => {}
173        }
174    }
175}
176
177#[cfg(feature = "with-json")]
178impl<E> serde::Serialize for HasOne<E>
179where
180    E: EntityTrait,
181    E::ModelEx: serde::Serialize,
182{
183    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
184    where
185        S: serde::Serializer,
186    {
187        match self {
188            HasOne::Unloaded => None,
189            HasOne::NotFound => None,
190            HasOne::Loaded(model) => Some(model),
191        }
192        .serialize(serializer)
193    }
194}
195
196#[cfg(feature = "with-json")]
197impl<'de, E> serde::Deserialize<'de> for HasOne<E>
198where
199    E: EntityTrait,
200    E::ModelEx: serde::Deserialize<'de>,
201{
202    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
203    where
204        D: serde::Deserializer<'de>,
205    {
206        match <Option<E::ModelEx>>::deserialize(deserializer)? {
207            Some(model) => Ok(HasOne::Loaded(Box::new(model))),
208            None => Ok(HasOne::Unloaded),
209        }
210    }
211}