type_bridge_schema_codegen/
lib.rs1#![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
30pub 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() != &ProjectionConfig::python() {
56 return Err(invalid(
57 "schema_codegen_projection_evidence_mismatch",
58 "Python runtime projection carries a foreign target configuration",
59 ));
60 }
61 let emitter = PythonEmitter::new();
62 reproject(
63 BindingTarget::Python,
64 &emitter.generator_handlers_for(schema),
65 &emitter.code_resources_for(schema)?,
66 )?
67 }
68 BindingTarget::TypeScript => {
69 if projection.config() != &ProjectionConfig::typescript() {
70 return Err(invalid(
71 "schema_codegen_projection_evidence_mismatch",
72 "TypeScript runtime projection carries a foreign target configuration",
73 ));
74 }
75 let emitter = TypeScriptEmitter::new();
76 reproject(
77 BindingTarget::TypeScript,
78 &emitter.generator_handlers_for(schema),
79 &emitter.code_resources_for(schema)?,
80 )?
81 }
82 BindingTarget::Rust => {
83 if projection.config() != &ProjectionConfig::rust() {
84 return Err(invalid(
85 "schema_codegen_projection_evidence_mismatch",
86 "Rust runtime projection carries a foreign target configuration",
87 ));
88 }
89 let emitter = RustEmitter::new();
90 reproject(
91 BindingTarget::Rust,
92 &emitter.generator_handlers_for(schema),
93 &emitter.code_resources_for(schema)?,
94 )?
95 }
96 BindingTarget::C => {
97 if projection.config().c_symbol_prefix().is_none() {
98 return Err(invalid(
99 "schema_codegen_projection_evidence_mismatch",
100 "C runtime projection omits its generated link-namespace configuration",
101 ));
102 }
103 let emitter = CEmitter::new();
104 reproject(
105 BindingTarget::C,
106 &emitter.generator_handlers_for(schema),
107 &emitter.code_resources_for(schema)?,
108 )?
109 }
110 _ => {
111 return Err(invalid(
112 "schema_codegen_projection_evidence_mismatch",
113 "runtime projection targets an unsupported generated binding",
114 ));
115 }
116 };
117 if &expected != projection {
118 return Err(invalid(
119 "schema_codegen_projection_evidence_mismatch",
120 "runtime projection differs from the exact feature-selected emitter evidence",
121 ));
122 }
123 Ok(())
124}
125
126#[derive(Clone, Debug, Eq, PartialEq)]
127struct EmbeddedAuthority {
128 canonical_envelope_json: String,
129 declared_schema_json: String,
130 managed_scope_id: String,
131 semantic_profile_id: String,
132}
133
134fn embedded_authority(
135 projection: &type_bridge_contract::projection::RuntimeProjection,
136 authority: &VerifiedSchemaAuthority,
137) -> Result<EmbeddedAuthority, Diagnostic> {
138 if projection.semantic_fingerprint() != authority.resolved_schema().semantic_fingerprint() {
139 return Err(invalid(
140 "schema_codegen_authority_mismatch",
141 "runtime projection and verified schema authority have different semantic fingerprints",
142 ));
143 }
144
145 let canonical_envelope_json =
146 String::from_utf8(encode_schema_authority(authority)).map_err(|_| {
147 invalid(
148 "schema_codegen_non_utf8_authority",
149 "canonical schema-authority JSON must be UTF-8",
150 )
151 })?;
152 let declared_schema_json =
153 String::from_utf8(encode_declared_schema(authority.declared_schema())?).map_err(|_| {
154 invalid(
155 "schema_codegen_non_utf8_declared_schema",
156 "canonical declared-schema JSON must be UTF-8",
157 )
158 })?;
159
160 Ok(EmbeddedAuthority {
161 canonical_envelope_json,
162 declared_schema_json,
163 managed_scope_id: authority.managed_scope().id().as_str().to_owned(),
164 semantic_profile_id: authority.semantic_profile().id().as_str().to_owned(),
165 })
166}
167
168fn invalid(code: &'static str, message: impl Into<String>) -> Diagnostic {
169 Diagnostic::new(
170 DiagnosticCategory::InvalidContract,
171 DiagnosticCode::new(code).expect("schema-codegen diagnostic code is valid"),
172 message,
173 )
174}
175
176fn resolved_schema_uses_ordered_collections(schema: &ResolvedSchema) -> bool {
177 schema.types().values().any(|model| {
178 model
179 .owns()
180 .values()
181 .any(|owns| !owns.collection_mode().is_unordered())
182 || model
183 .relates()
184 .values()
185 .any(|relates| !relates.collection_mode().is_unordered())
186 })
187}
188
189fn projection_uses_ordered_collections(projection: &RuntimeProjection) -> bool {
190 projection.models().values().any(|model| {
191 model
192 .query_tokens()
193 .fields()
194 .values()
195 .any(|field| !field.multiplicity().collection_mode().is_unordered())
196 || model
197 .query_tokens()
198 .roles()
199 .values()
200 .any(|role| !role.multiplicity().collection_mode().is_unordered())
201 })
202}
203
204fn model_documentation(model: &ModelProjection) -> Option<String> {
205 let type_doc = documentation_annotation(model.declaration().annotations());
206 let direct_sub_doc = model
207 .declaration()
208 .direct_sub()
209 .and_then(|sub| documentation_annotation(sub.annotations()).map(|doc| (sub, doc)));
210
211 match (type_doc, direct_sub_doc) {
212 (None, None) => None,
213 (Some(type_doc), None) => Some(type_doc.to_owned()),
214 (type_doc, Some((sub, sub_doc))) => {
215 let edge_doc = format!(
216 "Direct subtype of `{}`:\n{sub_doc}",
217 sub.id().supertype().label()
218 );
219 Some(match type_doc {
220 Some(type_doc) => format!("{type_doc}\n\n{edge_doc}"),
221 None => edge_doc,
222 })
223 }
224 }
225}
226
227fn documentation_annotation(
228 annotations: &BTreeMap<AnnotationFactId, ProjectedAnnotation>,
229) -> Option<&str> {
230 annotations.values().find_map(|annotation| {
231 if annotation.id().kind() != &AnnotationKindId::Doc {
232 return None;
233 }
234 match annotation.value() {
235 SchemaAnnotationValue::Doc(doc) => Some(doc.as_str()),
236 _ => None,
237 }
238 })
239}