use std::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, Id, Lang, Note, ResourceReference, SourceReference,
Uri,
};
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, YaSerialize, YaDeserialize, PartialEq, Clone, Default)]
#[yaserde(
prefix = "gx",
default_namespace = "gx",
namespace = "gx: http://gedcomx.org/v1/"
)]
#[non_exhaustive]
pub struct Gender {
#[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(rename = "type", attribute)]
#[serde(rename = "type")]
pub gender_type: GenderType,
}
impl Gender {
pub fn new(
id: Option<Id>,
lang: Option<Lang>,
sources: Vec<SourceReference>,
analysis: Option<ResourceReference>,
notes: Vec<Note>,
confidence: Option<ConfidenceLevel>,
attribution: Option<Attribution>,
gender_type: GenderType,
) -> Self {
Self {
id,
lang,
sources,
analysis,
notes,
confidence,
attribution,
gender_type,
}
}
pub fn builder(gender_type: GenderType) -> GenderBuilder {
GenderBuilder::new(gender_type)
}
}
impl Arbitrary for Gender {
fn arbitrary(g: &mut Gen) -> Self {
let mut gender = Self::builder(GenderType::arbitrary(g))
.id(Id::arbitrary(g))
.lang(Lang::arbitrary(g))
.note(Note::arbitrary(g))
.confidence(ConfidenceLevel::arbitrary(g))
.attribution(Attribution::arbitrary(g))
.build();
gender.analysis = Some(ResourceReference::arbitrary(g));
gender.sources = vec![SourceReference::arbitrary(g)];
gender
}
}
pub struct GenderBuilder(Gender);
impl GenderBuilder {
conclusion_builder_functions!(Gender);
pub(crate) fn new(gender_type: GenderType) -> Self {
Self(Gender {
gender_type,
..Gender::default()
})
}
pub fn build(&self) -> Gender {
Gender::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.gender_type.clone(),
)
}
}
impl From<GenderType> for Gender {
fn from(gender_type: GenderType) -> Self {
Self {
gender_type,
..Self::default()
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)]
#[non_exhaustive]
#[serde(from = "EnumAsString", into = "EnumAsString")]
pub enum GenderType {
Male,
Female,
Unknown,
Intersex,
Custom(Uri),
}
impl Default for GenderType {
fn default() -> Self {
Self::Custom(Uri::from(String::default()))
}
}
impl_enumasstring_yaserialize_yadeserialize!(GenderType, "GenderType");
impl From<EnumAsString> for GenderType {
fn from(f: EnumAsString) -> Self {
match f.0.as_ref() {
"http://gedcomx.org/Male" => Self::Male,
"http://gedcomx.org/Female" => Self::Female,
"http://gedcomx.org/Unknown" => Self::Unknown,
"http://gedcomx.org/Intersex" => Self::Intersex,
_ => Self::Custom(f.0.into()),
}
}
}
impl fmt::Display for GenderType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
Self::Male => write!(f, "http://gedcomx.org/Male"),
Self::Female => write!(f, "http://gedcomx.org/Female"),
Self::Unknown => write!(f, "http://gedcomx.org/Unknown"),
Self::Intersex => write!(f, "http://gedcomx.org/Intersex"),
Self::Custom(c) => write!(f, "{c}"),
}
}
}
impl Arbitrary for GenderType {
fn arbitrary(g: &mut Gen) -> Self {
let options = vec![
Self::Male,
Self::Female,
Self::Unknown,
Self::Intersex,
Self::Custom(Uri::arbitrary(g)),
];
g.choose(&options).unwrap().clone()
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::*;
use crate::TestData;
#[test]
fn json_deserialize() {
let data = TestData::new();
let json = r#"{
"type" : "http://gedcomx.org/Male",
"id" : "local_id",
"lang" : "en",
"sources" : [ {
"description" : "SD-1",
"descriptionId" : "Description id of the target source",
"attribution" : {
"contributor" : {
"resource" : "A-1"
},
"modified" : 1394175600000
},
"qualifiers" : [ { "name" : "http://gedcomx.org/RectangleRegion", "value" : "rectangle region value" } ]
}],
"analysis" : {
"resource" : "http://identifier/for/analysis/document"
},
"notes" : [ {
"lang" : "en",
"subject" : "subject",
"text" : "This is a note",
"attribution" : {
"contributor" : {
"resource" : "A-1"
},
"modified" : 1394175600000
}
} ],
"confidence" : "http://gedcomx.org/High",
"attribution" : {
"contributor" : {
"resource" : "A-1"
},
"modified" : 1394175600000
}
}"#;
let gender: Gender = serde_json::from_str(json).unwrap();
assert_eq!(
gender,
Gender {
id: data.conclusion_data.id,
lang: data.conclusion_data.lang,
sources: data.conclusion_data.sources,
analysis: data.conclusion_data.analysis,
notes: data.conclusion_data.notes,
confidence: data.conclusion_data.confidence,
attribution: data.conclusion_data.attribution,
gender_type: GenderType::Male,
}
);
}
#[test]
fn xml_deserialize() {
let xml = "<Gender xmlns=\"http://gedcomx.org/v1/\" type=\"http://gedcomx.org/Male\" />";
let gender: Gender = yaserde::de::from_str(xml).unwrap();
assert_eq!(gender, Gender::builder(GenderType::Male).build());
}
#[test]
fn json_serialize() {
let data = TestData::new();
let gender = Gender {
id: data.conclusion_data.id,
lang: data.conclusion_data.lang,
sources: data.conclusion_data.sources,
analysis: data.conclusion_data.analysis,
notes: data.conclusion_data.notes,
confidence: data.conclusion_data.confidence,
attribution: data.conclusion_data.attribution,
gender_type: GenderType::Male,
};
let json = serde_json::to_string(&gender).unwrap();
assert_eq!(
json,
r#"{"id":"local_id","lang":"en","sources":[{"description":"SD-1","descriptionId":"Description id of the target source","attribution":{"contributor":{"resource":"A-1"},"modified":1394175600000},"qualifiers":[{"name":"http://gedcomx.org/RectangleRegion","value":"rectangle region value"}]}],"analysis":{"resource":"http://identifier/for/analysis/document"},"notes":[{"lang":"en","subject":"subject","text":"This is a note","attribution":{"contributor":{"resource":"A-1"},"modified":1394175600000}}],"confidence":"http://gedcomx.org/High","attribution":{"contributor":{"resource":"A-1"},"modified":1394175600000},"type":"http://gedcomx.org/Male"}"#
);
}
#[test]
fn xml_serialize() {
let gender = Gender::builder(GenderType::Male).build();
let config = yaserde::ser::Config {
write_document_declaration: false,
..yaserde::ser::Config::default()
};
let xml = yaserde::ser::to_string_with_config(&gender, &config).unwrap();
assert_eq!(
xml,
"<Gender xmlns=\"http://gedcomx.org/v1/\" type=\"http://gedcomx.org/Male\" />"
);
}
#[quickcheck_macros::quickcheck]
fn roundtrip_json(input: Gender) -> bool {
let json = serde_json::to_string(&input).unwrap();
let from_json: Gender = serde_json::from_str(&json).unwrap();
input == from_json
}
#[quickcheck_macros::quickcheck]
fn roundtrip_xml(input: Gender) -> bool {
let xml = yaserde::ser::to_string(&input).unwrap();
let from_xml: Gender = yaserde::de::from_str(&xml).unwrap();
input == from_xml
}
}