1#![deny(missing_docs)]
9
10mod adopted_genesis;
11mod descriptor;
12mod function_references;
13mod literal;
14mod live_authority;
15mod released_syntax;
16
17pub use adopted_genesis::{
18 ADOPTED_GENESIS_FILE_NAME, AdoptedGenesisAuthority, LEGACY_LEDGER_SCHEMA_TYPEQL,
19 is_legacy_ledger_label, parse_adopted_genesis, parse_adopted_genesis_authority,
20 parse_adopted_genesis_authority_with_internal,
21};
22pub use function_references::{FunctionBodyReferences, SchemaReference, TypeqlDeclaredSchema};
23pub use live_authority::{
24 LiveLegacyLedgerPresence, LiveQueryAuthorityState, LiveQueryControlPresence,
25 MANAGED_FENCE_SCHEMA_TYPEQL, legacy_ledger_schema_presence, managed_fence_schema_presence,
26 rebuild_live_query_authority, rebuild_live_query_authority_state,
27};
28
29pub use descriptor::{
30 GENERATED_DECLARED_DESCRIPTOR_PATH, GENERATED_DECLARED_DESCRIPTOR_V1,
31 GENERATED_DECLARED_DESCRIPTOR_V2, GeneratedDeclaredDescriptorSetV1,
32 attach_declared_descriptors, empty_generated_declared_descriptors_json,
33 generate_package_with_declared_descriptors, generated_declared_descriptors_json,
34 generated_descriptors_to_declared, released_typeql_to_declared_lossless_projection,
35 released_typeql_to_declared_lossless_projection_with_references,
36 released_typeql_to_declared_projection, released_typeql_to_declared_projection_with_references,
37 typeql_to_generated_descriptors,
38};
39
40pub mod shadow;
42
43pub use shadow::{
44 ShadowCompared, ShadowComparison, ShadowCoverage, ShadowCoverageState, ShadowDimension,
45 ShadowFinding, ShadowLaneNotRun, ShadowLaneOutcome, ShadowLaneRejection, ShadowLaneSummary,
46 ShadowUnavailableLane, ShadowVerdict, V1ShadowInternalError, V1ShadowReport, v1_shadow_report,
47};
48
49use std::collections::{BTreeMap, BTreeSet};
50
51use type_bridge_contract::codec::FormatVersion;
52use type_bridge_contract::diagnostic::{Diagnostic, DiagnosticCategory, DiagnosticCode};
53use type_bridge_contract::id::{
54 AttributeId, FunctionId, Label, RoleId, StructId, TypeId, TypeKind,
55};
56use type_bridge_contract::limits::MAX_CANONICAL_COLLECTION_LEN;
57use type_bridge_contract::schema::{
58 AnnotationFact, AnnotationFactId, AnnotationKindId, AnnotationSubjectId, CanonicalValueRange,
59 CanonicalValueSet, CollectionMode, DeclaredSchema, DocText, DocumentId, FunctionBody,
60 FunctionFact, FunctionParameter, FunctionReturnElement, FunctionReturnMode, FunctionSignature,
61 OwnsFact, OwnsFactId, PlaysFactId, RegexPattern, RelatesFactId, SchemaAnnotationValue,
62 SchemaDiagnostic, SchemaDiagnostics, SchemaFact, SourceSpan, StructFact, StructField, SubFact,
63 SubFactId, TypeFact, TypeReference, ValueFact, ValueFactId,
64};
65use type_bridge_contract::value::{CanonicalString, CanonicalValue, Cardinality, ValueTypeTag};
66use type_bridge_schema::FactAssembler;
67use typeql::Annotation;
68use typeql::annotation::CardinalityRange;
69use typeql::common::{Span, Spanned};
70use typeql::query::{QueryStructure, schema::SchemaQuery};
71use typeql::schema::definable::{
72 Definable,
73 function::{Function, Output},
74 struct_::Struct,
75 type_::{Capability, CapabilityBase, Type as TypeDeclaration},
76};
77use typeql::type_::{NamedType, NamedTypeAny, TypeRef, TypeRefAny};
78
79use crate::literal::{canonical_literal, validate_quoted_string};
80use crate::released_syntax::{ReleasedAnnotationTarget, ReleasedSyntax};
81
82pub const MAX_TYPEQL_SCHEMA_BYTES: usize = 16 * 1024 * 1024;
84
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub(crate) enum TypeqlSourceSizePolicy {
87 Defensive,
88 TrustedGenerator,
89}
90
91impl TypeqlSourceSizePolicy {
92 const fn allows(self, source_len: usize) -> bool {
93 matches!(self, Self::TrustedGenerator) || source_len <= MAX_TYPEQL_SCHEMA_BYTES
94 }
95}
96
97fn ensure_typeql_source_size(
98 source: &str,
99 size_policy: TypeqlSourceSizePolicy,
100) -> Result<(), SchemaDiagnostics> {
101 if size_policy.allows(source.len()) {
102 return Ok(());
103 }
104 Err(error(
105 DiagnosticCategory::InvalidContract,
106 "typeql_schema_size_limit",
107 "TypeQL schema source exceeds the compatibility parser limit",
108 None,
109 ))
110}
111
112pub fn typeql_to_declared_with_references(
115 document: DocumentId,
116 source: &str,
117) -> Result<TypeqlDeclaredSchema, SchemaDiagnostics> {
118 typeql_to_declared_with_references_with_size_policy(
119 document,
120 source,
121 TypeqlSourceSizePolicy::Defensive,
122 )
123}
124
125pub(crate) fn typeql_to_declared_with_references_with_size_policy(
126 document: DocumentId,
127 source: &str,
128 size_policy: TypeqlSourceSizePolicy,
129) -> Result<TypeqlDeclaredSchema, SchemaDiagnostics> {
130 ensure_typeql_source_size(source, size_policy)?;
131 typeql_to_declared_with_references_impl(document, source, source, None, None, None, None)
132}
133
134pub(crate) fn released_typeql_to_declared_with_references(
135 document: DocumentId,
136 released: &ReleasedSyntax,
137 size_policy: TypeqlSourceSizePolicy,
138) -> Result<TypeqlDeclaredSchema, SchemaDiagnostics> {
139 ensure_typeql_source_size(released.original_source(), size_policy)?;
140 let reference_projection = released_unresolved_capability_ranges(&document, released)?;
141 typeql_to_declared_with_references_impl(
142 document,
143 released.original_source(),
144 released.source(),
145 Some(released),
146 None,
147 None,
148 Some(&reference_projection.played_role_declarations),
149 )
150}
151
152pub(crate) fn released_typeql_to_declared_with_references_omitting_capabilities(
153 document: DocumentId,
154 released: &ReleasedSyntax,
155 omitted_declarations: &BTreeSet<usize>,
156 omitted_capabilities: &BTreeSet<usize>,
157 played_role_declarations: &BTreeMap<usize, String>,
158 size_policy: TypeqlSourceSizePolicy,
159) -> Result<TypeqlDeclaredSchema, SchemaDiagnostics> {
160 ensure_typeql_source_size(released.original_source(), size_policy)?;
161 typeql_to_declared_with_references_impl(
162 document,
163 released.original_source(),
164 released.source(),
165 Some(released),
166 Some(omitted_declarations),
167 Some(omitted_capabilities),
168 Some(played_role_declarations),
169 )
170}
171
172#[derive(Default)]
184pub(crate) struct ReleasedReferenceProjection {
185 pub(crate) omitted_declarations: BTreeMap<usize, usize>,
186 pub(crate) omitted: BTreeMap<usize, usize>,
187 pub(crate) omitted_from_render: BTreeMap<usize, usize>,
188 pub(crate) played_role_declarations: BTreeMap<usize, String>,
189}
190
191pub(crate) fn released_unresolved_capability_ranges(
192 document: &DocumentId,
193 released: &ReleasedSyntax,
194) -> Result<ReleasedReferenceProjection, SchemaDiagnostics> {
195 let queries = typeql::parse_queries(released.source()).map_err(|parse_error| {
196 error(
197 DiagnosticCategory::InvalidContract,
198 "invalid_typeql_schema",
199 format!("TypeQL schema parsing failed: {parse_error}"),
200 None,
201 )
202 })?;
203 let mut definables = Vec::new();
204 for query in queries {
205 match query.structure {
206 QueryStructure::Schema(SchemaQuery::Define(define)) => {
207 definables.extend(define.definables);
208 }
209 _ => {
210 return Err(error(
211 DiagnosticCategory::InvalidContract,
212 "expected_typeql_define",
213 "schema compatibility input must contain only define queries",
214 query_span(document, released.original_source(), query.span)?,
215 ));
216 }
217 }
218 }
219 restore_released_labels(released, &mut definables);
220 let declarations = definables
221 .iter()
222 .filter_map(|definable| match definable {
223 Definable::TypeDeclaration(declaration) => Some(declaration),
224 _ => None,
225 })
226 .collect::<Vec<_>>();
227 let kinds = infer_type_kinds(document, released.original_source(), &declarations, true)?;
228 let mut omitted_declarations = BTreeMap::new();
229 let mut invalid_type_labels = BTreeSet::new();
230 for (declaration, kind) in declarations.iter().zip(&kinds) {
231 let label = typeql_label(&declaration.label);
232 let portable = TypeId::new(*kind, label.clone())
233 .and_then(TypeFact::new)
234 .is_ok();
235 if !portable {
236 invalid_type_labels.insert(label);
237 if let Some(span) = declaration.span {
238 omitted_declarations.insert(span.begin_offset, span.end_offset);
239 }
240 }
241 }
242 for declaration in &declarations {
245 if invalid_type_labels.contains(&typeql_label(&declaration.label))
246 && let Some(span) = declaration.span
247 {
248 omitted_declarations.insert(span.begin_offset, span.end_offset);
249 }
250 }
251 let ids = declarations
252 .iter()
253 .zip(&kinds)
254 .filter(|(declaration, _)| {
255 !declaration
256 .span
257 .is_some_and(|span| omitted_declarations.contains_key(&span.begin_offset))
258 })
259 .map(|(declaration, kind)| (typeql_label(&declaration.label), *kind))
260 .collect::<BTreeMap<_, _>>();
261
262 let mut last_attribute_declaration = BTreeMap::new();
263 let mut last_sub_capability = BTreeMap::new();
264 let mut last_value_capability = BTreeMap::new();
265 for (declaration_index, (declaration, kind)) in declarations.iter().zip(&kinds).enumerate() {
266 let label = typeql_label(&declaration.label);
267 if *kind == TypeKind::Attribute {
268 last_attribute_declaration.insert(label.clone(), declaration_index);
269 }
270 for (capability_index, capability) in declaration.capabilities.iter().enumerate() {
271 if matches!(capability.base, CapabilityBase::Sub(_)) {
272 last_sub_capability.insert(label.clone(), (declaration_index, capability_index));
273 }
274 if matches!(capability.base, CapabilityBase::ValueType(_)) {
275 last_value_capability.insert(label.clone(), (declaration_index, capability_index));
276 }
277 }
278 }
279
280 struct IndexedCapability<'a> {
281 owner: String,
282 owner_kind: TypeKind,
283 capability: &'a Capability,
284 start: usize,
285 end: usize,
286 }
287
288 let mut effective = Vec::new();
289 let mut first_object_capability = BTreeSet::new();
290 for (declaration_index, (declaration, kind)) in declarations.iter().zip(&kinds).enumerate() {
291 if declaration
292 .span
293 .is_some_and(|span| omitted_declarations.contains_key(&span.begin_offset))
294 {
295 continue;
296 }
297 let owner = typeql_label(&declaration.label);
298 if *kind == TypeKind::Attribute
299 && last_attribute_declaration.get(&owner) != Some(&declaration_index)
300 {
301 continue;
302 }
303 for (capability_index, capability) in declaration.capabilities.iter().enumerate() {
304 if matches!(capability.base, CapabilityBase::Sub(_))
305 && last_sub_capability.get(&owner) != Some(&(declaration_index, capability_index))
306 {
307 continue;
308 }
309 if matches!(capability.base, CapabilityBase::ValueType(_))
310 && last_value_capability.get(&owner) != Some(&(declaration_index, capability_index))
311 {
312 continue;
313 }
314 if *kind != TypeKind::Attribute
315 && let Some(identity) = released_object_capability_identity(capability)
316 && !first_object_capability.insert((owner.clone(), identity))
317 {
318 continue;
319 }
320 let Some(span) = capability.span else {
321 continue;
322 };
323 effective.push(IndexedCapability {
324 owner: owner.clone(),
325 owner_kind: *kind,
326 capability,
327 start: span.begin_offset,
328 end: span.end_offset,
329 });
330 }
331 }
332
333 let mut omitted = BTreeMap::new();
334 let mut parents = BTreeMap::<String, String>::new();
335 for indexed in &effective {
336 match &indexed.capability.base {
337 CapabilityBase::Sub(sub) => {
338 let parent = typeql_label(&sub.supertype_label);
339 if root_kind(&parent).is_some() {
340 continue;
341 }
342 match ids.get(&parent) {
343 None => {
344 omitted.insert(indexed.start, indexed.end);
345 }
346 Some(parent_kind) if *parent_kind == indexed.owner_kind => {
347 parents.insert(indexed.owner.clone(), parent);
348 }
349 Some(_) => {}
350 }
351 }
352 CapabilityBase::Owns(owns) => {
353 if let Ok((attribute, _)) = capability_type_ref(&owns.owned)
354 && !ids.contains_key(&attribute)
355 {
356 omitted.insert(indexed.start, indexed.end);
357 }
358 }
359 _ => {}
360 }
361 }
362
363 let mut roles = BTreeMap::<(String, String), ReleasedIndexedRole>::new();
364 let mut role_names_by_relation = BTreeMap::<String, Vec<String>>::new();
365 for (capability_index, indexed) in effective.iter().enumerate() {
366 let CapabilityBase::Relates(relates) = &indexed.capability.base else {
367 continue;
368 };
369 let Ok((role, _)) = capability_type_ref(&relates.related) else {
370 continue;
371 };
372 let specializes = relates.specialised.as_ref().and_then(|specialized| {
373 capability_type_ref(specialized)
374 .ok()
375 .map(|(label, _)| label)
376 });
377 let role_is_portable = Label::new(&role).is_ok()
378 && specializes
379 .as_ref()
380 .is_none_or(|label| Label::new(label).is_ok());
381 if !role_is_portable {
382 omitted.insert(indexed.start, indexed.end);
383 continue;
384 }
385 role_names_by_relation
386 .entry(indexed.owner.clone())
387 .or_default()
388 .push(role.clone());
389 roles.insert(
390 (indexed.owner.clone(), role),
391 ReleasedIndexedRole {
392 capability_index,
393 specializes,
394 },
395 );
396 }
397
398 let relation_names = ids
399 .iter()
400 .filter_map(|(name, kind)| (*kind == TypeKind::Relation).then_some(name.clone()))
401 .collect::<BTreeSet<_>>();
402 let mut played_role_names_by_relation = BTreeMap::<String, BTreeSet<String>>::new();
403 for indexed in &effective {
404 if let CapabilityBase::Plays(plays) = &indexed.capability.base {
405 let relation = typeql_label(&plays.role.scope);
406 let role = typeql_label(&plays.role.name);
407 if Label::new(&relation).is_err() || Label::new(&role).is_err() {
408 omitted.insert(indexed.start, indexed.end);
409 continue;
410 }
411 played_role_names_by_relation
412 .entry(relation)
413 .or_default()
414 .insert(role);
415 }
416 }
417 let role_resolution = released_role_validities(
418 &relation_names,
419 &role_names_by_relation,
420 &played_role_names_by_relation,
421 &roles,
422 &parents,
423 );
424 for (key, validity) in &role_resolution.direct {
425 if *validity == ReleasedRoleValidity::Invalid {
426 let indexed = &effective[roles[key].capability_index];
427 omitted.insert(indexed.start, indexed.end);
428 }
429 }
430
431 let mut omitted_from_render = BTreeMap::new();
432 for (key, validity) in &role_resolution.direct {
433 if *validity == ReleasedRoleValidity::Invalid {
434 let indexed = &effective[roles[key].capability_index];
435 omitted_from_render.insert(indexed.start, indexed.end);
436 }
437 }
438
439 let mut played_role_declarations = BTreeMap::new();
440 let mut first_portable_plays = BTreeSet::new();
441 for indexed in &effective {
442 let CapabilityBase::Plays(plays) = &indexed.capability.base else {
443 continue;
444 };
445 if omitted.contains_key(&indexed.start) {
446 continue;
447 }
448 let relation = typeql_label(&plays.role.scope);
449 let role = typeql_label(&plays.role.name);
450 match ids.get(&relation) {
451 None => {
452 omitted.insert(indexed.start, indexed.end);
453 }
454 Some(TypeKind::Relation) => {
455 match role_resolution.plays.get(&(relation.clone(), role.clone())) {
456 Some(ReleasedRoleValidity::Valid) => {
457 if let Some(declaration) =
458 role_resolution.play_declarations.get(&(relation, role))
459 {
460 let identity = (
461 indexed.owner.clone(),
462 declaration.clone(),
463 typeql_label(&plays.role.name),
464 );
465 if first_portable_plays.insert(identity) {
466 played_role_declarations.insert(indexed.start, declaration.clone());
467 } else {
468 omitted.insert(indexed.start, indexed.end);
475 }
476 }
477 }
478 Some(ReleasedRoleValidity::Invalid) | None => {
479 omitted.insert(indexed.start, indexed.end);
480 }
481 Some(ReleasedRoleValidity::Indeterminate) => {}
482 }
483 }
484 Some(_) => {}
485 }
486 }
487
488 Ok(ReleasedReferenceProjection {
489 omitted_declarations,
490 omitted,
491 omitted_from_render,
492 played_role_declarations,
493 })
494}
495
496#[derive(Clone, Copy, Debug, Eq, PartialEq)]
497enum ReleasedRoleValidity {
498 Valid,
499 Invalid,
500 Indeterminate,
501}
502
503#[derive(Clone, Debug)]
504struct ReleasedIndexedRole {
505 capability_index: usize,
506 specializes: Option<String>,
507}
508
509struct ReleasedRoleResolution {
510 direct: BTreeMap<(String, String), ReleasedRoleValidity>,
511 plays: BTreeMap<(String, String), ReleasedRoleValidity>,
512 play_declarations: BTreeMap<(String, String), String>,
513}
514
515fn released_role_validities(
534 relation_names: &BTreeSet<String>,
535 role_names_by_relation: &BTreeMap<String, Vec<String>>,
536 played_role_names_by_relation: &BTreeMap<String, BTreeSet<String>>,
537 roles: &BTreeMap<(String, String), ReleasedIndexedRole>,
538 parents: &BTreeMap<String, String>,
539) -> ReleasedRoleResolution {
540 let mut children = BTreeMap::<String, Vec<String>>::new();
541 let mut roots = Vec::new();
542 for relation in relation_names {
543 match parents
544 .get(relation)
545 .filter(|parent| relation_names.contains(*parent))
546 {
547 Some(parent) => children
548 .entry(parent.clone())
549 .or_default()
550 .push(relation.clone()),
551 None => roots.push(relation.clone()),
552 }
553 }
554
555 let mut direct = roles
560 .keys()
561 .cloned()
562 .map(|key| (key, ReleasedRoleValidity::Indeterminate))
563 .collect::<BTreeMap<_, _>>();
564 let mut plays = played_role_names_by_relation
565 .iter()
566 .flat_map(|(relation, role_names)| {
567 role_names.iter().map(|role_name| {
568 (
569 (relation.clone(), role_name.clone()),
570 ReleasedRoleValidity::Indeterminate,
571 )
572 })
573 })
574 .collect::<BTreeMap<_, _>>();
575 let mut direct_labels = BTreeSet::<String>::new();
576 let mut effective_roles = BTreeMap::<String, String>::new();
577 enum Traversal {
578 Enter(String),
579 Exit {
580 direct: Vec<(String, bool)>,
581 effective: Vec<(String, Option<String>)>,
582 },
583 }
584 let mut work = roots
585 .into_iter()
586 .rev()
587 .map(Traversal::Enter)
588 .collect::<Vec<_>>();
589 let mut play_declarations = BTreeMap::new();
590
591 while let Some(frame) = work.pop() {
592 match frame {
593 Traversal::Enter(relation) => {
594 let mut additions = BTreeSet::new();
595 let mut removals = BTreeSet::new();
596 if let Some(role_names) = role_names_by_relation.get(&relation) {
597 for role_name in role_names {
598 let key = (relation.clone(), role_name.clone());
599 let role = &roles[&key];
600 let resolved = match role.specializes.as_ref() {
601 Some(specialized) if direct_labels.contains(specialized) => {
602 removals.insert(specialized.clone());
603 ReleasedRoleValidity::Valid
604 }
605 Some(_) => ReleasedRoleValidity::Invalid,
606 None => ReleasedRoleValidity::Valid,
607 };
608 direct.insert(key, resolved);
609 if resolved == ReleasedRoleValidity::Valid {
610 additions.insert(role_name.clone());
611 }
612 }
613 }
614
615 let affected = removals.union(&additions).cloned().collect::<Vec<_>>();
616 let mut previous_effective = Vec::with_capacity(affected.len());
617 for role_name in affected {
618 let previous = effective_roles.get(&role_name).cloned();
619 previous_effective.push((role_name.clone(), previous));
620 if additions.contains(&role_name) {
621 effective_roles.insert(role_name, relation.clone());
622 } else {
623 effective_roles.remove(&role_name);
624 }
625 }
626 let mut previous_direct = Vec::with_capacity(additions.len());
627 for role_name in &additions {
628 let was_visible = direct_labels.contains(role_name);
629 previous_direct.push((role_name.clone(), was_visible));
630 direct_labels.insert(role_name.clone());
631 }
632
633 if let Some(played_role_names) = played_role_names_by_relation.get(&relation) {
634 for role_name in played_role_names {
635 let declaration = effective_roles.get(role_name);
636 plays.insert(
637 (relation.clone(), role_name.clone()),
638 if declaration.is_some() {
639 ReleasedRoleValidity::Valid
640 } else {
641 ReleasedRoleValidity::Invalid
642 },
643 );
644 if let Some(declaration) = declaration {
645 play_declarations
646 .insert((relation.clone(), role_name.clone()), declaration.clone());
647 }
648 }
649 }
650
651 work.push(Traversal::Exit {
652 direct: previous_direct,
653 effective: previous_effective,
654 });
655 if let Some(relation_children) = children.get(&relation) {
656 work.extend(
657 relation_children
658 .iter()
659 .rev()
660 .cloned()
661 .map(Traversal::Enter),
662 );
663 }
664 }
665 Traversal::Exit { direct, effective } => {
666 for (role_name, was_visible) in direct {
667 if was_visible {
668 direct_labels.insert(role_name);
669 } else {
670 direct_labels.remove(&role_name);
671 }
672 }
673 for (role_name, previous) in effective {
674 if let Some(declaration) = previous {
675 effective_roles.insert(role_name, declaration);
676 } else {
677 effective_roles.remove(&role_name);
678 }
679 }
680 }
681 }
682 }
683
684 ReleasedRoleResolution {
685 direct,
686 plays,
687 play_declarations,
688 }
689}
690
691fn typeql_to_declared_with_references_impl(
692 document: DocumentId,
693 source: &str,
694 parser_source: &str,
695 released: Option<&ReleasedSyntax>,
696 omitted_released_declarations: Option<&BTreeSet<usize>>,
697 omitted_released_capabilities: Option<&BTreeSet<usize>>,
698 played_role_declarations: Option<&BTreeMap<usize, String>>,
699) -> Result<TypeqlDeclaredSchema, SchemaDiagnostics> {
700 let queries = typeql::parse_queries(parser_source).map_err(|parse_error| {
703 error(
704 DiagnosticCategory::InvalidContract,
705 "invalid_typeql_schema",
706 format!("TypeQL schema parsing failed: {parse_error}"),
707 None,
708 )
709 })?;
710 if queries.is_empty() {
711 return Err(error(
712 DiagnosticCategory::InvalidContract,
713 "expected_typeql_define",
714 "schema compatibility input must contain at least one define query",
715 None,
716 ));
717 }
718 let mut definables = Vec::new();
719 for query in queries {
720 match query.structure {
721 QueryStructure::Schema(SchemaQuery::Define(define)) => {
722 definables.extend(define.definables);
723 }
724 _ => {
725 return Err(error(
726 DiagnosticCategory::InvalidContract,
727 "expected_typeql_define",
728 "schema compatibility input must contain only define queries",
729 query_span(&document, source, query.span)?,
730 ));
731 }
732 }
733 }
734 if let Some(released) = released {
735 restore_released_labels(released, &mut definables);
736 }
737
738 let declarations: Vec<&TypeDeclaration> = definables
739 .iter()
740 .filter_map(|definable| match definable {
741 Definable::TypeDeclaration(declaration) => Some(declaration),
742 _ => None,
743 })
744 .collect();
745 let kinds = infer_type_kinds(&document, source, &declarations, released.is_some())?;
746 let mut ids = BTreeMap::new();
747 let mut assembler = FactAssembler::new(FormatVersion::V1);
748 let mut function_body_references = BTreeMap::new();
749
750 for (declaration, kind) in declarations.iter().zip(&kinds) {
751 if released_declaration_is_omitted(declaration, omitted_released_declarations) {
752 continue;
753 }
754 let label = typeql_label(&declaration.label);
755 let declaration_span = source_span(&document, source, declaration.span)?;
756 let id = TypeId::new(*kind, label.clone())
757 .map_err(|diagnostic| contract(diagnostic, declaration_span.clone()))?;
758 if ids.contains_key(&label) {
764 continue;
765 }
766 assembler.insert_fact(
767 SchemaFact::Type(
768 TypeFact::new(id.clone())
769 .map_err(|diagnostic| contract(diagnostic, declaration_span.clone()))?,
770 ),
771 declaration_span,
772 )?;
773 ids.entry(label).or_insert(id);
774 }
775
776 if released.is_none() {
777 for declaration in &declarations {
781 let label = typeql_label(&declaration.label);
782 let id = ids.get(&label).cloned().ok_or_else(|| {
783 error(
784 DiagnosticCategory::InvalidContract,
785 "unknown_typeql_type",
786 format!("TypeQL declaration `{label}` has no inferred type identity"),
787 query_span(&document, source, declaration.span)
788 .ok()
789 .flatten(),
790 )
791 })?;
792 insert_annotations(
793 &mut assembler,
794 AnnotationSubjectId::Type(id.clone()),
795 &declaration.annotations,
796 &document,
797 source,
798 )?;
799 for capability in &declaration.capabilities {
800 insert_capability(
801 &mut assembler,
802 &ids,
803 &id,
804 capability,
805 CapabilityAnnotations::Strict(&capability.annotations),
806 &document,
807 source,
808 )?;
809 }
810 }
811 } else {
812 let mut last_attribute_declaration = BTreeMap::new();
818 let mut last_sub_capability = BTreeMap::new();
819 let mut last_value_capability = BTreeMap::new();
820 for (declaration_index, (declaration, kind)) in declarations.iter().zip(&kinds).enumerate()
821 {
822 if released_declaration_is_omitted(declaration, omitted_released_declarations) {
823 continue;
824 }
825 let label = typeql_label(&declaration.label);
826 if *kind == TypeKind::Attribute {
827 last_attribute_declaration.insert(label.clone(), declaration_index);
828 }
829 for (capability_index, capability) in declaration.capabilities.iter().enumerate() {
830 if matches!(&capability.base, CapabilityBase::Sub(_)) {
831 last_sub_capability
832 .insert(label.clone(), (declaration_index, capability_index));
833 }
834 if matches!(&capability.base, CapabilityBase::ValueType(_)) {
835 last_value_capability
836 .insert(label.clone(), (declaration_index, capability_index));
837 }
838 }
839 }
840
841 let mut merged_type_annotations: BTreeMap<String, Vec<Annotation>> = BTreeMap::new();
842 let mut merged_value_annotations: BTreeMap<String, Vec<Annotation>> = BTreeMap::new();
843 for (declaration_index, (declaration, kind)) in declarations.iter().zip(&kinds).enumerate()
844 {
845 if released_declaration_is_omitted(declaration, omitted_released_declarations) {
846 continue;
847 }
848 let label = typeql_label(&declaration.label);
849 if *kind == TypeKind::Attribute
850 && last_attribute_declaration.get(&label) != Some(&declaration_index)
851 {
852 continue;
853 }
854 if let Some(released) = released {
855 for annotation in declaration.annotations.iter().chain(
856 declaration
857 .capabilities
858 .iter()
859 .flat_map(|capability| &capability.annotations),
860 ) {
861 match released_annotation_target(released, annotation) {
862 Some(ReleasedAnnotationTarget::Value) => merged_value_annotations
863 .entry(label.clone())
864 .or_default()
865 .push(annotation.clone()),
866 Some(ReleasedAnnotationTarget::Type) => merged_type_annotations
867 .entry(label.clone())
868 .or_default()
869 .push(annotation.clone()),
870 Some(ReleasedAnnotationTarget::Capability) => {}
871 None if *kind == TypeKind::Attribute => {
872 let target = released_attribute_annotation_target(annotation);
873 let annotations = match target {
874 ReleasedAnnotationTarget::Value => &mut merged_value_annotations,
875 ReleasedAnnotationTarget::Type => &mut merged_type_annotations,
876 ReleasedAnnotationTarget::Capability => continue,
877 };
878 annotations
879 .entry(label.clone())
880 .or_default()
881 .push(annotation.clone());
882 }
883 None => {}
884 }
885 }
886 } else {
887 merged_type_annotations
888 .entry(label)
889 .or_default()
890 .extend(declaration.annotations.iter().cloned());
891 }
892 }
893 for (label, annotations) in merged_type_annotations {
894 let id = ids.get(&label).cloned().ok_or_else(|| {
895 error(
896 DiagnosticCategory::InvalidContract,
897 "unknown_typeql_type",
898 format!("TypeQL declaration `{label}` has no inferred type identity"),
899 None,
900 )
901 })?;
902 insert_released_annotations(
903 &mut assembler,
904 AnnotationSubjectId::Type(id.clone()),
905 &annotations,
906 &document,
907 source,
908 )?;
909 }
910
911 let mut first_object_capability = BTreeMap::new();
919 for (declaration_index, (declaration, kind)) in declarations.iter().zip(&kinds).enumerate()
920 {
921 if released_declaration_is_omitted(declaration, omitted_released_declarations) {
922 continue;
923 }
924 let label = typeql_label(&declaration.label);
925 if *kind == TypeKind::Attribute
926 && last_attribute_declaration.get(&label) != Some(&declaration_index)
927 {
928 continue;
929 }
930 let id = ids.get(&label).cloned().ok_or_else(|| {
931 error(
932 DiagnosticCategory::InvalidContract,
933 "unknown_typeql_type",
934 format!("TypeQL declaration `{label}` has no inferred type identity"),
935 query_span(&document, source, declaration.span)
936 .ok()
937 .flatten(),
938 )
939 })?;
940 for (capability_index, capability) in declaration.capabilities.iter().enumerate() {
941 if matches!(&capability.base, CapabilityBase::Sub(_))
942 && last_sub_capability.get(&label)
943 != Some(&(declaration_index, capability_index))
944 {
945 continue;
946 }
947 if matches!(&capability.base, CapabilityBase::ValueType(_))
948 && last_value_capability.get(&label)
949 != Some(&(declaration_index, capability_index))
950 {
951 continue;
952 }
953 if *kind != TypeKind::Attribute
954 && let Some(capability_id) = released_object_capability_identity(capability)
955 {
956 let key = (label.clone(), capability_id);
957 if first_object_capability.contains_key(&key) {
958 continue;
959 }
960 first_object_capability.insert(key, (declaration_index, capability_index));
961 }
962 if omitted_released_capabilities.is_some_and(|omitted| {
963 capability
964 .span
965 .is_some_and(|span| omitted.contains(&span.begin_offset))
966 }) {
967 continue;
968 }
969 let released_annotations;
970 let annotations = if let Some(released) = released {
971 released_annotations = capability
972 .annotations
973 .iter()
974 .filter(|annotation| {
975 released_annotation_target(released, annotation)
976 == Some(ReleasedAnnotationTarget::Capability)
977 })
978 .cloned()
979 .collect::<Vec<_>>();
980 released_annotations.as_slice()
981 } else {
982 capability.annotations.as_slice()
983 };
984 insert_capability(
985 &mut assembler,
986 &ids,
987 &id,
988 capability,
989 CapabilityAnnotations::Released {
990 annotations,
991 played_role_declaration: capability.span.and_then(|span| {
992 played_role_declarations
993 .and_then(|declarations| declarations.get(&span.begin_offset))
994 .map(String::as_str)
995 }),
996 },
997 &document,
998 source,
999 )?;
1000 }
1001 }
1002
1003 for (label, annotations) in merged_value_annotations {
1004 let Some(first) = annotations.first() else {
1005 continue;
1006 };
1007 let annotation_span = source_span(&document, source, first.span())?;
1008 let attribute = AttributeId::new(&label)
1009 .map_err(|diagnostic| contract(diagnostic, annotation_span))?;
1010 insert_released_annotations(
1011 &mut assembler,
1012 AnnotationSubjectId::Value(ValueFactId::new(attribute)),
1013 &annotations,
1014 &document,
1015 source,
1016 )?;
1017 }
1018 }
1019
1020 for definable in &definables {
1021 match definable {
1022 Definable::TypeDeclaration(_) => {}
1023 Definable::Struct(structure) => {
1024 insert_struct(&mut assembler, structure, &document, source)?;
1025 }
1026 Definable::Function(function) => {
1027 insert_function(&mut assembler, function, &document, source)?;
1028 let function_id = FunctionId::new(function.signature.ident.as_str_unchecked())
1029 .expect("TypeQL emitted a function identifier rejected by the contract");
1030 let body_span = source_span(&document, source, function.block.span)?;
1031 let references =
1032 function_references::collect_function_body_references(&function.block)
1033 .map_err(|diagnostic| contract(diagnostic, body_span))?;
1034 function_body_references.insert(function_id, references);
1035 }
1036 }
1037 }
1038
1039 assembler
1040 .finish()
1041 .map(|declared| TypeqlDeclaredSchema::new(declared, function_body_references))
1042}
1043
1044fn restore_released_labels(released: &ReleasedSyntax, definables: &mut [Definable]) {
1045 for definable in definables {
1046 if let Definable::TypeDeclaration(declaration) = definable {
1047 released.restore_label(&mut declaration.label);
1048 }
1049 }
1050}
1051
1052pub fn typeql_to_declared(
1057 document: DocumentId,
1058 source: &str,
1059) -> Result<DeclaredSchema, SchemaDiagnostics> {
1060 typeql_to_declared_with_references(document, source).map(TypeqlDeclaredSchema::into_declared)
1061}
1062
1063pub fn typeql_to_facts(
1065 document: DocumentId,
1066 source: &str,
1067) -> Result<Vec<SchemaFact>, SchemaDiagnostics> {
1068 let declared = typeql_to_declared(document, source)?;
1069 Ok(declared.facts().cloned().collect())
1070}
1071
1072pub fn toml_to_declared(
1081 rendered_typeql_document: DocumentId,
1082 toml_source: &str,
1083) -> Result<DeclaredSchema, SchemaDiagnostics> {
1084 let rendered_typeql =
1085 type_bridge_toml_transpiler::toml_to_typeql(toml_source).map_err(|transpile_error| {
1086 error(
1087 DiagnosticCategory::InvalidContract,
1088 "invalid_toml_schema",
1089 format!("TOML schema transpilation failed: {transpile_error}"),
1090 None,
1091 )
1092 })?;
1093 typeql_to_declared(rendered_typeql_document, &rendered_typeql)
1094}
1095
1096pub fn toml_to_facts(
1101 rendered_typeql_document: DocumentId,
1102 toml_source: &str,
1103) -> Result<Vec<SchemaFact>, SchemaDiagnostics> {
1104 let declared = toml_to_declared(rendered_typeql_document, toml_source)?;
1105 Ok(declared.facts().cloned().collect())
1106}
1107
1108#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
1109enum ReleasedObjectCapabilityIdentity {
1110 Owns(String),
1111 Relates(String),
1112 Plays(String, String),
1113}
1114
1115fn released_declaration_is_omitted(
1116 declaration: &TypeDeclaration,
1117 omitted: Option<&BTreeSet<usize>>,
1118) -> bool {
1119 omitted.is_some_and(|omitted| {
1120 declaration
1121 .span
1122 .is_some_and(|span| omitted.contains(&span.begin_offset))
1123 })
1124}
1125
1126fn released_object_capability_identity(
1127 capability: &Capability,
1128) -> Option<ReleasedObjectCapabilityIdentity> {
1129 match &capability.base {
1130 CapabilityBase::Owns(owns) => capability_type_ref(&owns.owned)
1131 .ok()
1132 .map(|(label, _)| label)
1133 .map(ReleasedObjectCapabilityIdentity::Owns),
1134 CapabilityBase::Relates(relates) => capability_type_ref(&relates.related)
1135 .ok()
1136 .map(|(label, _)| label)
1137 .map(ReleasedObjectCapabilityIdentity::Relates),
1138 CapabilityBase::Plays(plays) => Some(ReleasedObjectCapabilityIdentity::Plays(
1139 typeql_label(&plays.role.scope),
1140 typeql_label(&plays.role.name),
1141 )),
1142 _ => None,
1143 }
1144}
1145
1146fn released_annotation_target(
1147 released: &ReleasedSyntax,
1148 annotation: &Annotation,
1149) -> Option<ReleasedAnnotationTarget> {
1150 annotation
1151 .span()
1152 .map(|span| span.begin_offset)
1153 .and_then(|start| released.annotation_target(start))
1154}
1155
1156fn released_attribute_annotation_target(annotation: &Annotation) -> ReleasedAnnotationTarget {
1157 match annotation {
1158 Annotation::Regex(_) | Annotation::Range(_) | Annotation::Values(_) => {
1159 ReleasedAnnotationTarget::Value
1160 }
1161 _ => ReleasedAnnotationTarget::Type,
1162 }
1163}
1164
1165fn infer_type_kinds(
1166 document: &DocumentId,
1167 source: &str,
1168 declarations: &[&TypeDeclaration],
1169 released: bool,
1170) -> Result<Vec<TypeKind>, SchemaDiagnostics> {
1171 let mut inferred = Vec::with_capacity(declarations.len());
1172 for declaration in declarations {
1173 let mut kind = declaration
1174 .kind
1175 .as_ref()
1176 .map(|kind| kind_from_token(&kind.to_string()))
1177 .transpose()
1178 .map_err(|message| {
1179 at(
1180 document,
1181 source,
1182 declaration.span,
1183 "unsupported_typeql_kind",
1184 message,
1185 )
1186 })?;
1187 for capability in &declaration.capabilities {
1188 let hint = match &capability.base {
1189 CapabilityBase::ValueType(_) => Some(TypeKind::Attribute),
1190 CapabilityBase::Relates(_) => Some(TypeKind::Relation),
1191 CapabilityBase::Sub(sub) => root_kind(&typeql_label(&sub.supertype_label)),
1192 _ => None,
1193 };
1194 if let Some(hint) = hint {
1195 merge_kind(&mut kind, hint).map_err(|message| {
1196 at(
1197 document,
1198 source,
1199 capability.span,
1200 "conflicting_typeql_kind",
1201 message,
1202 )
1203 })?;
1204 }
1205 }
1206 inferred.push(kind);
1207 }
1208
1209 loop {
1210 let mut known = BTreeMap::new();
1211 for (declaration, kind) in declarations.iter().zip(&inferred) {
1212 if let Some(kind) = kind {
1213 let label = typeql_label(&declaration.label);
1214 if let Some(previous) = known.insert(label.clone(), *kind)
1215 && previous != *kind
1216 {
1217 return Err(at(
1218 document,
1219 source,
1220 declaration.span,
1221 "conflicting_typeql_kind",
1222 format!("TypeQL label `{label}` is declared with incompatible kinds"),
1223 ));
1224 }
1225 }
1226 }
1227 let mut changed = false;
1228 for (index, declaration) in declarations.iter().enumerate() {
1229 if inferred[index].is_some() {
1230 continue;
1231 }
1232 if let Some(own_kind) = known.get(&typeql_label(&declaration.label)) {
1236 inferred[index] = Some(*own_kind);
1237 changed = true;
1238 continue;
1239 }
1240 for capability in &declaration.capabilities {
1241 let CapabilityBase::Sub(sub) = &capability.base else {
1242 continue;
1243 };
1244 if let Some(parent_kind) = known.get(&typeql_label(&sub.supertype_label)) {
1245 inferred[index] = Some(*parent_kind);
1246 changed = true;
1247 break;
1248 }
1249 }
1250 }
1251 if !changed {
1252 break;
1253 }
1254 }
1255
1256 if released {
1257 for (declaration, kind) in declarations.iter().zip(&mut inferred) {
1258 let plays_only = declaration.kind.is_none()
1259 && !declaration.capabilities.is_empty()
1260 && declaration
1261 .capabilities
1262 .iter()
1263 .all(|capability| matches!(capability.base, CapabilityBase::Plays(_)));
1264 if !plays_only {
1265 continue;
1266 }
1267 match kind {
1268 Some(TypeKind::Attribute) => {
1269 return Err(at(
1270 document,
1271 source,
1272 declaration.span,
1273 "conflicting_typeql_kind",
1274 format!(
1275 "released standalone plays label `{}` conflicts with an attribute declaration",
1276 typeql_label(&declaration.label)
1277 ),
1278 ));
1279 }
1280 Some(TypeKind::Entity | TypeKind::Relation) => {}
1281 Some(TypeKind::Struct) => {
1282 return Err(at(
1283 document,
1284 source,
1285 declaration.span,
1286 "conflicting_typeql_kind",
1287 format!(
1288 "released standalone plays label `{}` cannot resolve to a struct",
1289 typeql_label(&declaration.label)
1290 ),
1291 ));
1292 }
1293 None => *kind = Some(TypeKind::Entity),
1294 }
1295 }
1296 }
1297
1298 declarations
1299 .iter()
1300 .zip(inferred)
1301 .map(|(declaration, kind)| {
1302 kind.ok_or_else(|| {
1303 at(
1304 document,
1305 source,
1306 declaration.span,
1307 "ambiguous_typeql_kind",
1308 format!(
1309 "TypeQL declaration `{}` does not identify an entity, relation, or attribute kind",
1310 typeql_label(&declaration.label)
1311 ),
1312 )
1313 })
1314 })
1315 .collect()
1316}
1317
1318#[derive(Clone, Copy)]
1319enum CapabilityAnnotations<'a> {
1320 Strict(&'a [Annotation]),
1321 Released {
1322 annotations: &'a [Annotation],
1323 played_role_declaration: Option<&'a str>,
1324 },
1325}
1326
1327impl CapabilityAnnotations<'_> {
1328 const fn annotations(&self) -> &[Annotation] {
1329 match self {
1330 Self::Strict(annotations) | Self::Released { annotations, .. } => annotations,
1331 }
1332 }
1333
1334 const fn played_role_declaration(&self) -> Option<&str> {
1335 match self {
1336 Self::Strict(_) => None,
1337 Self::Released {
1338 played_role_declaration,
1339 ..
1340 } => *played_role_declaration,
1341 }
1342 }
1343}
1344
1345fn insert_capability(
1346 assembler: &mut FactAssembler,
1347 ids: &BTreeMap<String, TypeId>,
1348 owner: &TypeId,
1349 capability: &Capability,
1350 annotations: CapabilityAnnotations<'_>,
1351 document: &DocumentId,
1352 source: &str,
1353) -> Result<(), SchemaDiagnostics> {
1354 let annotation_slice = annotations.annotations();
1355 let capability_span = source_span(document, source, capability.span)?;
1356 let subject = match &capability.base {
1357 CapabilityBase::Sub(sub) => {
1358 let parent_label = typeql_label(&sub.supertype_label);
1359 if root_kind(&parent_label).is_some() {
1360 reject_annotations_if_present(annotation_slice, document, source)?;
1361 return Ok(());
1362 }
1363 let parent = ids.get(&parent_label).cloned().ok_or_else(|| {
1364 at(
1365 document,
1366 source,
1367 sub.span,
1368 "unknown_typeql_parent",
1369 format!("unknown TypeQL parent `{parent_label}`"),
1370 )
1371 })?;
1372 let id = SubFactId::new(owner.clone(), parent)
1373 .map_err(|diagnostic| contract(diagnostic, capability_span.clone()))?;
1374 assembler.insert_fact(SchemaFact::Sub(SubFact::new(id.clone())), capability_span)?;
1375 AnnotationSubjectId::Sub(id)
1376 }
1377 CapabilityBase::ValueType(value) => {
1378 let value_type = named_value_type(&value.value_type).map_err(|message| {
1379 at(
1380 document,
1381 source,
1382 value.span,
1383 "unsupported_typeql_value_type",
1384 message,
1385 )
1386 })?;
1387 let attribute = AttributeId::new(owner.label().as_str())
1388 .map_err(|diagnostic| contract(diagnostic, capability_span.clone()))?;
1389 let id = ValueFactId::new(attribute);
1390 assembler.insert_fact(
1391 SchemaFact::Value(ValueFact::new(id.clone(), value_type)),
1392 capability_span,
1393 )?;
1394 AnnotationSubjectId::Value(id)
1395 }
1396 CapabilityBase::Owns(owns) => {
1397 let (attribute_label, collection_mode) =
1398 capability_type_ref(&owns.owned).map_err(|message| {
1399 at(
1400 document,
1401 source,
1402 owns.span,
1403 "unsupported_typeql_owns",
1404 message,
1405 )
1406 })?;
1407 let attribute_type = ids.get(&attribute_label).ok_or_else(|| {
1408 at(
1409 document,
1410 source,
1411 owns.span,
1412 "unknown_typeql_attribute",
1413 format!("unknown owned attribute `{attribute_label}`"),
1414 )
1415 })?;
1416 if attribute_type.kind() != TypeKind::Attribute {
1417 return Err(at(
1418 document,
1419 source,
1420 owns.span,
1421 "invalid_typeql_owned_kind",
1422 "TypeQL owns targets must be attribute types",
1423 ));
1424 }
1425 let attribute = AttributeId::new(attribute_label)
1426 .map_err(|diagnostic| contract(diagnostic, capability_span.clone()))?;
1427 let id = OwnsFactId::new(owner.clone(), attribute)
1428 .map_err(|diagnostic| contract(diagnostic, capability_span.clone()))?;
1429 assembler.insert_fact(
1430 SchemaFact::Owns(OwnsFact::new_with_collection_mode(
1431 id.clone(),
1432 collection_mode,
1433 )),
1434 capability_span,
1435 )?;
1436 AnnotationSubjectId::Owns(id)
1437 }
1438 CapabilityBase::Relates(relates) => {
1439 let (role_label, collection_mode) =
1440 capability_type_ref(&relates.related).map_err(|message| {
1441 at(
1442 document,
1443 source,
1444 relates.span,
1445 "unsupported_typeql_relates",
1446 message,
1447 )
1448 })?;
1449 let role = RoleId::new(owner.label().as_str(), &role_label)
1450 .map_err(|diagnostic| contract(diagnostic, capability_span.clone()))?;
1451 let id = RelatesFactId::new(owner.clone(), role)
1452 .map_err(|diagnostic| contract(diagnostic, capability_span.clone()))?;
1453 let specializes = relates
1454 .specialised
1455 .as_ref()
1456 .map(|specialized| {
1457 capability_type_ref(specialized)
1458 .map(|(label, _)| label)
1459 .map_err(|message| {
1460 at(
1461 document,
1462 source,
1463 specialized.span(),
1464 "unsupported_typeql_role_specialization",
1465 message,
1466 )
1467 })
1468 .and_then(|label| {
1469 let span = source_span(document, source, specialized.span())?;
1470 let label = Label::new(label)
1471 .map_err(|diagnostic| contract(diagnostic, span.clone()))?;
1472 Ok((label, span))
1473 })
1474 })
1475 .transpose()?;
1476 assembler.insert_relates_with_collection_mode(
1477 id.clone(),
1478 specializes,
1479 collection_mode,
1480 capability_span,
1481 )?;
1482 AnnotationSubjectId::Relates(id)
1483 }
1484 CapabilityBase::Plays(plays) => {
1485 let relation_label = annotations
1486 .played_role_declaration()
1487 .map(str::to_owned)
1488 .unwrap_or_else(|| typeql_label(&plays.role.scope));
1489 let role_label = typeql_label(&plays.role.name);
1490 let relation = Label::new(&relation_label)
1491 .map_err(|diagnostic| contract(diagnostic, capability_span.clone()))?;
1492 let role = Label::new(&role_label)
1493 .map_err(|diagnostic| contract(diagnostic, capability_span.clone()))?;
1494 assembler.insert_plays(
1495 owner.label().clone(),
1496 relation,
1497 role,
1498 capability_span.clone(),
1499 );
1500 let role_id = RoleId::new(relation_label, role_label)
1501 .map_err(|diagnostic| contract(diagnostic, capability_span.clone()))?;
1502 let id = PlaysFactId::new(owner.clone(), role_id)
1503 .map_err(|diagnostic| contract(diagnostic, capability_span.clone()))?;
1504 AnnotationSubjectId::Plays(id)
1505 }
1506 CapabilityBase::Alias(alias) => {
1507 return Err(at(
1508 document,
1509 source,
1510 alias.span,
1511 "unsupported_typeql_alias",
1512 "TypeQL aliases require an explicit capability contract",
1513 ));
1514 }
1515 };
1516
1517 match annotations {
1518 CapabilityAnnotations::Strict(_) => {
1519 insert_annotations(assembler, subject, annotation_slice, document, source)
1520 }
1521 CapabilityAnnotations::Released { .. } => {
1522 insert_released_annotations(assembler, subject, annotation_slice, document, source)
1523 }
1524 }
1525}
1526
1527fn insert_struct(
1528 assembler: &mut FactAssembler,
1529 structure: &Struct,
1530 document: &DocumentId,
1531 source: &str,
1532) -> Result<(), SchemaDiagnostics> {
1533 if let Some(annotation) = structure.annotations.first() {
1534 return Err(at(
1535 document,
1536 source,
1537 annotation.span(),
1538 "unsupported_typeql_struct_annotation",
1539 "struct annotations are not part of the portable V2 fact contract",
1540 ));
1541 }
1542 let structure_span = source_span(document, source, structure.span)?;
1543 let id = StructId::new(structure.ident.as_str_unchecked())
1544 .map_err(|diagnostic| contract(diagnostic, structure_span.clone()))?;
1545 let mut fields = Vec::with_capacity(structure.fields.len());
1546 for field in &structure.fields {
1547 if let Some(annotation) = field.annotations.first() {
1548 return Err(at(
1549 document,
1550 source,
1551 annotation.span(),
1552 "unsupported_typeql_struct_field_annotation",
1553 "struct field annotations are not live-pinned for the portable contract",
1554 ));
1555 }
1556 let (named, optional) = simple_or_optional_named(&field.type_).map_err(|message| {
1557 at(
1558 document,
1559 source,
1560 field.span,
1561 "unsupported_typeql_struct_field",
1562 message,
1563 )
1564 })?;
1565 let value_type = named_value_type(named).map_err(|message| {
1566 at(
1567 document,
1568 source,
1569 field.span,
1570 "unsupported_typeql_struct_field",
1571 message,
1572 )
1573 })?;
1574 let field_span = source_span(document, source, field.span)?;
1575 let name = Label::new(field.key.as_str_unchecked())
1576 .map_err(|diagnostic| contract(diagnostic, field_span))?;
1577 fields.push(StructField::new(name, value_type, optional));
1578 }
1579 let fact = StructFact::new(id, fields)
1580 .map_err(|diagnostic| contract(diagnostic, structure_span.clone()))?;
1581 assembler.insert_fact(SchemaFact::Struct(fact), structure_span)
1582}
1583
1584fn insert_function(
1585 assembler: &mut FactAssembler,
1586 function: &Function,
1587 document: &DocumentId,
1588 source: &str,
1589) -> Result<(), SchemaDiagnostics> {
1590 let function_span = source_span(document, source, function.span)?;
1591 let id = FunctionId::new(function.signature.ident.as_str_unchecked())
1592 .map_err(|diagnostic| contract(diagnostic, function_span.clone()))?;
1593 let mut parameters = Vec::with_capacity(function.signature.args.len());
1594 for argument in &function.signature.args {
1595 let (named, optional) = simple_or_optional_named(&argument.type_).map_err(|message| {
1596 at(
1597 document,
1598 source,
1599 argument.span,
1600 "unsupported_typeql_function_parameter",
1601 message,
1602 )
1603 })?;
1604 if optional {
1605 return Err(at(
1606 document,
1607 source,
1608 argument.span,
1609 "unsupported_typeql_function_parameter",
1610 "optional function parameters are not part of the V2 signature contract",
1611 ));
1612 }
1613 let name = argument.var.name().ok_or_else(|| {
1614 at(
1615 document,
1616 source,
1617 argument.span,
1618 "anonymous_typeql_function_parameter",
1619 "function parameters must use named variables",
1620 )
1621 })?;
1622 let argument_span = source_span(document, source, argument.span)?;
1623 let name = Label::new(name).map_err(|diagnostic| contract(diagnostic, argument_span))?;
1624 parameters.push(FunctionParameter::new(name, type_reference(named)?));
1625 }
1626 let returns = match &function.signature.output {
1627 Output::Single(single) => {
1628 let elements = return_elements(&single.types, document, source, single.span)?;
1629 if elements.len() == 1 {
1630 FunctionReturnMode::scalar(elements.into_iter().next().expect("one return element"))
1631 } else {
1632 FunctionReturnMode::tuple(elements)
1633 .map_err(|diagnostic| contract(diagnostic, function_span.clone()))?
1634 }
1635 }
1636 Output::Stream(stream) => FunctionReturnMode::stream(return_elements(
1637 &stream.types,
1638 document,
1639 source,
1640 stream.span,
1641 )?)
1642 .map_err(|diagnostic| contract(diagnostic, function_span.clone()))?,
1643 };
1644 let signature = FunctionSignature::new(parameters, returns)
1645 .map_err(|diagnostic| contract(diagnostic, function_span.clone()))?;
1646 let block_span = function.block.span.ok_or_else(|| {
1647 error(
1648 DiagnosticCategory::InvalidContract,
1649 "missing_typeql_function_body_span",
1650 "TypeQL parser did not retain the function body span",
1651 Some(function_span.clone()),
1652 )
1653 })?;
1654 let body_text = source
1655 .get(block_span.begin_offset..block_span.end_offset)
1656 .ok_or_else(|| {
1657 error(
1658 DiagnosticCategory::InvalidContract,
1659 "invalid_typeql_function_body_span",
1660 "TypeQL function body span is outside the original source",
1661 Some(function_span.clone()),
1662 )
1663 })?;
1664 let body_span = source_span(document, source, Some(block_span))?;
1665 let body =
1666 FunctionBody::new(body_text).map_err(|diagnostic| contract(diagnostic, body_span))?;
1667 assembler.insert_fact(
1668 SchemaFact::Function(FunctionFact::new(id.clone(), signature, body)),
1669 function_span,
1670 )?;
1671 insert_annotations(
1672 assembler,
1673 AnnotationSubjectId::Function(id),
1674 &function.annotations,
1675 document,
1676 source,
1677 )
1678}
1679
1680fn return_elements(
1681 types: &[NamedTypeAny],
1682 document: &DocumentId,
1683 source: &str,
1684 span: Option<Span>,
1685) -> Result<Vec<FunctionReturnElement>, SchemaDiagnostics> {
1686 types
1687 .iter()
1688 .map(|type_| {
1689 let (named, optional) = simple_or_optional_named(type_).map_err(|message| {
1690 at(
1691 document,
1692 source,
1693 span,
1694 "unsupported_typeql_function_return",
1695 message,
1696 )
1697 })?;
1698 Ok(FunctionReturnElement::new(type_reference(named)?, optional))
1699 })
1700 .collect()
1701}
1702
1703fn insert_annotations(
1704 assembler: &mut FactAssembler,
1705 subject: AnnotationSubjectId,
1706 annotations: &[Annotation],
1707 document: &DocumentId,
1708 source: &str,
1709) -> Result<(), SchemaDiagnostics> {
1710 for annotation in annotations {
1711 insert_released_annotations(
1712 assembler,
1713 subject.clone(),
1714 core::slice::from_ref(annotation),
1715 document,
1716 source,
1717 )?;
1718 }
1719 Ok(())
1720}
1721
1722fn insert_released_annotations(
1723 assembler: &mut FactAssembler,
1724 subject: AnnotationSubjectId,
1725 annotations: &[Annotation],
1726 document: &DocumentId,
1727 source: &str,
1728) -> Result<(), SchemaDiagnostics> {
1729 let mut merged = BTreeMap::new();
1736 let mut released_range: Option<(
1737 Option<&typeql::value::Literal>,
1738 Option<&typeql::value::Literal>,
1739 SourceSpan,
1740 )> = None;
1741 for annotation in annotations {
1742 let annotation_span = source_span(document, source, annotation.span())?;
1743 if let Annotation::Range(range) = annotation {
1744 let (lower, upper, latest_span) =
1745 released_range.get_or_insert((None, None, annotation_span.clone()));
1746 if let Some(minimum) = range.min.as_ref() {
1747 *lower = Some(minimum);
1748 }
1749 if let Some(maximum) = range.max.as_ref() {
1750 *upper = Some(maximum);
1751 }
1752 *latest_span = annotation_span;
1753 continue;
1754 }
1755 let kind = annotation_identity_kind(annotation, annotation_span.clone())?;
1756 merged.insert(
1757 AnnotationFactId::new(subject.clone(), kind),
1758 (annotation, annotation_span),
1759 );
1760 }
1761 for (id, (annotation, annotation_span)) in merged {
1762 let (kind, value) = match annotation {
1763 Annotation::Abstract(_) => {
1764 (AnnotationKindId::Abstract, SchemaAnnotationValue::Presence)
1765 }
1766 Annotation::Independent(_) => (
1767 AnnotationKindId::Independent,
1768 SchemaAnnotationValue::Presence,
1769 ),
1770 Annotation::Key(_) => (AnnotationKindId::Key, SchemaAnnotationValue::Presence),
1771 Annotation::Unique(_) => (AnnotationKindId::Unique, SchemaAnnotationValue::Presence),
1772 Annotation::Distinct(_) => {
1773 (AnnotationKindId::Distinct, SchemaAnnotationValue::Presence)
1774 }
1775 Annotation::Cardinality(cardinality) => {
1776 let cardinality = match &cardinality.range {
1777 CardinalityRange::Exact(exact) => {
1778 let exact = parse_u64(&exact.value, &annotation_span)?;
1779 Cardinality::new(exact, Some(exact))
1780 }
1781 CardinalityRange::Range(minimum, maximum) => Cardinality::new(
1782 parse_u64(&minimum.value, &annotation_span)?,
1783 maximum
1784 .as_ref()
1785 .map(|maximum| parse_u64(&maximum.value, &annotation_span))
1786 .transpose()?,
1787 ),
1788 }
1789 .map_err(|diagnostic| contract(diagnostic, annotation_span.clone()))?;
1790 (
1791 AnnotationKindId::Card,
1792 SchemaAnnotationValue::Cardinality(cardinality),
1793 )
1794 }
1795 Annotation::Regex(regex) => {
1796 validate_annotation_string(®ex.regex, "regex", annotation_span.clone())?;
1797 let text = regex.regex.unescape_regex().map_err(|unescape_error| {
1798 error(
1799 DiagnosticCategory::InvalidContract,
1800 "invalid_typeql_regex",
1801 format!("TypeQL regex decoding failed: {unescape_error}"),
1802 Some(annotation_span.clone()),
1803 )
1804 })?;
1805 let pattern = RegexPattern::new(text)
1806 .map_err(|diagnostic| contract(diagnostic, annotation_span.clone()))?;
1807 (
1808 AnnotationKindId::Regex,
1809 SchemaAnnotationValue::Regex(pattern),
1810 )
1811 }
1812 Annotation::Doc(doc) => {
1813 validate_annotation_string(&doc.doc, "doc", annotation_span.clone())?;
1814 let text = doc.doc.unescape().map_err(|unescape_error| {
1815 error(
1816 DiagnosticCategory::InvalidContract,
1817 "invalid_typeql_doc",
1818 format!("TypeQL documentation decoding failed: {unescape_error}"),
1819 Some(annotation_span.clone()),
1820 )
1821 })?;
1822 let text = DocText::new(text)
1823 .map_err(|diagnostic| contract(diagnostic, annotation_span.clone()))?;
1824 (AnnotationKindId::Doc, SchemaAnnotationValue::Doc(text))
1825 }
1826 Annotation::Meta(meta) => {
1827 validate_annotation_string(&meta.key, "meta_key", annotation_span.clone())?;
1828 let key = meta.key.unescape().map_err(|unescape_error| {
1829 error(
1830 DiagnosticCategory::InvalidContract,
1831 "invalid_typeql_meta_key",
1832 format!("TypeQL metadata key decoding failed: {unescape_error}"),
1833 Some(annotation_span.clone()),
1834 )
1835 })?;
1836 validate_annotation_string(&meta.value, "meta_value", annotation_span.clone())?;
1837 let value = meta.value.unescape().map_err(|unescape_error| {
1838 error(
1839 DiagnosticCategory::InvalidContract,
1840 "invalid_typeql_meta_value",
1841 format!("TypeQL metadata value decoding failed: {unescape_error}"),
1842 Some(annotation_span.clone()),
1843 )
1844 })?;
1845 let kind = AnnotationKindId::meta(key)
1846 .map_err(|diagnostic| contract(diagnostic, annotation_span.clone()))?;
1847 let value = CanonicalString::new(value)
1848 .map_err(|diagnostic| contract(diagnostic, annotation_span.clone()))?;
1849 (
1850 kind,
1851 SchemaAnnotationValue::Meta(CanonicalValue::String(value)),
1852 )
1853 }
1854 Annotation::Cascade(_) | Annotation::Subkey(_) => {
1855 return Err(error(
1856 DiagnosticCategory::UnsupportedCapability,
1857 "unsupported_typeql_annotation",
1858 "TypeQL annotation has no portable V2 annotation identity",
1859 Some(annotation_span),
1860 ));
1861 }
1862 Annotation::Range(range) => {
1863 let lower = range
1864 .min
1865 .as_ref()
1866 .map(|literal| annotation_literal(literal, document, source))
1867 .transpose()?;
1868 let upper = range
1869 .max
1870 .as_ref()
1871 .map(|literal| annotation_literal(literal, document, source))
1872 .transpose()?;
1873 let range = CanonicalValueRange::new(lower, upper)
1874 .map_err(|diagnostic| contract(diagnostic, annotation_span.clone()))?;
1875 (AnnotationKindId::Range, SchemaAnnotationValue::Range(range))
1876 }
1877 Annotation::Values(values) => {
1878 if values.values.len() > MAX_CANONICAL_COLLECTION_LEN {
1879 return Err(error(
1880 DiagnosticCategory::InvalidContract,
1881 "values_annotation_member_limit_exceeded",
1882 format!(
1883 "@values contains {} members; the maximum is {MAX_CANONICAL_COLLECTION_LEN}",
1884 values.values.len()
1885 ),
1886 Some(annotation_span),
1887 ));
1888 }
1889 let values = values
1890 .values
1891 .iter()
1892 .map(|literal| annotation_literal(literal, document, source))
1893 .collect::<Result<Vec<_>, _>>()?;
1894 let values = CanonicalValueSet::new(values)
1895 .map_err(|diagnostic| contract(diagnostic, annotation_span.clone()))?;
1896 (
1897 AnnotationKindId::Values,
1898 SchemaAnnotationValue::Values(values),
1899 )
1900 }
1901 };
1902 debug_assert_eq!(id.kind(), &kind);
1903 let fact = AnnotationFact::new(id, value)
1904 .map_err(|diagnostic| contract(diagnostic, annotation_span.clone()))?;
1905 assembler.insert_fact(SchemaFact::Annotation(fact), annotation_span)?;
1906 }
1907 if let Some((lower, upper, annotation_span)) = released_range
1908 && (lower.is_some() || upper.is_some())
1909 {
1910 let lower = lower
1911 .map(|literal| annotation_literal(literal, document, source))
1912 .transpose()?;
1913 let upper = upper
1914 .map(|literal| annotation_literal(literal, document, source))
1915 .transpose()?;
1916 let range = CanonicalValueRange::new(lower, upper)
1917 .map_err(|diagnostic| contract(diagnostic, annotation_span.clone()))?;
1918 let id = AnnotationFactId::new(subject, AnnotationKindId::Range);
1919 let fact = AnnotationFact::new(id, SchemaAnnotationValue::Range(range))
1920 .map_err(|diagnostic| contract(diagnostic, annotation_span.clone()))?;
1921 assembler.insert_fact(SchemaFact::Annotation(fact), annotation_span)?;
1922 }
1923 Ok(())
1924}
1925
1926fn annotation_identity_kind(
1927 annotation: &Annotation,
1928 annotation_span: SourceSpan,
1929) -> Result<AnnotationKindId, SchemaDiagnostics> {
1930 Ok(match annotation {
1931 Annotation::Abstract(_) => AnnotationKindId::Abstract,
1932 Annotation::Independent(_) => AnnotationKindId::Independent,
1933 Annotation::Key(_) => AnnotationKindId::Key,
1934 Annotation::Unique(_) => AnnotationKindId::Unique,
1935 Annotation::Distinct(_) => AnnotationKindId::Distinct,
1936 Annotation::Cardinality(_) => AnnotationKindId::Card,
1937 Annotation::Regex(_) => AnnotationKindId::Regex,
1938 Annotation::Doc(_) => AnnotationKindId::Doc,
1939 Annotation::Range(_) => AnnotationKindId::Range,
1940 Annotation::Values(_) => AnnotationKindId::Values,
1941 Annotation::Meta(meta) => {
1942 validate_annotation_string(&meta.key, "meta_key", annotation_span.clone())?;
1943 let key = meta.key.unescape().map_err(|unescape_error| {
1944 error(
1945 DiagnosticCategory::InvalidContract,
1946 "invalid_typeql_meta_key",
1947 format!("TypeQL metadata key decoding failed: {unescape_error}"),
1948 Some(annotation_span.clone()),
1949 )
1950 })?;
1951 AnnotationKindId::meta(key)
1952 .map_err(|diagnostic| contract(diagnostic, annotation_span))?
1953 }
1954 Annotation::Cascade(_) | Annotation::Subkey(_) => {
1955 return Err(error(
1956 DiagnosticCategory::UnsupportedCapability,
1957 "unsupported_typeql_annotation",
1958 "TypeQL annotation has no portable V2 annotation identity",
1959 Some(annotation_span),
1960 ));
1961 }
1962 })
1963}
1964
1965fn validate_annotation_string(
1966 value: &typeql::value::StringLiteral,
1967 domain: &'static str,
1968 span: SourceSpan,
1969) -> Result<(), SchemaDiagnostics> {
1970 validate_quoted_string(value, domain).map_err(|conversion| {
1971 error(
1972 DiagnosticCategory::InvalidContract,
1973 conversion.code(),
1974 conversion.message(),
1975 Some(span),
1976 )
1977 })
1978}
1979
1980fn annotation_literal(
1981 literal: &typeql::value::Literal,
1982 document: &DocumentId,
1983 source: &str,
1984) -> Result<CanonicalValue, SchemaDiagnostics> {
1985 canonical_literal(literal).map_err(|conversion| {
1986 at(
1987 document,
1988 source,
1989 literal.span,
1990 conversion.code(),
1991 conversion.message(),
1992 )
1993 })
1994}
1995
1996fn reject_annotations_if_present(
1997 annotations: &[Annotation],
1998 document: &DocumentId,
1999 source: &str,
2000) -> Result<(), SchemaDiagnostics> {
2001 if let Some(annotation) = annotations.first() {
2002 return Err(at(
2003 document,
2004 source,
2005 annotation.span(),
2006 "unsupported_root_sub_annotation",
2007 "annotations on built-in root sub declarations have no portable subject identity",
2008 ));
2009 }
2010 Ok(())
2011}
2012
2013fn capability_type_ref(reference: &TypeRefAny) -> Result<(String, CollectionMode), String> {
2014 match reference {
2015 TypeRefAny::Type(inner) => {
2016 plain_inner_type_ref(inner).map(|label| (label, CollectionMode::Unordered))
2017 }
2018 TypeRefAny::List(list) => {
2019 plain_inner_type_ref(&list.inner).map(|label| (label, CollectionMode::OrderedList))
2020 }
2021 }
2022}
2023
2024fn plain_inner_type_ref(reference: &TypeRef) -> Result<String, String> {
2025 match reference {
2026 TypeRef::Label(label) => Ok(typeql_label(label)),
2027 TypeRef::Scoped(_) => {
2028 Err("scoped type references are not valid in this capability".to_owned())
2029 }
2030 TypeRef::Variable(_) => {
2031 Err("type variables are not valid in schema declarations".to_owned())
2032 }
2033 }
2034}
2035
2036fn simple_or_optional_named(reference: &NamedTypeAny) -> Result<(&NamedType, bool), String> {
2037 match reference {
2038 NamedTypeAny::Simple(named) => Ok((named, false)),
2039 NamedTypeAny::Optional(optional) => Ok((&optional.inner, true)),
2040 NamedTypeAny::List(_) => Err("list type references are not live-pinned".to_owned()),
2041 }
2042}
2043
2044fn type_reference(named: &NamedType) -> Result<TypeReference, SchemaDiagnostics> {
2045 TypeReference::from_token(named.to_string()).map_err(contract_without_span)
2046}
2047
2048fn named_value_type(named: &NamedType) -> Result<ValueTypeTag, String> {
2049 match named {
2050 NamedType::BuiltinValueType(value_type) => {
2051 value_type_from_token(&value_type.token.to_string())
2052 }
2053 NamedType::Label(label) => value_type_from_token(&typeql_label(label)),
2057 }
2058}
2059
2060fn value_type_from_token(token: &str) -> Result<ValueTypeTag, String> {
2061 match token {
2062 "string" => Ok(ValueTypeTag::String),
2063 "integer" | "int" | "long" => Ok(ValueTypeTag::Long),
2064 "double" => Ok(ValueTypeTag::Double),
2065 "boolean" | "bool" => Ok(ValueTypeTag::Boolean),
2066 "date" => Ok(ValueTypeTag::Date),
2067 "datetime" => Ok(ValueTypeTag::DateTime),
2068 "datetime-tz" => Ok(ValueTypeTag::DateTimeTz),
2069 "decimal" => Ok(ValueTypeTag::Decimal),
2070 "duration" => Ok(ValueTypeTag::Duration),
2071 _ => Err(format!("unsupported TypeQL value type `{token}`")),
2072 }
2073}
2074
2075fn kind_from_token(token: &str) -> Result<TypeKind, String> {
2076 match token {
2077 "entity" => Ok(TypeKind::Entity),
2078 "relation" => Ok(TypeKind::Relation),
2079 "attribute" => Ok(TypeKind::Attribute),
2080 "role" => Err("roles must be declared through relation relates facts".to_owned()),
2081 _ => Err(format!("unsupported TypeQL type kind `{token}`")),
2082 }
2083}
2084
2085fn root_kind(label: &str) -> Option<TypeKind> {
2086 match label {
2087 "entity" => Some(TypeKind::Entity),
2088 "relation" => Some(TypeKind::Relation),
2089 "attribute" => Some(TypeKind::Attribute),
2090 _ => None,
2091 }
2092}
2093
2094fn merge_kind(kind: &mut Option<TypeKind>, hint: TypeKind) -> Result<(), String> {
2095 match kind {
2096 Some(current) if *current != hint => {
2097 Err("TypeQL declaration contains incompatible kind evidence".to_owned())
2098 }
2099 Some(_) => Ok(()),
2100 None => {
2101 *kind = Some(hint);
2102 Ok(())
2103 }
2104 }
2105}
2106
2107fn typeql_label(label: &typeql::type_::Label) -> String {
2108 label.ident.as_str_unchecked().to_owned()
2109}
2110
2111fn parse_u64(value: &str, span: &SourceSpan) -> Result<u64, SchemaDiagnostics> {
2112 value.parse().map_err(|_| {
2113 error(
2114 DiagnosticCategory::InvalidContract,
2115 "invalid_typeql_cardinality",
2116 "TypeQL cardinality is outside the portable unsigned 64-bit domain",
2117 Some(span.clone()),
2118 )
2119 })
2120}
2121
2122fn source_span(
2123 document: &DocumentId,
2124 source: &str,
2125 span: Option<Span>,
2126) -> Result<SourceSpan, SchemaDiagnostics> {
2127 let span = span.unwrap_or(Span {
2128 begin_offset: 0,
2129 end_offset: source.len(),
2130 });
2131 if span.begin_offset > span.end_offset
2132 || span.end_offset > source.len()
2133 || !source.is_char_boundary(span.begin_offset)
2134 || !source.is_char_boundary(span.end_offset)
2135 {
2136 return Err(error(
2137 DiagnosticCategory::InvalidContract,
2138 "invalid_typeql_source_span",
2139 "TypeQL parser returned a source span outside the original input",
2140 None,
2141 ));
2142 }
2143 let (line, column) = line_column(source, span.begin_offset)?;
2144 let (end_line, end_column) = line_column(source, span.end_offset)?;
2145 SourceSpan::new(
2146 document.clone(),
2147 span.begin_offset as u64,
2148 span.end_offset as u64,
2149 line,
2150 column,
2151 end_line,
2152 end_column,
2153 )
2154 .map_err(contract_without_span)
2155}
2156
2157fn query_span(
2158 document: &DocumentId,
2159 source: &str,
2160 span: Option<Span>,
2161) -> Result<Option<SourceSpan>, SchemaDiagnostics> {
2162 source_span(document, source, span).map(Some)
2163}
2164
2165fn line_column(source: &str, offset: usize) -> Result<(u32, u32), SchemaDiagnostics> {
2166 let prefix = &source[..offset];
2167 let line_count = prefix.bytes().filter(|byte| *byte == b'\n').count() + 1;
2168 let column_count = prefix
2169 .rsplit_once('\n')
2170 .map_or(prefix, |(_, tail)| tail)
2171 .chars()
2172 .count()
2173 + 1;
2174 let line = u32::try_from(line_count).map_err(|_| {
2175 error(
2176 DiagnosticCategory::InvalidContract,
2177 "typeql_source_position_overflow",
2178 "TypeQL source line exceeds the portable position domain",
2179 None,
2180 )
2181 })?;
2182 let column = u32::try_from(column_count).map_err(|_| {
2183 error(
2184 DiagnosticCategory::InvalidContract,
2185 "typeql_source_position_overflow",
2186 "TypeQL source column exceeds the portable position domain",
2187 None,
2188 )
2189 })?;
2190 Ok((line, column))
2191}
2192
2193fn at(
2194 document: &DocumentId,
2195 source: &str,
2196 span: Option<Span>,
2197 code: &'static str,
2198 message: impl Into<String>,
2199) -> SchemaDiagnostics {
2200 match source_span(document, source, span) {
2201 Ok(span) => error(
2202 DiagnosticCategory::InvalidContract,
2203 code,
2204 message,
2205 Some(span),
2206 ),
2207 Err(error) => error,
2208 }
2209}
2210
2211fn error(
2212 category: DiagnosticCategory,
2213 code: &'static str,
2214 message: impl Into<String>,
2215 primary: Option<SourceSpan>,
2216) -> SchemaDiagnostics {
2217 let diagnostic = Diagnostic::new(
2218 category,
2219 DiagnosticCode::new(code).expect("static schema compatibility diagnostic code is valid"),
2220 message,
2221 );
2222 SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, primary))
2223}
2224
2225fn contract(diagnostic: Diagnostic, span: SourceSpan) -> SchemaDiagnostics {
2226 SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, Some(span)))
2227}
2228
2229fn contract_without_span(diagnostic: Diagnostic) -> SchemaDiagnostics {
2230 SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, None))
2231}
2232
2233#[cfg(test)]
2234mod tests {
2235 use super::*;
2236
2237 #[test]
2238 fn trusted_generator_size_policy_does_not_widen_defensive_inputs() {
2239 assert!(TypeqlSourceSizePolicy::Defensive.allows(MAX_TYPEQL_SCHEMA_BYTES));
2240 assert!(!TypeqlSourceSizePolicy::Defensive.allows(MAX_TYPEQL_SCHEMA_BYTES + 1));
2241 assert!(TypeqlSourceSizePolicy::TrustedGenerator.allows(MAX_TYPEQL_SCHEMA_BYTES + 1));
2242 }
2243
2244 #[test]
2245 fn deep_role_specialization_index_uses_heap_stack_and_one_tree_walk() {
2246 const DEPTH: usize = 25_000;
2247 let mut relation_names = BTreeSet::new();
2248 let mut role_names_by_relation = BTreeMap::<String, Vec<String>>::new();
2249 let mut roles = BTreeMap::new();
2250 let mut parents = BTreeMap::new();
2251
2252 for index in 0..DEPTH {
2253 let relation = format!("relation-{index}");
2254 let role = if index == 0 {
2255 "root-role".to_owned()
2256 } else {
2257 format!("role-{index}")
2258 };
2259 relation_names.insert(relation.clone());
2260 role_names_by_relation.insert(relation.clone(), vec![role.clone()]);
2261 roles.insert(
2262 (relation.clone(), role),
2263 ReleasedIndexedRole {
2264 capability_index: index,
2265 specializes: (index != 0).then(|| "root-role".to_owned()),
2266 },
2267 );
2268 if index != 0 {
2269 parents.insert(relation, format!("relation-{}", index - 1));
2270 }
2271 }
2272
2273 let leaf = format!("relation-{}", DEPTH - 1);
2274 let leaf_role = format!("role-{}", DEPTH - 1);
2275 let played = BTreeMap::from([(
2276 leaf.clone(),
2277 BTreeSet::from(["root-role".to_owned(), leaf_role.clone()]),
2278 )]);
2279 let resolution = released_role_validities(
2280 &relation_names,
2281 &role_names_by_relation,
2282 &played,
2283 &roles,
2284 &parents,
2285 );
2286
2287 assert_eq!(resolution.direct.len(), DEPTH);
2288 assert!(
2289 resolution
2290 .direct
2291 .values()
2292 .all(|validity| *validity == ReleasedRoleValidity::Valid)
2293 );
2294 assert_eq!(
2295 resolution.plays.get(&(leaf.clone(), leaf_role)),
2296 Some(&ReleasedRoleValidity::Valid)
2297 );
2298 assert_eq!(
2299 resolution.plays.get(&(leaf, "root-role".to_owned())),
2300 Some(&ReleasedRoleValidity::Invalid)
2301 );
2302 }
2303}