type-bridge-contract 2.2.2

Versioned binding-neutral contract primitives for type-bridge
Documentation
use serde_json::json;
use type_bridge_contract::codec::to_canonical_json;
use type_bridge_contract::fingerprint::SemanticProfileId;
use type_bridge_contract::id::{AttributeId, TypeId, TypeKind};
use type_bridge_contract::migration_backfill::{
    AttributeBackfillPlan, BackfillConflictPolicy, BackfillPartition, BackfillPostcondition,
    BackfillReverseProgram, BackfillValueTransform, COPY_ATTRIBUTE_BACKFILL_CAPABILITY,
    MAX_BACKFILL_BATCH_ROWS, decode_attribute_backfill_plan,
};
use type_bridge_contract::schema_fingerprint::ManagedSemanticSchemaFingerprint;

fn semantics() -> ManagedSemanticSchemaFingerprint {
    ManagedSemanticSchemaFingerprint::compute(
        SemanticProfileId::new("typedb-3.12.1/v1").expect("semantic profile"),
        b"migration-backfill-intermediate-schema",
    )
    .expect("managed semantic fingerprint")
}

fn plan() -> AttributeBackfillPlan {
    AttributeBackfillPlan::new(
        TypeId::new(TypeKind::Entity, "person").expect("owner"),
        AttributeId::new("legacy-name").expect("source"),
        AttributeId::new("display-name").expect("destination"),
        BackfillPartition::new(
            512,
            AttributeId::new("person-id").expect("stable partition attribute"),
        )
        .expect("partition"),
        semantics(),
        Some(BackfillReverseProgram::RemoveEqualCopiedDestination),
    )
    .expect("backfill plan")
}

#[test]
fn copy_attribute_plan_freezes_closed_semantics_and_canonical_wire() {
    let plan = plan();
    assert_eq!(
        plan.conflict(),
        BackfillConflictPolicy::SkipEqualRejectDifferent
    );
    assert_eq!(plan.transform(), BackfillValueTransform::Identity);
    assert_eq!(
        plan.postcondition(),
        BackfillPostcondition::SourceValueCopiedExactly
    );
    assert_eq!(
        plan.reverse(),
        Some(BackfillReverseProgram::RemoveEqualCopiedDestination)
    );
    assert_eq!(
        plan.required_capabilities()
            .iter()
            .map(|capability| capability.as_str())
            .collect::<Vec<_>>(),
        [COPY_ATTRIBUTE_BACKFILL_CAPABILITY]
    );
    assert_eq!(plan.partition().batch_rows(), 512);

    let value: serde_json::Value =
        serde_json::from_slice(&plan.canonical_bytes().expect("canonical bytes"))
            .expect("canonical JSON");
    assert_eq!(
        value,
        json!({
            "conflict": "skip_equal_reject_different",
            "destination": "display-name",
            "format": 1,
            "managed_semantics": plan.managed_semantics(),
            "owner": {"kind": "entity", "label": "person"},
            "partition": {"batch_rows": 512, "stable_attribute": "person-id"},
            "postcondition": "source_value_copied_exactly",
            "required_capabilities": ["migration.backfill.copy-attribute"],
            "reverse": "remove_equal_copied_destination",
            "source": "legacy-name",
            "transform": "identity"
        })
    );
    assert_eq!(
        plan.canonical_bytes().unwrap(),
        to_canonical_json(&value).unwrap()
    );
    assert_eq!(plan.fingerprint().unwrap(), plan.fingerprint().unwrap());
    assert_eq!(
        decode_attribute_backfill_plan(&plan.canonical_bytes().unwrap()).unwrap(),
        plan
    );

    let mut tampered = value;
    tampered["conflict"] = json!("overwrite");
    assert_eq!(
        decode_attribute_backfill_plan(&to_canonical_json(&tampered).unwrap())
            .unwrap_err()
            .code()
            .as_str(),
        "migration_backfill_contract_mismatch"
    );
}

#[test]
fn copy_attribute_plan_rejects_ambiguous_or_unbounded_work() {
    assert_eq!(
        BackfillPartition::new(
            0,
            AttributeId::new("person-id").expect("stable partition attribute")
        )
        .expect_err("zero batch rejects")
        .code()
        .as_str(),
        "migration_backfill_batch_rows_out_of_range"
    );
    assert!(
        BackfillPartition::new(
            MAX_BACKFILL_BATCH_ROWS + 1,
            AttributeId::new("person-id").expect("stable partition attribute")
        )
        .is_err()
    );
    let same = AttributeId::new("name").expect("attribute");
    assert_eq!(
        AttributeBackfillPlan::new(
            TypeId::new(TypeKind::Entity, "person").expect("owner"),
            same.clone(),
            same,
            BackfillPartition::new(
                1,
                AttributeId::new("person-id").expect("stable partition attribute")
            )
            .unwrap(),
            semantics(),
            None,
        )
        .expect_err("same source and destination reject")
        .code()
        .as_str(),
        "migration_backfill_same_attribute"
    );
}