type-bridge-schema-codegen 2.2.2

Deterministic projection-only schema emitters for type-bridge
Documentation
use std::collections::BTreeSet;
use std::str;

use type_bridge_contract::codec::to_canonical_json;
use type_bridge_contract::fingerprint::SemanticProfileId;
use type_bridge_contract::id::{FunctionId, RoleId, StructId, TypeId};
use type_bridge_contract::projection::{
    BindingTarget, CSymbolPrefix, CodeResourceDigest, ProjectionConfig, RuntimeProjection,
};
use type_bridge_contract::schema::{DocumentId, OwnsFactId, PlaysFactId};
use type_bridge_schema::{
    SchemaDocumentSet, VerifiedSchemaAuthority, encode_schema_authority, normalize_documents,
    project, resolve,
};
use type_bridge_schema_codegen::{
    CEmitter, GeneratedPackage, PythonEmitter, RustEmitter, TypeScriptEmitter,
};

mod support;

#[derive(Debug, Eq, PartialEq)]
struct CanonicalProjectionIds {
    models: BTreeSet<TypeId>,
    owns: BTreeSet<OwnsFactId>,
    roles: BTreeSet<(TypeId, RoleId)>,
    structs: BTreeSet<StructId>,
    functions: BTreeSet<FunctionId>,
    plays: BTreeSet<PlaysFactId>,
}

fn canonical_ids(projection: &RuntimeProjection) -> CanonicalProjectionIds {
    CanonicalProjectionIds {
        models: projection.models().keys().cloned().collect(),
        owns: projection
            .models()
            .values()
            .flat_map(|model| model.query_tokens().fields().keys().cloned())
            .collect(),
        roles: projection
            .models()
            .iter()
            .flat_map(|(owner, model)| {
                model
                    .query_tokens()
                    .roles()
                    .keys()
                    .map(|role| (owner.clone(), role.clone()))
            })
            .collect(),
        structs: projection.structs().keys().cloned().collect(),
        functions: projection.functions().keys().cloned().collect(),
        plays: projection.playing_facts().keys().cloned().collect(),
    }
}

fn missing_resource(resources: &[CodeResourceDigest]) -> Vec<CodeResourceDigest> {
    assert!(
        !resources.is_empty(),
        "emitter must declare fixed resource evidence"
    );
    resources.iter().skip(1).cloned().collect()
}

fn mutated_resource(resources: &[CodeResourceDigest]) -> Vec<CodeResourceDigest> {
    assert!(
        !resources.is_empty(),
        "emitter must declare fixed resource evidence"
    );
    let mut mutated = resources.to_vec();
    let id = mutated[0].id().as_str().to_owned();
    mutated[0] = CodeResourceDigest::from_bytes(id, b"mutated cross-binding evidence").unwrap();
    mutated.sort_by(|left, right| left.id().cmp(right.id()));
    mutated
}

fn assert_embeds_fingerprints(
    package: &GeneratedPackage,
    schema_path: &str,
    projection: &RuntimeProjection,
) {
    let source = str::from_utf8(
        package
            .get(schema_path)
            .expect("schema evidence file is emitted"),
    )
    .expect("generated schema evidence is UTF-8");
    let semantic_digest = projection
        .semantic_fingerprint()
        .as_fingerprint()
        .digest()
        .to_hex();
    let projection_digest = projection
        .projection_fingerprint()
        .as_fingerprint()
        .digest()
        .to_hex();

    assert!(source.contains("SEMANTIC_SCHEMA_FINGERPRINT_JSON"));
    assert!(source.contains("PROJECTION_FINGERPRINT_JSON"));
    assert!(source.contains("RUNTIME_PROJECTION_JSON"));
    assert!(source.contains(&semantic_digest));
    assert!(source.contains(&projection_digest));
}

fn assert_embeds_authority(
    package: &GeneratedPackage,
    authority_path: &str,
    authority: &VerifiedSchemaAuthority,
) {
    let source = str::from_utf8(
        package
            .get(authority_path)
            .expect("private authority source is emitted"),
    )
    .expect("private authority source is UTF-8");
    let envelope = String::from_utf8(encode_schema_authority(authority)).unwrap();
    let escaped = String::from_utf8(to_canonical_json(&envelope).unwrap()).unwrap();
    assert!(
        source.contains(&escaped),
        "{authority_path} did not embed exact authority bytes"
    );
}

