gedcomkit 0.1.11

A byte-preserving GEDCOM document model: decoding, parsing, readings, version conversion, plausibility checks, and the GEDZIP container, for GEDCOM 5.5 through 7.x.
Documentation
//! The `ts` feature: TypeScript definitions of the shapes the `serde` feature
//! serializes. What is pinned here is what a caller's generated types would
//! silently get wrong if it drifted — camelCase field names, `null` for an
//! absent value, and the string unions a `&'static str` field really holds.
#![cfg(feature = "ts")]

use gedcomkit::{build, convert, dates, decode, family, validate, view};
use ts_rs::{Config, TS};

fn config() -> Config {
    // JSON numbers, not `bigint`: `serde_json` writes an `i64` as a number.
    Config::new().with_large_int("number")
}

/// The declaration's field lines, without the documentation between them.
fn fields<T: TS>() -> Vec<String> {
    T::decl(&config())
        .lines()
        .map(str::trim)
        .filter(|line| !line.starts_with("/**") && !line.starts_with('*'))
        .filter(|line| line.contains(": "))
        .map(|line| line.trim_end_matches(['{', ',', ' ', ';', '}']).to_owned())
        .collect()
}

#[test]
fn a_structure_kind_is_one_of_the_five_words_the_view_writes() {
    assert!(
        fields::<view::StructureView>().contains(
            &r#"kind: "record" | "event" | "attribute" | "structure" | "unknown""#.to_owned()
        ),
        "{:?}",
        fields::<view::StructureView>()
    );
}

#[test]
fn fields_are_camel_case_and_an_absent_value_is_null() {
    assert_eq!(
        fields::<view::IndividualView>(),
        [
            "xref: string",
            "names: Array<NameView>",
            "sex: string | null",
            "events: Array<EventView>",
            "childIn: Array<FamilyLinkView>",
            "partnerIn: Array<FamilyLinkView>",
            "citations: Array<string>",
            "media: Array<string>",
            "identifiers: Array<IdentifierView>",
        ]
    );
    assert!(fields::<view::EventView>().contains(&"sortKey: number | null".to_owned()));
}

#[test]
fn unit_enums_are_the_variant_names_serde_writes() {
    assert_eq!(
        convert::ConversionKind::decl(&config()),
        r#"type ConversionKind = "Converted" | "Dropped" | "Kept";"#
    );
    assert!(validate::Severity::decl(&config()).contains(r#""Contradiction" | "Question""#));
}

#[test]
fn every_serialized_type_has_a_definition() {
    let config = config();
    for exported in [
        build::WrappedLine::export_to_string(&config),
        convert::ConversionNote::export_to_string(&config),
        dates::Timestamp::export_to_string(&config),
        dates::Precision::export_to_string(&config),
        decode::EncodingReport::export_to_string(&config),
        family::FamilyMembers::export_to_string(&config),
        gedcomkit::UnresolvedPointer::export_to_string(&config),
        validate::Finding::export_to_string(&config),
        view::RecordView::export_to_string(&config),
        view::FamilyView::export_to_string(&config),
        view::IndividualView::export_to_string(&config),
    ] {
        let exported = exported.expect("exports");
        assert!(exported.contains("export type "), "{exported}");
    }
}