1use std::marker::PhantomData;
2
3use serde::de;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6use super::{Links, Meta, ResourceIdentifier};
7
8#[non_exhaustive]
10#[derive(Debug, Clone, PartialEq)]
11pub enum RelationshipData {
12 ToOne(Option<ResourceIdentifier>),
14 ToMany(Vec<ResourceIdentifier>),
16}
17
18impl Serialize for RelationshipData {
19 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
20 match self {
21 RelationshipData::ToOne(None) => serializer.serialize_none(),
22 RelationshipData::ToOne(Some(rid)) => rid.serialize(serializer),
23 RelationshipData::ToMany(rids) => rids.serialize(serializer),
24 }
25 }
26}
27
28impl<'de> Deserialize<'de> for RelationshipData {
29 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
30 let value = serde_json::Value::deserialize(deserializer)?;
31 match value {
32 serde_json::Value::Null => Ok(RelationshipData::ToOne(None)),
33 serde_json::Value::Array(arr) => {
34 let rids: Vec<ResourceIdentifier> = arr
35 .into_iter()
36 .map(serde_json::from_value)
37 .collect::<Result<_, _>>()
38 .map_err(de::Error::custom)?;
39 Ok(RelationshipData::ToMany(rids))
40 }
41 serde_json::Value::Object(_) => {
42 let rid: ResourceIdentifier =
43 serde_json::from_value(value).map_err(de::Error::custom)?;
44 Ok(RelationshipData::ToOne(Some(rid)))
45 }
46 _ => Err(de::Error::custom(
47 "relationship data must be null, object, or array",
48 )),
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56pub struct Relationship<T> {
57 pub data: RelationshipData,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub links: Option<Links>,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub meta: Option<Meta>,
65 #[serde(skip)]
66 _phantom: PhantomData<T>,
67}
68
69impl<T> Relationship<T> {
70 pub fn new(data: RelationshipData) -> Self {
72 Self {
73 data,
74 links: None,
75 meta: None,
76 _phantom: PhantomData,
77 }
78 }
79
80 #[must_use]
87 pub fn identifiers(&self) -> &[ResourceIdentifier] {
88 match &self.data {
89 RelationshipData::ToOne(None) => &[],
90 RelationshipData::ToOne(Some(rid)) => std::slice::from_ref(rid),
91 RelationshipData::ToMany(rids) => rids.as_slice(),
92 }
93 }
94
95 pub fn ids(&self) -> impl Iterator<Item = &str> + '_ {
98 self.identifiers()
99 .iter()
100 .filter_map(|rid| rid.identity.as_id())
101 }
102
103 #[must_use]
107 pub fn first_id(&self) -> Option<&str> {
108 self.ids().next()
109 }
110
111 #[must_use]
116 pub fn first_id_or_lid(&self) -> Option<&str> {
117 self.identifiers()
118 .first()
119 .and_then(|rid| rid.identity.as_id().or_else(|| rid.identity.as_lid()))
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::model::Identity;
127
128 #[test]
129 fn test_relationship_data_to_one() {
130 let json = r#"{"type":"people","id":"9"}"#;
131 let data: RelationshipData = serde_json::from_str(json).unwrap();
132 match &data {
133 RelationshipData::ToOne(Some(rid)) => {
134 assert_eq!(rid.type_, "people");
135 assert_eq!(rid.identity, Identity::Id("9".into()));
136 }
137 _ => panic!("expected ToOne(Some(...))"),
138 }
139 assert_eq!(serde_json::to_string(&data).unwrap(), json);
140 }
141
142 #[test]
143 fn test_relationship_data_to_one_null() {
144 let json = "null";
145 let data: RelationshipData = serde_json::from_str(json).unwrap();
146 assert!(matches!(data, RelationshipData::ToOne(None)));
147 assert_eq!(serde_json::to_string(&data).unwrap(), json);
148 }
149
150 #[test]
151 fn test_relationship_data_to_many() {
152 let json = r#"[{"type":"tags","id":"1"},{"type":"tags","id":"2"}]"#;
153 let data: RelationshipData = serde_json::from_str(json).unwrap();
154 match &data {
155 RelationshipData::ToMany(rids) => assert_eq!(rids.len(), 2),
156 _ => panic!("expected ToMany"),
157 }
158 assert_eq!(serde_json::to_string(&data).unwrap(), json);
159 }
160
161 #[test]
162 fn test_relationship_data_to_many_empty() {
163 let json = "[]";
164 let data: RelationshipData = serde_json::from_str(json).unwrap();
165 assert!(matches!(data, RelationshipData::ToMany(ref v) if v.is_empty()));
166 }
167
168 fn rid(type_: &str, id: &str) -> ResourceIdentifier {
171 ResourceIdentifier {
172 type_: type_.into(),
173 identity: Identity::Id(id.into()),
174 meta: None,
175 }
176 }
177
178 fn lid_rid(type_: &str, lid: &str) -> ResourceIdentifier {
179 ResourceIdentifier {
180 type_: type_.into(),
181 identity: Identity::Lid(lid.into()),
182 meta: None,
183 }
184 }
185
186 struct Target;
189
190 #[test]
191 fn relationship_ids_skips_null_to_one() {
192 let rel: Relationship<Target> = Relationship::new(RelationshipData::ToOne(None));
193 let collected: Vec<&str> = rel.ids().collect();
194 assert!(collected.is_empty());
195 }
196
197 #[test]
198 fn relationship_ids_returns_single_id_for_to_one() {
199 let rel: Relationship<Target> =
200 Relationship::new(RelationshipData::ToOne(Some(rid("people", "9"))));
201 let collected: Vec<&str> = rel.ids().collect();
202 assert_eq!(collected, vec!["9"]);
203 }
204
205 #[test]
206 fn relationship_ids_returns_all_ids_for_to_many() {
207 let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
208 rid("tags", "1"),
209 rid("tags", "2"),
210 rid("tags", "3"),
211 ]));
212 let collected: Vec<&str> = rel.ids().collect();
213 assert_eq!(collected, vec!["1", "2", "3"]);
214 }
215
216 #[test]
217 fn relationship_ids_skips_lid_entries() {
218 let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
219 rid("tags", "1"),
220 lid_rid("tags", "local-a"),
221 rid("tags", "3"),
222 ]));
223 let collected: Vec<&str> = rel.ids().collect();
224 assert_eq!(collected, vec!["1", "3"]);
225 }
226
227 #[test]
228 fn relationship_first_id_for_to_one() {
229 let rel: Relationship<Target> =
230 Relationship::new(RelationshipData::ToOne(Some(rid("people", "9"))));
231 assert_eq!(rel.first_id(), Some("9"));
232 }
233
234 #[test]
235 fn relationship_first_id_for_null_to_one_is_none() {
236 let rel: Relationship<Target> = Relationship::new(RelationshipData::ToOne(None));
237 assert_eq!(rel.first_id(), None);
238 }
239
240 #[test]
241 fn relationship_first_id_for_to_many_returns_first() {
242 let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
243 rid("tags", "first"),
244 rid("tags", "second"),
245 ]));
246 assert_eq!(rel.first_id(), Some("first"));
247 }
248
249 #[test]
250 fn relationship_first_id_for_empty_to_many_is_none() {
251 let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![]));
252 assert_eq!(rel.first_id(), None);
253 }
254
255 #[test]
256 fn relationship_first_id_skips_lid_only_to_one() {
257 let rel: Relationship<Target> =
258 Relationship::new(RelationshipData::ToOne(Some(lid_rid("tags", "local"))));
259 assert_eq!(rel.first_id(), None);
260 }
261
262 #[test]
263 fn relationship_identifiers_for_null_to_one_is_empty() {
264 let rel: Relationship<Target> = Relationship::new(RelationshipData::ToOne(None));
265 assert!(rel.identifiers().is_empty());
266 }
267
268 #[test]
269 fn relationship_identifiers_for_to_one_has_one_element() {
270 let rel: Relationship<Target> =
271 Relationship::new(RelationshipData::ToOne(Some(rid("people", "9"))));
272 let slice = rel.identifiers();
273 assert_eq!(slice.len(), 1);
274 assert_eq!(slice[0].type_, "people");
275 }
276
277 #[test]
278 fn relationship_identifiers_for_to_many_returns_all() {
279 let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
280 rid("tags", "1"),
281 lid_rid("tags", "local"),
282 ]));
283 assert_eq!(rel.identifiers().len(), 2);
284 }
285
286 #[test]
287 fn relationship_first_id_or_lid_returns_id() {
288 let rel: Relationship<Target> =
289 Relationship::new(RelationshipData::ToOne(Some(rid("tags", "42"))));
290 assert_eq!(rel.first_id_or_lid(), Some("42"));
291 }
292
293 #[test]
294 fn relationship_first_id_or_lid_returns_lid_when_that_is_all_there_is() {
295 let rel: Relationship<Target> =
296 Relationship::new(RelationshipData::ToOne(Some(lid_rid("tags", "local-a"))));
297 assert_eq!(rel.first_id_or_lid(), Some("local-a"));
298 }
299
300 #[test]
301 fn relationship_first_id_or_lid_returns_first_of_to_many() {
302 let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
303 lid_rid("tags", "local"),
304 rid("tags", "server-id"),
305 ]));
306 assert_eq!(rel.first_id_or_lid(), Some("local"));
307 }
308}