#[test]
fn shared_schema_preserves_canonical_ids_and_target_specific_evidence() {
    let documents = SchemaDocumentSet::parse([(
        DocumentId::new("cross-binding.yaml").unwrap(),
        include_str!("acceptance/schema.yaml"),
    )])
    .unwrap();
    let declared = normalize_documents(&documents).unwrap();
    let profile = SemanticProfileId::new("typedb-3.12.1/v1").unwrap();
    let resolved = resolve(&declared, &profile).unwrap();
    let authority = support::authority(include_str!("acceptance/schema.yaml"));

    let python_emitter = PythonEmitter::new();
    let python_handlers = python_emitter.generator_handlers_for(&resolved);
    let python_resources = python_emitter.code_resources_for(&resolved).unwrap();
    let python = project(
        &resolved,
        BindingTarget::Python,
        &ProjectionConfig::python(),
        &python_handlers,
        &python_resources,
    )
    .unwrap();

    let typescript_emitter = TypeScriptEmitter::new();
    let typescript_handlers = typescript_emitter.generator_handlers_for(&resolved);
    let typescript_resources = typescript_emitter.code_resources_for(&resolved).unwrap();
    let typescript = project(
        &resolved,
        BindingTarget::TypeScript,
        &ProjectionConfig::typescript(),
        &typescript_handlers,
        &typescript_resources,
    )
    .unwrap();

    let rust_emitter = RustEmitter::new();
    let rust_handlers = rust_emitter.generator_handlers_for(&resolved);
    let rust_resources = rust_emitter.code_resources_for(&resolved).unwrap();
    let rust = project(
        &resolved,
        BindingTarget::Rust,
        &ProjectionConfig::rust(),
        &rust_handlers,
        &rust_resources,
    )
    .unwrap();

    let c_emitter = CEmitter::new();
    let c_handlers = c_emitter.generator_handlers_for(&resolved);
    let c_resources = c_emitter.code_resources_for(&resolved).unwrap();
    let c = project(
        &resolved,
        BindingTarget::C,
        &ProjectionConfig::c(CSymbolPrefix::new("cross_binding").unwrap()),
        &c_handlers,
        &c_resources,
    )
    .unwrap();

    let python_ids = canonical_ids(&python);
    assert_eq!(python_ids, canonical_ids(&typescript));
    assert_eq!(python_ids, canonical_ids(&rust));
    assert_eq!(python_ids, canonical_ids(&c));
    assert_eq!(
        python.semantic_fingerprint(),
        typescript.semantic_fingerprint()
    );
    assert_eq!(python.semantic_fingerprint(), rust.semantic_fingerprint());
    assert_eq!(python.semantic_fingerprint(), c.semantic_fingerprint());
    assert_ne!(
        python.projection_fingerprint(),
        typescript.projection_fingerprint()
    );
    assert_ne!(
        python.projection_fingerprint(),
        rust.projection_fingerprint()
    );
    assert_ne!(
        typescript.projection_fingerprint(),
        rust.projection_fingerprint()
    );
    for other in [&python, &typescript, &rust] {
        assert_ne!(other.projection_fingerprint(), c.projection_fingerprint());
    }

    let python_package = python_emitter.emit(&python, &authority).unwrap();
    let typescript_package = typescript_emitter.emit(&typescript, &authority).unwrap();
    let rust_package = rust_emitter.emit(&rust, &authority).unwrap();
    let c_package = c_emitter.emit(&c, &authority).unwrap();
    assert_embeds_fingerprints(&python_package, "_schema.py", &python);
    assert_embeds_fingerprints(&typescript_package, "src/schema.ts", &typescript);
    assert_embeds_fingerprints(&rust_package, "src/schema.rs", &rust);
    assert_embeds_authority(&python_package, "_authority.py", &authority);
    assert_embeds_authority(&typescript_package, "src/authority.ts", &authority);
    assert_embeds_authority(&rust_package, "src/schema.rs", &authority);
    assert!(c_package.get("include/cross_binding/models.h").is_some());
    assert!(c_package.get("src/models.c").is_some());

    let foreign_authority =
        support::authority("format: typebridge.schema/v2\nentities:\n  foreign-workspace: {}\n");
    for error in [
        python_emitter
            .emit(&python, &foreign_authority)
            .unwrap_err(),
        typescript_emitter
            .emit(&typescript, &foreign_authority)
            .unwrap_err(),
        rust_emitter.emit(&rust, &foreign_authority).unwrap_err(),
        c_emitter.emit(&c, &foreign_authority).unwrap_err(),
    ] {
        assert_eq!(error.code().as_str(), "schema_codegen_authority_mismatch");
    }

    for resources in [
        missing_resource(&python_resources),
        mutated_resource(&python_resources),
    ] {
        let invalid = project(
            &resolved,
            BindingTarget::Python,
            &ProjectionConfig::python(),
            &python_handlers,
            &resources,
        )
        .unwrap();
        assert_eq!(
            python_emitter
                .emit(&invalid, &authority)
                .unwrap_err()
                .code()
                .as_str(),
            "python_emitter_evidence_mismatch",
        );
    }

    for resources in [
        missing_resource(&typescript_resources),
        mutated_resource(&typescript_resources),
    ] {
        let invalid = project(
            &resolved,
            BindingTarget::TypeScript,
            &ProjectionConfig::typescript(),
            &typescript_handlers,
            &resources,
        )
        .unwrap();
        assert_eq!(
            typescript_emitter
                .emit(&invalid, &authority)
                .unwrap_err()
                .code()
                .as_str(),
            "typescript_emitter_evidence_mismatch",
        );
    }

    for resources in [
        missing_resource(&rust_resources),
        mutated_resource(&rust_resources),
    ] {
        let invalid = project(
            &resolved,
            BindingTarget::Rust,
            &ProjectionConfig::rust(),
            &rust_handlers,
            &resources,
        )
        .unwrap();
        assert_eq!(
            rust_emitter
                .emit(&invalid, &authority)
                .unwrap_err()
                .code()
                .as_str(),
            "rust_emitter_evidence_mismatch",
        );
    }

    for resources in [
        missing_resource(&c_resources),
        mutated_resource(&c_resources),
    ] {
        let invalid = project(
            &resolved,
            BindingTarget::C,
            &ProjectionConfig::c(CSymbolPrefix::new("cross_binding").unwrap()),
            &c_handlers,
            &resources,
        )
        .unwrap();
        assert_eq!(
            c_emitter
                .emit(&invalid, &authority)
                .unwrap_err()
                .code()
                .as_str(),
            "c_emitter_evidence_mismatch",
        );
    }
}

