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().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}