gedcomx/conclusion/
relationship.rs

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/// A relationship between two persons.
14///
15/// Note: When a relationship type implies direction, the relationship is said
16/// to be from person1 to person2. For example, in a parent-child relationship,
17/// the relationship is said to be "from a parent to a child"; therefore, the
18/// person1 property refers to the parent and the person2 property refers to the
19/// child.
20#[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    /// An identifier for the conclusion data. The id is to be used as a "fragment identifier" as defined by [RFC 3986, Section 3.5](https://tools.ietf.org/html/rfc3986#section-3.5).
30    #[yaserde(attribute)]
31    pub id: Option<Id>,
32
33    /// The locale identifier for the conclusion.
34    #[yaserde(attribute, prefix = "xml")]
35    pub lang: Option<Lang>,
36
37    /// The list of references to the sources of related to this conclusion.
38    /// Note that the sources referenced from conclusions are also considered
39    /// to be sources of the entities that contain them. For example, a source
40    /// associated with the `Name` of a `Person` is also source for the
41    /// `Person`.
42    #[yaserde(rename = "source", prefix = "gx")]
43    #[serde(skip_serializing_if = "Vec::is_empty", default)]
44    pub sources: Vec<SourceReference>,
45
46    /// A reference to the analysis document explaining the analysis that went
47    /// into this conclusion. If provided, MUST resolve to an instance of
48    /// [Document](crate::Document) of type
49    /// [Analysis](crate::DocumentType::Analysis).
50    #[yaserde(prefix = "gx")]
51    pub analysis: Option<ResourceReference>,
52
53    /// A list of notes about this conclusion.
54    #[yaserde(rename = "note", prefix = "gx")]
55    #[serde(skip_serializing_if = "Vec::is_empty", default)]
56    pub notes: Vec<Note>,
57
58    /// The level of confidence the contributor has about the data.
59    #[yaserde(attribute)]
60    pub confidence: Option<ConfidenceLevel>,
61
62    /// The attribution of this conclusion.
63    /// If not provided, the attribution of the containing data set (e.g. file)
64    /// of the conclusion is assumed.
65    #[yaserde(prefix = "gx")]
66    pub attribution: Option<Attribution>,
67
68    /// Whether this subject is to be constrained as an extracted conclusion.
69    #[yaserde(attribute)]
70    pub extracted: Option<bool>,
71
72    /// References to other subjects that support this subject.
73    ///
74    /// If provided, each reference MUST resolve to an instance of subject of
75    /// the same type as this instance (e.g., if the subject is an instance of
76    /// Person, all of its evidence references must resolve to instances of
77    /// Person).
78    #[yaserde(prefix = "gx")]
79    #[serde(skip_serializing_if = "Vec::is_empty", default)]
80    pub evidence: Vec<EvidenceReference>,
81
82    /// References to multimedia resources for this subject, such as photos or
83    /// videos, intended to provide additional context or illustration for the
84    /// subject and not considered evidence supporting the identity of the
85    /// subject or its supporting conclusions.
86    ///
87    /// Media references SHOULD be ordered by priority such that applications
88    /// that wish to display a single media item (such as an image) MAY choose
89    /// the first applicable media reference. Note that the SourceReference is
90    /// used for multimedia references and therefore MUST resolve to a
91    /// SourceDescription of the resource, which in turn provides a reference to
92    /// the resource itself.
93    #[yaserde(prefix = "gx")]
94    #[serde(skip_serializing_if = "Vec::is_empty", default)]
95    pub media: Vec<SourceReference>,
96
97    /// A list of identifiers for the subject.
98    #[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    /// The type of the relationship.
107    #[yaserde(rename = "type", attribute)]
108    #[serde(rename = "type")]
109    pub relationship_type: Option<RelationshipType>,
110
111    /// Reference to the first person in the relationship.
112    ///
113    /// MUST resolve to an instance of http://gedcomx.org/v1/Person.
114    #[yaserde(prefix = "gx")]
115    pub person1: ResourceReference,
116
117    /// Reference to the second person in the relationship.
118    ///
119    /// MUST resolve to an instance of http://gedcomx.org/v1/Person.
120    #[yaserde(prefix = "gx")]
121    pub person2: ResourceReference,
122
123    /// The facts about the relationship.
124    #[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    /// # Errors
167    ///
168    /// Will return [`GedcomxError::NoId`](crate::GedcomxError::NoId) if a
169    /// conversion into [`ResourceReference`](crate::ResourceReference) fails.
170    /// This happens if either `person1` or `person2` has no `id` set.
171    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    /// # Errors
219    ///
220    /// Will return [`GedcomxError::NoId`](crate::GedcomxError::NoId) if a
221    /// conversion into [`ResourceReference`](crate::ResourceReference) fails.
222    /// This happens if either `person1` or `person2` has no `id` set.
223    pub fn person_1(&mut self, person: &Person) -> Result<&mut Self> {
224        self.0.person1 = person.try_into()?;
225        Ok(self)
226    }
227
228    /// # Errors
229    ///
230    /// Will return [`GedcomxError::NoId`](crate::GedcomxError::NoId) if a
231    /// conversion into [`ResourceReference`](crate::ResourceReference) fails.
232    /// This happens if either `person1` or `person2` has no `id` set.
233    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/// Standard relationship types.
270#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)]
271#[non_exhaustive]
272#[serde(from = "EnumAsString", into = "EnumAsString")]
273pub enum RelationshipType {
274    /// A relationship from an ancestor to a descendant.
275    AncestorDescendant,
276
277    /// A relationship of a pair of persons.
278    Couple,
279
280    /// A relationship from an enslaved person to the enslaver or slaveholder of
281    /// the person.
282    EnslavedBy,
283
284    /// A relationship from a godparent to a person.
285    Godparent,
286
287    /// A relationship from a parent to a child.
288    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}