Skip to main content

type_bridge_schema_codegen/
lib.rs

1//! Deterministic emitters over validated binding-neutral runtime projections.
2
3#![deny(missing_docs)]
4
5use std::collections::BTreeMap;
6
7mod c;
8mod package;
9mod python;
10mod rust;
11mod typescript;
12
13pub use c::CEmitter;
14pub use package::{GeneratedPackage, MIGRATION_HISTORY_BUNDLE_RESOURCE};
15pub use python::PythonEmitter;
16pub use rust::RustEmitter;
17pub use typescript::TypeScriptEmitter;
18
19use type_bridge_contract::diagnostic::{Diagnostic, DiagnosticCategory, DiagnosticCode};
20use type_bridge_contract::projection::{
21    BindingTarget, ModelProjection, ProjectedAnnotation, ProjectionConfig, RuntimeProjection,
22};
23use type_bridge_contract::schema::{
24    AnnotationFactId, AnnotationKindId, SchemaAnnotationValue, encode_declared_schema,
25};
26use type_bridge_schema::{
27    ResolvedSchema, VerifiedSchemaAuthority, encode_schema_authority, project,
28};
29
30/// Verify that a runtime projection carries the exact feature-selected
31/// handler and fixed-resource evidence emitted for its binding target.
32///
33/// This is the shared package-admission boundary for generated runtimes. It
34/// derives all evidence from the verified schema authority, recomputes the
35/// complete projection, and rejects self-consistent projections forged with a
36/// legacy or foreign emitter ledger.
37pub fn verify_projection_evidence(
38    authority: &VerifiedSchemaAuthority,
39    projection: &RuntimeProjection,
40) -> Result<(), Diagnostic> {
41    let schema = authority.resolved_schema();
42    let reproject =
43        |target,
44         handlers: &[type_bridge_contract::projection::ProjectionHandler],
45         resources: &[type_bridge_contract::projection::CodeResourceDigest]| {
46            project(schema, target, projection.config(), handlers, resources).map_err(|_| {
47            invalid(
48                "schema_codegen_projection_evidence_mismatch",
49                "runtime projection cannot be reproduced from the verified schema authority and emitter evidence",
50            )
51        })
52        };
53    let expected = match projection.target() {
54        BindingTarget::Python => {
55            if projection.config().python_naming_policy()
56                != ProjectionConfig::python().python_naming_policy()
57            {
58                return Err(invalid(
59                    "schema_codegen_projection_evidence_mismatch",
60                    "Python runtime projection carries a foreign target configuration",
61                ));
62            }
63            let emitter = PythonEmitter::new();
64            reproject(
65                BindingTarget::Python,
66                &emitter.generator_handlers_for(schema),
67                &emitter.code_resources_for(schema)?,
68            )?
69        }
70        BindingTarget::TypeScript => {
71            if projection.config().typescript_naming_policy()
72                != ProjectionConfig::typescript().typescript_naming_policy()
73            {
74                return Err(invalid(
75                    "schema_codegen_projection_evidence_mismatch",
76                    "TypeScript runtime projection carries a foreign target configuration",
77                ));
78            }
79            let emitter = TypeScriptEmitter::new();
80            reproject(
81                BindingTarget::TypeScript,
82                &emitter.generator_handlers_for(schema),
83                &emitter.code_resources_for(schema)?,
84            )?
85        }
86        BindingTarget::Rust => {
87            if projection.config().rust_naming_policy()
88                != ProjectionConfig::rust().rust_naming_policy()
89                || projection.config().rust_create_policy()
90                    != ProjectionConfig::rust().rust_create_policy()
91            {
92                return Err(invalid(
93                    "schema_codegen_projection_evidence_mismatch",
94                    "Rust runtime projection carries a foreign target configuration",
95                ));
96            }
97            let emitter = RustEmitter::new();
98            reproject(
99                BindingTarget::Rust,
100                &emitter.generator_handlers_for(schema),
101                &emitter.code_resources_for(schema)?,
102            )?
103        }
104        BindingTarget::C => {
105            if projection.config().c_symbol_prefix().is_none() {
106                return Err(invalid(
107                    "schema_codegen_projection_evidence_mismatch",
108                    "C runtime projection omits its generated link-namespace configuration",
109                ));
110            }
111            let emitter = CEmitter::new();
112            reproject(
113                BindingTarget::C,
114                &emitter.generator_handlers_for(schema),
115                &emitter.code_resources_for(schema)?,
116            )?
117        }
118        _ => {
119            return Err(invalid(
120                "schema_codegen_projection_evidence_mismatch",
121                "runtime projection targets an unsupported generated binding",
122            ));
123        }
124    };
125    if &expected != projection {
126        return Err(invalid(
127            "schema_codegen_projection_evidence_mismatch",
128            "runtime projection differs from the exact feature-selected emitter evidence",
129        ));
130    }
131    Ok(())
132}
133
134#[derive(Clone, Debug, Eq, PartialEq)]
135struct EmbeddedAuthority {
136    canonical_envelope_json: String,
137    declared_schema_json: String,
138    managed_scope_id: String,
139    semantic_profile_id: String,
140}
141
142fn embedded_authority(
143    projection: &type_bridge_contract::projection::RuntimeProjection,
144    authority: &VerifiedSchemaAuthority,
145) -> Result<EmbeddedAuthority, Diagnostic> {
146    if projection.semantic_fingerprint() != authority.resolved_schema().semantic_fingerprint() {
147        return Err(invalid(
148            "schema_codegen_authority_mismatch",
149            "runtime projection and verified schema authority have different semantic fingerprints",
150        ));
151    }
152
153    let canonical_envelope_json =
154        String::from_utf8(encode_schema_authority(authority)).map_err(|_| {
155            invalid(
156                "schema_codegen_non_utf8_authority",
157                "canonical schema-authority JSON must be UTF-8",
158            )
159        })?;
160    let declared_schema_json =
161        String::from_utf8(encode_declared_schema(authority.declared_schema())?).map_err(|_| {
162            invalid(
163                "schema_codegen_non_utf8_declared_schema",
164                "canonical declared-schema JSON must be UTF-8",
165            )
166        })?;
167
168    Ok(EmbeddedAuthority {
169        canonical_envelope_json,
170        declared_schema_json,
171        managed_scope_id: authority.managed_scope().id().as_str().to_owned(),
172        semantic_profile_id: authority.semantic_profile().id().as_str().to_owned(),
173    })
174}
175
176fn invalid(code: &'static str, message: impl Into<String>) -> Diagnostic {
177    Diagnostic::new(
178        DiagnosticCategory::InvalidContract,
179        DiagnosticCode::new(code).expect("schema-codegen diagnostic code is valid"),
180        message,
181    )
182}
183
184fn resolved_schema_uses_ordered_collections(schema: &ResolvedSchema) -> bool {
185    schema.types().values().any(|model| {
186        model
187            .owns()
188            .values()
189            .any(|owns| !owns.collection_mode().is_unordered())
190            || model
191                .relates()
192                .values()
193                .any(|relates| !relates.collection_mode().is_unordered())
194    })
195}
196
197fn projection_uses_ordered_collections(projection: &RuntimeProjection) -> bool {
198    projection.models().values().any(|model| {
199        model
200            .query_tokens()
201            .fields()
202            .values()
203            .any(|field| !field.multiplicity().collection_mode().is_unordered())
204            || model
205                .query_tokens()
206                .roles()
207                .values()
208                .any(|role| !role.multiplicity().collection_mode().is_unordered())
209    })
210}
211
212fn model_documentation(model: &ModelProjection) -> Option<String> {
213    let type_doc = documentation_annotation(model.declaration().annotations());
214    let direct_sub_doc = model
215        .declaration()
216        .direct_sub()
217        .and_then(|sub| documentation_annotation(sub.annotations()).map(|doc| (sub, doc)));
218
219    match (type_doc, direct_sub_doc) {
220        (None, None) => None,
221        (Some(type_doc), None) => Some(type_doc.to_owned()),
222        (type_doc, Some((sub, sub_doc))) => {
223            let edge_doc = format!(
224                "Direct subtype of `{}`:\n{sub_doc}",
225                sub.id().supertype().label()
226            );
227            Some(match type_doc {
228                Some(type_doc) => format!("{type_doc}\n\n{edge_doc}"),
229                None => edge_doc,
230            })
231        }
232    }
233}
234
235fn documentation_annotation(
236    annotations: &BTreeMap<AnnotationFactId, ProjectedAnnotation>,
237) -> Option<&str> {
238    annotations.values().find_map(|annotation| {
239        if annotation.id().kind() != &AnnotationKindId::Doc {
240            return None;
241        }
242        match annotation.value() {
243            SchemaAnnotationValue::Doc(doc) => Some(doc.as_str()),
244            _ => None,
245        }
246    })
247}