use std::{convert::TryInto, fmt};
use quickcheck::{Arbitrary, Gen};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use yaserde_derive::{YaDeserialize, YaSerialize};
use crate::{
Attribution, ConfidenceLevel, EnumAsString, EvidenceReference, Fact, Id, Identifier, Lang,
Note, Person, ResourceReference, Result, SourceReference, Uri,
};
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, YaSerialize, YaDeserialize, PartialEq, Default, Clone)]
#[yaserde(
prefix = "gx",
default_namespace = "gx",
namespace = "gx: http://gedcomx.org/v1/"
)]
#[non_exhaustive]
pub struct Relationship {
#[yaserde(attribute)]
pub id: Option<Id>,
#[yaserde(attribute, prefix = "xml")]
pub lang: Option<Lang>,
#[yaserde(rename = "source", prefix = "gx")]
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub sources: Vec<SourceReference>,
#[yaserde(prefix = "gx")]
pub analysis: Option<ResourceReference>,
#[yaserde(rename = "note", prefix = "gx")]
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub notes: Vec<Note>,
#[yaserde(attribute)]
pub confidence: Option<ConfidenceLevel>,
#[yaserde(prefix = "gx")]
pub attribution: Option<Attribution>,
#[yaserde(attribute)]
pub extracted: Option<bool>,
#[yaserde(prefix = "gx")]
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub evidence: Vec<EvidenceReference>,
#[yaserde(prefix = "gx")]
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub media: Vec<SourceReference>,
#[yaserde(rename = "identifier", prefix = "gx")]
#[serde(
skip_serializing_if = "Vec::is_empty",
default,
with = "crate::serde_vec_identifier_to_map"
)]
pub identifiers: Vec<Identifier>,
#[yaserde(rename = "type", attribute)]
#[serde(rename = "type")]
pub relationship_type: Option<RelationshipType>,
#[yaserde(prefix = "gx")]
pub person1: ResourceReference,
#[yaserde(prefix = "gx")]
pub person2: ResourceReference,
#[yaserde(rename = "fact", prefix = "gx")]
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub facts: Vec<Fact>,
}
impl Relationship {
pub fn new(
id: Option<Id>,
lang: Option<Lang>,
sources: Vec<SourceReference>,
analysis: Option<ResourceReference>,
notes: Vec<Note>,
confidence: Option<ConfidenceLevel>,
attribution: Option<Attribution>,
extracted: Option<bool>,
evidence: Vec<EvidenceReference>,
media: Vec<SourceReference>,
identifiers: Vec<Identifier>,
relationship_type: Option<RelationshipType>,
person1: ResourceReference,
person2: ResourceReference,
facts: Vec<Fact>,
) -> Self {
Self {
id,
lang,
sources,
analysis,
notes,
confidence,
attribution,
extracted,
evidence,
media,
identifiers,
relationship_type,
person1,
person2,
facts,
}
}
pub fn builder(person1: &Person, person2: &Person) -> Result<RelationshipBuilder> {
RelationshipBuilder::new(person1, person2)
}
}
impl Arbitrary for Relationship {
fn arbitrary(g: &mut Gen) -> Self {
let mut relationship = Self::builder(&Person::arbitrary(g), &Person::arbitrary(g))
.unwrap()
.id(Id::arbitrary(g))
.lang(Lang::arbitrary(g))
.note(Note::arbitrary(g))
.confidence(ConfidenceLevel::arbitrary(g))
.attribution(Attribution::arbitrary(g))
.extracted(bool::arbitrary(g))
.identifier(Identifier::arbitrary(g))
.relationship_type(RelationshipType::arbitrary(g))
.facts(vec![Fact::arbitrary(g)])
.build();
relationship.sources = vec![SourceReference::arbitrary(g)];
relationship.analysis = Some(ResourceReference::arbitrary(g));
relationship.evidence = vec![EvidenceReference::arbitrary(g)];
relationship.media = vec![SourceReference::arbitrary(g)];
relationship
}
}
pub struct RelationshipBuilder(Relationship);
impl RelationshipBuilder {
subject_builder_functions!(Relationship);
pub(crate) fn new(person1: &Person, person2: &Person) -> Result<Self> {
Ok(Self(Relationship {
person1: person1.try_into()?,
person2: person2.try_into()?,
..Relationship::default()
}))
}
pub fn relationship_type(&mut self, relationship_type: RelationshipType) -> &mut Self {
self.0.relationship_type = Some(relationship_type);
self
}
pub fn person_1(&mut self, person: &Person) -> Result<&mut Self> {
self.0.person1 = person.try_into()?;
Ok(self)
}
pub fn person_2(&mut self, person: &Person) -> Result<&mut Self> {
self.0.person2 = person.try_into()?;
Ok(self)
}
pub fn fact(&mut self, fact: Fact) -> &mut Self {
self.0.facts.push(fact);
self
}
pub fn facts(&mut self, facts: Vec<Fact>) -> &mut Self {
self.0.facts = facts;
self
}
pub fn build(&self) -> Relationship {
Relationship::new(
self.0.id.clone(),
self.0.lang.clone(),
self.0.sources.clone(),
self.0.analysis.clone(),
self.0.notes.clone(),
self.0.confidence.clone(),
self.0.attribution.clone(),
self.0.extracted,
self.0.evidence.clone(),
self.0.media.clone(),
self.0.identifiers.clone(),
self.0.relationship_type.clone(),
self.0.person1.clone(),
self.0.person2.clone(),
self.0.facts.clone(),
)
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)]
#[non_exhaustive]
#[serde(from = "EnumAsString", into = "EnumAsString")]
pub enum RelationshipType {
AncestorDescendant,
Couple,
EnslavedBy,
Godparent,
ParentChild,
Custom(Uri),
}
impl_enumasstring_yaserialize_yadeserialize!(RelationshipType, "RelationshipType");
impl From<EnumAsString> for RelationshipType {
fn from(f: EnumAsString) -> Self {
match f.0.as_ref() {
"http://gedcomx.org/AncestorDescendant" => Self::AncestorDescendant,
"http://gedcomx.org/Couple" => Self::Couple,
"http://gedcomx.org/EnslavedBy" => Self::EnslavedBy,
"http://gedcomx.org/Godparent" => Self::Godparent,
"http://gedcomx.org/ParentChild" => Self::ParentChild,
_ => Self::Custom(f.0.into()),
}
}
}
impl fmt::Display for RelationshipType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
Self::AncestorDescendant => write!(f, "http://gedcomx.org/AncestorDescendant"),
Self::Couple => write!(f, "http://gedcomx.org/Couple"),
Self::EnslavedBy => write!(f, "http://gedcomx.org/EnslavedBy"),
Self::Godparent => write!(f, "http://gedcomx.org/Godparent"),
Self::ParentChild => write!(f, "http://gedcomx.org/ParentChild"),
Self::Custom(c) => write!(f, "{c}"),
}
}
}
impl Default for RelationshipType {
fn default() -> Self {
Self::Custom(Uri::default())
}
}
impl Arbitrary for RelationshipType {
fn arbitrary(g: &mut Gen) -> Self {
let options = vec![
Self::AncestorDescendant,
Self::Couple,
Self::EnslavedBy,
Self::Godparent,
Self::ParentChild,
Self::Custom(Uri::arbitrary(g)),
];
g.choose(&options).unwrap().clone()
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn json_deserialize() {
let json = r##"{
"type" : "http://gedcomx.org/Couple",
"person1" : {
"resource" : "#http://identifier/for/person/1"
},
"person2" : {
"resource" : "#http://identifier/for/person/2"
}
}"##;
let expected_relationship = Relationship::builder(
&Person::builder()
.id("http://identifier/for/person/1")
.build(),
&Person::builder()
.id("http://identifier/for/person/2")
.build(),
)
.unwrap()
.relationship_type(RelationshipType::Couple)
.build();
let relationship: Relationship = serde_json::from_str(json).unwrap();
assert_eq!(relationship, expected_relationship);
}
#[test]
fn xml_deserialize() {
let xml = r##" <Relationship id="local_id" type="http://gedcomx.org/Couple" extracted="false">
<person1 resource="#http://identifier/for/person/1"/>
<person2 resource="#http://identifier/for/person/2"/>
</Relationship>"##;
let expected_relationship = Relationship::builder(
&Person::builder()
.id("http://identifier/for/person/1")
.build(),
&Person::builder()
.id("http://identifier/for/person/2")
.build(),
)
.unwrap()
.id("local_id")
.extracted(false)
.relationship_type(RelationshipType::Couple)
.build();
let relationship: Relationship = yaserde::de::from_str(xml).unwrap();
assert_eq!(relationship, expected_relationship);
}
#[test]
fn json_serialize() {
let relationship = Relationship::builder(
&Person::builder()
.id("http://identifier/for/person/1")
.build(),
&Person::builder()
.id("http://identifier/for/person/2")
.build(),
)
.unwrap()
.relationship_type(RelationshipType::Couple)
.build();
let json = serde_json::to_string(&relationship).unwrap();
let expected_json = r##"{"type":"http://gedcomx.org/Couple","person1":{"resource":"#http://identifier/for/person/1"},"person2":{"resource":"#http://identifier/for/person/2"}}"##;
assert_eq!(json, expected_json);
}
#[test]
fn xml_serialize() {
let relationship = Relationship::builder(
&Person::builder()
.id("http://identifier/for/person/1")
.build(),
&Person::builder()
.id("http://identifier/for/person/2")
.build(),
)
.unwrap()
.id("local_id")
.extracted(false)
.relationship_type(RelationshipType::Couple)
.build();
let config = yaserde::ser::Config {
write_document_declaration: false,
..yaserde::ser::Config::default()
};
let xml = yaserde::ser::to_string_with_config(&relationship, &config).unwrap();
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>"##;
assert_eq!(xml, expected_xml);
}
#[quickcheck_macros::quickcheck]
fn roundtrip_json(input: Relationship) -> bool {
let json = serde_json::to_string(&input).unwrap();
let from_json: Relationship = serde_json::from_str(&json).unwrap();
assert_eq!(from_json, input);
input == from_json
}
#[quickcheck_macros::quickcheck]
fn roundtrip_xml(input: Relationship) -> bool {
let xml = yaserde::ser::to_string(&input).unwrap();
let from_xml: Relationship = yaserde::de::from_str(&xml).unwrap();
input == from_xml
}
}