type-bridge-schema 2.2.2

Canonical schema resolution and projection engine for type-bridge
Documentation
use type_bridge_contract::capability::CapabilityId;
use type_bridge_contract::codec::FormatVersion;
use type_bridge_contract::id::{Label, RoleId, TypeId, TypeKind};
use type_bridge_contract::schema::{
    CollectionMode, DocumentId, PlaysFactId, RelatesFactId, SchemaFact, SchemaFactId, SourceSpan,
    SubFact, SubFactId, TypeFact,
};
use type_bridge_schema::{FactAssembler, SchemaDocumentSet, normalize_documents};

fn span(document: &str, start: u64) -> SourceSpan {
    SourceSpan::new(
        DocumentId::new(document).expect("document ID is valid"),
        start,
        start + 1,
        1,
        u32::try_from(start + 1).expect("small test column"),
        1,
        u32::try_from(start + 2).expect("small test column"),
    )
    .expect("source span is valid")
}

fn type_fact(kind: TypeKind, label: &str) -> SchemaFact {
    SchemaFact::Type(
        TypeFact::new(TypeId::new(kind, label).expect("type ID is valid"))
            .expect("type fact is valid"),
    )
}

#[test]
fn duplicate_fact_reports_both_sources() {
    let mut assembler = FactAssembler::new(FormatVersion::V1);
    assembler
        .insert_fact(type_fact(TypeKind::Entity, "person"), span("first", 0))
        .expect("first fact is accepted");
    let error = assembler
        .insert_fact(type_fact(TypeKind::Entity, "person"), span("second", 1))
        .expect_err("duplicate fact is rejected");
    let diagnostic = error.iter().next().expect("one diagnostic exists");
    assert_eq!(
        diagnostic.diagnostic().code().as_str(),
        "duplicate_schema_fact"
    );
    assert_eq!(diagnostic.related().len(), 1);
}

#[test]
fn type_and_struct_labels_share_one_global_namespace() {
    let documents = SchemaDocumentSet::parse([(
        DocumentId::new("schema.yaml").unwrap(),
        r#"format: typebridge.schema/v2
entities:
  shared-label: {}
structs:
  shared-label:
    fields:
      - { name: count, type: integer }
"#,
    )])
    .expect("fixture YAML parses");
    let error = normalize_documents(&documents)
        .expect_err("a type and struct cannot share one schema label");
    let diagnostic = error.iter().next().expect("one diagnostic exists");
    assert_eq!(
        diagnostic.diagnostic().code().as_str(),
        "duplicate_schema_label"
    );
    assert_eq!(diagnostic.related().len(), 1);
}

#[test]
fn forward_plays_resolves_after_type_and_role_declarations() {
    let mut assembler = FactAssembler::new(FormatVersion::V1);
    assembler.insert_plays(
        Label::new("person").unwrap(),
        Label::new("membership").unwrap(),
        Label::new("member").unwrap(),
        span("plays", 0),
    );
    assembler
        .insert_fact(type_fact(TypeKind::Entity, "person"), span("types", 1))
        .unwrap();
    assembler
        .insert_fact(
            type_fact(TypeKind::Relation, "membership"),
            span("types", 2),
        )
        .unwrap();

    let relation = TypeId::new(TypeKind::Relation, "membership").unwrap();
    let role = RoleId::new("membership", "member").unwrap();
    assembler
        .insert_relates(
            RelatesFactId::new(relation, role.clone()).unwrap(),
            None,
            span("relates", 3),
        )
        .unwrap();

    let schema = assembler.finish().expect("forward references resolve");
    let player = TypeId::new(TypeKind::Entity, "person").unwrap();
    let id = SchemaFactId::Plays(PlaysFactId::new(player, role).unwrap());
    assert!(matches!(schema.fact(&id), Some(SchemaFact::Plays(_))));
}

#[test]
fn pending_relates_preserves_explicit_collection_mode() {
    let mut assembler = FactAssembler::new(FormatVersion::V1);
    let relation = TypeId::new(TypeKind::Relation, "collection").unwrap();
    assembler
        .insert_fact(
            type_fact(TypeKind::Relation, "collection"),
            span("types", 0),
        )
        .unwrap();
    let id = RelatesFactId::new(relation, RoleId::new("collection", "member").unwrap()).unwrap();
    assembler
        .insert_relates_with_collection_mode(
            id.clone(),
            None,
            CollectionMode::OrderedList,
            span("relates", 1),
        )
        .unwrap();

    let schema = assembler.finish().expect("ordered relates resolves");
    let Some(SchemaFact::Relates(fact)) = schema.fact(&SchemaFactId::Relates(id)) else {
        panic!("ordered relates fact exists");
    };
    assert_eq!(fact.collection_mode(), CollectionMode::OrderedList);
}

#[test]
fn role_specialization_resolves_the_declaring_ancestor() {
    let mut assembler = FactAssembler::new(FormatVersion::V1);
    let parent = TypeId::new(TypeKind::Relation, "membership").unwrap();
    let child = TypeId::new(TypeKind::Relation, "employment").unwrap();
    assembler
        .insert_fact(
            type_fact(TypeKind::Relation, "membership"),
            span("types", 0),
        )
        .unwrap();
    assembler
        .insert_fact(
            type_fact(TypeKind::Relation, "employment"),
            span("types", 1),
        )
        .unwrap();
    assembler
        .insert_fact(
            SchemaFact::Sub(SubFact::new(
                SubFactId::new(child.clone(), parent.clone()).unwrap(),
            )),
            span("sub", 2),
        )
        .unwrap();

    let parent_role = RoleId::new("membership", "member").unwrap();
    assembler
        .insert_relates(
            RelatesFactId::new(parent, parent_role.clone()).unwrap(),
            None,
            span("parent-role", 3),
        )
        .unwrap();
    let child_role = RoleId::new("employment", "employee").unwrap();
    let child_id = RelatesFactId::new(child, child_role).unwrap();
    assembler
        .insert_relates(
            child_id.clone(),
            Some((Label::new("member").unwrap(), span("as", 4))),
            span("child-role", 5),
        )
        .unwrap();

    let schema = assembler.finish().expect("specialization resolves");
    let Some(SchemaFact::Relates(fact)) = schema.fact(&SchemaFactId::Relates(child_id)) else {
        panic!("child relates fact exists");
    };
    assert_eq!(fact.specializes(), Some(&parent_role));
}

#[test]
fn required_capabilities_are_retained_and_duplicates_report_sources() {
    let capability = CapabilityId::new("schema.future-feature").unwrap();
    let mut assembler = FactAssembler::new(FormatVersion::V1);
    assembler
        .require_capability(capability.clone(), span("first", 0))
        .unwrap();
    let error = assembler
        .require_capability(capability.clone(), span("second", 1))
        .expect_err("duplicate capability is rejected");
    assert_eq!(error.iter().next().unwrap().related().len(), 1);

    let mut assembler = FactAssembler::new(FormatVersion::V1);
    assembler
        .require_capability(capability.clone(), span("only", 0))
        .unwrap();
    let schema = assembler.finish().expect("capability-only schema is valid");
    assert!(schema.required_capabilities().contains(&capability));
}