1use std::collections::{BTreeMap, BTreeSet};
2
3use type_bridge_contract::capability::{CapabilityId, CapabilitySet};
4use type_bridge_contract::codec::to_canonical_json;
5use type_bridge_contract::diagnostic::DiagnosticCategory;
6use type_bridge_contract::fingerprint::{
7 CanonicalizationVersion, Fingerprint, FingerprintDomain, SemanticProfileId,
8};
9use type_bridge_contract::id::{AttributeId, FunctionId, RoleId, StructId, TypeId, TypeKind};
10use type_bridge_contract::schema::{
11 AnnotationKindId, AnnotationSubjectId, CollectionMode, DeclaredIdentityFingerprint,
12 DeclaredSchema, FunctionFact, InterfaceKind, OwnsFact, OwnsFactId, PlaysFact, PlaysFactId,
13 RelatesFact, SchemaAnnotationValue, SchemaDiagnostics, SchemaFact, SchemaFactId,
14 SemanticProfile, SemanticSchemaFingerprint, StructFact, StructField, SubFactId, ValueFactId,
15};
16use type_bridge_contract::value::{Cardinality, ValueTypeTag};
17
18use crate::semantic::{semantic_schema_fingerprint, validate_collection_profile};
19
20#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct ResolutionOrigin {
23 declared: SchemaFactId,
24 inheritance_path: Vec<TypeId>,
25}
26
27impl ResolutionOrigin {
28 fn direct(declared: SchemaFactId) -> Self {
29 Self {
30 declared,
31 inheritance_path: Vec::new(),
32 }
33 }
34
35 fn inherited(&self, via: TypeId) -> Self {
36 let mut origin = self.clone();
37 origin.inheritance_path.push(via);
38 origin
39 }
40
41 #[must_use]
43 pub const fn declared(&self) -> &SchemaFactId {
44 &self.declared
45 }
46
47 #[must_use]
49 pub fn inheritance_path(&self) -> &[TypeId] {
50 &self.inheritance_path
51 }
52
53 #[must_use]
55 pub fn is_direct(&self) -> bool {
56 self.inheritance_path.is_empty()
57 }
58}
59
60#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct EffectiveOwns {
63 id: OwnsFactId,
64 origin: ResolutionOrigin,
65 collection_mode: CollectionMode,
66 annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
67 cardinality: Cardinality,
68 key: bool,
69 unique: bool,
70}
71
72impl EffectiveOwns {
73 #[must_use]
75 pub const fn id(&self) -> &OwnsFactId {
76 &self.id
77 }
78
79 #[must_use]
81 pub const fn origin(&self) -> &ResolutionOrigin {
82 &self.origin
83 }
84
85 #[must_use]
87 pub const fn collection_mode(&self) -> CollectionMode {
88 self.collection_mode
89 }
90
91 #[must_use]
93 pub fn is_distinct(&self) -> bool {
94 self.annotations.contains_key(&AnnotationKindId::Distinct)
95 }
96
97 #[must_use]
99 pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
100 &self.annotations
101 }
102
103 #[must_use]
105 pub const fn cardinality(&self) -> Cardinality {
106 self.cardinality
107 }
108
109 #[must_use]
111 pub const fn is_key(&self) -> bool {
112 self.key
113 }
114
115 #[must_use]
117 pub const fn is_unique(&self) -> bool {
118 self.unique
119 }
120}
121
122#[derive(Clone, Debug, Eq, PartialEq)]
124pub struct EffectivePlays {
125 id: PlaysFactId,
126 origin: ResolutionOrigin,
127 annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
128 cardinality: Cardinality,
129}
130
131impl EffectivePlays {
132 #[must_use]
134 pub const fn id(&self) -> &PlaysFactId {
135 &self.id
136 }
137
138 #[must_use]
140 pub const fn origin(&self) -> &ResolutionOrigin {
141 &self.origin
142 }
143
144 #[must_use]
146 pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
147 &self.annotations
148 }
149
150 #[must_use]
152 pub const fn cardinality(&self) -> Cardinality {
153 self.cardinality
154 }
155}
156
157#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
159pub struct EffectiveRelatesId {
160 relation: TypeId,
161 role: RoleId,
162}
163
164impl EffectiveRelatesId {
165 fn new(relation: TypeId, role: RoleId) -> Self {
166 Self { relation, role }
167 }
168
169 #[must_use]
171 pub const fn relation(&self) -> &TypeId {
172 &self.relation
173 }
174
175 #[must_use]
177 pub const fn role(&self) -> &RoleId {
178 &self.role
179 }
180}
181
182#[derive(Clone, Debug, Eq, PartialEq)]
184pub struct EffectiveRelates {
185 id: EffectiveRelatesId,
186 origin: ResolutionOrigin,
187 collection_mode: CollectionMode,
188 annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
189 cardinality: Cardinality,
190 replaced_roles: BTreeSet<RoleId>,
191 is_abstract: bool,
192}
193
194impl EffectiveRelates {
195 #[must_use]
197 pub const fn id(&self) -> &EffectiveRelatesId {
198 &self.id
199 }
200
201 #[must_use]
203 pub const fn origin(&self) -> &ResolutionOrigin {
204 &self.origin
205 }
206
207 #[must_use]
209 pub const fn collection_mode(&self) -> CollectionMode {
210 self.collection_mode
211 }
212
213 #[must_use]
215 pub fn is_distinct(&self) -> bool {
216 self.annotations.contains_key(&AnnotationKindId::Distinct)
217 }
218
219 #[must_use]
221 pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
222 &self.annotations
223 }
224
225 #[must_use]
227 pub const fn cardinality(&self) -> Cardinality {
228 self.cardinality
229 }
230
231 #[must_use]
233 pub const fn replaced_roles(&self) -> &BTreeSet<RoleId> {
234 &self.replaced_roles
235 }
236
237 #[must_use]
239 pub const fn is_abstract(&self) -> bool {
240 self.is_abstract
241 }
242
243 #[must_use]
249 pub fn accepts_players_at_effective_scope(&self) -> bool {
250 !self.is_abstract || !self.origin.is_direct()
251 }
252}
253
254#[derive(Clone, Debug, Eq, PartialEq)]
256pub struct EffectiveValueType {
257 value_type: ValueTypeTag,
258 origin: ResolutionOrigin,
259 annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
260}
261
262impl EffectiveValueType {
263 #[must_use]
265 pub const fn value_type(&self) -> ValueTypeTag {
266 self.value_type
267 }
268
269 #[must_use]
271 pub const fn origin(&self) -> &ResolutionOrigin {
272 &self.origin
273 }
274
275 #[must_use]
277 pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
278 &self.annotations
279 }
280}
281
282#[derive(Clone, Debug, Eq, PartialEq)]
284pub struct EffectiveSub {
285 id: SubFactId,
286 origin: ResolutionOrigin,
287 annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
288}
289
290impl EffectiveSub {
291 #[must_use]
293 pub const fn id(&self) -> &SubFactId {
294 &self.id
295 }
296
297 #[must_use]
299 pub const fn origin(&self) -> &ResolutionOrigin {
300 &self.origin
301 }
302
303 #[must_use]
305 pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
306 &self.annotations
307 }
308}
309
310#[derive(Clone, Debug, Eq, PartialEq)]
312pub struct ResolvedType {
313 id: TypeId,
314 supertypes: Vec<TypeId>,
315 subtypes: BTreeSet<TypeId>,
316 direct_sub: Option<EffectiveSub>,
317 annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
318 value_type: Option<EffectiveValueType>,
319 owns: BTreeMap<AttributeId, EffectiveOwns>,
320 plays: BTreeMap<RoleId, EffectivePlays>,
321 relates: BTreeMap<RoleId, EffectiveRelates>,
322 key_attributes: BTreeSet<AttributeId>,
323 unique_attributes: BTreeSet<AttributeId>,
324 owned_attribute_order: Vec<AttributeId>,
325 is_abstract: bool,
326 constructible: bool,
327}
328
329impl ResolvedType {
330 #[must_use]
332 pub const fn id(&self) -> &TypeId {
333 &self.id
334 }
335
336 #[must_use]
338 pub fn supertypes(&self) -> &[TypeId] {
339 &self.supertypes
340 }
341
342 #[must_use]
344 pub const fn subtypes(&self) -> &BTreeSet<TypeId> {
345 &self.subtypes
346 }
347
348 #[must_use]
350 pub const fn direct_sub(&self) -> Option<&EffectiveSub> {
351 self.direct_sub.as_ref()
352 }
353
354 #[must_use]
356 pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
357 &self.annotations
358 }
359
360 #[must_use]
362 pub const fn value_type(&self) -> Option<&EffectiveValueType> {
363 self.value_type.as_ref()
364 }
365
366 #[must_use]
368 pub const fn owns(&self) -> &BTreeMap<AttributeId, EffectiveOwns> {
369 &self.owns
370 }
371
372 #[must_use]
374 pub const fn plays(&self) -> &BTreeMap<RoleId, EffectivePlays> {
375 &self.plays
376 }
377
378 #[must_use]
380 pub const fn relates(&self) -> &BTreeMap<RoleId, EffectiveRelates> {
381 &self.relates
382 }
383
384 #[must_use]
386 pub const fn key_attributes(&self) -> &BTreeSet<AttributeId> {
387 &self.key_attributes
388 }
389
390 #[must_use]
392 pub const fn unique_attributes(&self) -> &BTreeSet<AttributeId> {
393 &self.unique_attributes
394 }
395
396 #[must_use]
398 pub fn owned_attribute_order(&self) -> &[AttributeId] {
399 &self.owned_attribute_order
400 }
401
402 #[must_use]
404 pub const fn is_abstract(&self) -> bool {
405 self.is_abstract
406 }
407
408 #[must_use]
410 pub const fn is_constructible(&self) -> bool {
411 self.constructible
412 }
413}
414
415#[derive(Clone, Debug, Eq, PartialEq)]
417pub struct ResolvedRole {
418 id: RoleId,
419 accepted_players: BTreeSet<TypeId>,
420 replacing_roles: BTreeSet<RoleId>,
421 is_abstract: bool,
422}
423
424impl ResolvedRole {
425 #[must_use]
427 pub const fn id(&self) -> &RoleId {
428 &self.id
429 }
430
431 #[must_use]
433 pub const fn accepted_players(&self) -> &BTreeSet<TypeId> {
434 &self.accepted_players
435 }
436
437 #[must_use]
439 pub const fn replacing_roles(&self) -> &BTreeSet<RoleId> {
440 &self.replacing_roles
441 }
442
443 #[must_use]
445 pub const fn is_abstract(&self) -> bool {
446 self.is_abstract
447 }
448}
449
450#[derive(Clone, Debug, Eq, PartialEq)]
452pub struct ResolvedFunction {
453 declaration: FunctionFact,
454 annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
455}
456
457impl ResolvedFunction {
458 #[must_use]
460 pub const fn id(&self) -> &FunctionId {
461 self.declaration.id()
462 }
463
464 #[must_use]
466 pub const fn declaration(&self) -> &FunctionFact {
467 &self.declaration
468 }
469
470 #[must_use]
472 pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
473 &self.annotations
474 }
475}
476
477#[derive(Clone, Debug, Eq, PartialEq)]
479pub struct ResolvedStruct {
480 id: StructId,
481 fields: Vec<StructField>,
482}
483
484impl ResolvedStruct {
485 #[must_use]
487 pub const fn id(&self) -> &StructId {
488 &self.id
489 }
490
491 #[must_use]
493 pub fn fields(&self) -> &[StructField] {
494 &self.fields
495 }
496}
497
498#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
500pub struct DescriptorId(String);
501
502impl DescriptorId {
503 #[must_use]
505 pub fn as_str(&self) -> &str {
506 &self.0
507 }
508}
509
510#[derive(Clone, Debug, Default, Eq, PartialEq)]
512pub struct DescriptorIndex {
513 descriptors: BTreeMap<DescriptorId, SchemaFactId>,
514}
515
516impl DescriptorIndex {
517 #[must_use]
519 pub fn get(&self, descriptor: &DescriptorId) -> Option<&SchemaFactId> {
520 self.descriptors.get(descriptor)
521 }
522
523 pub fn iter(&self) -> impl ExactSizeIterator<Item = (&DescriptorId, &SchemaFactId)> {
525 self.descriptors.iter()
526 }
527}
528
529#[derive(Clone, Debug, Default, Eq, PartialEq)]
531pub struct SchemaDependencyGraph {
532 edges: BTreeMap<TypeId, BTreeSet<TypeId>>,
533 strongly_connected_components: Vec<BTreeSet<TypeId>>,
534}
535
536impl SchemaDependencyGraph {
537 #[must_use]
539 pub fn dependencies(&self, id: &TypeId) -> Option<&BTreeSet<TypeId>> {
540 self.edges.get(id)
541 }
542
543 #[must_use]
545 pub fn strongly_connected_components(&self) -> &[BTreeSet<TypeId>] {
546 &self.strongly_connected_components
547 }
548}
549
550#[derive(Clone, Debug, Eq, PartialEq)]
552pub struct ResolvedSchema {
553 declared_identity: DeclaredIdentityFingerprint,
554 semantic_fingerprint: SemanticSchemaFingerprint,
555 types: BTreeMap<TypeId, ResolvedType>,
556 roles: BTreeMap<RoleId, ResolvedRole>,
557 functions: BTreeMap<FunctionId, ResolvedFunction>,
558 structs: BTreeMap<StructId, ResolvedStruct>,
559 descriptor_index: DescriptorIndex,
560 dependency_graph: SchemaDependencyGraph,
561}
562
563impl ResolvedSchema {
564 #[must_use]
566 pub const fn declared_identity_fingerprint(&self) -> &DeclaredIdentityFingerprint {
567 &self.declared_identity
568 }
569
570 #[must_use]
572 pub const fn semantic_fingerprint(&self) -> &SemanticSchemaFingerprint {
573 &self.semantic_fingerprint
574 }
575
576 #[must_use]
578 pub const fn types(&self) -> &BTreeMap<TypeId, ResolvedType> {
579 &self.types
580 }
581
582 #[must_use]
584 pub const fn roles(&self) -> &BTreeMap<RoleId, ResolvedRole> {
585 &self.roles
586 }
587
588 #[must_use]
590 pub const fn functions(&self) -> &BTreeMap<FunctionId, ResolvedFunction> {
591 &self.functions
592 }
593
594 #[must_use]
596 pub const fn structs(&self) -> &BTreeMap<StructId, ResolvedStruct> {
597 &self.structs
598 }
599
600 #[must_use]
602 pub const fn descriptor_index(&self) -> &DescriptorIndex {
603 &self.descriptor_index
604 }
605
606 #[must_use]
608 pub const fn dependency_graph(&self) -> &SchemaDependencyGraph {
609 &self.dependency_graph
610 }
611}
612
613#[derive(Default)]
614struct DirectIndex {
615 types: BTreeSet<TypeId>,
616 parents: BTreeMap<TypeId, Vec<SubFactId>>,
617 values: BTreeMap<AttributeId, (ValueTypeTag, ValueFactId)>,
618 owns: BTreeMap<TypeId, Vec<OwnsFact>>,
619 relates: BTreeMap<TypeId, Vec<RelatesFact>>,
620 plays: BTreeMap<TypeId, Vec<PlaysFact>>,
621 annotations: BTreeMap<AnnotationSubjectId, BTreeMap<AnnotationKindId, SchemaAnnotationValue>>,
622 functions: BTreeMap<FunctionId, FunctionFact>,
623 structs: BTreeMap<StructId, StructFact>,
624}
625
626pub const BUILTIN_SCHEMA_CAPABILITY_IDS: &[&str] =
628 &["schema.annotations", "schema.doc-meta", "schema.roles"];
629
630fn builtin_schema_capabilities() -> CapabilitySet {
631 BUILTIN_SCHEMA_CAPABILITY_IDS
632 .iter()
633 .map(|capability| {
634 CapabilityId::new(*capability).expect("built-in schema capability IDs are canonical")
635 })
636 .collect()
637}
638
639pub fn resolve(
641 declared: &DeclaredSchema,
642 profile_id: &SemanticProfileId,
643) -> Result<ResolvedSchema, SchemaDiagnostics> {
644 resolve_schema_with_capabilities(declared, profile_id, &builtin_schema_capabilities())
645}
646
647pub fn resolve_schema_with_capabilities(
649 declared: &DeclaredSchema,
650 profile_id: &SemanticProfileId,
651 available_capabilities: &CapabilitySet,
652) -> Result<ResolvedSchema, SchemaDiagnostics> {
653 declared
654 .required_capabilities()
655 .ensure_supported_by(available_capabilities)
656 .map_err(|diagnostic| {
657 type_bridge_contract::schema::SchemaDiagnostics::one(
658 type_bridge_contract::schema::SchemaDiagnostic::new(diagnostic, None),
659 )
660 })?;
661 let profile = SemanticProfile::resolve(profile_id).map_err(|diagnostic| {
662 type_bridge_contract::schema::SchemaDiagnostics::one(
663 type_bridge_contract::schema::SchemaDiagnostic::new(diagnostic, None),
664 )
665 })?;
666 validate_collection_profile(declared, profile_id)?;
667 let index = DirectIndex::build(declared)?;
668 let parents = validate_parents(declared, &index)?;
669 validate_inheritance_cycles(declared, &index.types, &parents)?;
670
671 let mut order = index.types.iter().cloned().collect::<Vec<_>>();
672 order.sort_by_key(|id| (ancestor_chain(id, &parents).len(), id.clone()));
673 let mut types = BTreeMap::<TypeId, ResolvedType>::new();
674
675 for id in order {
676 let parent = parents.get(&id);
677 let inherited = parent.and_then(|parent| types.get(parent)).cloned();
678 let mut resolved = inherited
679 .as_ref()
680 .map(|parent| inherit_type(parent, &id, &profile))
681 .transpose()?
682 .unwrap_or_else(|| empty_resolved_type(id.clone()));
683 resolved.id = id.clone();
684 resolved.supertypes = ancestor_chain(&id, &parents);
685 if let Some(parent) = parent {
686 let sub_id = SubFactId::new(id.clone(), parent.clone()).map_err(no_source)?;
687 resolved.direct_sub = Some(EffectiveSub {
688 id: sub_id.clone(),
689 origin: ResolutionOrigin::direct(SchemaFactId::Sub(sub_id.clone())),
690 annotations: annotations_for(&index, AnnotationSubjectId::Sub(sub_id)),
691 });
692 }
693
694 let direct_annotations = annotations_for(&index, AnnotationSubjectId::Type(id.clone()));
695 if let Some(independent) = inherited
696 .as_ref()
697 .and_then(|parent| parent.annotations.get(&AnnotationKindId::Independent))
698 {
699 resolved
700 .annotations
701 .insert(AnnotationKindId::Independent, independent.clone());
702 }
703 resolved.annotations.extend(direct_annotations);
704 resolved.is_abstract = resolved
705 .annotations
706 .contains_key(&AnnotationKindId::Abstract);
707
708 if id.kind() == TypeKind::Attribute {
709 let attribute = AttributeId::new(id.label().as_str()).map_err(no_source)?;
710 if let Some((value_type, value_id)) = index.values.get(&attribute) {
711 resolved.value_type = Some(EffectiveValueType {
712 value_type: *value_type,
713 origin: ResolutionOrigin::direct(SchemaFactId::Value(value_id.clone())),
714 annotations: annotations_for(
715 &index,
716 AnnotationSubjectId::Value(value_id.clone()),
717 ),
718 });
719 }
720 }
721
722 for owns in index.owns.get(&id).into_iter().flatten() {
723 let annotations = annotations_for(&index, AnnotationSubjectId::Owns(owns.id().clone()));
724 if let Some(inherited) = resolved.owns.get(owns.id().attribute())
725 && inherited.collection_mode != owns.collection_mode()
726 {
727 return Err(collection_mode_conflict(
728 declared,
729 &SchemaFactId::Owns(owns.id().clone()),
730 inherited.origin.declared(),
731 "inherited_owns_collection_mode_conflict",
732 "a direct ownership redeclaration must preserve the inherited collection mode",
733 "inherited ownership collection mode is declared here",
734 ));
735 }
736 let effective = EffectiveOwns {
737 id: owns.id().clone(),
738 origin: ResolutionOrigin::direct(SchemaFactId::Owns(owns.id().clone())),
739 collection_mode: owns.collection_mode(),
740 cardinality: cardinality(&annotations, &profile, InterfaceKind::Owns),
741 key: annotations.contains_key(&AnnotationKindId::Key),
742 unique: annotations.contains_key(&AnnotationKindId::Unique)
743 || annotations.contains_key(&AnnotationKindId::Key),
744 annotations,
745 };
746 resolved
747 .owns
748 .insert(owns.id().attribute().clone(), effective);
749 }
750
751 for plays in index.plays.get(&id).into_iter().flatten() {
752 let annotations =
753 annotations_for(&index, AnnotationSubjectId::Plays(plays.id().clone()));
754 let effective = EffectivePlays {
755 id: plays.id().clone(),
756 origin: ResolutionOrigin::direct(SchemaFactId::Plays(plays.id().clone())),
757 cardinality: cardinality(&annotations, &profile, InterfaceKind::Plays),
758 annotations,
759 };
760 resolved.plays.insert(plays.id().role().clone(), effective);
761 }
762
763 for relates in index.relates.get(&id).into_iter().flatten() {
764 let annotations =
765 annotations_for(&index, AnnotationSubjectId::Relates(relates.id().clone()));
766 let mut replaced_roles = BTreeSet::new();
767 if let Some(specialized) = relates.specializes() {
768 let replaced_role = resolved
769 .relates
770 .iter()
771 .find_map(|(role, effective)| {
772 (role == specialized || effective.replaced_roles.contains(specialized))
773 .then(|| role.clone())
774 })
775 .ok_or_else(|| {
776 source_error(
777 declared,
778 &SchemaFactId::Relates(relates.id().clone()),
779 "invalid_role_specialization",
780 "specialized role is not effective on the child relation",
781 )
782 })?;
783 let replaced = resolved
784 .relates
785 .get(&replaced_role)
786 .expect("located effective role exists");
787 if replaced.collection_mode != relates.collection_mode() {
788 return Err(collection_mode_conflict(
789 declared,
790 &SchemaFactId::Relates(relates.id().clone()),
791 replaced.origin.declared(),
792 "specialized_role_collection_mode_conflict",
793 "a specialized role must preserve the effective parent-role collection mode",
794 "effective parent-role collection mode is declared here",
795 ));
796 }
797 let replaced = resolved
798 .relates
799 .remove(&replaced_role)
800 .expect("located effective role exists");
801 replaced_roles.extend(replaced.replaced_roles);
802 replaced_roles.insert(specialized.clone());
803 }
804 let effective = EffectiveRelates {
805 id: EffectiveRelatesId::new(id.clone(), relates.id().role().clone()),
806 origin: ResolutionOrigin::direct(SchemaFactId::Relates(relates.id().clone())),
807 collection_mode: relates.collection_mode(),
808 cardinality: cardinality(&annotations, &profile, InterfaceKind::Relates),
809 is_abstract: annotations.contains_key(&AnnotationKindId::Abstract),
810 annotations,
811 replaced_roles,
812 };
813 resolved
814 .relates
815 .insert(relates.id().role().clone(), effective);
816 }
817
818 resolved.key_attributes = resolved
819 .owns
820 .values()
821 .filter(|owns| owns.key)
822 .map(|owns| owns.id.attribute().clone())
823 .collect();
824 resolved.unique_attributes = resolved
825 .owns
826 .values()
827 .filter(|owns| owns.unique)
828 .map(|owns| owns.id.attribute().clone())
829 .collect();
830 resolved.owned_attribute_order = resolved.owns.keys().cloned().collect();
831 resolved.constructible = !resolved.is_abstract
832 && resolved
833 .relates
834 .values()
835 .all(EffectiveRelates::accepts_players_at_effective_scope);
836 types.insert(id, resolved);
837 }
838
839 populate_subtypes(&mut types, &parents);
840 let roles = resolve_roles(&types, &index);
841 let functions = index
842 .functions
843 .values()
844 .map(|function| {
845 (
846 function.id().clone(),
847 ResolvedFunction {
848 declaration: function.clone(),
849 annotations: annotations_for(
850 &index,
851 AnnotationSubjectId::Function(function.id().clone()),
852 ),
853 },
854 )
855 })
856 .collect();
857 let structs = index
858 .structs
859 .values()
860 .map(|fact| {
861 (
862 fact.id().clone(),
863 ResolvedStruct {
864 id: fact.id().clone(),
865 fields: fact.fields().to_vec(),
866 },
867 )
868 })
869 .collect();
870 let descriptor_index = descriptor_index(declared)?;
871 let dependency_graph = dependency_graph(&types, &roles);
872 let semantic_fingerprint = semantic_schema_fingerprint(declared, profile_id)?;
873
874 Ok(ResolvedSchema {
875 declared_identity: declared.declared_identity_fingerprint().clone(),
876 semantic_fingerprint,
877 types,
878 roles,
879 functions,
880 structs,
881 descriptor_index,
882 dependency_graph,
883 })
884}
885
886impl DirectIndex {
887 fn build(declared: &DeclaredSchema) -> Result<Self, SchemaDiagnostics> {
888 let mut index = Self::default();
889 for fact in declared.facts() {
890 match fact {
891 SchemaFact::Type(fact) => {
892 index.types.insert(fact.id().clone());
893 }
894 SchemaFact::Sub(fact) => index
895 .parents
896 .entry(fact.id().subtype().clone())
897 .or_default()
898 .push(fact.id().clone()),
899 SchemaFact::Value(fact) => {
900 index.values.insert(
901 fact.id().attribute().clone(),
902 (fact.value_type(), fact.id().clone()),
903 );
904 }
905 SchemaFact::Owns(fact) => index
906 .owns
907 .entry(fact.id().owner().clone())
908 .or_default()
909 .push(fact.clone()),
910 SchemaFact::Relates(fact) => index
911 .relates
912 .entry(fact.id().relation().clone())
913 .or_default()
914 .push(fact.clone()),
915 SchemaFact::Plays(fact) => index
916 .plays
917 .entry(fact.id().player().clone())
918 .or_default()
919 .push(fact.clone()),
920 SchemaFact::Annotation(fact) => {
921 index
922 .annotations
923 .entry(fact.id().subject().clone())
924 .or_default()
925 .insert(fact.id().kind().clone(), fact.value().clone());
926 }
927 SchemaFact::Function(fact) => {
928 index.functions.insert(fact.id().clone(), fact.clone());
929 }
930 SchemaFact::Struct(fact) => {
931 index.structs.insert(fact.id().clone(), fact.clone());
932 }
933 }
934 }
935 Ok(index)
936 }
937}
938
939fn validate_parents(
940 declared: &DeclaredSchema,
941 index: &DirectIndex,
942) -> Result<BTreeMap<TypeId, TypeId>, SchemaDiagnostics> {
943 let mut parents = BTreeMap::new();
944 for (subtype, facts) in &index.parents {
945 if facts.len() > 1 {
946 let first = SchemaFactId::Sub(facts[0].clone());
947 let second = SchemaFactId::Sub(facts[1].clone());
948 let primary = declared.source(&second).cloned();
949 let related = declared.source(&first).cloned();
950 if let (Some(primary), Some(related)) = (primary, related) {
951 return Err(crate::yaml::diagnostic_with_related(
952 DiagnosticCategory::InvalidContract,
953 "multiple_type_parents",
954 "a schema type has more than one same-kind parent",
955 primary,
956 related,
957 "first parent declaration is here",
958 ));
959 }
960 return Err(source_error(
961 declared,
962 &second,
963 "multiple_type_parents",
964 "a schema type has more than one same-kind parent",
965 ));
966 }
967 let parent = facts[0].supertype().clone();
968 if subtype.kind() != parent.kind() {
969 return Err(source_error(
970 declared,
971 &SchemaFactId::Sub(facts[0].clone()),
972 "invalid_type_parent_kind",
973 "schema subtype and supertype must have the same kind",
974 ));
975 }
976 parents.insert(subtype.clone(), parent);
977 }
978 Ok(parents)
979}
980
981fn validate_inheritance_cycles(
982 declared: &DeclaredSchema,
983 types: &BTreeSet<TypeId>,
984 parents: &BTreeMap<TypeId, TypeId>,
985) -> Result<(), SchemaDiagnostics> {
986 for start in types {
987 let mut current = start;
988 let mut visited = BTreeSet::new();
989 while let Some(parent) = parents.get(current) {
990 if !visited.insert(current.clone()) {
991 let fact = SubFactId::new(current.clone(), parent.clone()).map_err(no_source)?;
992 return Err(source_error(
993 declared,
994 &SchemaFactId::Sub(fact),
995 "schema_inheritance_cycle",
996 "schema type inheritance contains a cycle",
997 ));
998 }
999 current = parent;
1000 }
1001 }
1002 Ok(())
1003}
1004
1005fn ancestor_chain(id: &TypeId, parents: &BTreeMap<TypeId, TypeId>) -> Vec<TypeId> {
1006 let mut ancestors = Vec::new();
1007 let mut current = id;
1008 while let Some(parent) = parents.get(current) {
1009 ancestors.push(parent.clone());
1010 current = parent;
1011 }
1012 ancestors
1013}
1014
1015fn empty_resolved_type(id: TypeId) -> ResolvedType {
1016 ResolvedType {
1017 id,
1018 supertypes: Vec::new(),
1019 subtypes: BTreeSet::new(),
1020 direct_sub: None,
1021 annotations: BTreeMap::new(),
1022 value_type: None,
1023 owns: BTreeMap::new(),
1024 plays: BTreeMap::new(),
1025 relates: BTreeMap::new(),
1026 key_attributes: BTreeSet::new(),
1027 unique_attributes: BTreeSet::new(),
1028 owned_attribute_order: Vec::new(),
1029 is_abstract: false,
1030 constructible: true,
1031 }
1032}
1033
1034fn inherit_type(
1035 parent: &ResolvedType,
1036 child: &TypeId,
1037 profile: &SemanticProfile,
1038) -> Result<ResolvedType, SchemaDiagnostics> {
1039 let mut resolved = empty_resolved_type(child.clone());
1040 resolved.annotations = parent
1041 .annotations
1042 .iter()
1043 .filter(|(kind, _)| **kind == AnnotationKindId::Independent)
1044 .map(|(kind, value)| (kind.clone(), value.clone()))
1045 .collect();
1046 resolved.value_type = parent.value_type.as_ref().map(|value| EffectiveValueType {
1047 value_type: value.value_type,
1048 origin: value.origin.inherited(parent.id.clone()),
1049 annotations: value.annotations.clone(),
1050 });
1051 for owns in parent.owns.values() {
1052 let id = OwnsFactId::new(child.clone(), owns.id.attribute().clone()).map_err(no_source)?;
1053 let mut inherited = owns.clone();
1054 inherited.id = id;
1055 inherited.origin = inherited.origin.inherited(parent.id.clone());
1056 inherited.cardinality = profile.effective_cardinality(
1057 InterfaceKind::Owns,
1058 inherited
1059 .annotations
1060 .get(&AnnotationKindId::Card)
1061 .and_then(cardinality_value),
1062 inherited.key,
1063 );
1064 resolved
1065 .owns
1066 .insert(inherited.id.attribute().clone(), inherited);
1067 }
1068 for plays in parent.plays.values() {
1069 let id = PlaysFactId::new(child.clone(), plays.id.role().clone()).map_err(no_source)?;
1070 let mut inherited = plays.clone();
1071 inherited.id = id;
1072 inherited.origin = inherited.origin.inherited(parent.id.clone());
1073 resolved
1074 .plays
1075 .insert(inherited.id.role().clone(), inherited);
1076 }
1077 for relates in parent.relates.values() {
1078 let mut inherited = relates.clone();
1079 inherited.id = EffectiveRelatesId::new(child.clone(), relates.id.role().clone());
1080 inherited.origin = inherited.origin.inherited(parent.id.clone());
1081 resolved
1082 .relates
1083 .insert(inherited.id.role().clone(), inherited);
1084 }
1085 Ok(resolved)
1086}
1087
1088fn annotations_for(
1089 index: &DirectIndex,
1090 subject: AnnotationSubjectId,
1091) -> BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
1092 index.annotations.get(&subject).cloned().unwrap_or_default()
1093}
1094
1095fn cardinality(
1096 annotations: &BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
1097 profile: &SemanticProfile,
1098 kind: InterfaceKind,
1099) -> Cardinality {
1100 profile.effective_cardinality(
1101 kind,
1102 annotations
1103 .get(&AnnotationKindId::Card)
1104 .and_then(cardinality_value),
1105 kind == InterfaceKind::Owns && annotations.contains_key(&AnnotationKindId::Key),
1106 )
1107}
1108
1109fn cardinality_value(value: &SchemaAnnotationValue) -> Option<Cardinality> {
1110 match value {
1111 SchemaAnnotationValue::Cardinality(cardinality) => Some(*cardinality),
1112 _ => None,
1113 }
1114}
1115
1116fn populate_subtypes(
1117 types: &mut BTreeMap<TypeId, ResolvedType>,
1118 parents: &BTreeMap<TypeId, TypeId>,
1119) {
1120 let ids = types.keys().cloned().collect::<Vec<_>>();
1121 for id in ids {
1122 for parent in ancestor_chain(&id, parents) {
1123 if let Some(parent) = types.get_mut(&parent) {
1124 parent.subtypes.insert(id.clone());
1125 }
1126 }
1127 }
1128}
1129
1130fn resolve_roles(
1131 types: &BTreeMap<TypeId, ResolvedType>,
1132 index: &DirectIndex,
1133) -> BTreeMap<RoleId, ResolvedRole> {
1134 let mut roles = BTreeMap::new();
1135 for relates in index.relates.values().flatten() {
1136 let annotations =
1137 annotations_for(index, AnnotationSubjectId::Relates(relates.id().clone()));
1138 roles.insert(
1139 relates.id().role().clone(),
1140 ResolvedRole {
1141 id: relates.id().role().clone(),
1142 accepted_players: BTreeSet::new(),
1143 replacing_roles: BTreeSet::new(),
1144 is_abstract: annotations.contains_key(&AnnotationKindId::Abstract),
1145 },
1146 );
1147 }
1148 for resolved in types.values() {
1149 for relates in resolved.relates.values() {
1150 if let Some(role) = roles.get_mut(relates.id.role()) {
1151 role.replacing_roles.extend(relates.replaced_roles.clone());
1152 }
1153 }
1154 }
1155 for resolved in types.values().filter(|resolved| resolved.constructible) {
1156 for plays in resolved.plays.values() {
1157 if let Some(role) = roles.get_mut(plays.id.role()) {
1158 role.accepted_players.insert(resolved.id.clone());
1159 }
1160 }
1161 }
1162 roles
1163}
1164
1165fn descriptor_index(declared: &DeclaredSchema) -> Result<DescriptorIndex, SchemaDiagnostics> {
1166 let domain = FingerprintDomain::new("typebridge.schema.descriptor").map_err(no_source)?;
1167 let canonicalization =
1168 CanonicalizationVersion::new("typebridge.schema-fact-id-json/v1").map_err(no_source)?;
1169 let mut descriptors = BTreeMap::new();
1170 for fact in declared.facts() {
1171 let id = fact.id();
1172 let canonical = to_canonical_json(&id).map_err(no_source)?;
1173 let digest =
1174 Fingerprint::compute(domain.clone(), canonicalization.clone(), None, &canonical)
1175 .digest()
1176 .to_hex();
1177 descriptors.insert(DescriptorId(format!("schema:{digest}")), id);
1178 }
1179 Ok(DescriptorIndex { descriptors })
1180}
1181
1182fn dependency_graph(
1183 types: &BTreeMap<TypeId, ResolvedType>,
1184 roles: &BTreeMap<RoleId, ResolvedRole>,
1185) -> SchemaDependencyGraph {
1186 let mut edges = types
1187 .keys()
1188 .cloned()
1189 .map(|id| (id, BTreeSet::new()))
1190 .collect::<BTreeMap<_, _>>();
1191 for resolved in types.values() {
1192 if let Some(parent) = resolved.supertypes.first() {
1193 edges
1194 .entry(resolved.id.clone())
1195 .or_default()
1196 .insert(parent.clone());
1197 }
1198 for attribute in resolved.owns.keys() {
1199 if let Ok(attribute) = TypeId::new(TypeKind::Attribute, attribute.label().as_str()) {
1200 edges
1201 .entry(resolved.id.clone())
1202 .or_default()
1203 .insert(attribute);
1204 }
1205 }
1206 for role in resolved.plays.keys() {
1207 if let Ok(relation) =
1208 TypeId::new(TypeKind::Relation, role.declaring_relation().as_str())
1209 {
1210 edges
1211 .entry(resolved.id.clone())
1212 .or_default()
1213 .insert(relation);
1214 }
1215 }
1216 for relates in resolved.relates.values() {
1217 if let Some(role) = roles.get(relates.id().role()) {
1218 edges
1219 .entry(resolved.id.clone())
1220 .or_default()
1221 .extend(role.accepted_players().iter().cloned());
1222 }
1223 }
1224 }
1225 let strongly_connected_components = strongly_connected_components(&edges);
1226 SchemaDependencyGraph {
1227 edges,
1228 strongly_connected_components,
1229 }
1230}
1231
1232fn strongly_connected_components(
1233 edges: &BTreeMap<TypeId, BTreeSet<TypeId>>,
1234) -> Vec<BTreeSet<TypeId>> {
1235 struct Tarjan<'a> {
1236 edges: &'a BTreeMap<TypeId, BTreeSet<TypeId>>,
1237 index: usize,
1238 indices: BTreeMap<TypeId, usize>,
1239 lowlinks: BTreeMap<TypeId, usize>,
1240 stack: Vec<TypeId>,
1241 on_stack: BTreeSet<TypeId>,
1242 components: Vec<BTreeSet<TypeId>>,
1243 }
1244 impl Tarjan<'_> {
1245 fn visit(&mut self, node: TypeId) {
1246 let index = self.index;
1247 self.index += 1;
1248 self.indices.insert(node.clone(), index);
1249 self.lowlinks.insert(node.clone(), index);
1250 self.stack.push(node.clone());
1251 self.on_stack.insert(node.clone());
1252
1253 for dependency in self.edges.get(&node).into_iter().flatten() {
1254 if !self.indices.contains_key(dependency) {
1255 self.visit(dependency.clone());
1256 let low = self.lowlinks[&node].min(self.lowlinks[dependency]);
1257 self.lowlinks.insert(node.clone(), low);
1258 } else if self.on_stack.contains(dependency) {
1259 let low = self.lowlinks[&node].min(self.indices[dependency]);
1260 self.lowlinks.insert(node.clone(), low);
1261 }
1262 }
1263
1264 if self.lowlinks[&node] == self.indices[&node] {
1265 let mut component = BTreeSet::new();
1266 loop {
1267 let member = self.stack.pop().expect("SCC root has a stack member");
1268 self.on_stack.remove(&member);
1269 component.insert(member.clone());
1270 if member == node {
1271 break;
1272 }
1273 }
1274 self.components.push(component);
1275 }
1276 }
1277 }
1278
1279 let mut tarjan = Tarjan {
1280 edges,
1281 index: 0,
1282 indices: BTreeMap::new(),
1283 lowlinks: BTreeMap::new(),
1284 stack: Vec::new(),
1285 on_stack: BTreeSet::new(),
1286 components: Vec::new(),
1287 };
1288 for node in edges.keys() {
1289 if !tarjan.indices.contains_key(node) {
1290 tarjan.visit(node.clone());
1291 }
1292 }
1293 tarjan
1294 .components
1295 .sort_by(|left, right| left.first().cmp(&right.first()));
1296 tarjan.components
1297}
1298
1299fn source_error(
1300 declared: &DeclaredSchema,
1301 fact: &SchemaFactId,
1302 code: &'static str,
1303 message: &'static str,
1304) -> SchemaDiagnostics {
1305 crate::yaml::diagnostic(
1306 DiagnosticCategory::InvalidContract,
1307 code,
1308 message,
1309 declared.source(fact).cloned(),
1310 )
1311}
1312
1313fn collection_mode_conflict(
1314 declared: &DeclaredSchema,
1315 direct: &SchemaFactId,
1316 inherited: &SchemaFactId,
1317 code: &'static str,
1318 message: &'static str,
1319 related_message: &'static str,
1320) -> SchemaDiagnostics {
1321 match (declared.source(direct), declared.source(inherited)) {
1322 (Some(primary), Some(related)) => crate::yaml::diagnostic_with_related(
1323 DiagnosticCategory::InvalidContract,
1324 code,
1325 message,
1326 primary.clone(),
1327 related.clone(),
1328 related_message,
1329 ),
1330 _ => source_error(declared, direct, code, message),
1331 }
1332}
1333
1334fn no_source(diagnostic: type_bridge_contract::diagnostic::Diagnostic) -> SchemaDiagnostics {
1335 type_bridge_contract::schema::SchemaDiagnostics::one(
1336 type_bridge_contract::schema::SchemaDiagnostic::new(diagnostic, None),
1337 )
1338}