1use std::collections::{BTreeMap, BTreeSet};
2
3use type_bridge_contract::diagnostic::{Diagnostic, DiagnosticCategory};
4use type_bridge_contract::fingerprint::{CanonicalizationVersion, Fingerprint, FingerprintDomain};
5use type_bridge_contract::id::{AttributeId, RoleId, TypeId, TypeKind};
6use type_bridge_contract::projection::{
7 BindingTarget, CNamingPolicy, CodeResourceDigest, CompleteReadProjection,
8 CreateFieldProjection, CreateProjection, CreateRoleProjection, DeclarationProjection,
9 DeclaredRoleProjection, DirectSubProjection, EmissionPlan, FieldTokenProjection,
10 FunctionParameterProjection, FunctionProjection, FunctionReturnElementProjection,
11 FunctionReturnProjection, ModelProjection, PlayingProjection, ProjectedAnnotation,
12 ProjectedModelForm, ProjectedModelUse, ProjectedMultiplicity, ProjectedTypeRef,
13 ProjectionConfig, ProjectionHandler, QueryTokenProjection, ReadFieldProjection,
14 ReadRoleProjection, ReferenceReadProjection, RoleTokenProjection, RuntimeProjection,
15 RustCreatePolicy, StructFieldProjection, StructProjection, TYPESCRIPT_MODEL_RESERVED_NAMES,
16 TargetIdentifier,
17};
18use type_bridge_contract::schema::{
19 AnnotationFactId, AnnotationKindId, AnnotationSubjectId, FunctionReturnMode, OwnsFactId,
20 RelatesFactId, SchemaAnnotationValue, SchemaDiagnostic, SchemaDiagnostics, TypeReference,
21 ValueFactId,
22};
23
24use crate::resolve::{EffectiveRelates, ResolvedSchema, ResolvedType};
25
26const C_MAX_TARGET_IDENTIFIER_BYTES: usize = 255;
27const C_IDENTIFIER_DIGEST_DOMAIN: &str = "typebridge.binding.c-identifier";
28const C_IDENTIFIER_CANONICALIZATION: &str = "typebridge.c-identifier/v1";
29
30fn no_source(error: Diagnostic) -> SchemaDiagnostics {
31 SchemaDiagnostics::one(SchemaDiagnostic::new(error, None))
32}
33
34fn projection_error(code: &'static str, message: impl Into<String>) -> SchemaDiagnostics {
35 crate::diagnostic::diagnostic(DiagnosticCategory::InvalidContract, code, message, None)
36}
37
38fn python_class_name(label: &str) -> String {
39 label
40 .replace('_', "-")
41 .split('-')
42 .map(|part| {
43 let mut chars = part.chars();
44 match chars.next() {
45 Some(first) => {
46 let mut output = first.to_uppercase().collect::<String>();
47 let rest = chars.as_str();
48 let uniform = part.chars().all(char::is_uppercase)
49 || part.chars().all(char::is_lowercase);
50 if uniform {
51 output.push_str(&rest.to_lowercase());
52 } else {
53 output.push_str(rest);
54 }
55 output
56 }
57 None => String::new(),
58 }
59 })
60 .collect()
61}
62
63fn python_member_name(label: &str) -> String {
64 label.replace('-', "_")
65}
66
67fn typescript_member_name(label: &str) -> String {
68 let class_name = python_class_name(label);
69 let mut chars = class_name.chars();
70 chars.next().map_or_else(String::new, |first| {
71 first.to_lowercase().chain(chars).collect()
72 })
73}
74
75fn rust_member_name(label: &str) -> String {
76 label.replace('-', "_").to_ascii_lowercase()
77}
78
79fn c_v1_escape(label: &str) -> String {
80 let mut escaped = String::with_capacity(label.len());
81 for byte in label.bytes() {
82 match byte {
83 b'a'..=b'y' | b'A'..=b'Z' | b'0'..=b'9' => escaped.push(char::from(byte)),
84 b'z' => escaped.push_str("zz"),
85 b'-' => escaped.push_str("zh"),
86 b'_' => escaped.push_str("zu"),
87 _ => escaped.push_str(&format!("zx{byte:02x}")),
88 }
89 }
90 escaped
91}
92
93fn c_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
94 let bounded = if value.len() <= C_MAX_TARGET_IDENTIFIER_BYTES {
95 value
96 } else {
97 let digest = Fingerprint::compute(
98 FingerprintDomain::new(C_IDENTIFIER_DIGEST_DOMAIN)
99 .expect("the built-in C identifier fingerprint domain is valid"),
100 CanonicalizationVersion::new(C_IDENTIFIER_CANONICALIZATION)
101 .expect("the built-in C identifier canonicalization is valid"),
102 None,
103 value.as_bytes(),
104 )
105 .digest()
106 .to_hex();
107 let mut stem_bytes = C_MAX_TARGET_IDENTIFIER_BYTES - digest.len() - 1;
108 while value.as_bytes().get(stem_bytes - 1) == Some(&b'_') {
109 stem_bytes -= 1;
110 }
111 format!("{}_{}", &value[..stem_bytes], digest)
112 };
113 TargetIdentifier::c(bounded).map_err(no_source)
114}
115
116fn python_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
117 if matches!(value.as_str(), "iid" | "model_config" | "model_fields")
118 || value.starts_with("__") && value.ends_with("__")
119 {
120 return Err(projection_error(
121 "reserved_python_projection_identifier",
122 "projected Python name collides with generated runtime state",
123 ));
124 }
125 TargetIdentifier::python(value).map_err(no_source)
126}
127
128fn typescript_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
129 if matches!(
130 value.as_str(),
131 "iid"
132 | "typeToken"
133 | "fields"
134 | "roles"
135 | "plays"
136 | "create"
137 | "reference"
138 | "prototype"
139 | "__proto__"
140 ) {
141 return Err(projection_error(
142 "reserved_typescript_projection_identifier",
143 "projected TypeScript name collides with generated runtime state",
144 ));
145 }
146 TargetIdentifier::typescript(value).map_err(no_source)
147}
148
149fn rust_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
150 TargetIdentifier::rust(value).map_err(no_source)
151}
152
153struct ProjectionNamer<'a> {
154 target: BindingTarget,
155 c_symbol_prefix: Option<&'a str>,
156}
157
158impl<'a> ProjectionNamer<'a> {
159 fn new(target: BindingTarget, config: &'a ProjectionConfig) -> Result<Self, SchemaDiagnostics> {
160 let c_symbol_prefix = if target == BindingTarget::C {
161 if config.c_naming_policy() != Some(CNamingPolicy::TypeBridgeV1) {
162 return Err(projection_error(
163 "unsupported_c_naming_policy",
164 "C projection does not recognize the configured naming policy",
165 ));
166 }
167 Some(
168 config
169 .c_symbol_prefix()
170 .ok_or_else(|| {
171 projection_error(
172 "missing_c_symbol_prefix",
173 "C projection configuration omits its symbol prefix",
174 )
175 })?
176 .as_str(),
177 )
178 } else {
179 None
180 };
181 Ok(Self {
182 target,
183 c_symbol_prefix,
184 })
185 }
186
187 fn c_symbol_prefix(&self) -> &str {
188 self.c_symbol_prefix
189 .expect("C naming is only requested for a validated C configuration")
190 }
191
192 fn c_label_identifier(&self, label: &str) -> Result<TargetIdentifier, SchemaDiagnostics> {
193 c_identifier(format!("{}_{}", self.c_symbol_prefix(), c_v1_escape(label)))
194 }
195
196 fn class_identifier(&self, label: &str) -> Result<TargetIdentifier, SchemaDiagnostics> {
197 let value = python_class_name(label);
198 if self.target == BindingTarget::Python {
199 python_identifier(value)
200 } else if self.target == BindingTarget::TypeScript {
201 typescript_identifier(value)
202 } else if self.target == BindingTarget::Rust {
203 rust_identifier(value)
204 } else if self.target == BindingTarget::C {
205 self.c_label_identifier(label)
206 } else {
207 Err(projection_error(
208 "unsupported_binding_projection_target",
209 "schema projection does not support this binding target",
210 ))
211 }
212 }
213
214 fn member_identifier(&self, label: &str) -> Result<TargetIdentifier, SchemaDiagnostics> {
215 if self.target == BindingTarget::Python {
216 python_identifier(python_member_name(label))
217 } else if self.target == BindingTarget::TypeScript {
218 typescript_identifier(typescript_member_name(label))
219 } else if self.target == BindingTarget::Rust {
220 rust_identifier(rust_member_name(label))
221 } else if self.target == BindingTarget::C {
222 self.c_label_identifier(label)
223 } else {
224 Err(projection_error(
225 "unsupported_binding_projection_target",
226 "schema projection does not support this binding target",
227 ))
228 }
229 }
230
231 fn model_member_identifier(&self, label: &str) -> Result<TargetIdentifier, SchemaDiagnostics> {
232 if self.target == BindingTarget::TypeScript {
233 let mut name = typescript_member_name(label);
234 if TYPESCRIPT_MODEL_RESERVED_NAMES.contains(&name.as_str()) {
235 name.push('_');
238 }
239 TargetIdentifier::typescript(name).map_err(no_source)
240 } else {
241 self.member_identifier(label)
242 }
243 }
244
245 fn reference_identifier(
246 &self,
247 name: &TargetIdentifier,
248 ) -> Result<TargetIdentifier, SchemaDiagnostics> {
249 if self.target == BindingTarget::Python {
250 python_identifier(format!("{}Ref", name.as_str()))
251 } else if self.target == BindingTarget::TypeScript {
252 typescript_identifier(format!("{}Ref", name.as_str()))
253 } else if self.target == BindingTarget::Rust {
254 rust_identifier(format!("{}Ref", name.as_str()))
255 } else if self.target == BindingTarget::C {
256 c_identifier(format!("{}_ref", name.as_str()))
257 } else {
258 Err(projection_error(
259 "unsupported_binding_projection_target",
260 "schema projection does not support this binding target",
261 ))
262 }
263 }
264
265 fn query_token_identifier(
266 &self,
267 model_name: &TargetIdentifier,
268 ) -> Result<Option<TargetIdentifier>, SchemaDiagnostics> {
269 if self.target == BindingTarget::Rust {
270 rust_identifier(format!("{}Type", model_name.as_str())).map(Some)
271 } else if self.target == BindingTarget::C {
272 c_identifier(format!("{}_type", model_name.as_str())).map(Some)
273 } else {
274 Ok(None)
275 }
276 }
277
278 fn player_union_identifier(
279 &self,
280 model_name: &TargetIdentifier,
281 role_label: &str,
282 ) -> Result<Option<TargetIdentifier>, SchemaDiagnostics> {
283 if self.target == BindingTarget::Rust {
284 rust_identifier(format!(
285 "{}{}Player",
286 model_name.as_str(),
287 python_class_name(role_label),
288 ))
289 .map(Some)
290 } else if self.target == BindingTarget::C {
291 c_identifier(format!(
292 "{}_{}_player",
293 model_name.as_str(),
294 c_v1_escape(role_label),
295 ))
296 .map(Some)
297 } else {
298 Ok(None)
299 }
300 }
301
302 fn create_identifier(
303 &self,
304 model_name: &TargetIdentifier,
305 ) -> Result<Option<TargetIdentifier>, SchemaDiagnostics> {
306 if self.target == BindingTarget::Rust {
307 rust_identifier(format!("{}Create", model_name.as_str())).map(Some)
308 } else if self.target == BindingTarget::C {
309 c_identifier(format!("{}_create", model_name.as_str())).map(Some)
310 } else {
311 Ok(None)
312 }
313 }
314
315 fn playing_identifier(
316 &self,
317 player_label: &str,
318 relation_label: &str,
319 role_label: &str,
320 ) -> Result<TargetIdentifier, SchemaDiagnostics> {
321 if self.target == BindingTarget::C {
322 c_identifier(format!(
323 "{}_plays_{}_relation_{}_role_{}",
324 self.c_symbol_prefix(),
325 c_v1_escape(player_label),
326 c_v1_escape(relation_label),
327 c_v1_escape(role_label),
328 ))
329 } else {
330 self.member_identifier(&format!(
331 "plays-{player_label}-{relation_label}-{role_label}"
332 ))
333 }
334 }
335
336 fn c_schema_package_identifier(&self) -> Result<Option<TargetIdentifier>, SchemaDiagnostics> {
337 if self.target == BindingTarget::C {
338 c_identifier(format!("{}_schema_package_open", self.c_symbol_prefix())).map(Some)
339 } else {
340 Ok(None)
341 }
342 }
343}
344
345#[derive(Default)]
346struct NameRegistry {
347 names: BTreeMap<(String, String), String>,
348}
349
350impl NameRegistry {
351 fn insert(
352 &mut self,
353 namespace: impl Into<String>,
354 name: &TargetIdentifier,
355 identity: impl Into<String>,
356 ) -> Result<(), SchemaDiagnostics> {
357 let key = (namespace.into(), name.as_str().to_owned());
358 let identity = identity.into();
359 if let Some(previous) = self.names.get(&key) {
360 if previous != &identity {
361 return Err(projection_error(
362 "projection_name_collision",
363 format!(
364 "projected name `{}` in namespace `{}` collides between `{}` and `{}`",
365 key.1, key.0, previous, identity
366 ),
367 ));
368 }
369 } else {
370 self.names.insert(key, identity);
371 }
372 Ok(())
373 }
374}
375
376fn annotations(
377 subject: AnnotationSubjectId,
378 values: &BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
379) -> Result<BTreeMap<AnnotationFactId, ProjectedAnnotation>, SchemaDiagnostics> {
380 values
381 .iter()
382 .map(|(kind, value)| {
383 let id = AnnotationFactId::new(subject.clone(), kind.clone());
384 ProjectedAnnotation::new(id.clone(), value.clone())
385 .map(|annotation| (id, annotation))
386 .map_err(no_source)
387 })
388 .collect()
389}
390
391fn effective_relates_annotation_subject(
392 relates: &EffectiveRelates,
393) -> Result<AnnotationSubjectId, SchemaDiagnostics> {
394 let role = RoleId::new(
395 relates.id().relation().label().as_str(),
396 relates.id().role().label().as_str(),
397 )
398 .map_err(no_source)?;
399 RelatesFactId::new(relates.id().relation().clone(), role)
400 .map(AnnotationSubjectId::Relates)
401 .map_err(no_source)
402}
403
404fn ordered_owns(resolved: &ResolvedType) -> Vec<OwnsFactId> {
405 let mut ordered = Vec::new();
406 let mut seen = BTreeSet::new();
407 for attribute in resolved.owned_attribute_order() {
408 if let Some(owns) = resolved.owns().get(attribute) {
409 seen.insert(attribute.clone());
410 ordered.push(owns.id().clone());
411 }
412 }
413 ordered.extend(
414 resolved
415 .owns()
416 .iter()
417 .filter(|(attribute, _)| !seen.contains(*attribute))
418 .map(|(_, owns)| owns.id().clone()),
419 );
420 ordered
421}
422
423fn read_model_use(id: &TypeId) -> Result<ProjectedModelUse, SchemaDiagnostics> {
424 let form = match id.kind() {
425 TypeKind::Entity => ProjectedModelForm::Complete,
426 TypeKind::Relation => ProjectedModelForm::Reference,
427 TypeKind::Attribute | TypeKind::Struct => {
428 return Err(projection_error(
429 "invalid_projection_reference",
430 "only entity and relation types can be projected as role players",
431 ));
432 }
433 };
434 Ok(ProjectedModelUse::new(id.clone(), form))
435}
436
437fn create_model_uses(id: &TypeId) -> Result<BTreeSet<ProjectedModelUse>, SchemaDiagnostics> {
438 match id.kind() {
439 TypeKind::Entity => Ok(BTreeSet::from([ProjectedModelUse::new(
440 id.clone(),
441 ProjectedModelForm::Complete,
442 )])),
443 TypeKind::Relation => Ok(BTreeSet::from([
444 ProjectedModelUse::new(id.clone(), ProjectedModelForm::Complete),
445 ProjectedModelUse::new(id.clone(), ProjectedModelForm::Reference),
446 ])),
447 TypeKind::Attribute | TypeKind::Struct => Err(projection_error(
448 "invalid_projection_reference",
449 "only entity and relation types can be projected as role players",
450 )),
451 }
452}
453
454fn immediate_specialization(
455 resolved: &ResolvedType,
456 relates: &EffectiveRelates,
457) -> Result<Option<RoleId>, SchemaDiagnostics> {
458 if relates.replaced_roles().is_empty() {
459 return Ok(None);
460 }
461 let mut ranked = relates
462 .replaced_roles()
463 .iter()
464 .filter_map(|role| {
465 resolved
466 .supertypes()
467 .iter()
468 .position(|parent| parent.label() == role.declaring_relation())
469 .map(|distance| (distance, role.clone()))
470 })
471 .collect::<Vec<_>>();
472 ranked.sort();
473 let Some((distance, role)) = ranked.first().cloned() else {
474 return Err(projection_error(
475 "invalid_projection_reference",
476 "specialized role has no declaring relation in the resolved ancestor chain",
477 ));
478 };
479 if ranked.get(1).is_some_and(|next| next.0 == distance) {
480 return Err(projection_error(
481 "ambiguous_role_specialization_projection",
482 "more than one replaced role is nearest to the specializing relation",
483 ));
484 }
485 Ok(Some(role))
486}
487
488fn resolve_type_reference(
489 reference: &TypeReference,
490 resolved: &ResolvedSchema,
491) -> Result<ProjectedTypeRef, SchemaDiagnostics> {
492 match reference {
493 TypeReference::Value(value) => Ok(ProjectedTypeRef::Scalar(*value)),
494 TypeReference::Schema(label) => {
495 let models = resolved
496 .types()
497 .keys()
498 .filter(|id| id.label() == label)
499 .cloned()
500 .collect::<Vec<_>>();
501 let structures = resolved
502 .structs()
503 .keys()
504 .filter(|id| id.label() == label)
505 .cloned()
506 .collect::<Vec<_>>();
507 match (models.as_slice(), structures.as_slice()) {
508 ([model], []) => Ok(ProjectedTypeRef::Model(ProjectedModelUse::new(
509 model.clone(),
510 ProjectedModelForm::Complete,
511 ))),
512 ([], [structure]) => Ok(ProjectedTypeRef::Struct(structure.clone())),
513 ([], []) => Err(projection_error(
514 "unknown_projection_type_reference",
515 "function signature references an unknown projected type",
516 )),
517 _ => Err(projection_error(
518 "ambiguous_projection_type_reference",
519 "function signature label resolves to more than one projected type",
520 )),
521 }
522 }
523 }
524}
525
526fn function_return(
527 returns: &FunctionReturnMode,
528 resolved: &ResolvedSchema,
529) -> Result<FunctionReturnProjection, SchemaDiagnostics> {
530 let project = |element: &type_bridge_contract::schema::FunctionReturnElement| {
531 Ok(FunctionReturnElementProjection::new(
532 resolve_type_reference(element.type_ref(), resolved)?,
533 element.optional(),
534 ))
535 };
536 match returns {
537 FunctionReturnMode::Scalar(element) => {
538 Ok(FunctionReturnProjection::Scalar(project(element)?))
539 }
540 FunctionReturnMode::Tuple(elements) => elements
541 .iter()
542 .map(project)
543 .collect::<Result<Vec<_>, _>>()
544 .map(FunctionReturnProjection::Tuple),
545 FunctionReturnMode::Stream(elements) => elements
546 .iter()
547 .map(project)
548 .collect::<Result<Vec<_>, _>>()
549 .map(FunctionReturnProjection::Stream),
550 }
551}
552
553fn link_components(resolved: &ResolvedSchema) -> Result<Vec<BTreeSet<TypeId>>, SchemaDiagnostics> {
554 let components = resolved.dependency_graph().strongly_connected_components();
555 let mut membership = BTreeMap::new();
556 for (index, component) in components.iter().enumerate() {
557 for member in component {
558 if membership.insert(member.clone(), index).is_some() {
559 return Err(projection_error(
560 "invalid_projection_emission_plan",
561 "resolver SCC partition repeats a model identity",
562 ));
563 }
564 }
565 }
566 if membership.len() != resolved.types().len()
567 || resolved
568 .types()
569 .keys()
570 .any(|id| !membership.contains_key(id))
571 {
572 return Err(projection_error(
573 "invalid_projection_emission_plan",
574 "resolver SCC partition does not cover every model",
575 ));
576 }
577 let mut dependents = vec![BTreeSet::<usize>::new(); components.len()];
578 let mut indegree = vec![0usize; components.len()];
579 for id in resolved.types().keys() {
580 let source = membership[id];
581 for dependency in resolved
582 .dependency_graph()
583 .dependencies(id)
584 .into_iter()
585 .flatten()
586 {
587 let target = membership[dependency];
588 if source != target && dependents[target].insert(source) {
589 indegree[source] += 1;
590 }
591 }
592 }
593 let minimum = components
594 .iter()
595 .map(|component| {
596 component
597 .first()
598 .cloned()
599 .expect("resolver SCCs are non-empty")
600 })
601 .collect::<Vec<_>>();
602 let mut ready = indegree
603 .iter()
604 .enumerate()
605 .filter(|(_, degree)| **degree == 0)
606 .map(|(index, _)| (minimum[index].clone(), index))
607 .collect::<BTreeSet<_>>();
608 let mut ordered = Vec::with_capacity(components.len());
609 while let Some((_, index)) = ready.pop_first() {
610 ordered.push(components[index].clone());
611 for dependent in &dependents[index] {
612 indegree[*dependent] -= 1;
613 if indegree[*dependent] == 0 {
614 ready.insert((minimum[*dependent].clone(), *dependent));
615 }
616 }
617 }
618 if ordered.len() != components.len() {
619 return Err(projection_error(
620 "invalid_projection_emission_plan",
621 "resolver SCC condensation graph is cyclic",
622 ));
623 }
624 Ok(ordered)
625}
626
627pub fn project(
629 resolved: &ResolvedSchema,
630 target: BindingTarget,
631 config: &ProjectionConfig,
632 handlers: &[ProjectionHandler],
633 resources: &[CodeResourceDigest],
634) -> Result<RuntimeProjection, SchemaDiagnostics> {
635 if config.target() != target {
636 return Err(projection_error(
637 "projection_config_target_mismatch",
638 "projection configuration belongs to a different target",
639 ));
640 }
641 let namer = ProjectionNamer::new(target, config)?;
642 let mut names = NameRegistry::default();
643 if let Some(descriptor_name) = namer.c_schema_package_identifier()? {
644 names.insert("root", &descriptor_name, "runtime:c-schema-package-open")?;
645 }
646 let mut models = BTreeMap::new();
647 let mut playing_facts = BTreeMap::new();
648
649 for (id, resolved_type) in resolved.types() {
650 let target_name = namer.class_identifier(id.label().as_str())?;
651 names.insert("root", &target_name, format!("model:{id:?}"))?;
652 let reference_name = if matches!(id.kind(), TypeKind::Entity | TypeKind::Relation) {
653 let reference = namer.reference_identifier(&target_name)?;
654 names.insert("root", &reference, format!("reference:{id:?}"))?;
655 Some(reference)
656 } else {
657 None
658 };
659 let query_token_name = if let Some(name) = namer.query_token_identifier(&target_name)? {
660 names.insert("root", &name, format!("query-token:{id:?}"))?;
661 Some(name)
662 } else {
663 None
664 };
665 let type_annotations = annotations(
666 AnnotationSubjectId::Type(id.clone()),
667 resolved_type.annotations(),
668 )?;
669 let ordered_field_ids = ordered_owns(resolved_type);
670 let mut field_tokens = BTreeMap::new();
671 let mut direct_fields = Vec::new();
672 let mut create_fields = Vec::new();
673 let mut read_fields = Vec::new();
674 for owns_id in &ordered_field_ids {
675 let owns = resolved_type
676 .owns()
677 .get(owns_id.attribute())
678 .ok_or_else(|| {
679 projection_error(
680 "invalid_projection_reference",
681 "ordered ownership is absent from the effective ownership map",
682 )
683 })?;
684 let name = namer.model_member_identifier(owns.id().attribute().label().as_str())?;
685 names.insert(
686 format!("model:{id:?}"),
687 &name,
688 format!("field:{:?}", owns.id()),
689 )?;
690 let declaring_id = match owns.origin().declared() {
691 type_bridge_contract::schema::SchemaFactId::Owns(fact_id) => fact_id.clone(),
692 _ => {
693 return Err(projection_error(
694 "invalid_projection_reference",
695 "declared resolution origin for owns fact is not an Owns fact ID",
696 ));
697 }
698 };
699 let multiplicity =
700 ProjectedMultiplicity::new(owns.cardinality(), owns.collection_mode());
701 let token = FieldTokenProjection::new(
702 owns.id().clone(),
703 declaring_id,
704 name,
705 multiplicity,
706 owns.is_key(),
707 owns.is_unique(),
708 annotations(
709 AnnotationSubjectId::Owns(owns.id().clone()),
710 owns.annotations(),
711 )?,
712 )
713 .map_err(no_source)?;
714 let attribute_model =
715 TypeId::new(TypeKind::Attribute, owns.id().attribute().label().as_str())
716 .map_err(no_source)?;
717 let value = ProjectedTypeRef::Model(ProjectedModelUse::new(
718 attribute_model,
719 ProjectedModelForm::Complete,
720 ));
721 if owns.origin().is_direct() {
722 direct_fields.push(owns.id().clone());
723 }
724 create_fields.push(CreateFieldProjection::new(
725 owns.id().clone(),
726 value.clone(),
727 multiplicity,
728 ));
729 read_fields.push(ReadFieldProjection::new(
730 owns.id().clone(),
731 value,
732 multiplicity,
733 ));
734 field_tokens.insert(owns.id().clone(), token);
735 }
736
737 let replaced = resolved_type
738 .relates()
739 .values()
740 .flat_map(|relates| relates.replaced_roles().iter().cloned())
741 .collect::<BTreeSet<_>>();
742 let mut role_tokens = BTreeMap::new();
743 let mut direct_roles = BTreeMap::new();
744 let mut create_roles = BTreeMap::new();
745 let mut read_roles = BTreeMap::new();
746 let mut create_enabled = resolved_type.is_constructible() && !resolved_type.is_abstract();
747 for (role_id, relates) in resolved_type.relates() {
748 if replaced.contains(role_id) {
749 continue;
750 }
751 let name = namer.model_member_identifier(role_id.label().as_str())?;
752 names.insert(format!("model:{id:?}"), &name, format!("role:{role_id:?}"))?;
753 let resolved_role = resolved.roles().get(role_id).ok_or_else(|| {
754 projection_error(
755 "invalid_projection_reference",
756 "effective relates role is absent from the resolved role index",
757 )
758 })?;
759 let specializes = immediate_specialization(resolved_type, relates)?;
760 let multiplicity =
761 ProjectedMultiplicity::new(relates.cardinality(), relates.collection_mode());
762 let player_union_name = if let Some(name) =
763 namer.player_union_identifier(&target_name, role_id.label().as_str())?
764 {
765 names.insert("root", &name, format!("player-union:{id:?}:{role_id:?}"))?;
766 Some(name)
767 } else {
768 None
769 };
770 let mut token = RoleTokenProjection::new(
771 id.clone(),
772 role_id.clone(),
773 name,
774 resolved_role.accepted_players().clone(),
775 specializes.clone(),
776 multiplicity,
777 relates.is_abstract(),
778 annotations(
779 effective_relates_annotation_subject(relates)?,
780 relates.annotations(),
781 )?,
782 )
783 .map_err(no_source)?;
784 if let Some(name) = player_union_name {
785 token = token.with_player_union_target_name(name);
786 }
787 let read_players = resolved_role
788 .accepted_players()
789 .iter()
790 .map(read_model_use)
791 .collect::<Result<BTreeSet<_>, _>>()?;
792 let mut create_players = BTreeSet::new();
793 for player in resolved_role.accepted_players() {
794 create_players.extend(create_model_uses(player)?);
795 }
796 if relates.origin().is_direct() {
797 direct_roles.insert(
798 role_id.clone(),
799 DeclaredRoleProjection::new(role_id.clone(), specializes),
800 );
801 }
802 if relates.accepts_players_at_effective_scope() {
803 if create_players.is_empty() && multiplicity.required() {
804 create_enabled = false;
805 } else if !create_players.is_empty() {
806 create_roles.insert(
807 role_id.clone(),
808 CreateRoleProjection::new(role_id.clone(), create_players, multiplicity)
809 .map_err(no_source)?,
810 );
811 }
812 }
813 read_roles.insert(
814 role_id.clone(),
815 ReadRoleProjection::new(role_id.clone(), read_players, multiplicity)
816 .map_err(no_source)?,
817 );
818 role_tokens.insert(role_id.clone(), token);
819 }
820
821 let mut direct_plays = BTreeSet::new();
822 for plays in resolved_type.plays().values() {
823 if plays.origin().is_direct() {
824 direct_plays.insert(plays.id().clone());
825 }
826 let plays_name = namer.playing_identifier(
827 id.label().as_str(),
828 plays.id().role().declaring_relation().as_str(),
829 plays.id().role().label().as_str(),
830 )?;
831 names.insert("root", &plays_name, format!("plays:{:?}", plays.id()))?;
832 let projected = PlayingProjection::new(
833 plays.id().clone(),
834 plays.id().role().clone(),
835 ProjectedMultiplicity::from_cardinality(plays.cardinality()),
836 annotations(
837 AnnotationSubjectId::Plays(plays.id().clone()),
838 plays.annotations(),
839 )?,
840 )
841 .map_err(no_source)?
842 .with_target_name(plays_name);
843 if playing_facts
844 .insert(plays.id().clone(), projected)
845 .is_some()
846 {
847 return Err(projection_error(
848 "invalid_runtime_projection_map",
849 "an effective playing identity appears more than once",
850 ));
851 }
852 }
853
854 let key_fields = ordered_field_ids
855 .iter()
856 .filter(|owns| resolved_type.key_attributes().contains(owns.attribute()))
857 .cloned()
858 .collect();
859 let value_annotations = if let Some(value) = resolved_type.value_type() {
860 let attribute = AttributeId::new(id.label().as_str()).map_err(no_source)?;
861 annotations(
862 AnnotationSubjectId::Value(ValueFactId::new(attribute)),
863 value.annotations(),
864 )?
865 } else {
866 BTreeMap::new()
867 };
868 let direct_sub = resolved_type
869 .direct_sub()
870 .map(|sub| {
871 let annotations = annotations(
872 AnnotationSubjectId::Sub(sub.id().clone()),
873 sub.annotations(),
874 )?;
875 DirectSubProjection::new(
876 sub.id().clone(),
877 sub.origin().declared().clone(),
878 annotations,
879 )
880 .map_err(no_source)
881 })
882 .transpose()?;
883 let declaration = DeclarationProjection::new(
884 resolved_type.supertypes().first().cloned(),
885 resolved_type.value_type().map(|value| value.value_type()),
886 resolved_type.is_abstract(),
887 resolved_type.is_constructible(),
888 type_annotations,
889 direct_fields,
890 direct_roles,
891 direct_plays,
892 )
893 .map_err(no_source)?
894 .with_direct_sub(direct_sub)
895 .map_err(no_source)?
896 .with_value_annotations(value_annotations)
897 .map_err(no_source)?;
898 let create_target_name = if create_enabled {
899 if target == BindingTarget::Rust
900 && config.rust_create_policy() != Some(RustCreatePolicy::ValidatedInputV1)
901 {
902 return Err(projection_error(
903 "unsupported_rust_create_policy",
904 "Rust projection does not recognize the configured create policy",
905 ));
906 }
907 if let Some(name) = namer.create_identifier(&target_name)? {
908 names.insert("root", &name, format!("create:{id:?}"))?;
909 Some(name)
910 } else {
911 None
912 }
913 } else {
914 None
915 };
916 let mut create = CreateProjection::new(create_enabled, create_fields, create_roles)
917 .map_err(no_source)?;
918 if let Some(name) = create_target_name {
919 create = create.with_target_name(name);
920 }
921 let role_upcasts = resolved_type
922 .relates()
923 .iter()
924 .filter_map(|(role, relates)| {
925 if relates.replaced_roles().is_empty() {
926 return None;
927 }
928 let mut ancestors = relates.replaced_roles().iter().cloned().collect::<Vec<_>>();
929 ancestors.sort_by_key(|ancestor| {
930 resolved_type
931 .supertypes()
932 .iter()
933 .position(|parent| parent.label() == ancestor.declaring_relation())
934 .unwrap_or(usize::MAX)
935 });
936 Some((role.clone(), ancestors))
937 })
938 .collect();
939 let complete_read = CompleteReadProjection::new(
940 read_fields,
941 read_roles,
942 resolved_type.supertypes().to_vec(),
943 )
944 .map_err(no_source)?
945 .with_role_upcasts(role_upcasts)
946 .map_err(no_source)?;
947 let reference_read =
948 ReferenceReadProjection::new(reference_name, key_fields).map_err(no_source)?;
949 let mut query_tokens =
950 QueryTokenProjection::new(id.clone(), field_tokens, role_tokens).map_err(no_source)?;
951 if let Some(name) = query_token_name {
952 query_tokens = query_tokens.with_target_name(name);
953 }
954 let model = ModelProjection::new(
955 id.clone(),
956 target_name,
957 declaration,
958 create,
959 complete_read,
960 reference_read,
961 query_tokens,
962 )
963 .map_err(no_source)?;
964 models.insert(id.clone(), model);
965 }
966
967 let mut structs = BTreeMap::new();
968 for (id, structure) in resolved.structs() {
969 let target_name = namer.class_identifier(id.label().as_str())?;
970 names.insert("root", &target_name, format!("struct:{id:?}"))?;
971 let mut fields = Vec::new();
972 for field in structure.fields() {
973 let target_field = namer.member_identifier(field.name().as_str())?;
974 names.insert(
975 format!("struct:{id:?}"),
976 &target_field,
977 format!("field:{:?}", field.name()),
978 )?;
979 fields.push(StructFieldProjection::new(
980 field.name().clone(),
981 target_field,
982 field.value_type(),
983 field.optional(),
984 ));
985 }
986 structs.insert(
987 id.clone(),
988 StructProjection::new(id.clone(), target_name, fields).map_err(no_source)?,
989 );
990 }
991
992 let mut functions = BTreeMap::new();
993 for (id, function) in resolved.functions() {
994 let target_name = namer.member_identifier(id.label().as_str())?;
995 names.insert("root", &target_name, format!("function:{id:?}"))?;
996 let mut parameters = Vec::new();
997 for parameter in function.declaration().signature().parameters() {
998 let target_parameter = namer.member_identifier(parameter.name().as_str())?;
999 names.insert(
1000 format!("function:{id:?}"),
1001 &target_parameter,
1002 format!("parameter:{:?}", parameter.name()),
1003 )?;
1004 parameters.push(FunctionParameterProjection::new(
1005 parameter.name().clone(),
1006 target_parameter,
1007 resolve_type_reference(parameter.type_ref(), resolved)?,
1008 ));
1009 }
1010 functions.insert(
1011 id.clone(),
1012 FunctionProjection::new(
1013 id.clone(),
1014 target_name,
1015 parameters,
1016 function_return(function.declaration().signature().returns(), resolved)?,
1017 )
1018 .map_err(no_source)?
1019 .with_annotations(annotations(
1020 AnnotationSubjectId::Function(id.clone()),
1021 function.annotations(),
1022 )?)
1023 .map_err(no_source)?,
1024 );
1025 }
1026
1027 let mut shells = resolved.types().values().collect::<Vec<_>>();
1028 shells.sort_by_key(|model| (model.supertypes().len(), model.id().clone()));
1029 let emission = EmissionPlan::new(
1030 shells.into_iter().map(|model| model.id().clone()).collect(),
1031 link_components(resolved)?,
1032 structs.keys().cloned().collect(),
1033 functions.keys().cloned().collect(),
1034 )
1035 .map_err(no_source)?;
1036 RuntimeProjection::try_new(
1037 target,
1038 config.clone(),
1039 resolved.semantic_fingerprint().clone(),
1040 handlers,
1041 resources,
1042 models,
1043 structs,
1044 functions,
1045 playing_facts,
1046 emission,
1047 )
1048 .map_err(no_source)
1049}