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