1use std::cmp::Ordering;
4use std::collections::{BTreeMap, BTreeSet};
5use std::fmt;
6
7use serde::{Serialize, Serializer};
8
9use crate::codec::{FormatVersion, to_canonical_json};
10use crate::diagnostic::{Diagnostic, DiagnosticCategory};
11use crate::fingerprint::{CanonicalizationVersion, Fingerprint, FingerprintDomain};
12use crate::id::{AttributeId, FunctionId, Label, RoleId, StructId, TypeId, TypeKind};
13use crate::limits::MAX_CANONICAL_COLLECTION_LEN;
14use crate::schema::{
15 AnnotationFact, AnnotationFactId, AnnotationSubjectId, OwnsFactId, PlaysFactId,
16 SchemaAnnotationValue, SchemaFactId, SubFactId, ValueFactId,
17};
18use crate::schema_fingerprint::SemanticSchemaFingerprint;
19use crate::value::{Cardinality, ValueTypeTag};
20
21const MAX_PROJECTION_COMPONENT_ID_BYTES: usize = 255;
22const PYTHON_GENERATOR_HANDLER_ID: &str = "typebridge.generator.python";
23const TYPESCRIPT_GENERATOR_HANDLER_ID: &str = "typebridge.generator.typescript";
24const RUST_GENERATOR_HANDLER_ID: &str = "typebridge.generator.rust";
25const CODE_RESOURCE_DOMAIN: &str = "typebridge.binding.code-resource";
26const RAW_BYTES_CANONICALIZATION: &str = "typebridge.raw-bytes/v1";
27const BINDING_PROJECTION_DOMAIN: &str = "typebridge.binding.projection";
28const BINDING_PROJECTION_CANONICALIZATION: &str = "typebridge.binding-projection/v1";
29const BINDING_PROJECTION_CONTENT_DOMAIN: &str = "typebridge.binding.projection-content";
30const MAX_TARGET_IDENTIFIER_BYTES: usize = 255;
31
32#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
34#[serde(rename_all = "snake_case")]
35pub enum BindingTarget {
36 Python,
38 #[serde(rename = "typescript")]
40 TypeScript,
41 Rust,
43}
44
45impl BindingTarget {
46 const fn required_generator_handler_id(self) -> &'static str {
47 match self {
48 Self::Python => PYTHON_GENERATOR_HANDLER_ID,
49 Self::TypeScript => TYPESCRIPT_GENERATOR_HANDLER_ID,
50 Self::Rust => RUST_GENERATOR_HANDLER_ID,
51 }
52 }
53}
54
55#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
57pub enum PythonNamingPolicy {
58 #[serde(rename = "typebridge.python/v1")]
60 TypeBridgeV1,
61}
62
63#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
65pub enum TypeScriptNamingPolicy {
66 #[serde(rename = "typebridge.typescript/v1")]
68 TypeBridgeV1,
69}
70
71#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
73pub enum RustNamingPolicy {
74 #[serde(rename = "typebridge.rust/v1")]
76 TypeBridgeV1,
77}
78
79#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
81pub enum RustCreatePolicy {
82 #[serde(rename = "typebridge.rust.validated-create-input/v1")]
86 ValidatedInputV1,
87}
88
89#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
91#[serde(tag = "binding")]
92pub enum ProjectionConfig {
93 #[serde(rename = "python")]
95 Python {
96 naming_policy: PythonNamingPolicy,
98 },
99 #[serde(rename = "typescript")]
101 TypeScript {
102 naming_policy: TypeScriptNamingPolicy,
104 },
105 #[serde(rename = "rust")]
107 Rust {
108 naming_policy: RustNamingPolicy,
110 create_policy: RustCreatePolicy,
112 },
113}
114
115impl ProjectionConfig {
116 #[must_use]
118 pub const fn python() -> Self {
119 Self::Python {
120 naming_policy: PythonNamingPolicy::TypeBridgeV1,
121 }
122 }
123
124 #[must_use]
126 pub const fn typescript() -> Self {
127 Self::TypeScript {
128 naming_policy: TypeScriptNamingPolicy::TypeBridgeV1,
129 }
130 }
131
132 #[must_use]
134 pub const fn rust() -> Self {
135 Self::Rust {
136 naming_policy: RustNamingPolicy::TypeBridgeV1,
137 create_policy: RustCreatePolicy::ValidatedInputV1,
138 }
139 }
140
141 #[must_use]
143 pub const fn target(&self) -> BindingTarget {
144 match self {
145 Self::Python { .. } => BindingTarget::Python,
146 Self::TypeScript { .. } => BindingTarget::TypeScript,
147 Self::Rust { .. } => BindingTarget::Rust,
148 }
149 }
150
151 #[must_use]
153 pub const fn typescript_naming_policy(&self) -> Option<TypeScriptNamingPolicy> {
154 match self {
155 Self::TypeScript { naming_policy } => Some(*naming_policy),
156 Self::Python { .. } | Self::Rust { .. } => None,
157 }
158 }
159
160 #[must_use]
162 pub const fn rust_naming_policy(&self) -> Option<RustNamingPolicy> {
163 match self {
164 Self::Rust { naming_policy, .. } => Some(*naming_policy),
165 Self::Python { .. } | Self::TypeScript { .. } => None,
166 }
167 }
168
169 #[must_use]
171 pub const fn rust_create_policy(&self) -> Option<RustCreatePolicy> {
172 match self {
173 Self::Rust { create_policy, .. } => Some(*create_policy),
174 Self::Python { .. } | Self::TypeScript { .. } => None,
175 }
176 }
177}
178
179fn validate_component_id(value: String) -> Result<String, Diagnostic> {
180 let segments = value.split('.').collect::<Vec<_>>();
181 let valid_segment = |segment: &str| {
182 let mut bytes = segment.bytes();
183 bytes.next().is_some_and(|byte| byte.is_ascii_lowercase())
184 && bytes.all(|byte| {
185 byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'-' | b'_')
186 })
187 };
188 if value.len() <= MAX_PROJECTION_COMPONENT_ID_BYTES
189 && segments.len() >= 2
190 && segments.iter().all(|segment| valid_segment(segment))
191 {
192 Ok(value)
193 } else {
194 Err(Diagnostic::stable(
195 DiagnosticCategory::InvalidContract,
196 "malformed_projection_component_id",
197 "projection component ID must be a bounded lowercase namespaced identifier",
198 ))
199 }
200}
201
202macro_rules! projection_component_id {
203 ($name:ident, $doc:literal) => {
204 #[doc = $doc]
205 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
206 pub struct $name(String);
207
208 impl $name {
209 pub fn new(value: impl Into<String>) -> Result<Self, Diagnostic> {
211 Ok(Self(validate_component_id(value.into())?))
212 }
213
214 #[must_use]
216 pub fn as_str(&self) -> &str {
217 &self.0
218 }
219 }
220
221 impl fmt::Display for $name {
222 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
223 formatter.write_str(self.as_str())
224 }
225 }
226
227 impl Serialize for $name {
228 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
229 where
230 S: Serializer,
231 {
232 serializer.serialize_str(self.as_str())
233 }
234 }
235 };
236}
237
238projection_component_id!(
239 ProjectionHandlerId,
240 "A stable identity for one generator or projection handler."
241);
242projection_component_id!(
243 CodeResourceId,
244 "A stable identity for exact code-resource bytes referenced during emission."
245);
246
247#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
249#[serde(transparent)]
250pub struct ProjectionHandlerVersion(u16);
251
252impl ProjectionHandlerVersion {
253 pub const V1: Self = Self(1);
255
256 pub fn new(value: u16) -> Result<Self, Diagnostic> {
258 if value == 0 {
259 Err(Diagnostic::stable(
260 DiagnosticCategory::InvalidContract,
261 "invalid_projection_handler_version",
262 "projection handler version must be nonzero",
263 ))
264 } else {
265 Ok(Self(value))
266 }
267 }
268
269 #[must_use]
271 pub const fn get(self) -> u16 {
272 self.0
273 }
274}
275
276#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
278pub struct ProjectionHandler {
279 id: ProjectionHandlerId,
280 version: ProjectionHandlerVersion,
281}
282
283impl ProjectionHandler {
284 pub fn new(id: impl Into<String>, version: u16) -> Result<Self, Diagnostic> {
286 Ok(Self {
287 id: ProjectionHandlerId::new(id)?,
288 version: ProjectionHandlerVersion::new(version)?,
289 })
290 }
291
292 #[must_use]
294 pub fn python_v1() -> Self {
295 Self {
296 id: ProjectionHandlerId::new(PYTHON_GENERATOR_HANDLER_ID)
297 .expect("the built-in Python generator ID is valid"),
298 version: ProjectionHandlerVersion::V1,
299 }
300 }
301
302 #[must_use]
304 pub fn typescript_v1() -> Self {
305 Self {
306 id: ProjectionHandlerId::new(TYPESCRIPT_GENERATOR_HANDLER_ID)
307 .expect("built-in TypeScript projection handler ID is valid"),
308 version: ProjectionHandlerVersion::V1,
309 }
310 }
311
312 #[must_use]
314 pub fn rust_v1() -> Self {
315 Self {
316 id: ProjectionHandlerId::new(RUST_GENERATOR_HANDLER_ID)
317 .expect("built-in Rust projection handler ID is valid"),
318 version: ProjectionHandlerVersion::V1,
319 }
320 }
321
322 #[must_use]
324 pub const fn id(&self) -> &ProjectionHandlerId {
325 &self.id
326 }
327
328 #[must_use]
330 pub const fn version(&self) -> ProjectionHandlerVersion {
331 self.version
332 }
333}
334
335#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
337pub struct CodeResourceDigest {
338 id: CodeResourceId,
339 content_fingerprint: Fingerprint,
340}
341
342impl CodeResourceDigest {
343 pub fn from_bytes(id: impl Into<String>, bytes: &[u8]) -> Result<Self, Diagnostic> {
345 Ok(Self {
346 id: CodeResourceId::new(id)?,
347 content_fingerprint: Fingerprint::compute(
348 FingerprintDomain::new(CODE_RESOURCE_DOMAIN)?,
349 CanonicalizationVersion::new(RAW_BYTES_CANONICALIZATION)?,
350 None,
351 bytes,
352 ),
353 })
354 }
355
356 #[must_use]
358 pub const fn id(&self) -> &CodeResourceId {
359 &self.id
360 }
361
362 #[must_use]
364 pub const fn content_fingerprint(&self) -> &Fingerprint {
365 &self.content_fingerprint
366 }
367}
368
369#[derive(Serialize)]
370struct BindingProjectionView<'a> {
371 format_version: FormatVersion,
372 target: BindingTarget,
373 semantic_schema_fingerprint: &'a SemanticSchemaFingerprint,
374 config: &'a ProjectionConfig,
375 generator_handlers: Vec<&'a ProjectionHandler>,
376 referenced_code_resources: Vec<&'a CodeResourceDigest>,
377}
378
379fn ordered_handlers(
380 target: BindingTarget,
381 handlers: &[ProjectionHandler],
382) -> Result<Vec<&ProjectionHandler>, Diagnostic> {
383 let mut ordered = handlers.iter().collect::<Vec<_>>();
384 ordered.sort_by(|left, right| match left.id().cmp(right.id()) {
385 Ordering::Equal => left.version().cmp(&right.version()),
386 ordering => ordering,
387 });
388 if ordered.windows(2).any(|pair| pair[0].id() == pair[1].id()) {
389 return Err(Diagnostic::stable(
390 DiagnosticCategory::InvalidContract,
391 "duplicate_projection_handler_id",
392 "a projection handler identity may appear only once",
393 ));
394 }
395 if !ordered
396 .iter()
397 .any(|handler| handler.id().as_str() == target.required_generator_handler_id())
398 {
399 return Err(Diagnostic::stable(
400 DiagnosticCategory::InvalidContract,
401 "missing_target_projection_handler",
402 "projection fingerprint inputs omit the target's generator handler",
403 ));
404 }
405 Ok(ordered)
406}
407
408fn ordered_resources(
409 resources: &[CodeResourceDigest],
410) -> Result<Vec<&CodeResourceDigest>, Diagnostic> {
411 let mut ordered = resources.iter().collect::<Vec<_>>();
412 ordered.sort_by(|left, right| left.id().cmp(right.id()));
413 if ordered.windows(2).any(|pair| pair[0].id() == pair[1].id()) {
414 return Err(Diagnostic::stable(
415 DiagnosticCategory::InvalidContract,
416 "duplicate_projection_resource_id",
417 "a referenced code-resource identity may appear only once",
418 ));
419 }
420 Ok(ordered)
421}
422
423pub fn canonical_binding_projection_bytes(
425 target: BindingTarget,
426 semantic_schema: &SemanticSchemaFingerprint,
427 config: &ProjectionConfig,
428 handlers: &[ProjectionHandler],
429 resources: &[CodeResourceDigest],
430) -> Result<Vec<u8>, Diagnostic> {
431 if config.target() != target {
432 return Err(Diagnostic::stable(
433 DiagnosticCategory::InvalidContract,
434 "projection_config_target_mismatch",
435 "projection configuration belongs to a different binding target",
436 ));
437 }
438 let view = BindingProjectionView {
439 format_version: FormatVersion::V1,
440 target,
441 semantic_schema_fingerprint: semantic_schema,
442 config,
443 generator_handlers: ordered_handlers(target, handlers)?,
444 referenced_code_resources: ordered_resources(resources)?,
445 };
446 to_canonical_json(&view)
447}
448
449#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
451#[serde(transparent)]
452pub struct BindingProjectionFingerprint(Fingerprint);
453
454impl BindingProjectionFingerprint {
455 pub fn compute(
457 target: BindingTarget,
458 semantic_schema: &SemanticSchemaFingerprint,
459 config: &ProjectionConfig,
460 handlers: &[ProjectionHandler],
461 resources: &[CodeResourceDigest],
462 ) -> Result<Self, Diagnostic> {
463 let canonical = canonical_binding_projection_bytes(
464 target,
465 semantic_schema,
466 config,
467 handlers,
468 resources,
469 )?;
470 let semantic_profile = semantic_schema
471 .as_fingerprint()
472 .semantic_profile()
473 .cloned()
474 .ok_or_else(|| {
475 Diagnostic::stable(
476 DiagnosticCategory::InvalidContract,
477 "projection_semantic_profile_missing",
478 "semantic schema fingerprint does not carry its semantic profile",
479 )
480 })?;
481 Ok(Self(Fingerprint::compute(
482 FingerprintDomain::new(BINDING_PROJECTION_DOMAIN)?,
483 CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
484 Some(semantic_profile),
485 &canonical,
486 )))
487 }
488
489 #[must_use]
491 pub const fn as_fingerprint(&self) -> &Fingerprint {
492 &self.0
493 }
494
495 pub fn compute_with_projection(
497 target: BindingTarget,
498 semantic_schema: &SemanticSchemaFingerprint,
499 config: &ProjectionConfig,
500 handlers: &[ProjectionHandler],
501 resources: &[CodeResourceDigest],
502 canonical_projection: &[u8],
503 ) -> Result<Self, Diagnostic> {
504 #[derive(Serialize)]
505 struct CompleteProjectionView<'a> {
506 inputs: BindingProjectionView<'a>,
507 projection_content: Fingerprint,
508 }
509
510 if config.target() != target {
511 return Err(Diagnostic::stable(
512 DiagnosticCategory::InvalidContract,
513 "projection_config_target_mismatch",
514 "projection configuration belongs to a different binding target",
515 ));
516 }
517 let inputs = BindingProjectionView {
518 format_version: FormatVersion::V1,
519 target,
520 semantic_schema_fingerprint: semantic_schema,
521 config,
522 generator_handlers: ordered_handlers(target, handlers)?,
523 referenced_code_resources: ordered_resources(resources)?,
524 };
525 let projection_content = Fingerprint::compute(
526 FingerprintDomain::new(BINDING_PROJECTION_CONTENT_DOMAIN)?,
527 CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
528 semantic_schema.as_fingerprint().semantic_profile().cloned(),
529 canonical_projection,
530 );
531 let canonical = to_canonical_json(&CompleteProjectionView {
532 inputs,
533 projection_content,
534 })?;
535 let semantic_profile = semantic_schema
536 .as_fingerprint()
537 .semantic_profile()
538 .cloned()
539 .ok_or_else(|| {
540 Diagnostic::stable(
541 DiagnosticCategory::InvalidContract,
542 "projection_semantic_profile_missing",
543 "semantic schema fingerprint does not carry its semantic profile",
544 )
545 })?;
546 Ok(Self(Fingerprint::compute(
547 FingerprintDomain::new(BINDING_PROJECTION_DOMAIN)?,
548 CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
549 Some(semantic_profile),
550 &canonical,
551 )))
552 }
553}
554
555fn serialize_map_values<S, K, V>(map: &BTreeMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
556where
557 S: Serializer,
558 V: Serialize,
559{
560 map.values().collect::<Vec<_>>().serialize(serializer)
561}
562
563#[derive(Serialize)]
564struct RoleUpcastEntry<'a> {
565 role: &'a RoleId,
566 ancestors: &'a [RoleId],
567}
568
569fn serialize_role_upcasts<S>(
570 map: &BTreeMap<RoleId, Vec<RoleId>>,
571 serializer: S,
572) -> Result<S::Ok, S::Error>
573where
574 S: Serializer,
575{
576 map.iter()
577 .map(|(role, ancestors)| RoleUpcastEntry { role, ancestors })
578 .collect::<Vec<_>>()
579 .serialize(serializer)
580}
581
582fn invalid_projection(code: &'static str, message: &'static str) -> Diagnostic {
583 Diagnostic::stable(DiagnosticCategory::InvalidContract, code, message)
584}
585
586fn ensure_collection_limit(length: usize, code: &'static str) -> Result<(), Diagnostic> {
587 if length > MAX_CANONICAL_COLLECTION_LEN {
588 Err(Diagnostic::stable(
589 DiagnosticCategory::ResourceLimit,
590 code,
591 "projection collection exceeds the canonical collection limit",
592 ))
593 } else {
594 Ok(())
595 }
596}
597
598#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
600#[serde(transparent)]
601pub struct TargetIdentifier(String);
602
603impl TargetIdentifier {
604 pub fn python(value: impl Into<String>) -> Result<Self, Diagnostic> {
606 let value = value.into();
607 let mut bytes = value.bytes();
608 let valid = value.len() <= MAX_TARGET_IDENTIFIER_BYTES
609 && bytes
610 .next()
611 .is_some_and(|byte| byte == b'_' || byte.is_ascii_alphabetic())
612 && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric());
613 if !valid || is_python_keyword(&value) {
614 return Err(invalid_projection(
615 "invalid_python_projection_identifier",
616 "projected Python name is not a bounded non-keyword identifier",
617 ));
618 }
619 Ok(Self(value))
620 }
621
622 pub fn typescript(value: impl Into<String>) -> Result<Self, Diagnostic> {
624 let value = value.into();
625 let mut bytes = value.bytes();
626 let valid = value.len() <= MAX_TARGET_IDENTIFIER_BYTES
627 && bytes
628 .next()
629 .is_some_and(|byte| byte == b'_' || byte == b'$' || byte.is_ascii_alphabetic())
630 && bytes.all(|byte| byte == b'_' || byte == b'$' || byte.is_ascii_alphanumeric());
631 if !valid || is_typescript_keyword(&value) {
632 return Err(invalid_projection(
633 "invalid_typescript_projection_identifier",
634 "projected TypeScript name is not a bounded non-keyword identifier",
635 ));
636 }
637 Ok(Self(value))
638 }
639
640 pub fn rust(value: impl Into<String>) -> Result<Self, Diagnostic> {
642 let value = value.into();
643 let mut bytes = value.bytes();
644 let valid = value != "_"
645 && value.len() <= MAX_TARGET_IDENTIFIER_BYTES
646 && bytes
647 .next()
648 .is_some_and(|byte| byte == b'_' || byte.is_ascii_alphabetic())
649 && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric());
650 if !valid || is_rust_keyword(&value) {
651 return Err(invalid_projection(
652 "invalid_rust_projection_identifier",
653 "projected Rust name is not a bounded non-keyword ASCII identifier",
654 ));
655 }
656 Ok(Self(value))
657 }
658
659 #[must_use]
661 pub fn as_str(&self) -> &str {
662 &self.0
663 }
664}
665
666fn is_python_keyword(value: &str) -> bool {
667 matches!(
668 value,
669 "False"
670 | "None"
671 | "True"
672 | "and"
673 | "as"
674 | "assert"
675 | "async"
676 | "await"
677 | "break"
678 | "case"
679 | "class"
680 | "continue"
681 | "def"
682 | "del"
683 | "elif"
684 | "else"
685 | "except"
686 | "finally"
687 | "for"
688 | "from"
689 | "global"
690 | "if"
691 | "import"
692 | "in"
693 | "is"
694 | "lambda"
695 | "match"
696 | "nonlocal"
697 | "not"
698 | "or"
699 | "pass"
700 | "raise"
701 | "return"
702 | "try"
703 | "while"
704 | "with"
705 | "yield"
706 )
707}
708
709fn is_typescript_keyword(value: &str) -> bool {
710 matches!(
711 value,
712 "abstract"
713 | "any"
714 | "as"
715 | "asserts"
716 | "async"
717 | "await"
718 | "bigint"
719 | "boolean"
720 | "break"
721 | "case"
722 | "catch"
723 | "class"
724 | "const"
725 | "constructor"
726 | "continue"
727 | "debugger"
728 | "declare"
729 | "default"
730 | "delete"
731 | "do"
732 | "else"
733 | "enum"
734 | "export"
735 | "extends"
736 | "false"
737 | "finally"
738 | "for"
739 | "from"
740 | "function"
741 | "get"
742 | "if"
743 | "implements"
744 | "import"
745 | "in"
746 | "infer"
747 | "instanceof"
748 | "interface"
749 | "is"
750 | "keyof"
751 | "let"
752 | "module"
753 | "namespace"
754 | "never"
755 | "new"
756 | "null"
757 | "number"
758 | "object"
759 | "of"
760 | "out"
761 | "override"
762 | "package"
763 | "private"
764 | "protected"
765 | "public"
766 | "readonly"
767 | "require"
768 | "return"
769 | "satisfies"
770 | "set"
771 | "static"
772 | "string"
773 | "super"
774 | "switch"
775 | "symbol"
776 | "this"
777 | "throw"
778 | "true"
779 | "try"
780 | "type"
781 | "typeof"
782 | "undefined"
783 | "unique"
784 | "unknown"
785 | "using"
786 | "var"
787 | "void"
788 | "while"
789 | "with"
790 | "yield"
791 )
792}
793
794fn is_rust_keyword(value: &str) -> bool {
795 matches!(
796 value,
797 "Self"
798 | "abstract"
799 | "as"
800 | "async"
801 | "await"
802 | "become"
803 | "box"
804 | "break"
805 | "const"
806 | "continue"
807 | "crate"
808 | "do"
809 | "dyn"
810 | "else"
811 | "enum"
812 | "extern"
813 | "false"
814 | "final"
815 | "fn"
816 | "for"
817 | "gen"
818 | "if"
819 | "impl"
820 | "in"
821 | "let"
822 | "loop"
823 | "macro"
824 | "match"
825 | "mod"
826 | "move"
827 | "mut"
828 | "override"
829 | "priv"
830 | "pub"
831 | "ref"
832 | "return"
833 | "self"
834 | "static"
835 | "struct"
836 | "super"
837 | "trait"
838 | "true"
839 | "try"
840 | "type"
841 | "typeof"
842 | "unsafe"
843 | "unsized"
844 | "use"
845 | "virtual"
846 | "where"
847 | "while"
848 | "yield"
849 )
850}
851
852#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
854#[serde(rename_all = "snake_case")]
855pub enum ProjectedModelForm {
856 Complete,
858 Reference,
860}
861
862#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
864pub struct ProjectedModelUse {
865 id: TypeId,
866 form: ProjectedModelForm,
867}
868
869impl ProjectedModelUse {
870 #[must_use]
872 pub const fn new(id: TypeId, form: ProjectedModelForm) -> Self {
873 Self { id, form }
874 }
875 #[must_use]
877 pub const fn id(&self) -> &TypeId {
878 &self.id
879 }
880 #[must_use]
882 pub const fn form(&self) -> ProjectedModelForm {
883 self.form
884 }
885}
886
887#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
889#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
890pub enum ProjectedTypeRef {
891 Scalar(ValueTypeTag),
893 Model(ProjectedModelUse),
895 Struct(StructId),
897}
898
899#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
901#[serde(rename_all = "snake_case")]
902pub enum ProjectedContainer {
903 Scalar,
905 Sequence,
907}
908
909#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
911pub struct ProjectedMultiplicity {
912 cardinality: Cardinality,
913 required: bool,
914 container: ProjectedContainer,
915}
916
917impl ProjectedMultiplicity {
918 #[must_use]
920 pub const fn from_cardinality(cardinality: Cardinality) -> Self {
921 let container = match cardinality.max() {
922 Some(0 | 1) => ProjectedContainer::Scalar,
923 Some(_) | None => ProjectedContainer::Sequence,
924 };
925 Self {
926 cardinality,
927 required: cardinality.min() > 0,
928 container,
929 }
930 }
931 #[must_use]
933 pub const fn cardinality(&self) -> Cardinality {
934 self.cardinality
935 }
936 #[must_use]
938 pub const fn required(&self) -> bool {
939 self.required
940 }
941 #[must_use]
943 pub const fn container(&self) -> ProjectedContainer {
944 self.container
945 }
946}
947
948#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
957pub struct ProjectedAnnotation {
958 id: AnnotationFactId,
959 value: SchemaAnnotationValue,
960}
961
962impl ProjectedAnnotation {
963 pub fn new(id: AnnotationFactId, value: SchemaAnnotationValue) -> Result<Self, Diagnostic> {
965 AnnotationFact::new(id.clone(), value.clone())?;
966 Ok(Self { id, value })
967 }
968 #[must_use]
970 pub const fn id(&self) -> &AnnotationFactId {
971 &self.id
972 }
973 #[must_use]
975 pub const fn value(&self) -> &SchemaAnnotationValue {
976 &self.value
977 }
978}
979
980#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
982pub struct FieldTokenProjection {
983 id: OwnsFactId,
984 declaring_id: OwnsFactId,
985 target_name: TargetIdentifier,
986 multiplicity: ProjectedMultiplicity,
987 key: bool,
988 unique: bool,
989 #[serde(serialize_with = "serialize_map_values")]
990 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
991}
992
993impl FieldTokenProjection {
994 pub fn new(
996 id: OwnsFactId,
997 declaring_id: OwnsFactId,
998 target_name: TargetIdentifier,
999 multiplicity: ProjectedMultiplicity,
1000 key: bool,
1001 unique: bool,
1002 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1003 ) -> Result<Self, Diagnostic> {
1004 if id.attribute() != declaring_id.attribute() {
1005 return Err(invalid_projection(
1006 "invalid_projection_reference",
1007 "effective owns fact attribute does not match declaring owns fact attribute",
1008 ));
1009 }
1010 if annotations.iter().any(|(key, value)| {
1011 key != value.id()
1012 || !matches!(
1013 value.id().subject(),
1014 AnnotationSubjectId::Owns(subject) if subject == &id
1015 )
1016 }) {
1017 return Err(invalid_projection(
1018 "invalid_projected_owns_annotation",
1019 "owns annotations require matching exact effective owns subjects",
1020 ));
1021 }
1022 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1023 Ok(Self {
1024 id,
1025 declaring_id,
1026 target_name,
1027 multiplicity,
1028 key,
1029 unique,
1030 annotations,
1031 })
1032 }
1033 #[must_use]
1035 pub const fn id(&self) -> &OwnsFactId {
1036 &self.id
1037 }
1038 #[must_use]
1040 pub const fn declaring_id(&self) -> &OwnsFactId {
1041 &self.declaring_id
1042 }
1043 #[must_use]
1045 pub const fn target_name(&self) -> &TargetIdentifier {
1046 &self.target_name
1047 }
1048 #[must_use]
1050 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1051 self.multiplicity
1052 }
1053 #[must_use]
1055 pub const fn is_key(&self) -> bool {
1056 self.key
1057 }
1058 #[must_use]
1060 pub const fn is_unique(&self) -> bool {
1061 self.unique
1062 }
1063 #[must_use]
1065 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1066 &self.annotations
1067 }
1068}
1069
1070#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1072pub struct RoleTokenProjection {
1073 owner: TypeId,
1074 role: RoleId,
1075 target_name: TargetIdentifier,
1076 #[serde(skip_serializing_if = "Option::is_none")]
1077 player_union_target_name: Option<TargetIdentifier>,
1078 accepted_players: BTreeSet<TypeId>,
1079 specializes: Option<RoleId>,
1080 multiplicity: ProjectedMultiplicity,
1081 is_abstract: bool,
1082 #[serde(serialize_with = "serialize_map_values")]
1083 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1084}
1085
1086impl RoleTokenProjection {
1087 #[allow(clippy::too_many_arguments)]
1089 pub fn new(
1090 owner: TypeId,
1091 role: RoleId,
1092 target_name: TargetIdentifier,
1093 accepted_players: BTreeSet<TypeId>,
1094 specializes: Option<RoleId>,
1095 multiplicity: ProjectedMultiplicity,
1096 is_abstract: bool,
1097 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1098 ) -> Result<Self, Diagnostic> {
1099 if owner.kind() != TypeKind::Relation
1100 || accepted_players
1101 .iter()
1102 .any(|id| !matches!(id.kind(), TypeKind::Entity | TypeKind::Relation))
1103 {
1104 return Err(invalid_projection(
1105 "invalid_projected_role_token",
1106 "role tokens require a relation owner and entity/relation players",
1107 ));
1108 }
1109 ensure_collection_limit(accepted_players.len(), "too_many_projected_role_players")?;
1110 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1111 Ok(Self {
1112 owner,
1113 role,
1114 target_name,
1115 player_union_target_name: None,
1116 accepted_players,
1117 specializes,
1118 multiplicity,
1119 is_abstract,
1120 annotations,
1121 })
1122 }
1123 #[must_use]
1125 pub fn with_player_union_target_name(mut self, target_name: TargetIdentifier) -> Self {
1126 self.player_union_target_name = Some(target_name);
1127 self
1128 }
1129 #[must_use]
1131 pub const fn owner(&self) -> &TypeId {
1132 &self.owner
1133 }
1134 #[must_use]
1136 pub const fn role(&self) -> &RoleId {
1137 &self.role
1138 }
1139 #[must_use]
1141 pub const fn target_name(&self) -> &TargetIdentifier {
1142 &self.target_name
1143 }
1144 #[must_use]
1146 pub const fn player_union_target_name(&self) -> Option<&TargetIdentifier> {
1147 self.player_union_target_name.as_ref()
1148 }
1149 #[must_use]
1151 pub const fn accepted_players(&self) -> &BTreeSet<TypeId> {
1152 &self.accepted_players
1153 }
1154 #[must_use]
1156 pub const fn specializes(&self) -> Option<&RoleId> {
1157 self.specializes.as_ref()
1158 }
1159 #[must_use]
1161 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1162 self.multiplicity
1163 }
1164 #[must_use]
1166 pub const fn is_abstract(&self) -> bool {
1167 self.is_abstract
1168 }
1169 #[must_use]
1171 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1172 &self.annotations
1173 }
1174}
1175
1176#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1178pub struct DeclaredRoleProjection {
1179 role: RoleId,
1180 specializes: Option<RoleId>,
1181}
1182
1183impl DeclaredRoleProjection {
1184 #[must_use]
1186 pub const fn new(role: RoleId, specializes: Option<RoleId>) -> Self {
1187 Self { role, specializes }
1188 }
1189 #[must_use]
1191 pub const fn role(&self) -> &RoleId {
1192 &self.role
1193 }
1194 #[must_use]
1196 pub const fn specializes(&self) -> Option<&RoleId> {
1197 self.specializes.as_ref()
1198 }
1199}
1200
1201#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1203pub struct DirectSubProjection {
1204 id: SubFactId,
1205 origin: SchemaFactId,
1206 #[serde(serialize_with = "serialize_map_values")]
1207 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1208}
1209
1210impl DirectSubProjection {
1211 pub fn new(
1213 id: SubFactId,
1214 origin: SchemaFactId,
1215 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1216 ) -> Result<Self, Diagnostic> {
1217 if origin != SchemaFactId::Sub(id.clone()) {
1218 return Err(invalid_projection(
1219 "invalid_projected_sub_origin",
1220 "projected subtype origin must identify its exact direct edge",
1221 ));
1222 }
1223 if annotations.iter().any(|(key, value)| {
1224 key != value.id()
1225 || !matches!(key.subject(), AnnotationSubjectId::Sub(subject) if subject == &id)
1226 }) {
1227 return Err(invalid_projection(
1228 "invalid_projected_sub_annotation",
1229 "subtype annotations require matching exact edge subjects",
1230 ));
1231 }
1232 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1233 Ok(Self {
1234 id,
1235 origin,
1236 annotations,
1237 })
1238 }
1239
1240 #[must_use]
1242 pub const fn id(&self) -> &SubFactId {
1243 &self.id
1244 }
1245
1246 #[must_use]
1248 pub const fn origin(&self) -> &SchemaFactId {
1249 &self.origin
1250 }
1251
1252 #[must_use]
1254 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1255 &self.annotations
1256 }
1257}
1258
1259#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1261pub struct DeclarationProjection {
1262 parent: Option<TypeId>,
1263 direct_sub: Option<DirectSubProjection>,
1266 value_type: Option<ValueTypeTag>,
1267 is_abstract: bool,
1268 is_constructible: bool,
1269 #[serde(serialize_with = "serialize_map_values")]
1270 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1271 #[serde(serialize_with = "serialize_map_values")]
1272 value_annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1273 direct_fields: Vec<OwnsFactId>,
1274 #[serde(serialize_with = "serialize_map_values")]
1275 direct_roles: BTreeMap<RoleId, DeclaredRoleProjection>,
1276 direct_plays: BTreeSet<PlaysFactId>,
1277}
1278
1279impl DeclarationProjection {
1280 #[allow(clippy::too_many_arguments)]
1282 pub fn new(
1283 parent: Option<TypeId>,
1284 value_type: Option<ValueTypeTag>,
1285 is_abstract: bool,
1286 is_constructible: bool,
1287 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1288 direct_fields: Vec<OwnsFactId>,
1289 direct_roles: BTreeMap<RoleId, DeclaredRoleProjection>,
1290 direct_plays: BTreeSet<PlaysFactId>,
1291 ) -> Result<Self, Diagnostic> {
1292 for length in [
1293 annotations.len(),
1294 direct_fields.len(),
1295 direct_roles.len(),
1296 direct_plays.len(),
1297 ] {
1298 ensure_collection_limit(length, "projection_declaration_limit_exceeded")?;
1299 }
1300 Ok(Self {
1301 parent,
1302 direct_sub: None,
1303 value_type,
1304 is_abstract,
1305 is_constructible,
1306 annotations,
1307 value_annotations: BTreeMap::new(),
1308 direct_fields,
1309 direct_roles,
1310 direct_plays,
1311 })
1312 }
1313 pub fn with_direct_sub(
1315 mut self,
1316 direct_sub: Option<DirectSubProjection>,
1317 ) -> Result<Self, Diagnostic> {
1318 if direct_sub
1319 .as_ref()
1320 .is_some_and(|sub| self.parent.as_ref() != Some(sub.id().supertype()))
1321 {
1322 return Err(invalid_projection(
1323 "invalid_projected_sub_parent",
1324 "projected direct subtype edge must match the nominal parent",
1325 ));
1326 }
1327 self.direct_sub = direct_sub;
1328 Ok(self)
1329 }
1330 pub fn with_value_annotations(
1332 mut self,
1333 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1334 ) -> Result<Self, Diagnostic> {
1335 if annotations.iter().any(|(key, value)| {
1336 key != value.id() || !matches!(key.subject(), AnnotationSubjectId::Value(_))
1337 }) {
1338 return Err(invalid_projection(
1339 "invalid_projected_value_annotation",
1340 "attribute value annotations require matching effective value subjects",
1341 ));
1342 }
1343 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1344 self.value_annotations = annotations;
1345 Ok(self)
1346 }
1347 #[must_use]
1349 pub const fn parent(&self) -> Option<&TypeId> {
1350 self.parent.as_ref()
1351 }
1352 #[must_use]
1354 pub const fn direct_sub(&self) -> Option<&DirectSubProjection> {
1355 self.direct_sub.as_ref()
1356 }
1357 #[must_use]
1359 pub const fn value_type(&self) -> Option<ValueTypeTag> {
1360 self.value_type
1361 }
1362 #[must_use]
1364 pub const fn is_abstract(&self) -> bool {
1365 self.is_abstract
1366 }
1367 #[must_use]
1369 pub const fn is_constructible(&self) -> bool {
1370 self.is_constructible
1371 }
1372 #[must_use]
1374 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1375 &self.annotations
1376 }
1377 #[must_use]
1379 pub const fn value_annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1380 &self.value_annotations
1381 }
1382 #[must_use]
1384 pub fn direct_fields(&self) -> &[OwnsFactId] {
1385 &self.direct_fields
1386 }
1387 #[must_use]
1389 pub const fn direct_roles(&self) -> &BTreeMap<RoleId, DeclaredRoleProjection> {
1390 &self.direct_roles
1391 }
1392 #[must_use]
1394 pub const fn direct_plays(&self) -> &BTreeSet<PlaysFactId> {
1395 &self.direct_plays
1396 }
1397}
1398
1399#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1401pub struct CreateFieldProjection {
1402 token: OwnsFactId,
1403 value: ProjectedTypeRef,
1404 multiplicity: ProjectedMultiplicity,
1405}
1406
1407impl CreateFieldProjection {
1408 #[must_use]
1410 pub const fn new(
1411 token: OwnsFactId,
1412 value: ProjectedTypeRef,
1413 multiplicity: ProjectedMultiplicity,
1414 ) -> Self {
1415 Self {
1416 token,
1417 value,
1418 multiplicity,
1419 }
1420 }
1421 #[must_use]
1423 pub const fn token(&self) -> &OwnsFactId {
1424 &self.token
1425 }
1426 #[must_use]
1428 pub const fn value(&self) -> &ProjectedTypeRef {
1429 &self.value
1430 }
1431 #[must_use]
1433 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1434 self.multiplicity
1435 }
1436}
1437
1438#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1440pub struct CreateRoleProjection {
1441 role: RoleId,
1442 players: BTreeSet<ProjectedModelUse>,
1443 multiplicity: ProjectedMultiplicity,
1444}
1445
1446impl CreateRoleProjection {
1447 pub fn new(
1449 role: RoleId,
1450 players: BTreeSet<ProjectedModelUse>,
1451 multiplicity: ProjectedMultiplicity,
1452 ) -> Result<Self, Diagnostic> {
1453 ensure_collection_limit(players.len(), "too_many_projected_role_players")?;
1454 Ok(Self {
1455 role,
1456 players,
1457 multiplicity,
1458 })
1459 }
1460 #[must_use]
1462 pub const fn role(&self) -> &RoleId {
1463 &self.role
1464 }
1465 #[must_use]
1467 pub const fn players(&self) -> &BTreeSet<ProjectedModelUse> {
1468 &self.players
1469 }
1470 #[must_use]
1472 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1473 self.multiplicity
1474 }
1475}
1476
1477#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1479pub struct CreateProjection {
1480 #[serde(skip_serializing_if = "Option::is_none")]
1481 target_name: Option<TargetIdentifier>,
1482 enabled: bool,
1483 fields: Vec<CreateFieldProjection>,
1484 #[serde(serialize_with = "serialize_map_values")]
1485 roles: BTreeMap<RoleId, CreateRoleProjection>,
1486}
1487
1488impl CreateProjection {
1489 pub fn new(
1491 enabled: bool,
1492 fields: Vec<CreateFieldProjection>,
1493 roles: BTreeMap<RoleId, CreateRoleProjection>,
1494 ) -> Result<Self, Diagnostic> {
1495 ensure_collection_limit(fields.len(), "too_many_projected_create_fields")?;
1496 ensure_collection_limit(roles.len(), "too_many_projected_create_roles")?;
1497 Ok(Self {
1498 target_name: None,
1499 enabled,
1500 fields,
1501 roles,
1502 })
1503 }
1504 #[must_use]
1506 pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
1507 self.target_name = Some(target_name);
1508 self
1509 }
1510 #[must_use]
1512 pub const fn target_name(&self) -> Option<&TargetIdentifier> {
1513 self.target_name.as_ref()
1514 }
1515 #[must_use]
1517 pub const fn enabled(&self) -> bool {
1518 self.enabled
1519 }
1520 #[must_use]
1522 pub fn fields(&self) -> &[CreateFieldProjection] {
1523 &self.fields
1524 }
1525 #[must_use]
1527 pub const fn roles(&self) -> &BTreeMap<RoleId, CreateRoleProjection> {
1528 &self.roles
1529 }
1530}
1531
1532#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1534pub struct ReadFieldProjection {
1535 token: OwnsFactId,
1536 value: ProjectedTypeRef,
1537 multiplicity: ProjectedMultiplicity,
1538}
1539
1540impl ReadFieldProjection {
1541 #[must_use]
1543 pub const fn new(
1544 token: OwnsFactId,
1545 value: ProjectedTypeRef,
1546 multiplicity: ProjectedMultiplicity,
1547 ) -> Self {
1548 Self {
1549 token,
1550 value,
1551 multiplicity,
1552 }
1553 }
1554 #[must_use]
1556 pub const fn token(&self) -> &OwnsFactId {
1557 &self.token
1558 }
1559 #[must_use]
1561 pub const fn value(&self) -> &ProjectedTypeRef {
1562 &self.value
1563 }
1564 #[must_use]
1566 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1567 self.multiplicity
1568 }
1569}
1570
1571#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1573pub struct ReadRoleProjection {
1574 role: RoleId,
1575 players: BTreeSet<ProjectedModelUse>,
1576 multiplicity: ProjectedMultiplicity,
1577}
1578
1579impl ReadRoleProjection {
1580 pub fn new(
1582 role: RoleId,
1583 players: BTreeSet<ProjectedModelUse>,
1584 multiplicity: ProjectedMultiplicity,
1585 ) -> Result<Self, Diagnostic> {
1586 ensure_collection_limit(players.len(), "too_many_projected_role_players")?;
1587 Ok(Self {
1588 role,
1589 players,
1590 multiplicity,
1591 })
1592 }
1593 #[must_use]
1595 pub const fn role(&self) -> &RoleId {
1596 &self.role
1597 }
1598 #[must_use]
1600 pub const fn players(&self) -> &BTreeSet<ProjectedModelUse> {
1601 &self.players
1602 }
1603 #[must_use]
1605 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1606 self.multiplicity
1607 }
1608}
1609
1610#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1612pub struct CompleteReadProjection {
1613 fields: Vec<ReadFieldProjection>,
1614 #[serde(serialize_with = "serialize_map_values")]
1615 roles: BTreeMap<RoleId, ReadRoleProjection>,
1616 nominal_upcasts: Vec<TypeId>,
1617 #[serde(serialize_with = "serialize_role_upcasts")]
1618 role_upcasts: BTreeMap<RoleId, Vec<RoleId>>,
1619}
1620
1621impl CompleteReadProjection {
1622 pub fn new(
1624 fields: Vec<ReadFieldProjection>,
1625 roles: BTreeMap<RoleId, ReadRoleProjection>,
1626 nominal_upcasts: Vec<TypeId>,
1627 ) -> Result<Self, Diagnostic> {
1628 for length in [fields.len(), roles.len(), nominal_upcasts.len()] {
1629 ensure_collection_limit(length, "projection_read_limit_exceeded")?;
1630 }
1631 Ok(Self {
1632 fields,
1633 roles,
1634 nominal_upcasts,
1635 role_upcasts: BTreeMap::new(),
1636 })
1637 }
1638 pub fn with_role_upcasts(
1640 mut self,
1641 role_upcasts: BTreeMap<RoleId, Vec<RoleId>>,
1642 ) -> Result<Self, Diagnostic> {
1643 if role_upcasts.iter().any(|(role, ancestors)| {
1644 !self.roles.contains_key(role)
1645 || ancestors.is_empty()
1646 || ancestors.iter().collect::<BTreeSet<_>>().len() != ancestors.len()
1647 }) {
1648 return Err(invalid_projection(
1649 "invalid_projected_role_upcast",
1650 "role upcasts require an active role and unique non-empty ancestor roles",
1651 ));
1652 }
1653 ensure_collection_limit(role_upcasts.len(), "projection_read_limit_exceeded")?;
1654 self.role_upcasts = role_upcasts;
1655 Ok(self)
1656 }
1657 #[must_use]
1659 pub fn fields(&self) -> &[ReadFieldProjection] {
1660 &self.fields
1661 }
1662 #[must_use]
1664 pub const fn roles(&self) -> &BTreeMap<RoleId, ReadRoleProjection> {
1665 &self.roles
1666 }
1667 #[must_use]
1669 pub fn nominal_upcasts(&self) -> &[TypeId] {
1670 &self.nominal_upcasts
1671 }
1672 #[must_use]
1674 pub const fn role_upcasts(&self) -> &BTreeMap<RoleId, Vec<RoleId>> {
1675 &self.role_upcasts
1676 }
1677}
1678
1679#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1681#[serde(rename_all = "snake_case")]
1682pub enum ReferenceConstructionPolicy {
1683 IidOnly,
1685 KeyFallback,
1687}
1688
1689#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1691pub struct ReferenceReadProjection {
1692 target_name: Option<TargetIdentifier>,
1693 key_fields: Vec<OwnsFactId>,
1694 construction_policy: ReferenceConstructionPolicy,
1695}
1696
1697impl ReferenceReadProjection {
1698 pub fn new(
1700 target_name: Option<TargetIdentifier>,
1701 key_fields: Vec<OwnsFactId>,
1702 ) -> Result<Self, Diagnostic> {
1703 ensure_collection_limit(key_fields.len(), "too_many_projected_reference_keys")?;
1704 let mut unique = BTreeSet::new();
1705 if key_fields.iter().any(|id| !unique.insert(id)) {
1706 return Err(invalid_projection(
1707 "duplicate_projected_reference_key",
1708 "reference key identities must be unique",
1709 ));
1710 }
1711 let construction_policy = if key_fields.is_empty() {
1712 ReferenceConstructionPolicy::IidOnly
1713 } else {
1714 ReferenceConstructionPolicy::KeyFallback
1715 };
1716 Ok(Self {
1717 target_name,
1718 key_fields,
1719 construction_policy,
1720 })
1721 }
1722 #[must_use]
1724 pub const fn target_name(&self) -> Option<&TargetIdentifier> {
1725 self.target_name.as_ref()
1726 }
1727 #[must_use]
1729 pub fn key_fields(&self) -> &[OwnsFactId] {
1730 &self.key_fields
1731 }
1732 #[must_use]
1734 pub const fn construction_policy(&self) -> ReferenceConstructionPolicy {
1735 self.construction_policy
1736 }
1737}
1738
1739#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1741pub struct QueryTokenProjection {
1742 type_id: TypeId,
1743 #[serde(skip_serializing_if = "Option::is_none")]
1744 target_name: Option<TargetIdentifier>,
1745 #[serde(serialize_with = "serialize_map_values")]
1746 fields: BTreeMap<OwnsFactId, FieldTokenProjection>,
1747 #[serde(serialize_with = "serialize_map_values")]
1748 roles: BTreeMap<RoleId, RoleTokenProjection>,
1749}
1750
1751impl QueryTokenProjection {
1752 pub fn new(
1754 type_id: TypeId,
1755 fields: BTreeMap<OwnsFactId, FieldTokenProjection>,
1756 roles: BTreeMap<RoleId, RoleTokenProjection>,
1757 ) -> Result<Self, Diagnostic> {
1758 ensure_collection_limit(fields.len(), "too_many_projected_field_tokens")?;
1759 ensure_collection_limit(roles.len(), "too_many_projected_role_tokens")?;
1760 Ok(Self {
1761 type_id,
1762 target_name: None,
1763 fields,
1764 roles,
1765 })
1766 }
1767 #[must_use]
1769 pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
1770 self.target_name = Some(target_name);
1771 self
1772 }
1773 #[must_use]
1775 pub const fn type_id(&self) -> &TypeId {
1776 &self.type_id
1777 }
1778 #[must_use]
1780 pub const fn target_name(&self) -> Option<&TargetIdentifier> {
1781 self.target_name.as_ref()
1782 }
1783 #[must_use]
1785 pub const fn fields(&self) -> &BTreeMap<OwnsFactId, FieldTokenProjection> {
1786 &self.fields
1787 }
1788 #[must_use]
1790 pub const fn roles(&self) -> &BTreeMap<RoleId, RoleTokenProjection> {
1791 &self.roles
1792 }
1793}
1794
1795#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1797pub struct ModelProjection {
1798 id: TypeId,
1799 target_name: TargetIdentifier,
1800 declaration: DeclarationProjection,
1801 create: CreateProjection,
1802 complete_read: CompleteReadProjection,
1803 reference_read: ReferenceReadProjection,
1804 query_tokens: QueryTokenProjection,
1805}
1806
1807impl ModelProjection {
1808 #[allow(clippy::too_many_arguments)]
1810 pub fn new(
1811 id: TypeId,
1812 target_name: TargetIdentifier,
1813 declaration: DeclarationProjection,
1814 create: CreateProjection,
1815 complete_read: CompleteReadProjection,
1816 reference_read: ReferenceReadProjection,
1817 query_tokens: QueryTokenProjection,
1818 ) -> Result<Self, Diagnostic> {
1819 let exact_required_scalar = |multiplicity: ProjectedMultiplicity| {
1820 multiplicity.required()
1821 && multiplicity.container() == ProjectedContainer::Scalar
1822 && multiplicity.cardinality().min() == 1
1823 && multiplicity.cardinality().max() == Some(1)
1824 };
1825 let reference_keys_valid = reference_read.key_fields().iter().all(|key| {
1826 let Some(token) = query_tokens.fields().get(key) else {
1827 return false;
1828 };
1829 if !token.is_key() || !exact_required_scalar(token.multiplicity()) {
1830 return false;
1831 }
1832 let mut complete = complete_read
1833 .fields()
1834 .iter()
1835 .filter(|field| field.token() == key);
1836 let Some(field) = complete.next() else {
1837 return false;
1838 };
1839 if complete.next().is_some() || !exact_required_scalar(field.multiplicity()) {
1840 return false;
1841 }
1842 matches!(
1843 field.value(),
1844 ProjectedTypeRef::Model(value)
1845 if value.form() == ProjectedModelForm::Complete
1846 && value.id().kind() == TypeKind::Attribute
1847 && value.id().label() == key.attribute().label()
1848 )
1849 });
1850 if (!reference_read.key_fields().is_empty() && reference_read.target_name().is_none())
1851 || !reference_keys_valid
1852 {
1853 return Err(invalid_projection(
1854 "invalid_projected_reference_key",
1855 "reference keys require exact required-scalar complete/query key facets",
1856 ));
1857 }
1858 if query_tokens.type_id() != &id
1859 || match (declaration.parent(), declaration.direct_sub()) {
1860 (None, None) => false,
1861 (Some(parent), Some(sub)) => {
1862 sub.id().subtype() != &id || sub.id().supertype() != parent
1863 }
1864 (None, Some(_)) | (Some(_), None) => true,
1865 }
1866 || query_tokens
1867 .fields()
1868 .values()
1869 .any(|field| field.id().owner() != &id)
1870 || query_tokens
1871 .roles()
1872 .values()
1873 .any(|role| role.owner() != &id)
1874 || create
1875 .fields()
1876 .iter()
1877 .any(|field| !query_tokens.fields().contains_key(field.token()))
1878 || complete_read
1879 .fields()
1880 .iter()
1881 .any(|field| !query_tokens.fields().contains_key(field.token()))
1882 || create
1883 .roles()
1884 .iter()
1885 .any(|(id, role)| id != role.role() || !query_tokens.roles().contains_key(id))
1886 || complete_read
1887 .roles()
1888 .iter()
1889 .any(|(id, role)| id != role.role() || !query_tokens.roles().contains_key(id))
1890 {
1891 return Err(invalid_projection(
1892 "invalid_model_projection_reference",
1893 "model facets contain a mismatched owner or token reference",
1894 ));
1895 }
1896 Ok(Self {
1897 id,
1898 target_name,
1899 declaration,
1900 create,
1901 complete_read,
1902 reference_read,
1903 query_tokens,
1904 })
1905 }
1906 #[must_use]
1908 pub const fn id(&self) -> &TypeId {
1909 &self.id
1910 }
1911 #[must_use]
1913 pub const fn target_name(&self) -> &TargetIdentifier {
1914 &self.target_name
1915 }
1916 #[must_use]
1918 pub const fn declaration(&self) -> &DeclarationProjection {
1919 &self.declaration
1920 }
1921 #[must_use]
1923 pub const fn create(&self) -> &CreateProjection {
1924 &self.create
1925 }
1926 #[must_use]
1928 pub const fn complete_read(&self) -> &CompleteReadProjection {
1929 &self.complete_read
1930 }
1931 #[must_use]
1933 pub const fn reference_read(&self) -> &ReferenceReadProjection {
1934 &self.reference_read
1935 }
1936 #[must_use]
1938 pub const fn query_tokens(&self) -> &QueryTokenProjection {
1939 &self.query_tokens
1940 }
1941}
1942
1943#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1945pub struct StructFieldProjection {
1946 name: Label,
1947 target_name: TargetIdentifier,
1948 value_type: ValueTypeTag,
1949 optional: bool,
1950}
1951
1952impl StructFieldProjection {
1953 #[must_use]
1955 pub const fn new(
1956 name: Label,
1957 target_name: TargetIdentifier,
1958 value_type: ValueTypeTag,
1959 optional: bool,
1960 ) -> Self {
1961 Self {
1962 name,
1963 target_name,
1964 value_type,
1965 optional,
1966 }
1967 }
1968 #[must_use]
1970 pub const fn name(&self) -> &Label {
1971 &self.name
1972 }
1973 #[must_use]
1975 pub const fn target_name(&self) -> &TargetIdentifier {
1976 &self.target_name
1977 }
1978 #[must_use]
1980 pub const fn value_type(&self) -> ValueTypeTag {
1981 self.value_type
1982 }
1983 #[must_use]
1985 pub const fn optional(&self) -> bool {
1986 self.optional
1987 }
1988}
1989
1990#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1992pub struct StructProjection {
1993 id: StructId,
1994 target_name: TargetIdentifier,
1995 fields: Vec<StructFieldProjection>,
1996}
1997
1998impl StructProjection {
1999 pub fn new(
2001 id: StructId,
2002 target_name: TargetIdentifier,
2003 fields: Vec<StructFieldProjection>,
2004 ) -> Result<Self, Diagnostic> {
2005 ensure_collection_limit(fields.len(), "too_many_projected_struct_fields")?;
2006 Ok(Self {
2007 id,
2008 target_name,
2009 fields,
2010 })
2011 }
2012 #[must_use]
2014 pub const fn id(&self) -> &StructId {
2015 &self.id
2016 }
2017 #[must_use]
2019 pub const fn target_name(&self) -> &TargetIdentifier {
2020 &self.target_name
2021 }
2022 #[must_use]
2024 pub fn fields(&self) -> &[StructFieldProjection] {
2025 &self.fields
2026 }
2027}
2028
2029#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2031pub struct FunctionParameterProjection {
2032 name: Label,
2033 target_name: TargetIdentifier,
2034 type_ref: ProjectedTypeRef,
2035}
2036
2037impl FunctionParameterProjection {
2038 #[must_use]
2040 pub const fn new(
2041 name: Label,
2042 target_name: TargetIdentifier,
2043 type_ref: ProjectedTypeRef,
2044 ) -> Self {
2045 Self {
2046 name,
2047 target_name,
2048 type_ref,
2049 }
2050 }
2051 #[must_use]
2053 pub const fn name(&self) -> &Label {
2054 &self.name
2055 }
2056 #[must_use]
2058 pub const fn target_name(&self) -> &TargetIdentifier {
2059 &self.target_name
2060 }
2061 #[must_use]
2063 pub const fn type_ref(&self) -> &ProjectedTypeRef {
2064 &self.type_ref
2065 }
2066}
2067
2068#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2070pub struct FunctionReturnElementProjection {
2071 type_ref: ProjectedTypeRef,
2072 optional: bool,
2073}
2074
2075impl FunctionReturnElementProjection {
2076 #[must_use]
2078 pub const fn new(type_ref: ProjectedTypeRef, optional: bool) -> Self {
2079 Self { type_ref, optional }
2080 }
2081 #[must_use]
2083 pub const fn type_ref(&self) -> &ProjectedTypeRef {
2084 &self.type_ref
2085 }
2086 #[must_use]
2088 pub const fn optional(&self) -> bool {
2089 self.optional
2090 }
2091}
2092
2093#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2095#[serde(tag = "kind", content = "elements", rename_all = "snake_case")]
2096pub enum FunctionReturnProjection {
2097 Scalar(FunctionReturnElementProjection),
2099 Tuple(Vec<FunctionReturnElementProjection>),
2101 Stream(Vec<FunctionReturnElementProjection>),
2103}
2104
2105#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2107pub struct FunctionProjection {
2108 id: FunctionId,
2109 target_name: TargetIdentifier,
2110 parameters: Vec<FunctionParameterProjection>,
2111 returns: FunctionReturnProjection,
2112 #[serde(serialize_with = "serialize_map_values")]
2113 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2114}
2115
2116impl FunctionProjection {
2117 pub fn new(
2119 id: FunctionId,
2120 target_name: TargetIdentifier,
2121 parameters: Vec<FunctionParameterProjection>,
2122 returns: FunctionReturnProjection,
2123 ) -> Result<Self, Diagnostic> {
2124 ensure_collection_limit(parameters.len(), "too_many_projected_function_parameters")?;
2125 Ok(Self {
2126 id,
2127 target_name,
2128 parameters,
2129 returns,
2130 annotations: BTreeMap::new(),
2131 })
2132 }
2133 pub fn with_annotations(
2135 mut self,
2136 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2137 ) -> Result<Self, Diagnostic> {
2138 if annotations.iter().any(|(key, value)| {
2139 key != value.id() || key.subject() != &AnnotationSubjectId::Function(self.id.clone())
2140 }) {
2141 return Err(invalid_projection(
2142 "invalid_projected_function_annotation",
2143 "function annotations require the projected function subject",
2144 ));
2145 }
2146 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
2147 self.annotations = annotations;
2148 Ok(self)
2149 }
2150 #[must_use]
2152 pub const fn id(&self) -> &FunctionId {
2153 &self.id
2154 }
2155 #[must_use]
2157 pub const fn target_name(&self) -> &TargetIdentifier {
2158 &self.target_name
2159 }
2160 #[must_use]
2162 pub fn parameters(&self) -> &[FunctionParameterProjection] {
2163 &self.parameters
2164 }
2165 #[must_use]
2167 pub const fn returns(&self) -> &FunctionReturnProjection {
2168 &self.returns
2169 }
2170 #[must_use]
2172 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
2173 &self.annotations
2174 }
2175}
2176
2177#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2179pub struct PlayingProjection {
2180 id: PlaysFactId,
2181 role: RoleId,
2182 target_name: Option<TargetIdentifier>,
2183 multiplicity: ProjectedMultiplicity,
2184 #[serde(serialize_with = "serialize_map_values")]
2185 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2186}
2187
2188impl PlayingProjection {
2189 pub fn new(
2191 id: PlaysFactId,
2192 role: RoleId,
2193 multiplicity: ProjectedMultiplicity,
2194 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2195 ) -> Result<Self, Diagnostic> {
2196 if id.role() != &role
2197 || annotations.iter().any(|(key, value)| {
2198 key != value.id() || key.subject() != &AnnotationSubjectId::Plays(id.clone())
2199 })
2200 {
2201 return Err(invalid_projection(
2202 "invalid_playing_projection_reference",
2203 "playing metadata has a mismatched role or annotation subject",
2204 ));
2205 }
2206 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
2207 Ok(Self {
2208 id,
2209 role,
2210 target_name: None,
2211 multiplicity,
2212 annotations,
2213 })
2214 }
2215 #[must_use]
2217 pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
2218 self.target_name = Some(target_name);
2219 self
2220 }
2221 #[must_use]
2223 pub const fn id(&self) -> &PlaysFactId {
2224 &self.id
2225 }
2226 #[must_use]
2228 pub const fn role(&self) -> &RoleId {
2229 &self.role
2230 }
2231 #[must_use]
2233 pub const fn target_name(&self) -> Option<&TargetIdentifier> {
2234 self.target_name.as_ref()
2235 }
2236 #[must_use]
2238 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
2239 self.multiplicity
2240 }
2241 #[must_use]
2243 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
2244 &self.annotations
2245 }
2246}
2247
2248#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2250pub struct EmissionPlan {
2251 model_shells: Vec<TypeId>,
2252 model_link_components: Vec<BTreeSet<TypeId>>,
2253 structs: Vec<StructId>,
2254 functions: Vec<FunctionId>,
2255}
2256
2257impl EmissionPlan {
2258 pub fn new(
2260 model_shells: Vec<TypeId>,
2261 model_link_components: Vec<BTreeSet<TypeId>>,
2262 structs: Vec<StructId>,
2263 functions: Vec<FunctionId>,
2264 ) -> Result<Self, Diagnostic> {
2265 for length in [
2266 model_shells.len(),
2267 model_link_components.len(),
2268 structs.len(),
2269 functions.len(),
2270 ] {
2271 ensure_collection_limit(length, "projection_emission_limit_exceeded")?;
2272 }
2273 Ok(Self {
2274 model_shells,
2275 model_link_components,
2276 structs,
2277 functions,
2278 })
2279 }
2280 #[must_use]
2282 pub fn model_shells(&self) -> &[TypeId] {
2283 &self.model_shells
2284 }
2285 #[must_use]
2287 pub fn model_link_components(&self) -> &[BTreeSet<TypeId>] {
2288 &self.model_link_components
2289 }
2290 #[must_use]
2292 pub fn structs(&self) -> &[StructId] {
2293 &self.structs
2294 }
2295 #[must_use]
2297 pub fn functions(&self) -> &[FunctionId] {
2298 &self.functions
2299 }
2300}
2301
2302#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2304pub struct RuntimeProjection {
2305 target: BindingTarget,
2306 config: ProjectionConfig,
2307 semantic_fingerprint: SemanticSchemaFingerprint,
2308 projection_fingerprint: BindingProjectionFingerprint,
2309 generator_handlers: Vec<ProjectionHandler>,
2310 code_resources: Vec<CodeResourceDigest>,
2311 #[serde(serialize_with = "serialize_map_values")]
2312 models: BTreeMap<TypeId, ModelProjection>,
2313 #[serde(serialize_with = "serialize_map_values")]
2314 structs: BTreeMap<StructId, StructProjection>,
2315 #[serde(serialize_with = "serialize_map_values")]
2316 functions: BTreeMap<FunctionId, FunctionProjection>,
2317 #[serde(serialize_with = "serialize_map_values")]
2318 playing_facts: BTreeMap<PlaysFactId, PlayingProjection>,
2319 emission: EmissionPlan,
2320}
2321
2322#[derive(Serialize)]
2323struct RuntimeProjectionContentView<'a> {
2324 #[serde(serialize_with = "serialize_map_values")]
2325 models: &'a BTreeMap<TypeId, ModelProjection>,
2326 #[serde(serialize_with = "serialize_map_values")]
2327 structs: &'a BTreeMap<StructId, StructProjection>,
2328 #[serde(serialize_with = "serialize_map_values")]
2329 functions: &'a BTreeMap<FunctionId, FunctionProjection>,
2330 #[serde(serialize_with = "serialize_map_values")]
2331 playing_facts: &'a BTreeMap<PlaysFactId, PlayingProjection>,
2332 emission: &'a EmissionPlan,
2333}
2334
2335impl RuntimeProjection {
2336 #[allow(clippy::too_many_arguments)]
2338 pub fn try_new(
2339 target: BindingTarget,
2340 config: ProjectionConfig,
2341 semantic_fingerprint: SemanticSchemaFingerprint,
2342 handlers: &[ProjectionHandler],
2343 resources: &[CodeResourceDigest],
2344 models: BTreeMap<TypeId, ModelProjection>,
2345 structs: BTreeMap<StructId, StructProjection>,
2346 functions: BTreeMap<FunctionId, FunctionProjection>,
2347 playing_facts: BTreeMap<PlaysFactId, PlayingProjection>,
2348 emission: EmissionPlan,
2349 ) -> Result<Self, Diagnostic> {
2350 if config.target() != target
2351 || models.iter().any(|(key, value)| key != value.id())
2352 || structs.iter().any(|(key, value)| key != value.id())
2353 || functions.iter().any(|(key, value)| key != value.id())
2354 || playing_facts.iter().any(|(key, value)| key != value.id())
2355 {
2356 return Err(invalid_projection(
2357 "invalid_runtime_projection_map",
2358 "runtime projection map keys or target configuration are inconsistent",
2359 ));
2360 }
2361 for length in [
2362 models.len(),
2363 structs.len(),
2364 functions.len(),
2365 playing_facts.len(),
2366 ] {
2367 ensure_collection_limit(length, "runtime_projection_limit_exceeded")?;
2368 }
2369 if target == BindingTarget::Rust {
2370 let rust_names_complete = models.values().all(|model| {
2371 model.create().enabled() == model.create().target_name().is_some()
2372 && model.query_tokens().target_name().is_some()
2373 && model
2374 .query_tokens()
2375 .roles()
2376 .values()
2377 .all(|role| role.player_union_target_name().is_some())
2378 && matches!(model.id().kind(), TypeKind::Entity | TypeKind::Relation)
2379 == model.reference_read().target_name().is_some()
2380 }) && playing_facts
2381 .values()
2382 .all(|playing| playing.target_name().is_some());
2383 if !rust_names_complete {
2384 return Err(invalid_projection(
2385 "missing_rust_projection_identifier",
2386 "Rust projection omits a required create, reference, query-token, player-union, or plays identifier",
2387 ));
2388 }
2389 }
2390 let model_ids = models.keys().cloned().collect::<BTreeSet<_>>();
2391 if emission
2392 .model_shells()
2393 .iter()
2394 .cloned()
2395 .collect::<BTreeSet<_>>()
2396 != model_ids
2397 || emission.model_shells().len() != model_ids.len()
2398 || emission
2399 .model_link_components()
2400 .iter()
2401 .flat_map(BTreeSet::iter)
2402 .cloned()
2403 .collect::<BTreeSet<_>>()
2404 != model_ids
2405 || emission
2406 .model_link_components()
2407 .iter()
2408 .map(BTreeSet::len)
2409 .sum::<usize>()
2410 != model_ids.len()
2411 || emission.structs() != structs.keys().cloned().collect::<Vec<_>>()
2412 || emission.functions() != functions.keys().cloned().collect::<Vec<_>>()
2413 {
2414 return Err(invalid_projection(
2415 "invalid_projection_emission_plan",
2416 "emission plan does not cover each projected value exactly once",
2417 ));
2418 }
2419 let all_model_refs_valid = models.values().all(|model| {
2420 model.query_tokens().roles().values().all(|role| {
2421 role.accepted_players()
2422 .iter()
2423 .all(|id| models.contains_key(id))
2424 }) && model.create().roles().values().all(|role| {
2425 role.players()
2426 .iter()
2427 .all(|value| models.contains_key(value.id()))
2428 }) && model.complete_read().roles().values().all(|role| {
2429 role.players()
2430 .iter()
2431 .all(|value| models.contains_key(value.id()))
2432 })
2433 });
2434 if !all_model_refs_valid {
2435 return Err(invalid_projection(
2436 "invalid_projection_reference",
2437 "projection references a model that is not present",
2438 ));
2439 }
2440 let declaring_owners_valid = models.values().all(|model| {
2441 model.query_tokens().fields().values().all(|token| {
2442 let declaring_owner = token.declaring_id().owner();
2443 let mut curr = Some(model.id());
2444 let mut found = false;
2445 let mut visited = BTreeSet::new();
2446 while let Some(curr_id) = curr {
2447 if !visited.insert(curr_id) {
2448 return false;
2449 }
2450 if curr_id == declaring_owner {
2451 found = true;
2452 break;
2453 }
2454 curr = models.get(curr_id).and_then(|m| m.declaration().parent());
2455 }
2456 found
2457 })
2458 });
2459 if !declaring_owners_valid {
2460 return Err(invalid_projection(
2461 "invalid_projection_reference",
2462 "field token declaring owner is not the effective owner or a valid ancestor",
2463 ));
2464 }
2465 let model_use_is_valid = |value: &ProjectedModelUse| {
2466 models.get(value.id()).is_some_and(|model| {
2467 value.form() != ProjectedModelForm::Reference
2468 || model.reference_read().target_name().is_some()
2469 })
2470 };
2471 let type_ref_is_valid = |value: &ProjectedTypeRef| match value {
2472 ProjectedTypeRef::Scalar(_) => true,
2473 ProjectedTypeRef::Model(value) => model_use_is_valid(value),
2474 ProjectedTypeRef::Struct(id) => structs.contains_key(id),
2475 };
2476 let role_exists = |role: &RoleId| {
2477 models.values().any(|model| {
2478 model.id().kind() == TypeKind::Relation
2479 && model.id().label() == role.declaring_relation()
2480 && model.query_tokens().roles().contains_key(role)
2481 })
2482 };
2483 let shell_positions = emission
2484 .model_shells()
2485 .iter()
2486 .enumerate()
2487 .map(|(index, id)| (id.clone(), index))
2488 .collect::<BTreeMap<_, _>>();
2489 let closed_models = models.values().all(|model| {
2490 let declaration = model.declaration();
2491 let parent_is_valid = declaration.parent().is_none_or(|parent| {
2492 models.contains_key(parent) && shell_positions[parent] < shell_positions[model.id()]
2493 });
2494 let direct_sub_is_valid = match (declaration.parent(), declaration.direct_sub()) {
2495 (None, None) => true,
2496 (Some(parent), Some(sub)) => {
2497 sub.id().subtype() == model.id() && sub.id().supertype() == parent
2498 }
2499 (None, Some(_)) | (Some(_), None) => false,
2500 };
2501 let direct_fields_are_valid = declaration
2502 .direct_fields()
2503 .iter()
2504 .all(|id| model.query_tokens().fields().contains_key(id));
2505 let direct_roles_are_valid = declaration.direct_roles().iter().all(|(id, role)| {
2506 id == role.role()
2507 && model.query_tokens().roles().contains_key(id)
2508 && role.specializes().is_none_or(&role_exists)
2509 });
2510 let direct_plays_are_valid = declaration
2511 .direct_plays()
2512 .iter()
2513 .all(|id| id.player() == model.id() && playing_facts.contains_key(id));
2514 let fields_are_valid = model.query_tokens().fields().values().all(|field| {
2515 models.keys().any(|id| {
2516 id.kind() == TypeKind::Attribute && id.label() == field.id().attribute().label()
2517 })
2518 }) && model
2519 .create()
2520 .fields()
2521 .iter()
2522 .all(|field| type_ref_is_valid(field.value()))
2523 && model
2524 .complete_read()
2525 .fields()
2526 .iter()
2527 .all(|field| type_ref_is_valid(field.value()));
2528 let roles_are_valid = model
2529 .query_tokens()
2530 .roles()
2531 .values()
2532 .all(|role| role.specializes().is_none_or(&role_exists))
2533 && model
2534 .create()
2535 .roles()
2536 .values()
2537 .all(|role| role.players().iter().all(&model_use_is_valid))
2538 && model
2539 .complete_read()
2540 .roles()
2541 .values()
2542 .all(|role| role.players().iter().all(&model_use_is_valid));
2543 let role_upcasts_are_valid =
2544 model
2545 .complete_read()
2546 .role_upcasts()
2547 .iter()
2548 .all(|(active, ancestors)| {
2549 model.complete_read().roles().contains_key(active)
2550 && ancestors.iter().all(&role_exists)
2551 });
2552 let references_are_valid = model.reference_read().key_fields().iter().all(|id| {
2553 model
2554 .query_tokens()
2555 .fields()
2556 .get(id)
2557 .is_some_and(FieldTokenProjection::is_key)
2558 });
2559 let value_subject = model.id().kind() != TypeKind::Attribute
2560 || model.declaration().value_annotations().keys().all(|id| {
2561 id.subject()
2562 == &AnnotationSubjectId::Value(ValueFactId::new(
2563 AttributeId::new(model.id().label().as_str())
2564 .expect("projected attribute label is valid"),
2565 ))
2566 });
2567 parent_is_valid
2568 && direct_sub_is_valid
2569 && direct_fields_are_valid
2570 && direct_roles_are_valid
2571 && direct_plays_are_valid
2572 && fields_are_valid
2573 && roles_are_valid
2574 && role_upcasts_are_valid
2575 && references_are_valid
2576 && value_subject
2577 });
2578 let closed_playing = playing_facts.values().all(|playing| {
2579 models.contains_key(playing.id().player())
2580 && role_exists(playing.role())
2581 && (!matches!(target, BindingTarget::TypeScript | BindingTarget::Rust)
2582 || playing.target_name().is_some())
2583 });
2584 let closed_functions = functions.values().all(|function| {
2585 function
2586 .parameters()
2587 .iter()
2588 .all(|parameter| type_ref_is_valid(parameter.type_ref()))
2589 && match function.returns() {
2590 FunctionReturnProjection::Scalar(element) => {
2591 type_ref_is_valid(element.type_ref())
2592 }
2593 FunctionReturnProjection::Tuple(elements)
2594 | FunctionReturnProjection::Stream(elements) => elements
2595 .iter()
2596 .all(|element| type_ref_is_valid(element.type_ref())),
2597 }
2598 });
2599 if !closed_models || !closed_playing || !closed_functions {
2600 return Err(invalid_projection(
2601 "invalid_projection_reference",
2602 "projection graph contains an unavailable type, field, role, specialization, reference, or function dependency",
2603 ));
2604 }
2605 let content = to_canonical_json(&RuntimeProjectionContentView {
2606 models: &models,
2607 structs: &structs,
2608 functions: &functions,
2609 playing_facts: &playing_facts,
2610 emission: &emission,
2611 })?;
2612 let projection_fingerprint = BindingProjectionFingerprint::compute_with_projection(
2613 target,
2614 &semantic_fingerprint,
2615 &config,
2616 handlers,
2617 resources,
2618 &content,
2619 )?;
2620 let mut generator_handlers = handlers.to_vec();
2621 generator_handlers.sort_by(|left, right| left.id().cmp(right.id()));
2622 let mut code_resources = resources.to_vec();
2623 code_resources.sort_by(|left, right| left.id().cmp(right.id()));
2624 Ok(Self {
2625 target,
2626 config,
2627 semantic_fingerprint,
2628 projection_fingerprint,
2629 generator_handlers,
2630 code_resources,
2631 models,
2632 structs,
2633 functions,
2634 playing_facts,
2635 emission,
2636 })
2637 }
2638 #[must_use]
2640 pub const fn target(&self) -> BindingTarget {
2641 self.target
2642 }
2643 #[must_use]
2645 pub const fn config(&self) -> &ProjectionConfig {
2646 &self.config
2647 }
2648 #[must_use]
2650 pub const fn semantic_fingerprint(&self) -> &SemanticSchemaFingerprint {
2651 &self.semantic_fingerprint
2652 }
2653 #[must_use]
2655 pub const fn projection_fingerprint(&self) -> &BindingProjectionFingerprint {
2656 &self.projection_fingerprint
2657 }
2658 #[must_use]
2660 pub fn generator_handlers(&self) -> &[ProjectionHandler] {
2661 &self.generator_handlers
2662 }
2663 #[must_use]
2665 pub fn code_resources(&self) -> &[CodeResourceDigest] {
2666 &self.code_resources
2667 }
2668 #[must_use]
2670 pub const fn models(&self) -> &BTreeMap<TypeId, ModelProjection> {
2671 &self.models
2672 }
2673 #[must_use]
2675 pub const fn structs(&self) -> &BTreeMap<StructId, StructProjection> {
2676 &self.structs
2677 }
2678 #[must_use]
2680 pub const fn functions(&self) -> &BTreeMap<FunctionId, FunctionProjection> {
2681 &self.functions
2682 }
2683 #[must_use]
2685 pub const fn playing_facts(&self) -> &BTreeMap<PlaysFactId, PlayingProjection> {
2686 &self.playing_facts
2687 }
2688 #[must_use]
2690 pub const fn emission(&self) -> &EmissionPlan {
2691 &self.emission
2692 }
2693}
2694
2695impl CodeResourceDigest {
2696 pub(crate) fn from_wire(
2698 id: impl Into<String>,
2699 content_fingerprint: Fingerprint,
2700 ) -> Result<Self, Diagnostic> {
2701 if content_fingerprint.domain().as_str() != CODE_RESOURCE_DOMAIN
2702 || content_fingerprint.canonicalization().as_str() != RAW_BYTES_CANONICALIZATION
2703 || content_fingerprint.semantic_profile().is_some()
2704 {
2705 return Err(Diagnostic::stable(
2706 DiagnosticCategory::Integrity,
2707 "invalid_code_resource_fingerprint",
2708 "code resource fingerprint wire metadata is inconsistent",
2709 ));
2710 }
2711 Ok(Self {
2712 id: CodeResourceId::new(id)?,
2713 content_fingerprint,
2714 })
2715 }
2716}