#[test]
fn exact_split_yaml_docs_fixture_emits_every_binding_target() {
    let source = include_str!("../../../../docs/fixtures/split-yaml-v1/schema/fixture.yaml");
    let documents =
        SchemaDocumentSet::parse([(DocumentId::new("fixture.yaml").unwrap(), source)]).unwrap();
    let declared = normalize_documents(&documents).unwrap();
    let profile = SemanticProfileId::new("typedb-3.12.1/v1").unwrap();
    let resolved = resolve(&declared, &profile).unwrap();
    let authority = support::authority(source);

    let python_emitter = PythonEmitter::new();
    let python = project(
        &resolved,
        BindingTarget::Python,
        &ProjectionConfig::python(),
        &python_emitter.generator_handlers_for(&resolved),
        &python_emitter.code_resources_for(&resolved).unwrap(),
    )
    .unwrap();
    let python_package = python_emitter.emit(&python, &authority).unwrap();
    assert!(python_package.get("_models.py").is_some());

    let typescript_emitter = TypeScriptEmitter::new();
    let typescript = project(
        &resolved,
        BindingTarget::TypeScript,
        &ProjectionConfig::typescript(),
        &typescript_emitter.generator_handlers_for(&resolved),
        &typescript_emitter.code_resources_for(&resolved).unwrap(),
    )
    .unwrap();
    let typescript_package = typescript_emitter.emit(&typescript, &authority).unwrap();
    assert!(typescript_package.get("src/models.ts").is_some());

    let rust_emitter = RustEmitter::new();
    let rust = project(
        &resolved,
        BindingTarget::Rust,
        &ProjectionConfig::rust(),
        &rust_emitter.generator_handlers_for(&resolved),
        &rust_emitter.code_resources_for(&resolved).unwrap(),
    )
    .unwrap();
    let rust_package = rust_emitter.emit(&rust, &authority).unwrap();
    let rust_read = str::from_utf8(rust_package.get("src/read.rs").unwrap()).unwrap();
    assert!(rust_read.contains("pub enum IdentifierFamily"));
    assert!(rust_read.contains("pub fn value(&self) -> &String"));

    let c_emitter = CEmitter::new();
    let c = project(
        &resolved,
        BindingTarget::C,
        &ProjectionConfig::c(CSymbolPrefix::new("docs_fixture").unwrap()),
        &c_emitter.generator_handlers_for(&resolved),
        &c_emitter.code_resources_for(&resolved).unwrap(),
    )
    .unwrap();
    let c_package = c_emitter.emit(&c, &authority).unwrap();
    assert!(c_package.get("include/docs_fixture/models.h").is_some());
    assert!(c_package.get("src/models.c").is_some());
}