1use std::{convert::TryInto, fmt};
2
3use quickcheck::{Arbitrary, Gen};
4use serde::{Deserialize, Serialize};
5use serde_with::skip_serializing_none;
6use yaserde_derive::{YaDeserialize, YaSerialize};
7
8use crate::{
9 Attribution, ConfidenceLevel, EnumAsString, EvidenceReference, Fact, Id, Identifier, Lang,
10 Note, Person, ResourceReference, Result, SourceReference, Uri,
11};
12
13#[skip_serializing_none]
21#[derive(Debug, Serialize, Deserialize, YaSerialize, YaDeserialize, PartialEq, Default, Clone)]
22#[yaserde(
23 prefix = "gx",
24 default_namespace = "gx",
25 namespace = "gx: http://gedcomx.org/v1/"
26)]
27#[non_exhaustive]
28pub struct Relationship {
29 #[yaserde(attribute)]
31 pub id: Option<Id>,
32
33 #[yaserde(attribute, prefix = "xml")]
35 pub lang: Option<Lang>,
36
37 #[yaserde(rename = "source", prefix = "gx")]
43 #[serde(skip_serializing_if = "Vec::is_empty", default)]
44 pub sources: Vec<SourceReference>,
45
46 #[yaserde(prefix = "gx")]
51 pub analysis: Option<ResourceReference>,
52
53 #[yaserde(rename = "note", prefix = "gx")]
55 #[serde(skip_serializing_if = "Vec::is_empty", default)]
56 pub notes: Vec<Note>,
57
58 #[yaserde(attribute)]
60 pub confidence: Option<ConfidenceLevel>,
61
62 #[yaserde(prefix = "gx")]
66 pub attribution: Option<Attribution>,
67
68 #[yaserde(attribute)]
70 pub extracted: Option<bool>,
71
72 #[yaserde(prefix = "gx")]
79 #[serde(skip_serializing_if = "Vec::is_empty", default)]
80 pub evidence: Vec<EvidenceReference>,
81
82 #[yaserde(prefix = "gx")]
94 #[serde(skip_serializing_if = "Vec::is_empty", default)]
95 pub media: Vec<SourceReference>,
96
97 #[yaserde(rename = "identifier", prefix = "gx")]
99 #[serde(
100 skip_serializing_if = "Vec::is_empty",
101 default,
102 with = "crate::serde_vec_identifier_to_map"
103 )]
104 pub identifiers: Vec<Identifier>,
105
106 #[yaserde(rename = "type", attribute)]
108 #[serde(rename = "type")]
109 pub relationship_type: Option<RelationshipType>,
110
111 #[yaserde(prefix = "gx")]
115 pub person1: ResourceReference,
116
117 #[yaserde(prefix = "gx")]
121 pub person2: ResourceReference,
122
123 #[yaserde(rename = "fact", prefix = "gx")]
125 #[serde(skip_serializing_if = "Vec::is_empty", default)]
126 pub facts: Vec<Fact>,
127}
128
129impl Relationship {
130 pub fn new(
131 id: Option<Id>,
132 lang: Option<Lang>,
133 sources: Vec<SourceReference>,
134 analysis: Option<ResourceReference>,
135 notes: Vec<Note>,
136 confidence: Option<ConfidenceLevel>,
137 attribution: Option<Attribution>,
138 extracted: Option<bool>,
139 evidence: Vec<EvidenceReference>,
140 media: Vec<SourceReference>,
141 identifiers: Vec<Identifier>,
142 relationship_type: Option<RelationshipType>,
143 person1: ResourceReference,
144 person2: ResourceReference,
145 facts: Vec<Fact>,
146 ) -> Self {
147 Self {
148 id,
149 lang,
150 sources,
151 analysis,
152 notes,
153 confidence,
154 attribution,
155 extracted,
156 evidence,
157 media,
158 identifiers,
159 relationship_type,
160 person1,
161 person2,
162 facts,
163 }
164 }
165
166 pub fn builder(person1: &Person, person2: &Person) -> Result<RelationshipBuilder> {
172 RelationshipBuilder::new(person1, person2)
173 }
174}
175
176impl Arbitrary for Relationship {
177 fn arbitrary(g: &mut Gen) -> Self {
178 let mut relationship = Self::builder(&Person::arbitrary(g), &Person::arbitrary(g))
179 .unwrap()
180 .id(Id::arbitrary(g))
181 .lang(Lang::arbitrary(g))
182 .note(Note::arbitrary(g))
183 .confidence(ConfidenceLevel::arbitrary(g))
184 .attribution(Attribution::arbitrary(g))
185 .extracted(bool::arbitrary(g))
186 .identifier(Identifier::arbitrary(g))
187 .relationship_type(RelationshipType::arbitrary(g))
188 .facts(vec![Fact::arbitrary(g)])
189 .build();
190
191 relationship.sources = vec![SourceReference::arbitrary(g)];
192 relationship.analysis = Some(ResourceReference::arbitrary(g));
193 relationship.evidence = vec![EvidenceReference::arbitrary(g)];
194 relationship.media = vec![SourceReference::arbitrary(g)];
195
196 relationship
197 }
198}
199
200pub struct RelationshipBuilder(Relationship);
201
202impl RelationshipBuilder {
203 subject_builder_functions!(Relationship);
204
205 pub(crate) fn new(person1: &Person, person2: &Person) -> Result<Self> {
206 Ok(Self(Relationship {
207 person1: person1.try_into()?,
208 person2: person2.try_into()?,
209 ..Relationship::default()
210 }))
211 }
212
213 pub fn relationship_type(&mut self, relationship_type: RelationshipType) -> &mut Self {
214 self.0.relationship_type = Some(relationship_type);
215 self
216 }
217
218 pub fn person_1(&mut self, person: &Person) -> Result<&mut Self> {
224 self.0.person1 = person.try_into()?;
225 Ok(self)
226 }
227
228 pub fn person_2(&mut self, person: &Person) -> Result<&mut Self> {
234 self.0.person2 = person.try_into()?;
235 Ok(self)
236 }
237
238 pub fn fact(&mut self, fact: Fact) -> &mut Self {
239 self.0.facts.push(fact);
240 self
241 }
242
243 pub fn facts(&mut self, facts: Vec<Fact>) -> &mut Self {
244 self.0.facts = facts;
245 self
246 }
247
248 pub fn build(&self) -> Relationship {
249 Relationship::new(
250 self.0.id.clone(),
251 self.0.lang.clone(),
252 self.0.sources.clone(),
253 self.0.analysis.clone(),
254 self.0.notes.clone(),
255 self.0.confidence.clone(),
256 self.0.attribution.clone(),
257 self.0.extracted,
258 self.0.evidence.clone(),
259 self.0.media.clone(),
260 self.0.identifiers.clone(),
261 self.0.relationship_type.clone(),
262 self.0.person1.clone(),
263 self.0.person2.clone(),
264 self.0.facts.clone(),
265 )
266 }
267}
268
269#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)]
271#[non_exhaustive]
272#[serde(from = "EnumAsString", into = "EnumAsString")]
273pub enum RelationshipType {
274 AncestorDescendant,
276
277 Couple,
279
280 EnslavedBy,
283
284 Godparent,
286
287 ParentChild,
289
290 Custom(Uri),
291}
292
293impl_enumasstring_yaserialize_yadeserialize!(RelationshipType, "RelationshipType");
294
295impl From<EnumAsString> for RelationshipType {
296 fn from(f: EnumAsString) -> Self {
297 match f.0.as_ref() {
298 "http://gedcomx.org/AncestorDescendant" => Self::AncestorDescendant,
299 "http://gedcomx.org/Couple" => Self::Couple,
300 "http://gedcomx.org/EnslavedBy" => Self::EnslavedBy,
301 "http://gedcomx.org/Godparent" => Self::Godparent,
302 "http://gedcomx.org/ParentChild" => Self::ParentChild,
303 _ => Self::Custom(f.0.into()),
304 }
305 }
306}
307
308impl fmt::Display for RelationshipType {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
310 match self {
311 Self::AncestorDescendant => write!(f, "http://gedcomx.org/AncestorDescendant"),
312 Self::Couple => write!(f, "http://gedcomx.org/Couple"),
313 Self::EnslavedBy => write!(f, "http://gedcomx.org/EnslavedBy"),
314 Self::Godparent => write!(f, "http://gedcomx.org/Godparent"),
315 Self::ParentChild => write!(f, "http://gedcomx.org/ParentChild"),
316 Self::Custom(c) => write!(f, "{c}"),
317 }
318 }
319}
320
321impl Default for RelationshipType {
322 fn default() -> Self {
323 Self::Custom(Uri::default())
324 }
325}
326
327impl Arbitrary for RelationshipType {
328 fn arbitrary(g: &mut Gen) -> Self {
329 let options = vec![
330 Self::AncestorDescendant,
331 Self::Couple,
332 Self::EnslavedBy,
333 Self::Godparent,
334 Self::ParentChild,
335 Self::Custom(Uri::arbitrary(g)),
336 ];
337
338 g.choose(&options).unwrap().clone()
339 }
340}
341
342#[cfg(test)]
343mod test {
344 use pretty_assertions::assert_eq;
345
346 use super::*;
347
348 #[test]
349 fn json_deserialize() {
350 let json = r##"{
351 "type" : "http://gedcomx.org/Couple",
352 "person1" : {
353 "resource" : "#http://identifier/for/person/1"
354 },
355 "person2" : {
356 "resource" : "#http://identifier/for/person/2"
357 }
358 }"##;
359
360 let expected_relationship = Relationship::builder(
361 &Person::builder()
362 .id("http://identifier/for/person/1")
363 .build(),
364 &Person::builder()
365 .id("http://identifier/for/person/2")
366 .build(),
367 )
368 .unwrap()
369 .relationship_type(RelationshipType::Couple)
370 .build();
371
372 let relationship: Relationship = serde_json::from_str(json).unwrap();
373
374 assert_eq!(relationship, expected_relationship);
375 }
376
377 #[test]
378 fn xml_deserialize() {
379 let xml = r##"<Relationship xmlns="http://gedcomx.org/v1/" id="local_id" type="http://gedcomx.org/Couple" extracted="false">
380 <person1 resource="#http://identifier/for/person/1"/>
381 <person2 resource="#http://identifier/for/person/2"/>
382
383 </Relationship>"##;
384
385 let expected_relationship = Relationship::builder(
386 &Person::builder()
387 .id("http://identifier/for/person/1")
388 .build(),
389 &Person::builder()
390 .id("http://identifier/for/person/2")
391 .build(),
392 )
393 .unwrap()
394 .id("local_id")
395 .extracted(false)
396 .relationship_type(RelationshipType::Couple)
397 .build();
398
399 let relationship: Relationship = yaserde::de::from_str(xml).unwrap();
400
401 assert_eq!(relationship, expected_relationship);
402 }
403
404 #[test]
405 fn json_serialize() {
406 let relationship = Relationship::builder(
407 &Person::builder()
408 .id("http://identifier/for/person/1")
409 .build(),
410 &Person::builder()
411 .id("http://identifier/for/person/2")
412 .build(),
413 )
414 .unwrap()
415 .relationship_type(RelationshipType::Couple)
416 .build();
417
418 let json = serde_json::to_string(&relationship).unwrap();
419
420 let expected_json = r##"{"type":"http://gedcomx.org/Couple","person1":{"resource":"#http://identifier/for/person/1"},"person2":{"resource":"#http://identifier/for/person/2"}}"##;
421
422 assert_eq!(json, expected_json);
423 }
424
425 #[test]
426 fn xml_serialize() {
427 let relationship = Relationship::builder(
428 &Person::builder()
429 .id("http://identifier/for/person/1")
430 .build(),
431 &Person::builder()
432 .id("http://identifier/for/person/2")
433 .build(),
434 )
435 .unwrap()
436 .id("local_id")
437 .extracted(false)
438 .relationship_type(RelationshipType::Couple)
439 .build();
440
441 let config = yaserde::ser::Config {
442 write_document_declaration: false,
443 ..yaserde::ser::Config::default()
444 };
445
446 let xml = yaserde::ser::to_string_with_config(&relationship, &config).unwrap();
447
448 let expected_xml = r##"<Relationship xmlns="http://gedcomx.org/v1/" id="local_id" extracted="false" type="http://gedcomx.org/Couple"><person1 resource="#http://identifier/for/person/1" /><person2 resource="#http://identifier/for/person/2" /></Relationship>"##;
449
450 assert_eq!(xml, expected_xml);
451 }
452
453 #[quickcheck_macros::quickcheck]
454 fn roundtrip_json(input: Relationship) -> bool {
455 let json = serde_json::to_string(&input).unwrap();
456 let from_json: Relationship = serde_json::from_str(&json).unwrap();
457 assert_eq!(from_json, input);
458 input == from_json
459 }
460
461 #[quickcheck_macros::quickcheck]
462 fn roundtrip_xml(input: Relationship) -> bool {
463 let xml = yaserde::ser::to_string(&input).unwrap();
464 let from_xml: Relationship = yaserde::de::from_str(&xml).unwrap();
465 input == from_xml
466 }
467}