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, AnnotationKindId, AnnotationSubjectId, CollectionMode,
16 OwnsFactId, PlaysFactId, RelatesFactId, SchemaAnnotationValue, SchemaFactId, SubFactId,
17 ValueFactId,
18};
19use crate::schema_fingerprint::SemanticSchemaFingerprint;
20use crate::value::{Cardinality, ValueTypeTag};
21
22const MAX_PROJECTION_COMPONENT_ID_BYTES: usize = 255;
23const PYTHON_GENERATOR_HANDLER_ID: &str = "typebridge.generator.python";
24const TYPESCRIPT_GENERATOR_HANDLER_ID: &str = "typebridge.generator.typescript";
25const RUST_GENERATOR_HANDLER_ID: &str = "typebridge.generator.rust";
26const C_GENERATOR_HANDLER_ID: &str = "typebridge.generator.c";
27const CODE_RESOURCE_DOMAIN: &str = "typebridge.binding.code-resource";
28const RAW_BYTES_CANONICALIZATION: &str = "typebridge.raw-bytes/v1";
29const BINDING_PROJECTION_DOMAIN: &str = "typebridge.binding.projection";
30const BINDING_PROJECTION_CANONICALIZATION: &str = "typebridge.binding-projection/v1";
31const BINDING_PROJECTION_CONTENT_DOMAIN: &str = "typebridge.binding.projection-content";
32const MAX_TARGET_IDENTIFIER_BYTES: usize = 255;
33const MAX_C_SYMBOL_PREFIX_BYTES: usize = 63;
34
35pub const TYPESCRIPT_MODEL_RESERVED_NAMES: &[&str] = &[
41 "__proto__",
42 "completeRead",
43 "constructor",
44 "create",
45 "declaration",
46 "fields",
47 "id",
48 "iid",
49 "metadata",
50 "manager",
51 "name",
52 "plays",
53 "prototype",
54 "reference",
55 "roles",
56 "typeKey",
57 "typeToken",
58 "valueType",
59];
60
61pub const TYPE_BRIDGE_C_ABI_MAJOR: u32 = 1;
63pub const TYPE_BRIDGE_C_ABI_MINOR: u32 = 6;
65pub const TYPE_BRIDGE_PROJECTED_TOKEN_VERSION: u32 = 1;
67pub const TYPE_BRIDGE_C_CREATE_FIELD_MAX: usize = 1_020;
73pub const TYPE_BRIDGE_C_CREATE_ROLE_MAX: usize = 1_020;
76pub const TYPE_BRIDGE_C_CREATE_MEMBER_MAX: usize = 1_020;
79
80#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
86#[non_exhaustive]
87pub enum ProjectedTokenKind {
88 Model,
90 Field,
92 Role,
94 Function,
96 Struct,
98 Attribute,
100}
101
102impl ProjectedTokenKind {
103 #[must_use]
105 pub const fn as_u32(self) -> u32 {
106 match self {
107 Self::Model => 1,
108 Self::Field => 2,
109 Self::Role => 3,
110 Self::Function => 4,
111 Self::Struct => 5,
112 Self::Attribute => 6,
113 }
114 }
115
116 #[must_use]
118 pub const fn from_u32(value: u32) -> Option<Self> {
119 match value {
120 1 => Some(Self::Model),
121 2 => Some(Self::Field),
122 3 => Some(Self::Role),
123 4 => Some(Self::Function),
124 5 => Some(Self::Struct),
125 6 => Some(Self::Attribute),
126 _ => None,
127 }
128 }
129}
130
131#[derive(Clone, Debug, Eq, PartialEq)]
137#[non_exhaustive]
138pub enum ProjectedTokenIdentity {
139 Model(TypeId),
141 Field {
143 owner: TypeId,
145 field: OwnsFactId,
147 },
148 Role {
150 owner: TypeId,
152 role: RoleId,
154 },
155 Function(FunctionId),
157 Struct(StructId),
159 Attribute(AttributeId),
161}
162
163impl ProjectedTokenIdentity {
164 #[must_use]
166 pub const fn kind(&self) -> ProjectedTokenKind {
167 match self {
168 Self::Model(_) => ProjectedTokenKind::Model,
169 Self::Field { .. } => ProjectedTokenKind::Field,
170 Self::Role { .. } => ProjectedTokenKind::Role,
171 Self::Function(_) => ProjectedTokenKind::Function,
172 Self::Struct(_) => ProjectedTokenKind::Struct,
173 Self::Attribute(_) => ProjectedTokenKind::Attribute,
174 }
175 }
176}
177
178#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
180#[serde(rename_all = "snake_case")]
181#[non_exhaustive]
182pub enum BindingTarget {
183 Python,
185 #[serde(rename = "typescript")]
187 TypeScript,
188 Rust,
190 C,
192}
193
194impl BindingTarget {
195 #[must_use]
197 pub const fn as_str(self) -> &'static str {
198 match self {
199 Self::Python => "python",
200 Self::TypeScript => "typescript",
201 Self::Rust => "rust",
202 Self::C => "c",
203 }
204 }
205
206 const fn required_generator_handler_id(self) -> &'static str {
207 match self {
208 Self::Python => PYTHON_GENERATOR_HANDLER_ID,
209 Self::TypeScript => TYPESCRIPT_GENERATOR_HANDLER_ID,
210 Self::Rust => RUST_GENERATOR_HANDLER_ID,
211 Self::C => C_GENERATOR_HANDLER_ID,
212 }
213 }
214}
215
216#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
218pub enum PythonNamingPolicy {
219 #[serde(rename = "typebridge.python/v1")]
221 TypeBridgeV1,
222}
223
224#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
226pub enum TypeScriptNamingPolicy {
227 #[serde(rename = "typebridge.typescript/v1")]
229 TypeBridgeV1,
230}
231
232#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
234pub enum RustNamingPolicy {
235 #[serde(rename = "typebridge.rust/v1")]
237 TypeBridgeV1,
238}
239
240#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
242pub enum RustCreatePolicy {
243 #[serde(rename = "typebridge.rust.validated-create-input/v1")]
247 ValidatedInputV1,
248}
249
250#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
252pub enum CNamingPolicy {
253 #[serde(rename = "typebridge.c/v1")]
255 TypeBridgeV1,
256}
257
258#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
260#[serde(transparent)]
261pub struct CSymbolPrefix(String);
262
263impl CSymbolPrefix {
264 pub fn new(value: impl Into<String>) -> Result<Self, Diagnostic> {
266 let value = value.into();
267 let uses_runtime_namespace = value == "type_bridge" || value.starts_with("type_bridge_");
268 let portable_path_component = value
269 .bytes()
270 .all(|byte| byte == b'_' || byte.is_ascii_lowercase() || byte.is_ascii_digit())
271 && !value.ends_with('_')
272 && !is_windows_device_name(&value);
273 if !is_valid_c_identifier(&value, MAX_C_SYMBOL_PREFIX_BYTES)
274 || !portable_path_component
275 || uses_runtime_namespace
276 {
277 return Err(Diagnostic::stable(
278 DiagnosticCategory::InvalidContract,
279 "invalid_c_symbol_prefix",
280 "C symbol prefix must be a bounded lowercase portable path outside the reserved TypeBridge runtime namespace",
281 ));
282 }
283 Ok(Self(value))
284 }
285
286 #[must_use]
288 pub fn as_str(&self) -> &str {
289 &self.0
290 }
291}
292
293impl fmt::Display for CSymbolPrefix {
294 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
295 formatter.write_str(self.as_str())
296 }
297}
298
299#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
301#[serde(tag = "binding")]
302#[non_exhaustive]
303pub enum ProjectionConfig {
304 #[serde(rename = "python")]
306 #[non_exhaustive]
307 Python {
308 naming_policy: PythonNamingPolicy,
310 },
311 #[serde(rename = "typescript")]
313 #[non_exhaustive]
314 TypeScript {
315 naming_policy: TypeScriptNamingPolicy,
317 },
318 #[serde(rename = "rust")]
320 #[non_exhaustive]
321 Rust {
322 naming_policy: RustNamingPolicy,
324 create_policy: RustCreatePolicy,
326 },
327 #[serde(rename = "c")]
329 #[non_exhaustive]
330 C {
331 naming_policy: CNamingPolicy,
333 symbol_prefix: CSymbolPrefix,
335 },
336}
337
338impl ProjectionConfig {
339 #[must_use]
341 pub const fn python() -> Self {
342 Self::Python {
343 naming_policy: PythonNamingPolicy::TypeBridgeV1,
344 }
345 }
346
347 #[must_use]
349 pub const fn typescript() -> Self {
350 Self::TypeScript {
351 naming_policy: TypeScriptNamingPolicy::TypeBridgeV1,
352 }
353 }
354
355 #[must_use]
357 pub const fn rust() -> Self {
358 Self::Rust {
359 naming_policy: RustNamingPolicy::TypeBridgeV1,
360 create_policy: RustCreatePolicy::ValidatedInputV1,
361 }
362 }
363
364 #[must_use]
366 pub fn c(symbol_prefix: CSymbolPrefix) -> Self {
367 Self::C {
368 naming_policy: CNamingPolicy::TypeBridgeV1,
369 symbol_prefix,
370 }
371 }
372
373 #[must_use]
375 pub const fn target(&self) -> BindingTarget {
376 match self {
377 Self::Python { .. } => BindingTarget::Python,
378 Self::TypeScript { .. } => BindingTarget::TypeScript,
379 Self::Rust { .. } => BindingTarget::Rust,
380 Self::C { .. } => BindingTarget::C,
381 }
382 }
383
384 #[must_use]
386 pub const fn python_naming_policy(&self) -> Option<PythonNamingPolicy> {
387 match self {
388 Self::Python { naming_policy } => Some(*naming_policy),
389 Self::TypeScript { .. } | Self::Rust { .. } | Self::C { .. } => None,
390 }
391 }
392
393 #[must_use]
395 pub const fn typescript_naming_policy(&self) -> Option<TypeScriptNamingPolicy> {
396 match self {
397 Self::TypeScript { naming_policy } => Some(*naming_policy),
398 Self::Python { .. } | Self::Rust { .. } | Self::C { .. } => None,
399 }
400 }
401
402 #[must_use]
404 pub const fn rust_naming_policy(&self) -> Option<RustNamingPolicy> {
405 match self {
406 Self::Rust { naming_policy, .. } => Some(*naming_policy),
407 Self::Python { .. } | Self::TypeScript { .. } | Self::C { .. } => None,
408 }
409 }
410
411 #[must_use]
413 pub const fn rust_create_policy(&self) -> Option<RustCreatePolicy> {
414 match self {
415 Self::Rust { create_policy, .. } => Some(*create_policy),
416 Self::Python { .. } | Self::TypeScript { .. } | Self::C { .. } => None,
417 }
418 }
419
420 #[must_use]
422 pub const fn c_naming_policy(&self) -> Option<CNamingPolicy> {
423 match self {
424 Self::C { naming_policy, .. } => Some(*naming_policy),
425 Self::Python { .. } | Self::TypeScript { .. } | Self::Rust { .. } => None,
426 }
427 }
428
429 #[must_use]
431 pub const fn c_symbol_prefix(&self) -> Option<&CSymbolPrefix> {
432 match self {
433 Self::C { symbol_prefix, .. } => Some(symbol_prefix),
434 Self::Python { .. } | Self::TypeScript { .. } | Self::Rust { .. } => None,
435 }
436 }
437}
438
439fn validate_component_id(value: String) -> Result<String, Diagnostic> {
440 let segments = value.split('.').collect::<Vec<_>>();
441 let valid_segment = |segment: &str| {
442 let mut bytes = segment.bytes();
443 bytes.next().is_some_and(|byte| byte.is_ascii_lowercase())
444 && bytes.all(|byte| {
445 byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'-' | b'_')
446 })
447 };
448 if value.len() <= MAX_PROJECTION_COMPONENT_ID_BYTES
449 && segments.len() >= 2
450 && segments.iter().all(|segment| valid_segment(segment))
451 {
452 Ok(value)
453 } else {
454 Err(Diagnostic::stable(
455 DiagnosticCategory::InvalidContract,
456 "malformed_projection_component_id",
457 "projection component ID must be a bounded lowercase namespaced identifier",
458 ))
459 }
460}
461
462macro_rules! projection_component_id {
463 ($name:ident, $doc:literal) => {
464 #[doc = $doc]
465 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
466 pub struct $name(String);
467
468 impl $name {
469 pub fn new(value: impl Into<String>) -> Result<Self, Diagnostic> {
471 Ok(Self(validate_component_id(value.into())?))
472 }
473
474 #[must_use]
476 pub fn as_str(&self) -> &str {
477 &self.0
478 }
479 }
480
481 impl fmt::Display for $name {
482 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
483 formatter.write_str(self.as_str())
484 }
485 }
486
487 impl Serialize for $name {
488 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
489 where
490 S: Serializer,
491 {
492 serializer.serialize_str(self.as_str())
493 }
494 }
495 };
496}
497
498projection_component_id!(
499 ProjectionHandlerId,
500 "A stable identity for one generator or projection handler."
501);
502projection_component_id!(
503 CodeResourceId,
504 "A stable identity for exact code-resource bytes referenced during emission."
505);
506
507#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
509#[serde(transparent)]
510pub struct ProjectionHandlerVersion(u16);
511
512impl ProjectionHandlerVersion {
513 pub const V1: Self = Self(1);
515 pub const V2: Self = Self(2);
517 pub const V3: Self = Self(3);
519
520 pub fn new(value: u16) -> Result<Self, Diagnostic> {
522 if value == 0 {
523 Err(Diagnostic::stable(
524 DiagnosticCategory::InvalidContract,
525 "invalid_projection_handler_version",
526 "projection handler version must be nonzero",
527 ))
528 } else {
529 Ok(Self(value))
530 }
531 }
532
533 #[must_use]
535 pub const fn get(self) -> u16 {
536 self.0
537 }
538}
539
540#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
542pub struct ProjectionHandler {
543 id: ProjectionHandlerId,
544 version: ProjectionHandlerVersion,
545}
546
547impl ProjectionHandler {
548 pub fn new(id: impl Into<String>, version: u16) -> Result<Self, Diagnostic> {
550 Ok(Self {
551 id: ProjectionHandlerId::new(id)?,
552 version: ProjectionHandlerVersion::new(version)?,
553 })
554 }
555
556 #[must_use]
558 pub fn python_v1() -> Self {
559 Self {
560 id: ProjectionHandlerId::new(PYTHON_GENERATOR_HANDLER_ID)
561 .expect("the built-in Python generator ID is valid"),
562 version: ProjectionHandlerVersion::V1,
563 }
564 }
565
566 #[must_use]
568 pub fn python_v2() -> Self {
569 Self {
570 id: ProjectionHandlerId::new(PYTHON_GENERATOR_HANDLER_ID)
571 .expect("the built-in Python generator ID is valid"),
572 version: ProjectionHandlerVersion::V2,
573 }
574 }
575
576 #[must_use]
578 pub fn typescript_v1() -> Self {
579 Self {
580 id: ProjectionHandlerId::new(TYPESCRIPT_GENERATOR_HANDLER_ID)
581 .expect("built-in TypeScript projection handler ID is valid"),
582 version: ProjectionHandlerVersion::V1,
583 }
584 }
585
586 #[must_use]
588 pub fn typescript_v2() -> Self {
589 Self {
590 id: ProjectionHandlerId::new(TYPESCRIPT_GENERATOR_HANDLER_ID)
591 .expect("built-in TypeScript projection handler ID is valid"),
592 version: ProjectionHandlerVersion::V2,
593 }
594 }
595
596 #[must_use]
598 pub fn rust_v1() -> Self {
599 Self {
600 id: ProjectionHandlerId::new(RUST_GENERATOR_HANDLER_ID)
601 .expect("built-in Rust projection handler ID is valid"),
602 version: ProjectionHandlerVersion::V1,
603 }
604 }
605
606 #[must_use]
608 pub fn rust_v2() -> Self {
609 Self {
610 id: ProjectionHandlerId::new(RUST_GENERATOR_HANDLER_ID)
611 .expect("built-in Rust projection handler ID is valid"),
612 version: ProjectionHandlerVersion::V2,
613 }
614 }
615
616 #[must_use]
618 pub fn c_v1() -> Self {
619 Self {
620 id: ProjectionHandlerId::new(C_GENERATOR_HANDLER_ID)
621 .expect("the built-in C generator ID is valid"),
622 version: ProjectionHandlerVersion::V1,
623 }
624 }
625
626 #[must_use]
628 pub fn c_v2() -> Self {
629 Self {
630 id: ProjectionHandlerId::new(C_GENERATOR_HANDLER_ID)
631 .expect("the built-in C generator ID is valid"),
632 version: ProjectionHandlerVersion::V2,
633 }
634 }
635
636 #[must_use]
638 pub fn c_v3() -> Self {
639 Self {
640 id: ProjectionHandlerId::new(C_GENERATOR_HANDLER_ID)
641 .expect("the built-in C generator ID is valid"),
642 version: ProjectionHandlerVersion::V3,
643 }
644 }
645
646 #[must_use]
648 pub const fn id(&self) -> &ProjectionHandlerId {
649 &self.id
650 }
651
652 #[must_use]
654 pub const fn version(&self) -> ProjectionHandlerVersion {
655 self.version
656 }
657}
658
659#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
661pub struct CodeResourceDigest {
662 id: CodeResourceId,
663 content_fingerprint: Fingerprint,
664}
665
666impl CodeResourceDigest {
667 pub fn from_bytes(id: impl Into<String>, bytes: &[u8]) -> Result<Self, Diagnostic> {
669 Ok(Self {
670 id: CodeResourceId::new(id)?,
671 content_fingerprint: Fingerprint::compute(
672 FingerprintDomain::new(CODE_RESOURCE_DOMAIN)?,
673 CanonicalizationVersion::new(RAW_BYTES_CANONICALIZATION)?,
674 None,
675 bytes,
676 ),
677 })
678 }
679
680 #[must_use]
682 pub const fn id(&self) -> &CodeResourceId {
683 &self.id
684 }
685
686 #[must_use]
688 pub const fn content_fingerprint(&self) -> &Fingerprint {
689 &self.content_fingerprint
690 }
691}
692
693#[derive(Serialize)]
694struct BindingProjectionView<'a> {
695 format_version: FormatVersion,
696 target: BindingTarget,
697 semantic_schema_fingerprint: &'a SemanticSchemaFingerprint,
698 config: &'a ProjectionConfig,
699 generator_handlers: Vec<&'a ProjectionHandler>,
700 referenced_code_resources: Vec<&'a CodeResourceDigest>,
701}
702
703fn ordered_handlers(
704 target: BindingTarget,
705 handlers: &[ProjectionHandler],
706) -> Result<Vec<&ProjectionHandler>, Diagnostic> {
707 let mut ordered = handlers.iter().collect::<Vec<_>>();
708 ordered.sort_by(|left, right| match left.id().cmp(right.id()) {
709 Ordering::Equal => left.version().cmp(&right.version()),
710 ordering => ordering,
711 });
712 if ordered.windows(2).any(|pair| pair[0].id() == pair[1].id()) {
713 return Err(Diagnostic::stable(
714 DiagnosticCategory::InvalidContract,
715 "duplicate_projection_handler_id",
716 "a projection handler identity may appear only once",
717 ));
718 }
719 if !ordered
720 .iter()
721 .any(|handler| handler.id().as_str() == target.required_generator_handler_id())
722 {
723 return Err(Diagnostic::stable(
724 DiagnosticCategory::InvalidContract,
725 "missing_target_projection_handler",
726 "projection fingerprint inputs omit the target's generator handler",
727 ));
728 }
729 Ok(ordered)
730}
731
732fn ordered_resources(
733 resources: &[CodeResourceDigest],
734) -> Result<Vec<&CodeResourceDigest>, Diagnostic> {
735 let mut ordered = resources.iter().collect::<Vec<_>>();
736 ordered.sort_by(|left, right| left.id().cmp(right.id()));
737 if ordered.windows(2).any(|pair| pair[0].id() == pair[1].id()) {
738 return Err(Diagnostic::stable(
739 DiagnosticCategory::InvalidContract,
740 "duplicate_projection_resource_id",
741 "a referenced code-resource identity may appear only once",
742 ));
743 }
744 Ok(ordered)
745}
746
747pub fn canonical_binding_projection_bytes(
749 target: BindingTarget,
750 semantic_schema: &SemanticSchemaFingerprint,
751 config: &ProjectionConfig,
752 handlers: &[ProjectionHandler],
753 resources: &[CodeResourceDigest],
754) -> Result<Vec<u8>, Diagnostic> {
755 if config.target() != target {
756 return Err(Diagnostic::stable(
757 DiagnosticCategory::InvalidContract,
758 "projection_config_target_mismatch",
759 "projection configuration belongs to a different binding target",
760 ));
761 }
762 let view = BindingProjectionView {
763 format_version: FormatVersion::V1,
764 target,
765 semantic_schema_fingerprint: semantic_schema,
766 config,
767 generator_handlers: ordered_handlers(target, handlers)?,
768 referenced_code_resources: ordered_resources(resources)?,
769 };
770 to_canonical_json(&view)
771}
772
773#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
775#[serde(transparent)]
776pub struct BindingProjectionFingerprint(Fingerprint);
777
778impl BindingProjectionFingerprint {
779 pub fn compute(
781 target: BindingTarget,
782 semantic_schema: &SemanticSchemaFingerprint,
783 config: &ProjectionConfig,
784 handlers: &[ProjectionHandler],
785 resources: &[CodeResourceDigest],
786 ) -> Result<Self, Diagnostic> {
787 let canonical = canonical_binding_projection_bytes(
788 target,
789 semantic_schema,
790 config,
791 handlers,
792 resources,
793 )?;
794 let semantic_profile = semantic_schema
795 .as_fingerprint()
796 .semantic_profile()
797 .cloned()
798 .ok_or_else(|| {
799 Diagnostic::stable(
800 DiagnosticCategory::InvalidContract,
801 "projection_semantic_profile_missing",
802 "semantic schema fingerprint does not carry its semantic profile",
803 )
804 })?;
805 Ok(Self(Fingerprint::compute(
806 FingerprintDomain::new(BINDING_PROJECTION_DOMAIN)?,
807 CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
808 Some(semantic_profile),
809 &canonical,
810 )))
811 }
812
813 #[must_use]
815 pub const fn as_fingerprint(&self) -> &Fingerprint {
816 &self.0
817 }
818
819 pub fn compute_with_projection(
821 target: BindingTarget,
822 semantic_schema: &SemanticSchemaFingerprint,
823 config: &ProjectionConfig,
824 handlers: &[ProjectionHandler],
825 resources: &[CodeResourceDigest],
826 canonical_projection: &[u8],
827 ) -> Result<Self, Diagnostic> {
828 #[derive(Serialize)]
829 struct CompleteProjectionView<'a> {
830 inputs: BindingProjectionView<'a>,
831 projection_content: Fingerprint,
832 }
833
834 if config.target() != target {
835 return Err(Diagnostic::stable(
836 DiagnosticCategory::InvalidContract,
837 "projection_config_target_mismatch",
838 "projection configuration belongs to a different binding target",
839 ));
840 }
841 let inputs = BindingProjectionView {
842 format_version: FormatVersion::V1,
843 target,
844 semantic_schema_fingerprint: semantic_schema,
845 config,
846 generator_handlers: ordered_handlers(target, handlers)?,
847 referenced_code_resources: ordered_resources(resources)?,
848 };
849 let projection_content = Fingerprint::compute(
850 FingerprintDomain::new(BINDING_PROJECTION_CONTENT_DOMAIN)?,
851 CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
852 semantic_schema.as_fingerprint().semantic_profile().cloned(),
853 canonical_projection,
854 );
855 let canonical = to_canonical_json(&CompleteProjectionView {
856 inputs,
857 projection_content,
858 })?;
859 let semantic_profile = semantic_schema
860 .as_fingerprint()
861 .semantic_profile()
862 .cloned()
863 .ok_or_else(|| {
864 Diagnostic::stable(
865 DiagnosticCategory::InvalidContract,
866 "projection_semantic_profile_missing",
867 "semantic schema fingerprint does not carry its semantic profile",
868 )
869 })?;
870 Ok(Self(Fingerprint::compute(
871 FingerprintDomain::new(BINDING_PROJECTION_DOMAIN)?,
872 CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
873 Some(semantic_profile),
874 &canonical,
875 )))
876 }
877}
878
879fn serialize_map_values<S, K, V>(map: &BTreeMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
880where
881 S: Serializer,
882 V: Serialize,
883{
884 map.values().collect::<Vec<_>>().serialize(serializer)
885}
886
887#[derive(Serialize)]
888struct RoleUpcastEntry<'a> {
889 role: &'a RoleId,
890 ancestors: &'a [RoleId],
891}
892
893fn serialize_role_upcasts<S>(
894 map: &BTreeMap<RoleId, Vec<RoleId>>,
895 serializer: S,
896) -> Result<S::Ok, S::Error>
897where
898 S: Serializer,
899{
900 map.iter()
901 .map(|(role, ancestors)| RoleUpcastEntry { role, ancestors })
902 .collect::<Vec<_>>()
903 .serialize(serializer)
904}
905
906fn invalid_projection(code: &'static str, message: &'static str) -> Diagnostic {
907 Diagnostic::stable(DiagnosticCategory::InvalidContract, code, message)
908}
909
910fn ensure_collection_limit(length: usize, code: &'static str) -> Result<(), Diagnostic> {
911 if length > MAX_CANONICAL_COLLECTION_LEN {
912 Err(Diagnostic::stable(
913 DiagnosticCategory::ResourceLimit,
914 code,
915 "projection collection exceeds the canonical collection limit",
916 ))
917 } else {
918 Ok(())
919 }
920}
921
922fn validate_c_create_limits(models: &BTreeMap<TypeId, ModelProjection>) -> Result<(), Diagnostic> {
923 for model in models.values() {
924 if model.create().target_name().is_none() {
925 continue;
926 }
927 let field_count = model.create().fields().len();
928 let role_count = model.create().roles().len();
929 if field_count > TYPE_BRIDGE_C_CREATE_FIELD_MAX {
930 return Err(Diagnostic::stable(
931 DiagnosticCategory::ResourceLimit,
932 "c_projection_create_field_limit_exceeded",
933 "C create fields exceed the 1020-member translation ceiling",
934 ));
935 }
936 if role_count > TYPE_BRIDGE_C_CREATE_ROLE_MAX {
937 return Err(Diagnostic::stable(
938 DiagnosticCategory::ResourceLimit,
939 "c_projection_create_role_limit_exceeded",
940 "C create roles exceed the 1020-member translation ceiling",
941 ));
942 }
943 if field_count
944 .checked_add(role_count)
945 .is_none_or(|count| count > TYPE_BRIDGE_C_CREATE_MEMBER_MAX)
946 {
947 return Err(Diagnostic::stable(
948 DiagnosticCategory::ResourceLimit,
949 "c_projection_create_member_limit_exceeded",
950 "combined C create fields and roles exceed the 1020-member translation ceiling",
951 ));
952 }
953 }
954 Ok(())
955}
956
957#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
959#[serde(transparent)]
960pub struct TargetIdentifier(String);
961
962impl TargetIdentifier {
963 pub fn python(value: impl Into<String>) -> Result<Self, Diagnostic> {
965 let value = value.into();
966 let mut bytes = value.bytes();
967 let valid = value.len() <= MAX_TARGET_IDENTIFIER_BYTES
968 && bytes
969 .next()
970 .is_some_and(|byte| byte == b'_' || byte.is_ascii_alphabetic())
971 && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric());
972 if !valid || is_python_keyword(&value) {
973 return Err(invalid_projection(
974 "invalid_python_projection_identifier",
975 "projected Python name is not a bounded non-keyword identifier",
976 ));
977 }
978 Ok(Self(value))
979 }
980
981 pub fn typescript(value: impl Into<String>) -> Result<Self, Diagnostic> {
983 let value = value.into();
984 let mut bytes = value.bytes();
985 let valid = value.len() <= MAX_TARGET_IDENTIFIER_BYTES
986 && bytes
987 .next()
988 .is_some_and(|byte| byte == b'_' || byte == b'$' || byte.is_ascii_alphabetic())
989 && bytes.all(|byte| byte == b'_' || byte == b'$' || byte.is_ascii_alphanumeric());
990 if !valid || is_typescript_keyword(&value) {
991 return Err(invalid_projection(
992 "invalid_typescript_projection_identifier",
993 "projected TypeScript name is not a bounded non-keyword identifier",
994 ));
995 }
996 Ok(Self(value))
997 }
998
999 pub fn rust(value: impl Into<String>) -> Result<Self, Diagnostic> {
1001 let value = value.into();
1002 let mut bytes = value.bytes();
1003 let valid = value != "_"
1004 && value.len() <= MAX_TARGET_IDENTIFIER_BYTES
1005 && bytes
1006 .next()
1007 .is_some_and(|byte| byte == b'_' || byte.is_ascii_alphabetic())
1008 && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric());
1009 if !valid || is_rust_keyword(&value) {
1010 return Err(invalid_projection(
1011 "invalid_rust_projection_identifier",
1012 "projected Rust name is not a bounded non-keyword ASCII identifier",
1013 ));
1014 }
1015 Ok(Self(value))
1016 }
1017
1018 pub fn c(value: impl Into<String>) -> Result<Self, Diagnostic> {
1020 let value = value.into();
1021 if !is_valid_c_identifier(&value, MAX_TARGET_IDENTIFIER_BYTES) {
1022 return Err(invalid_projection(
1023 "invalid_c_projection_identifier",
1024 "projected C name is not a bounded, non-keyword, non-reserved ASCII identifier",
1025 ));
1026 }
1027 Ok(Self(value))
1028 }
1029
1030 #[must_use]
1032 pub fn as_str(&self) -> &str {
1033 &self.0
1034 }
1035}
1036
1037fn is_python_keyword(value: &str) -> bool {
1038 matches!(
1039 value,
1040 "False"
1041 | "None"
1042 | "True"
1043 | "and"
1044 | "as"
1045 | "assert"
1046 | "async"
1047 | "await"
1048 | "break"
1049 | "case"
1050 | "class"
1051 | "continue"
1052 | "def"
1053 | "del"
1054 | "elif"
1055 | "else"
1056 | "except"
1057 | "finally"
1058 | "for"
1059 | "from"
1060 | "global"
1061 | "if"
1062 | "import"
1063 | "in"
1064 | "is"
1065 | "lambda"
1066 | "match"
1067 | "nonlocal"
1068 | "not"
1069 | "or"
1070 | "pass"
1071 | "raise"
1072 | "return"
1073 | "try"
1074 | "while"
1075 | "with"
1076 | "yield"
1077 )
1078}
1079
1080fn is_typescript_keyword(value: &str) -> bool {
1081 matches!(
1082 value,
1083 "abstract"
1084 | "any"
1085 | "as"
1086 | "asserts"
1087 | "async"
1088 | "await"
1089 | "bigint"
1090 | "boolean"
1091 | "break"
1092 | "case"
1093 | "catch"
1094 | "class"
1095 | "const"
1096 | "constructor"
1097 | "continue"
1098 | "debugger"
1099 | "declare"
1100 | "default"
1101 | "delete"
1102 | "do"
1103 | "else"
1104 | "enum"
1105 | "export"
1106 | "extends"
1107 | "false"
1108 | "finally"
1109 | "for"
1110 | "from"
1111 | "function"
1112 | "get"
1113 | "if"
1114 | "implements"
1115 | "import"
1116 | "in"
1117 | "infer"
1118 | "instanceof"
1119 | "interface"
1120 | "is"
1121 | "keyof"
1122 | "let"
1123 | "module"
1124 | "namespace"
1125 | "never"
1126 | "new"
1127 | "null"
1128 | "number"
1129 | "object"
1130 | "of"
1131 | "out"
1132 | "override"
1133 | "package"
1134 | "private"
1135 | "protected"
1136 | "public"
1137 | "readonly"
1138 | "require"
1139 | "return"
1140 | "satisfies"
1141 | "set"
1142 | "static"
1143 | "string"
1144 | "super"
1145 | "switch"
1146 | "symbol"
1147 | "this"
1148 | "throw"
1149 | "true"
1150 | "try"
1151 | "type"
1152 | "typeof"
1153 | "undefined"
1154 | "unique"
1155 | "unknown"
1156 | "using"
1157 | "var"
1158 | "void"
1159 | "while"
1160 | "with"
1161 | "yield"
1162 )
1163}
1164
1165fn is_rust_keyword(value: &str) -> bool {
1166 matches!(
1167 value,
1168 "Self"
1169 | "abstract"
1170 | "as"
1171 | "async"
1172 | "await"
1173 | "become"
1174 | "box"
1175 | "break"
1176 | "const"
1177 | "continue"
1178 | "crate"
1179 | "do"
1180 | "dyn"
1181 | "else"
1182 | "enum"
1183 | "extern"
1184 | "false"
1185 | "final"
1186 | "fn"
1187 | "for"
1188 | "gen"
1189 | "if"
1190 | "impl"
1191 | "in"
1192 | "let"
1193 | "loop"
1194 | "macro"
1195 | "match"
1196 | "mod"
1197 | "move"
1198 | "mut"
1199 | "override"
1200 | "priv"
1201 | "pub"
1202 | "ref"
1203 | "return"
1204 | "self"
1205 | "static"
1206 | "struct"
1207 | "super"
1208 | "trait"
1209 | "true"
1210 | "try"
1211 | "type"
1212 | "typeof"
1213 | "unsafe"
1214 | "unsized"
1215 | "use"
1216 | "virtual"
1217 | "where"
1218 | "while"
1219 | "yield"
1220 )
1221}
1222
1223fn is_valid_c_identifier(value: &str, max_bytes: usize) -> bool {
1224 let mut bytes = value.bytes();
1225 value.len() <= max_bytes
1226 && bytes.next().is_some_and(|byte| byte.is_ascii_alphabetic())
1227 && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric())
1228 && !value.contains("__")
1229 && !is_c_keyword(value)
1230}
1231
1232fn is_windows_device_name(value: &str) -> bool {
1233 matches!(
1234 value,
1235 "aux"
1236 | "clock$"
1237 | "con"
1238 | "nul"
1239 | "prn"
1240 | "com1"
1241 | "com2"
1242 | "com3"
1243 | "com4"
1244 | "com5"
1245 | "com6"
1246 | "com7"
1247 | "com8"
1248 | "com9"
1249 | "lpt1"
1250 | "lpt2"
1251 | "lpt3"
1252 | "lpt4"
1253 | "lpt5"
1254 | "lpt6"
1255 | "lpt7"
1256 | "lpt8"
1257 | "lpt9"
1258 )
1259}
1260
1261fn is_c_keyword(value: &str) -> bool {
1262 matches!(
1263 value,
1264 "_Alignas"
1265 | "_Alignof"
1266 | "_Atomic"
1267 | "_BitInt"
1268 | "_Bool"
1269 | "_Complex"
1270 | "_Decimal128"
1271 | "_Decimal32"
1272 | "_Decimal64"
1273 | "_Generic"
1274 | "_Imaginary"
1275 | "_Noreturn"
1276 | "_Static_assert"
1277 | "_Thread_local"
1278 | "alignas"
1279 | "alignof"
1280 | "auto"
1281 | "bool"
1282 | "break"
1283 | "case"
1284 | "char"
1285 | "const"
1286 | "constexpr"
1287 | "continue"
1288 | "default"
1289 | "do"
1290 | "double"
1291 | "else"
1292 | "enum"
1293 | "extern"
1294 | "false"
1295 | "float"
1296 | "for"
1297 | "goto"
1298 | "if"
1299 | "inline"
1300 | "int"
1301 | "long"
1302 | "nullptr"
1303 | "register"
1304 | "restrict"
1305 | "return"
1306 | "short"
1307 | "signed"
1308 | "sizeof"
1309 | "static"
1310 | "static_assert"
1311 | "struct"
1312 | "switch"
1313 | "thread_local"
1314 | "true"
1315 | "typedef"
1316 | "typeof"
1317 | "typeof_unqual"
1318 | "union"
1319 | "unsigned"
1320 | "void"
1321 | "volatile"
1322 | "while"
1323 )
1324}
1325
1326#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1328#[serde(rename_all = "snake_case")]
1329pub enum ProjectedModelForm {
1330 Complete,
1332 Reference,
1334}
1335
1336#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1338pub struct ProjectedModelUse {
1339 id: TypeId,
1340 form: ProjectedModelForm,
1341}
1342
1343impl ProjectedModelUse {
1344 #[must_use]
1346 pub const fn new(id: TypeId, form: ProjectedModelForm) -> Self {
1347 Self { id, form }
1348 }
1349 #[must_use]
1351 pub const fn id(&self) -> &TypeId {
1352 &self.id
1353 }
1354 #[must_use]
1356 pub const fn form(&self) -> ProjectedModelForm {
1357 self.form
1358 }
1359}
1360
1361#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1363#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
1364pub enum ProjectedTypeRef {
1365 Scalar(ValueTypeTag),
1367 Model(ProjectedModelUse),
1369 Struct(StructId),
1371}
1372
1373#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1375#[serde(rename_all = "snake_case")]
1376pub enum ProjectedContainer {
1377 Scalar,
1379 Sequence,
1381}
1382
1383#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
1385pub struct ProjectedMultiplicity {
1386 cardinality: Cardinality,
1387 required: bool,
1388 container: ProjectedContainer,
1389 #[serde(skip_serializing_if = "CollectionMode::is_unordered")]
1390 collection_mode: CollectionMode,
1391}
1392
1393impl ProjectedMultiplicity {
1394 #[must_use]
1396 pub const fn from_cardinality(cardinality: Cardinality) -> Self {
1397 Self::new(cardinality, CollectionMode::Unordered)
1398 }
1399 #[must_use]
1401 pub const fn new(cardinality: Cardinality, collection_mode: CollectionMode) -> Self {
1402 let container = match collection_mode {
1403 CollectionMode::OrderedList => ProjectedContainer::Sequence,
1404 CollectionMode::Unordered => match cardinality.max() {
1405 Some(0 | 1) => ProjectedContainer::Scalar,
1406 Some(_) | None => ProjectedContainer::Sequence,
1407 },
1408 };
1409 Self {
1410 cardinality,
1411 required: cardinality.min() > 0,
1412 container,
1413 collection_mode,
1414 }
1415 }
1416 #[must_use]
1418 pub const fn cardinality(&self) -> Cardinality {
1419 self.cardinality
1420 }
1421 #[must_use]
1423 pub const fn required(&self) -> bool {
1424 self.required
1425 }
1426 #[must_use]
1428 pub const fn container(&self) -> ProjectedContainer {
1429 self.container
1430 }
1431 #[must_use]
1433 pub const fn collection_mode(&self) -> CollectionMode {
1434 self.collection_mode
1435 }
1436}
1437
1438#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1447pub struct ProjectedAnnotation {
1448 id: AnnotationFactId,
1449 value: SchemaAnnotationValue,
1450}
1451
1452impl ProjectedAnnotation {
1453 pub fn new(id: AnnotationFactId, value: SchemaAnnotationValue) -> Result<Self, Diagnostic> {
1455 AnnotationFact::new(id.clone(), value.clone())?;
1456 Ok(Self { id, value })
1457 }
1458 #[must_use]
1460 pub const fn id(&self) -> &AnnotationFactId {
1461 &self.id
1462 }
1463 #[must_use]
1465 pub const fn value(&self) -> &SchemaAnnotationValue {
1466 &self.value
1467 }
1468}
1469
1470#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1472pub struct FieldTokenProjection {
1473 id: OwnsFactId,
1474 declaring_id: OwnsFactId,
1475 target_name: TargetIdentifier,
1476 multiplicity: ProjectedMultiplicity,
1477 key: bool,
1478 unique: bool,
1479 #[serde(serialize_with = "serialize_map_values")]
1480 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1481}
1482
1483impl FieldTokenProjection {
1484 pub fn new(
1486 id: OwnsFactId,
1487 declaring_id: OwnsFactId,
1488 target_name: TargetIdentifier,
1489 multiplicity: ProjectedMultiplicity,
1490 key: bool,
1491 unique: bool,
1492 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1493 ) -> Result<Self, Diagnostic> {
1494 if id.attribute() != declaring_id.attribute() {
1495 return Err(invalid_projection(
1496 "invalid_projection_reference",
1497 "effective owns fact attribute does not match declaring owns fact attribute",
1498 ));
1499 }
1500 if annotations.iter().any(|(key, value)| {
1501 key != value.id()
1502 || !matches!(
1503 value.id().subject(),
1504 AnnotationSubjectId::Owns(subject) if subject == &id
1505 )
1506 }) {
1507 return Err(invalid_projection(
1508 "invalid_projected_owns_annotation",
1509 "owns annotations require matching exact effective owns subjects",
1510 ));
1511 }
1512 if multiplicity.collection_mode().is_unordered()
1513 && annotations
1514 .keys()
1515 .any(|id| id.kind() == &AnnotationKindId::Distinct)
1516 {
1517 return Err(invalid_projection(
1518 "distinct_requires_ordered_collection",
1519 "distinct applies only to an ordered ownership collection",
1520 ));
1521 }
1522 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1523 Ok(Self {
1524 id,
1525 declaring_id,
1526 target_name,
1527 multiplicity,
1528 key,
1529 unique,
1530 annotations,
1531 })
1532 }
1533 #[must_use]
1535 pub const fn id(&self) -> &OwnsFactId {
1536 &self.id
1537 }
1538 #[must_use]
1540 pub const fn declaring_id(&self) -> &OwnsFactId {
1541 &self.declaring_id
1542 }
1543 #[must_use]
1545 pub const fn target_name(&self) -> &TargetIdentifier {
1546 &self.target_name
1547 }
1548 #[must_use]
1550 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1551 self.multiplicity
1552 }
1553 #[must_use]
1555 pub const fn is_key(&self) -> bool {
1556 self.key
1557 }
1558 #[must_use]
1560 pub const fn is_unique(&self) -> bool {
1561 self.unique
1562 }
1563 #[must_use]
1565 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1566 &self.annotations
1567 }
1568}
1569
1570#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1572pub struct RoleTokenProjection {
1573 owner: TypeId,
1574 role: RoleId,
1575 target_name: TargetIdentifier,
1576 #[serde(skip_serializing_if = "Option::is_none")]
1577 player_union_target_name: Option<TargetIdentifier>,
1578 accepted_players: BTreeSet<TypeId>,
1579 specializes: Option<RoleId>,
1580 multiplicity: ProjectedMultiplicity,
1581 is_abstract: bool,
1582 #[serde(serialize_with = "serialize_map_values")]
1583 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1584}
1585
1586impl RoleTokenProjection {
1587 #[allow(clippy::too_many_arguments)]
1589 pub fn new(
1590 owner: TypeId,
1591 role: RoleId,
1592 target_name: TargetIdentifier,
1593 accepted_players: BTreeSet<TypeId>,
1594 specializes: Option<RoleId>,
1595 multiplicity: ProjectedMultiplicity,
1596 is_abstract: bool,
1597 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1598 ) -> Result<Self, Diagnostic> {
1599 if owner.kind() != TypeKind::Relation
1600 || accepted_players
1601 .iter()
1602 .any(|id| !matches!(id.kind(), TypeKind::Entity | TypeKind::Relation))
1603 {
1604 return Err(invalid_projection(
1605 "invalid_projected_role_token",
1606 "role tokens require a relation owner and entity/relation players",
1607 ));
1608 }
1609 let effective_role = RoleId::new(
1610 owner.label().as_str().to_owned(),
1611 role.label().as_str().to_owned(),
1612 )?;
1613 let effective_subject =
1614 AnnotationSubjectId::Relates(RelatesFactId::new(owner.clone(), effective_role)?);
1615 if annotations
1616 .iter()
1617 .any(|(key, value)| key != value.id() || value.id().subject() != &effective_subject)
1618 {
1619 return Err(invalid_projection(
1620 "invalid_projected_relates_annotation",
1621 "relates annotations require matching exact effective relates subjects",
1622 ));
1623 }
1624 if multiplicity.collection_mode().is_unordered()
1625 && annotations
1626 .keys()
1627 .any(|id| id.kind() == &AnnotationKindId::Distinct)
1628 {
1629 return Err(invalid_projection(
1630 "distinct_requires_ordered_collection",
1631 "distinct applies only to an ordered related-role collection",
1632 ));
1633 }
1634 ensure_collection_limit(accepted_players.len(), "too_many_projected_role_players")?;
1635 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1636 Ok(Self {
1637 owner,
1638 role,
1639 target_name,
1640 player_union_target_name: None,
1641 accepted_players,
1642 specializes,
1643 multiplicity,
1644 is_abstract,
1645 annotations,
1646 })
1647 }
1648 #[must_use]
1650 pub fn with_player_union_target_name(mut self, target_name: TargetIdentifier) -> Self {
1651 self.player_union_target_name = Some(target_name);
1652 self
1653 }
1654 #[must_use]
1656 pub const fn owner(&self) -> &TypeId {
1657 &self.owner
1658 }
1659 #[must_use]
1661 pub const fn role(&self) -> &RoleId {
1662 &self.role
1663 }
1664 #[must_use]
1666 pub const fn target_name(&self) -> &TargetIdentifier {
1667 &self.target_name
1668 }
1669 #[must_use]
1671 pub const fn player_union_target_name(&self) -> Option<&TargetIdentifier> {
1672 self.player_union_target_name.as_ref()
1673 }
1674 #[must_use]
1676 pub const fn accepted_players(&self) -> &BTreeSet<TypeId> {
1677 &self.accepted_players
1678 }
1679 #[must_use]
1681 pub const fn specializes(&self) -> Option<&RoleId> {
1682 self.specializes.as_ref()
1683 }
1684 #[must_use]
1686 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1687 self.multiplicity
1688 }
1689 #[must_use]
1691 pub const fn is_abstract(&self) -> bool {
1692 self.is_abstract
1693 }
1694 #[must_use]
1696 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1697 &self.annotations
1698 }
1699}
1700
1701#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1703pub struct DeclaredRoleProjection {
1704 role: RoleId,
1705 specializes: Option<RoleId>,
1706}
1707
1708impl DeclaredRoleProjection {
1709 #[must_use]
1711 pub const fn new(role: RoleId, specializes: Option<RoleId>) -> Self {
1712 Self { role, specializes }
1713 }
1714 #[must_use]
1716 pub const fn role(&self) -> &RoleId {
1717 &self.role
1718 }
1719 #[must_use]
1721 pub const fn specializes(&self) -> Option<&RoleId> {
1722 self.specializes.as_ref()
1723 }
1724}
1725
1726#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1728pub struct DirectSubProjection {
1729 id: SubFactId,
1730 origin: SchemaFactId,
1731 #[serde(serialize_with = "serialize_map_values")]
1732 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1733}
1734
1735impl DirectSubProjection {
1736 pub fn new(
1738 id: SubFactId,
1739 origin: SchemaFactId,
1740 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1741 ) -> Result<Self, Diagnostic> {
1742 if origin != SchemaFactId::Sub(id.clone()) {
1743 return Err(invalid_projection(
1744 "invalid_projected_sub_origin",
1745 "projected subtype origin must identify its exact direct edge",
1746 ));
1747 }
1748 if annotations.iter().any(|(key, value)| {
1749 key != value.id()
1750 || !matches!(key.subject(), AnnotationSubjectId::Sub(subject) if subject == &id)
1751 }) {
1752 return Err(invalid_projection(
1753 "invalid_projected_sub_annotation",
1754 "subtype annotations require matching exact edge subjects",
1755 ));
1756 }
1757 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1758 Ok(Self {
1759 id,
1760 origin,
1761 annotations,
1762 })
1763 }
1764
1765 #[must_use]
1767 pub const fn id(&self) -> &SubFactId {
1768 &self.id
1769 }
1770
1771 #[must_use]
1773 pub const fn origin(&self) -> &SchemaFactId {
1774 &self.origin
1775 }
1776
1777 #[must_use]
1779 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1780 &self.annotations
1781 }
1782}
1783
1784#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1786pub struct DeclarationProjection {
1787 parent: Option<TypeId>,
1788 direct_sub: Option<DirectSubProjection>,
1791 value_type: Option<ValueTypeTag>,
1792 is_abstract: bool,
1793 is_constructible: bool,
1794 #[serde(serialize_with = "serialize_map_values")]
1795 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1796 #[serde(serialize_with = "serialize_map_values")]
1797 value_annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1798 direct_fields: Vec<OwnsFactId>,
1799 #[serde(serialize_with = "serialize_map_values")]
1800 direct_roles: BTreeMap<RoleId, DeclaredRoleProjection>,
1801 direct_plays: BTreeSet<PlaysFactId>,
1802}
1803
1804impl DeclarationProjection {
1805 #[allow(clippy::too_many_arguments)]
1807 pub fn new(
1808 parent: Option<TypeId>,
1809 value_type: Option<ValueTypeTag>,
1810 is_abstract: bool,
1811 is_constructible: bool,
1812 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1813 direct_fields: Vec<OwnsFactId>,
1814 direct_roles: BTreeMap<RoleId, DeclaredRoleProjection>,
1815 direct_plays: BTreeSet<PlaysFactId>,
1816 ) -> Result<Self, Diagnostic> {
1817 for length in [
1818 annotations.len(),
1819 direct_fields.len(),
1820 direct_roles.len(),
1821 direct_plays.len(),
1822 ] {
1823 ensure_collection_limit(length, "projection_declaration_limit_exceeded")?;
1824 }
1825 Ok(Self {
1826 parent,
1827 direct_sub: None,
1828 value_type,
1829 is_abstract,
1830 is_constructible,
1831 annotations,
1832 value_annotations: BTreeMap::new(),
1833 direct_fields,
1834 direct_roles,
1835 direct_plays,
1836 })
1837 }
1838 pub fn with_direct_sub(
1840 mut self,
1841 direct_sub: Option<DirectSubProjection>,
1842 ) -> Result<Self, Diagnostic> {
1843 if direct_sub
1844 .as_ref()
1845 .is_some_and(|sub| self.parent.as_ref() != Some(sub.id().supertype()))
1846 {
1847 return Err(invalid_projection(
1848 "invalid_projected_sub_parent",
1849 "projected direct subtype edge must match the nominal parent",
1850 ));
1851 }
1852 self.direct_sub = direct_sub;
1853 Ok(self)
1854 }
1855 pub fn with_value_annotations(
1857 mut self,
1858 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1859 ) -> Result<Self, Diagnostic> {
1860 if annotations.iter().any(|(key, value)| {
1861 key != value.id() || !matches!(key.subject(), AnnotationSubjectId::Value(_))
1862 }) {
1863 return Err(invalid_projection(
1864 "invalid_projected_value_annotation",
1865 "attribute value annotations require matching effective value subjects",
1866 ));
1867 }
1868 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1869 self.value_annotations = annotations;
1870 Ok(self)
1871 }
1872 #[must_use]
1874 pub const fn parent(&self) -> Option<&TypeId> {
1875 self.parent.as_ref()
1876 }
1877 #[must_use]
1879 pub const fn direct_sub(&self) -> Option<&DirectSubProjection> {
1880 self.direct_sub.as_ref()
1881 }
1882 #[must_use]
1884 pub const fn value_type(&self) -> Option<ValueTypeTag> {
1885 self.value_type
1886 }
1887 #[must_use]
1889 pub const fn is_abstract(&self) -> bool {
1890 self.is_abstract
1891 }
1892 #[must_use]
1894 pub const fn is_constructible(&self) -> bool {
1895 self.is_constructible
1896 }
1897 #[must_use]
1899 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1900 &self.annotations
1901 }
1902 #[must_use]
1904 pub const fn value_annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1905 &self.value_annotations
1906 }
1907 #[must_use]
1909 pub fn direct_fields(&self) -> &[OwnsFactId] {
1910 &self.direct_fields
1911 }
1912 #[must_use]
1914 pub const fn direct_roles(&self) -> &BTreeMap<RoleId, DeclaredRoleProjection> {
1915 &self.direct_roles
1916 }
1917 #[must_use]
1919 pub const fn direct_plays(&self) -> &BTreeSet<PlaysFactId> {
1920 &self.direct_plays
1921 }
1922}
1923
1924#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1926pub struct CreateFieldProjection {
1927 token: OwnsFactId,
1928 value: ProjectedTypeRef,
1929 multiplicity: ProjectedMultiplicity,
1930}
1931
1932impl CreateFieldProjection {
1933 #[must_use]
1935 pub const fn new(
1936 token: OwnsFactId,
1937 value: ProjectedTypeRef,
1938 multiplicity: ProjectedMultiplicity,
1939 ) -> Self {
1940 Self {
1941 token,
1942 value,
1943 multiplicity,
1944 }
1945 }
1946 #[must_use]
1948 pub const fn token(&self) -> &OwnsFactId {
1949 &self.token
1950 }
1951 #[must_use]
1953 pub const fn value(&self) -> &ProjectedTypeRef {
1954 &self.value
1955 }
1956 #[must_use]
1958 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1959 self.multiplicity
1960 }
1961}
1962
1963#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1965pub struct CreateRoleProjection {
1966 role: RoleId,
1967 players: BTreeSet<ProjectedModelUse>,
1968 multiplicity: ProjectedMultiplicity,
1969}
1970
1971impl CreateRoleProjection {
1972 pub fn new(
1974 role: RoleId,
1975 players: BTreeSet<ProjectedModelUse>,
1976 multiplicity: ProjectedMultiplicity,
1977 ) -> Result<Self, Diagnostic> {
1978 ensure_collection_limit(players.len(), "too_many_projected_role_players")?;
1979 Ok(Self {
1980 role,
1981 players,
1982 multiplicity,
1983 })
1984 }
1985 #[must_use]
1987 pub const fn role(&self) -> &RoleId {
1988 &self.role
1989 }
1990 #[must_use]
1992 pub const fn players(&self) -> &BTreeSet<ProjectedModelUse> {
1993 &self.players
1994 }
1995 #[must_use]
1997 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1998 self.multiplicity
1999 }
2000}
2001
2002#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2004pub struct CreateProjection {
2005 #[serde(skip_serializing_if = "Option::is_none")]
2006 target_name: Option<TargetIdentifier>,
2007 enabled: bool,
2008 fields: Vec<CreateFieldProjection>,
2009 #[serde(serialize_with = "serialize_map_values")]
2010 roles: BTreeMap<RoleId, CreateRoleProjection>,
2011}
2012
2013impl CreateProjection {
2014 pub fn new(
2016 enabled: bool,
2017 fields: Vec<CreateFieldProjection>,
2018 roles: BTreeMap<RoleId, CreateRoleProjection>,
2019 ) -> Result<Self, Diagnostic> {
2020 ensure_collection_limit(fields.len(), "too_many_projected_create_fields")?;
2021 ensure_collection_limit(roles.len(), "too_many_projected_create_roles")?;
2022 Ok(Self {
2023 target_name: None,
2024 enabled,
2025 fields,
2026 roles,
2027 })
2028 }
2029 #[must_use]
2031 pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
2032 self.target_name = Some(target_name);
2033 self
2034 }
2035 #[must_use]
2037 pub const fn target_name(&self) -> Option<&TargetIdentifier> {
2038 self.target_name.as_ref()
2039 }
2040 #[must_use]
2042 pub const fn enabled(&self) -> bool {
2043 self.enabled
2044 }
2045 #[must_use]
2047 pub fn fields(&self) -> &[CreateFieldProjection] {
2048 &self.fields
2049 }
2050 #[must_use]
2052 pub const fn roles(&self) -> &BTreeMap<RoleId, CreateRoleProjection> {
2053 &self.roles
2054 }
2055}
2056
2057#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2059pub struct ReadFieldProjection {
2060 token: OwnsFactId,
2061 value: ProjectedTypeRef,
2062 multiplicity: ProjectedMultiplicity,
2063}
2064
2065impl ReadFieldProjection {
2066 #[must_use]
2068 pub const fn new(
2069 token: OwnsFactId,
2070 value: ProjectedTypeRef,
2071 multiplicity: ProjectedMultiplicity,
2072 ) -> Self {
2073 Self {
2074 token,
2075 value,
2076 multiplicity,
2077 }
2078 }
2079 #[must_use]
2081 pub const fn token(&self) -> &OwnsFactId {
2082 &self.token
2083 }
2084 #[must_use]
2086 pub const fn value(&self) -> &ProjectedTypeRef {
2087 &self.value
2088 }
2089 #[must_use]
2091 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
2092 self.multiplicity
2093 }
2094}
2095
2096#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2098pub struct ReadRoleProjection {
2099 role: RoleId,
2100 players: BTreeSet<ProjectedModelUse>,
2101 multiplicity: ProjectedMultiplicity,
2102}
2103
2104impl ReadRoleProjection {
2105 pub fn new(
2107 role: RoleId,
2108 players: BTreeSet<ProjectedModelUse>,
2109 multiplicity: ProjectedMultiplicity,
2110 ) -> Result<Self, Diagnostic> {
2111 ensure_collection_limit(players.len(), "too_many_projected_role_players")?;
2112 Ok(Self {
2113 role,
2114 players,
2115 multiplicity,
2116 })
2117 }
2118 #[must_use]
2120 pub const fn role(&self) -> &RoleId {
2121 &self.role
2122 }
2123 #[must_use]
2125 pub const fn players(&self) -> &BTreeSet<ProjectedModelUse> {
2126 &self.players
2127 }
2128 #[must_use]
2130 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
2131 self.multiplicity
2132 }
2133}
2134
2135#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2137pub struct CompleteReadProjection {
2138 fields: Vec<ReadFieldProjection>,
2139 #[serde(serialize_with = "serialize_map_values")]
2140 roles: BTreeMap<RoleId, ReadRoleProjection>,
2141 nominal_upcasts: Vec<TypeId>,
2142 #[serde(serialize_with = "serialize_role_upcasts")]
2143 role_upcasts: BTreeMap<RoleId, Vec<RoleId>>,
2144}
2145
2146impl CompleteReadProjection {
2147 pub fn new(
2149 fields: Vec<ReadFieldProjection>,
2150 roles: BTreeMap<RoleId, ReadRoleProjection>,
2151 nominal_upcasts: Vec<TypeId>,
2152 ) -> Result<Self, Diagnostic> {
2153 for length in [fields.len(), roles.len(), nominal_upcasts.len()] {
2154 ensure_collection_limit(length, "projection_read_limit_exceeded")?;
2155 }
2156 Ok(Self {
2157 fields,
2158 roles,
2159 nominal_upcasts,
2160 role_upcasts: BTreeMap::new(),
2161 })
2162 }
2163 pub fn with_role_upcasts(
2165 mut self,
2166 role_upcasts: BTreeMap<RoleId, Vec<RoleId>>,
2167 ) -> Result<Self, Diagnostic> {
2168 if role_upcasts.iter().any(|(role, ancestors)| {
2169 !self.roles.contains_key(role)
2170 || ancestors.is_empty()
2171 || ancestors.iter().collect::<BTreeSet<_>>().len() != ancestors.len()
2172 }) {
2173 return Err(invalid_projection(
2174 "invalid_projected_role_upcast",
2175 "role upcasts require an active role and unique non-empty ancestor roles",
2176 ));
2177 }
2178 ensure_collection_limit(role_upcasts.len(), "projection_read_limit_exceeded")?;
2179 self.role_upcasts = role_upcasts;
2180 Ok(self)
2181 }
2182 #[must_use]
2184 pub fn fields(&self) -> &[ReadFieldProjection] {
2185 &self.fields
2186 }
2187 #[must_use]
2189 pub const fn roles(&self) -> &BTreeMap<RoleId, ReadRoleProjection> {
2190 &self.roles
2191 }
2192 #[must_use]
2194 pub fn nominal_upcasts(&self) -> &[TypeId] {
2195 &self.nominal_upcasts
2196 }
2197 #[must_use]
2199 pub const fn role_upcasts(&self) -> &BTreeMap<RoleId, Vec<RoleId>> {
2200 &self.role_upcasts
2201 }
2202}
2203
2204#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
2206#[serde(rename_all = "snake_case")]
2207pub enum ReferenceConstructionPolicy {
2208 IidOnly,
2210 KeyFallback,
2212}
2213
2214#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2216pub struct ReferenceReadProjection {
2217 target_name: Option<TargetIdentifier>,
2218 key_fields: Vec<OwnsFactId>,
2219 construction_policy: ReferenceConstructionPolicy,
2220}
2221
2222impl ReferenceReadProjection {
2223 pub fn new(
2225 target_name: Option<TargetIdentifier>,
2226 key_fields: Vec<OwnsFactId>,
2227 ) -> Result<Self, Diagnostic> {
2228 ensure_collection_limit(key_fields.len(), "too_many_projected_reference_keys")?;
2229 let mut unique = BTreeSet::new();
2230 if key_fields.iter().any(|id| !unique.insert(id)) {
2231 return Err(invalid_projection(
2232 "duplicate_projected_reference_key",
2233 "reference key identities must be unique",
2234 ));
2235 }
2236 let construction_policy = if key_fields.is_empty() {
2237 ReferenceConstructionPolicy::IidOnly
2238 } else {
2239 ReferenceConstructionPolicy::KeyFallback
2240 };
2241 Ok(Self {
2242 target_name,
2243 key_fields,
2244 construction_policy,
2245 })
2246 }
2247 #[must_use]
2249 pub const fn target_name(&self) -> Option<&TargetIdentifier> {
2250 self.target_name.as_ref()
2251 }
2252 #[must_use]
2254 pub fn key_fields(&self) -> &[OwnsFactId] {
2255 &self.key_fields
2256 }
2257 #[must_use]
2259 pub const fn construction_policy(&self) -> ReferenceConstructionPolicy {
2260 self.construction_policy
2261 }
2262}
2263
2264#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2266pub struct QueryTokenProjection {
2267 type_id: TypeId,
2268 #[serde(skip_serializing_if = "Option::is_none")]
2269 target_name: Option<TargetIdentifier>,
2270 #[serde(serialize_with = "serialize_map_values")]
2271 fields: BTreeMap<OwnsFactId, FieldTokenProjection>,
2272 #[serde(serialize_with = "serialize_map_values")]
2273 roles: BTreeMap<RoleId, RoleTokenProjection>,
2274}
2275
2276impl QueryTokenProjection {
2277 pub fn new(
2279 type_id: TypeId,
2280 fields: BTreeMap<OwnsFactId, FieldTokenProjection>,
2281 roles: BTreeMap<RoleId, RoleTokenProjection>,
2282 ) -> Result<Self, Diagnostic> {
2283 ensure_collection_limit(fields.len(), "too_many_projected_field_tokens")?;
2284 ensure_collection_limit(roles.len(), "too_many_projected_role_tokens")?;
2285 Ok(Self {
2286 type_id,
2287 target_name: None,
2288 fields,
2289 roles,
2290 })
2291 }
2292 #[must_use]
2294 pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
2295 self.target_name = Some(target_name);
2296 self
2297 }
2298 #[must_use]
2300 pub const fn type_id(&self) -> &TypeId {
2301 &self.type_id
2302 }
2303 #[must_use]
2305 pub const fn target_name(&self) -> Option<&TargetIdentifier> {
2306 self.target_name.as_ref()
2307 }
2308 #[must_use]
2310 pub const fn fields(&self) -> &BTreeMap<OwnsFactId, FieldTokenProjection> {
2311 &self.fields
2312 }
2313 #[must_use]
2315 pub const fn roles(&self) -> &BTreeMap<RoleId, RoleTokenProjection> {
2316 &self.roles
2317 }
2318}
2319
2320#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2322pub struct ModelProjection {
2323 id: TypeId,
2324 target_name: TargetIdentifier,
2325 declaration: DeclarationProjection,
2326 create: CreateProjection,
2327 complete_read: CompleteReadProjection,
2328 reference_read: ReferenceReadProjection,
2329 query_tokens: QueryTokenProjection,
2330}
2331
2332impl ModelProjection {
2333 #[allow(clippy::too_many_arguments)]
2335 pub fn new(
2336 id: TypeId,
2337 target_name: TargetIdentifier,
2338 declaration: DeclarationProjection,
2339 create: CreateProjection,
2340 complete_read: CompleteReadProjection,
2341 reference_read: ReferenceReadProjection,
2342 query_tokens: QueryTokenProjection,
2343 ) -> Result<Self, Diagnostic> {
2344 let exact_required_scalar = |multiplicity: ProjectedMultiplicity| {
2345 multiplicity.required()
2346 && multiplicity.container() == ProjectedContainer::Scalar
2347 && multiplicity.cardinality().min() == 1
2348 && multiplicity.cardinality().max() == Some(1)
2349 };
2350 let reference_keys_valid = reference_read.key_fields().iter().all(|key| {
2351 let Some(token) = query_tokens.fields().get(key) else {
2352 return false;
2353 };
2354 if !token.is_key() || !exact_required_scalar(token.multiplicity()) {
2355 return false;
2356 }
2357 let mut complete = complete_read
2358 .fields()
2359 .iter()
2360 .filter(|field| field.token() == key);
2361 let Some(field) = complete.next() else {
2362 return false;
2363 };
2364 if complete.next().is_some() || !exact_required_scalar(field.multiplicity()) {
2365 return false;
2366 }
2367 matches!(
2368 field.value(),
2369 ProjectedTypeRef::Model(value)
2370 if value.form() == ProjectedModelForm::Complete
2371 && value.id().kind() == TypeKind::Attribute
2372 && value.id().label() == key.attribute().label()
2373 )
2374 });
2375 if (!reference_read.key_fields().is_empty() && reference_read.target_name().is_none())
2376 || !reference_keys_valid
2377 {
2378 return Err(invalid_projection(
2379 "invalid_projected_reference_key",
2380 "reference keys require exact required-scalar complete/query key facets",
2381 ));
2382 }
2383 let field_multiplicities_match = create.fields().iter().all(|field| {
2384 query_tokens
2385 .fields()
2386 .get(field.token())
2387 .is_some_and(|token| token.multiplicity() == field.multiplicity())
2388 }) && complete_read.fields().iter().all(|field| {
2389 query_tokens
2390 .fields()
2391 .get(field.token())
2392 .is_some_and(|token| token.multiplicity() == field.multiplicity())
2393 });
2394 let role_multiplicities_match = create.roles().iter().all(|(id, role)| {
2395 query_tokens
2396 .roles()
2397 .get(id)
2398 .is_some_and(|token| token.multiplicity() == role.multiplicity())
2399 }) && complete_read.roles().iter().all(|(id, role)| {
2400 query_tokens
2401 .roles()
2402 .get(id)
2403 .is_some_and(|token| token.multiplicity() == role.multiplicity())
2404 });
2405 if !field_multiplicities_match || !role_multiplicities_match {
2406 return Err(invalid_projection(
2407 "projected_multiplicity_mismatch",
2408 "create, complete-read, and query-token facets require exact multiplicity equality",
2409 ));
2410 }
2411 if query_tokens.type_id() != &id
2412 || match (declaration.parent(), declaration.direct_sub()) {
2413 (None, None) => false,
2414 (Some(parent), Some(sub)) => {
2415 sub.id().subtype() != &id || sub.id().supertype() != parent
2416 }
2417 (None, Some(_)) | (Some(_), None) => true,
2418 }
2419 || query_tokens
2420 .fields()
2421 .values()
2422 .any(|field| field.id().owner() != &id)
2423 || query_tokens
2424 .roles()
2425 .values()
2426 .any(|role| role.owner() != &id)
2427 || create
2428 .fields()
2429 .iter()
2430 .any(|field| !query_tokens.fields().contains_key(field.token()))
2431 || complete_read
2432 .fields()
2433 .iter()
2434 .any(|field| !query_tokens.fields().contains_key(field.token()))
2435 || create
2436 .roles()
2437 .iter()
2438 .any(|(id, role)| id != role.role() || !query_tokens.roles().contains_key(id))
2439 || complete_read
2440 .roles()
2441 .iter()
2442 .any(|(id, role)| id != role.role() || !query_tokens.roles().contains_key(id))
2443 {
2444 return Err(invalid_projection(
2445 "invalid_model_projection_reference",
2446 "model facets contain a mismatched owner or token reference",
2447 ));
2448 }
2449 Ok(Self {
2450 id,
2451 target_name,
2452 declaration,
2453 create,
2454 complete_read,
2455 reference_read,
2456 query_tokens,
2457 })
2458 }
2459 #[must_use]
2461 pub const fn id(&self) -> &TypeId {
2462 &self.id
2463 }
2464 #[must_use]
2466 pub const fn target_name(&self) -> &TargetIdentifier {
2467 &self.target_name
2468 }
2469 #[must_use]
2471 pub const fn declaration(&self) -> &DeclarationProjection {
2472 &self.declaration
2473 }
2474 #[must_use]
2476 pub const fn create(&self) -> &CreateProjection {
2477 &self.create
2478 }
2479 #[must_use]
2481 pub const fn complete_read(&self) -> &CompleteReadProjection {
2482 &self.complete_read
2483 }
2484 #[must_use]
2486 pub const fn reference_read(&self) -> &ReferenceReadProjection {
2487 &self.reference_read
2488 }
2489 #[must_use]
2491 pub const fn query_tokens(&self) -> &QueryTokenProjection {
2492 &self.query_tokens
2493 }
2494}
2495
2496#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2498pub struct StructFieldProjection {
2499 name: Label,
2500 target_name: TargetIdentifier,
2501 value_type: ValueTypeTag,
2502 optional: bool,
2503}
2504
2505impl StructFieldProjection {
2506 #[must_use]
2508 pub const fn new(
2509 name: Label,
2510 target_name: TargetIdentifier,
2511 value_type: ValueTypeTag,
2512 optional: bool,
2513 ) -> Self {
2514 Self {
2515 name,
2516 target_name,
2517 value_type,
2518 optional,
2519 }
2520 }
2521 #[must_use]
2523 pub const fn name(&self) -> &Label {
2524 &self.name
2525 }
2526 #[must_use]
2528 pub const fn target_name(&self) -> &TargetIdentifier {
2529 &self.target_name
2530 }
2531 #[must_use]
2533 pub const fn value_type(&self) -> ValueTypeTag {
2534 self.value_type
2535 }
2536 #[must_use]
2538 pub const fn optional(&self) -> bool {
2539 self.optional
2540 }
2541}
2542
2543#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2545pub struct StructProjection {
2546 id: StructId,
2547 target_name: TargetIdentifier,
2548 fields: Vec<StructFieldProjection>,
2549}
2550
2551impl StructProjection {
2552 pub fn new(
2554 id: StructId,
2555 target_name: TargetIdentifier,
2556 fields: Vec<StructFieldProjection>,
2557 ) -> Result<Self, Diagnostic> {
2558 ensure_collection_limit(fields.len(), "too_many_projected_struct_fields")?;
2559 Ok(Self {
2560 id,
2561 target_name,
2562 fields,
2563 })
2564 }
2565 #[must_use]
2567 pub const fn id(&self) -> &StructId {
2568 &self.id
2569 }
2570 #[must_use]
2572 pub const fn target_name(&self) -> &TargetIdentifier {
2573 &self.target_name
2574 }
2575 #[must_use]
2577 pub fn fields(&self) -> &[StructFieldProjection] {
2578 &self.fields
2579 }
2580}
2581
2582#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2584pub struct FunctionParameterProjection {
2585 name: Label,
2586 target_name: TargetIdentifier,
2587 type_ref: ProjectedTypeRef,
2588}
2589
2590impl FunctionParameterProjection {
2591 #[must_use]
2593 pub const fn new(
2594 name: Label,
2595 target_name: TargetIdentifier,
2596 type_ref: ProjectedTypeRef,
2597 ) -> Self {
2598 Self {
2599 name,
2600 target_name,
2601 type_ref,
2602 }
2603 }
2604 #[must_use]
2606 pub const fn name(&self) -> &Label {
2607 &self.name
2608 }
2609 #[must_use]
2611 pub const fn target_name(&self) -> &TargetIdentifier {
2612 &self.target_name
2613 }
2614 #[must_use]
2616 pub const fn type_ref(&self) -> &ProjectedTypeRef {
2617 &self.type_ref
2618 }
2619}
2620
2621#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2623pub struct FunctionReturnElementProjection {
2624 type_ref: ProjectedTypeRef,
2625 optional: bool,
2626}
2627
2628impl FunctionReturnElementProjection {
2629 #[must_use]
2631 pub const fn new(type_ref: ProjectedTypeRef, optional: bool) -> Self {
2632 Self { type_ref, optional }
2633 }
2634 #[must_use]
2636 pub const fn type_ref(&self) -> &ProjectedTypeRef {
2637 &self.type_ref
2638 }
2639 #[must_use]
2641 pub const fn optional(&self) -> bool {
2642 self.optional
2643 }
2644}
2645
2646#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2648#[serde(tag = "kind", content = "elements", rename_all = "snake_case")]
2649pub enum FunctionReturnProjection {
2650 Scalar(FunctionReturnElementProjection),
2652 Tuple(Vec<FunctionReturnElementProjection>),
2654 Stream(Vec<FunctionReturnElementProjection>),
2656}
2657
2658#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2660pub struct FunctionProjection {
2661 id: FunctionId,
2662 target_name: TargetIdentifier,
2663 parameters: Vec<FunctionParameterProjection>,
2664 returns: FunctionReturnProjection,
2665 #[serde(serialize_with = "serialize_map_values")]
2666 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2667}
2668
2669impl FunctionProjection {
2670 pub fn new(
2672 id: FunctionId,
2673 target_name: TargetIdentifier,
2674 parameters: Vec<FunctionParameterProjection>,
2675 returns: FunctionReturnProjection,
2676 ) -> Result<Self, Diagnostic> {
2677 ensure_collection_limit(parameters.len(), "too_many_projected_function_parameters")?;
2678 Ok(Self {
2679 id,
2680 target_name,
2681 parameters,
2682 returns,
2683 annotations: BTreeMap::new(),
2684 })
2685 }
2686 pub fn with_annotations(
2688 mut self,
2689 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2690 ) -> Result<Self, Diagnostic> {
2691 if annotations.iter().any(|(key, value)| {
2692 key != value.id() || key.subject() != &AnnotationSubjectId::Function(self.id.clone())
2693 }) {
2694 return Err(invalid_projection(
2695 "invalid_projected_function_annotation",
2696 "function annotations require the projected function subject",
2697 ));
2698 }
2699 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
2700 self.annotations = annotations;
2701 Ok(self)
2702 }
2703 #[must_use]
2705 pub const fn id(&self) -> &FunctionId {
2706 &self.id
2707 }
2708 #[must_use]
2710 pub const fn target_name(&self) -> &TargetIdentifier {
2711 &self.target_name
2712 }
2713 #[must_use]
2715 pub fn parameters(&self) -> &[FunctionParameterProjection] {
2716 &self.parameters
2717 }
2718 #[must_use]
2720 pub const fn returns(&self) -> &FunctionReturnProjection {
2721 &self.returns
2722 }
2723 #[must_use]
2725 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
2726 &self.annotations
2727 }
2728}
2729
2730#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2732pub struct PlayingProjection {
2733 id: PlaysFactId,
2734 role: RoleId,
2735 target_name: Option<TargetIdentifier>,
2736 multiplicity: ProjectedMultiplicity,
2737 #[serde(serialize_with = "serialize_map_values")]
2738 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2739}
2740
2741impl PlayingProjection {
2742 pub fn new(
2744 id: PlaysFactId,
2745 role: RoleId,
2746 multiplicity: ProjectedMultiplicity,
2747 annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2748 ) -> Result<Self, Diagnostic> {
2749 if !multiplicity.collection_mode().is_unordered() {
2750 return Err(invalid_projection(
2751 "ordered_playing_projection",
2752 "playing multiplicity must remain unordered",
2753 ));
2754 }
2755 if id.role() != &role
2756 || annotations.iter().any(|(key, value)| {
2757 key != value.id() || key.subject() != &AnnotationSubjectId::Plays(id.clone())
2758 })
2759 {
2760 return Err(invalid_projection(
2761 "invalid_playing_projection_reference",
2762 "playing metadata has a mismatched role or annotation subject",
2763 ));
2764 }
2765 ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
2766 Ok(Self {
2767 id,
2768 role,
2769 target_name: None,
2770 multiplicity,
2771 annotations,
2772 })
2773 }
2774 #[must_use]
2776 pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
2777 self.target_name = Some(target_name);
2778 self
2779 }
2780 #[must_use]
2782 pub const fn id(&self) -> &PlaysFactId {
2783 &self.id
2784 }
2785 #[must_use]
2787 pub const fn role(&self) -> &RoleId {
2788 &self.role
2789 }
2790 #[must_use]
2792 pub const fn target_name(&self) -> Option<&TargetIdentifier> {
2793 self.target_name.as_ref()
2794 }
2795 #[must_use]
2797 pub const fn multiplicity(&self) -> ProjectedMultiplicity {
2798 self.multiplicity
2799 }
2800 #[must_use]
2802 pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
2803 &self.annotations
2804 }
2805}
2806
2807#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2809pub struct EmissionPlan {
2810 model_shells: Vec<TypeId>,
2811 model_link_components: Vec<BTreeSet<TypeId>>,
2812 structs: Vec<StructId>,
2813 functions: Vec<FunctionId>,
2814}
2815
2816impl EmissionPlan {
2817 pub fn new(
2819 model_shells: Vec<TypeId>,
2820 model_link_components: Vec<BTreeSet<TypeId>>,
2821 structs: Vec<StructId>,
2822 functions: Vec<FunctionId>,
2823 ) -> Result<Self, Diagnostic> {
2824 for length in [
2825 model_shells.len(),
2826 model_link_components.len(),
2827 structs.len(),
2828 functions.len(),
2829 ] {
2830 ensure_collection_limit(length, "projection_emission_limit_exceeded")?;
2831 }
2832 Ok(Self {
2833 model_shells,
2834 model_link_components,
2835 structs,
2836 functions,
2837 })
2838 }
2839 #[must_use]
2841 pub fn model_shells(&self) -> &[TypeId] {
2842 &self.model_shells
2843 }
2844 #[must_use]
2846 pub fn model_link_components(&self) -> &[BTreeSet<TypeId>] {
2847 &self.model_link_components
2848 }
2849 #[must_use]
2851 pub fn structs(&self) -> &[StructId] {
2852 &self.structs
2853 }
2854 #[must_use]
2856 pub fn functions(&self) -> &[FunctionId] {
2857 &self.functions
2858 }
2859}
2860
2861#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2863pub struct RuntimeProjection {
2864 target: BindingTarget,
2865 config: ProjectionConfig,
2866 semantic_fingerprint: SemanticSchemaFingerprint,
2867 projection_fingerprint: BindingProjectionFingerprint,
2868 generator_handlers: Vec<ProjectionHandler>,
2869 code_resources: Vec<CodeResourceDigest>,
2870 #[serde(serialize_with = "serialize_map_values")]
2871 models: BTreeMap<TypeId, ModelProjection>,
2872 #[serde(serialize_with = "serialize_map_values")]
2873 structs: BTreeMap<StructId, StructProjection>,
2874 #[serde(serialize_with = "serialize_map_values")]
2875 functions: BTreeMap<FunctionId, FunctionProjection>,
2876 #[serde(serialize_with = "serialize_map_values")]
2877 playing_facts: BTreeMap<PlaysFactId, PlayingProjection>,
2878 emission: EmissionPlan,
2879}
2880
2881#[derive(Serialize)]
2882struct RuntimeProjectionContentView<'a> {
2883 #[serde(serialize_with = "serialize_map_values")]
2884 models: &'a BTreeMap<TypeId, ModelProjection>,
2885 #[serde(serialize_with = "serialize_map_values")]
2886 structs: &'a BTreeMap<StructId, StructProjection>,
2887 #[serde(serialize_with = "serialize_map_values")]
2888 functions: &'a BTreeMap<FunctionId, FunctionProjection>,
2889 #[serde(serialize_with = "serialize_map_values")]
2890 playing_facts: &'a BTreeMap<PlaysFactId, PlayingProjection>,
2891 emission: &'a EmissionPlan,
2892}
2893
2894impl RuntimeProjection {
2895 #[allow(clippy::too_many_arguments)]
2897 pub fn try_new(
2898 target: BindingTarget,
2899 config: ProjectionConfig,
2900 semantic_fingerprint: SemanticSchemaFingerprint,
2901 handlers: &[ProjectionHandler],
2902 resources: &[CodeResourceDigest],
2903 models: BTreeMap<TypeId, ModelProjection>,
2904 structs: BTreeMap<StructId, StructProjection>,
2905 functions: BTreeMap<FunctionId, FunctionProjection>,
2906 playing_facts: BTreeMap<PlaysFactId, PlayingProjection>,
2907 emission: EmissionPlan,
2908 ) -> Result<Self, Diagnostic> {
2909 if config.target() != target
2910 || models.iter().any(|(key, value)| key != value.id())
2911 || structs.iter().any(|(key, value)| key != value.id())
2912 || functions.iter().any(|(key, value)| key != value.id())
2913 || playing_facts.iter().any(|(key, value)| key != value.id())
2914 {
2915 return Err(invalid_projection(
2916 "invalid_runtime_projection_map",
2917 "runtime projection map keys or target configuration are inconsistent",
2918 ));
2919 }
2920 for length in [
2921 models.len(),
2922 structs.len(),
2923 functions.len(),
2924 playing_facts.len(),
2925 ] {
2926 ensure_collection_limit(length, "runtime_projection_limit_exceeded")?;
2927 }
2928 if target == BindingTarget::C {
2929 validate_c_create_limits(&models)?;
2930 }
2931 if matches!(target, BindingTarget::Rust | BindingTarget::C) {
2932 let native_names_complete = models.values().all(|model| {
2933 model.create().enabled() == model.create().target_name().is_some()
2934 && model.query_tokens().target_name().is_some()
2935 && model
2936 .query_tokens()
2937 .roles()
2938 .values()
2939 .all(|role| role.player_union_target_name().is_some())
2940 && matches!(model.id().kind(), TypeKind::Entity | TypeKind::Relation)
2941 == model.reference_read().target_name().is_some()
2942 }) && playing_facts
2943 .values()
2944 .all(|playing| playing.target_name().is_some());
2945 if !native_names_complete {
2946 let (code, message) = match target {
2947 BindingTarget::Rust => (
2948 "missing_rust_projection_identifier",
2949 "Rust projection omits a required create, reference, query-token, player-union, or plays identifier",
2950 ),
2951 BindingTarget::C => (
2952 "missing_c_projection_identifier",
2953 "C projection omits a required create, reference, query-token, player-union, or plays identifier",
2954 ),
2955 BindingTarget::Python | BindingTarget::TypeScript => unreachable!(),
2956 };
2957 return Err(invalid_projection(code, message));
2958 }
2959 }
2960 let model_ids = models.keys().cloned().collect::<BTreeSet<_>>();
2961 if emission
2962 .model_shells()
2963 .iter()
2964 .cloned()
2965 .collect::<BTreeSet<_>>()
2966 != model_ids
2967 || emission.model_shells().len() != model_ids.len()
2968 || emission
2969 .model_link_components()
2970 .iter()
2971 .flat_map(BTreeSet::iter)
2972 .cloned()
2973 .collect::<BTreeSet<_>>()
2974 != model_ids
2975 || emission
2976 .model_link_components()
2977 .iter()
2978 .map(BTreeSet::len)
2979 .sum::<usize>()
2980 != model_ids.len()
2981 || emission.structs() != structs.keys().cloned().collect::<Vec<_>>()
2982 || emission.functions() != functions.keys().cloned().collect::<Vec<_>>()
2983 {
2984 return Err(invalid_projection(
2985 "invalid_projection_emission_plan",
2986 "emission plan does not cover each projected value exactly once",
2987 ));
2988 }
2989 let all_model_refs_valid = models.values().all(|model| {
2990 model.query_tokens().roles().values().all(|role| {
2991 role.accepted_players()
2992 .iter()
2993 .all(|id| models.contains_key(id))
2994 }) && model.create().roles().values().all(|role| {
2995 role.players()
2996 .iter()
2997 .all(|value| models.contains_key(value.id()))
2998 }) && model.complete_read().roles().values().all(|role| {
2999 role.players()
3000 .iter()
3001 .all(|value| models.contains_key(value.id()))
3002 })
3003 });
3004 if !all_model_refs_valid {
3005 return Err(invalid_projection(
3006 "invalid_projection_reference",
3007 "projection references a model that is not present",
3008 ));
3009 }
3010 let declaring_owners_valid = models.values().all(|model| {
3011 model.query_tokens().fields().values().all(|token| {
3012 let declaring_owner = token.declaring_id().owner();
3013 let mut curr = Some(model.id());
3014 let mut found = false;
3015 let mut visited = BTreeSet::new();
3016 while let Some(curr_id) = curr {
3017 if !visited.insert(curr_id) {
3018 return false;
3019 }
3020 if curr_id == declaring_owner {
3021 found = true;
3022 break;
3023 }
3024 curr = models.get(curr_id).and_then(|m| m.declaration().parent());
3025 }
3026 found
3027 })
3028 });
3029 if !declaring_owners_valid {
3030 return Err(invalid_projection(
3031 "invalid_projection_reference",
3032 "field token declaring owner is not the effective owner or a valid ancestor",
3033 ));
3034 }
3035 let model_use_is_valid = |value: &ProjectedModelUse| {
3036 models.get(value.id()).is_some_and(|model| {
3037 value.form() != ProjectedModelForm::Reference
3038 || model.reference_read().target_name().is_some()
3039 })
3040 };
3041 let read_model_use_is_valid = |value: &ProjectedModelUse| {
3042 model_use_is_valid(value)
3043 && (value.form() != ProjectedModelForm::Complete
3044 || value.id().kind() != TypeKind::Relation)
3045 };
3046 let type_ref_is_valid = |value: &ProjectedTypeRef| match value {
3047 ProjectedTypeRef::Scalar(_) => true,
3048 ProjectedTypeRef::Model(value) => model_use_is_valid(value),
3049 ProjectedTypeRef::Struct(id) => structs.contains_key(id),
3050 };
3051 let role_exists = |role: &RoleId| {
3052 models.values().any(|model| {
3053 model.id().kind() == TypeKind::Relation
3054 && model.id().label() == role.declaring_relation()
3055 && model.query_tokens().roles().contains_key(role)
3056 })
3057 };
3058 let shell_positions = emission
3059 .model_shells()
3060 .iter()
3061 .enumerate()
3062 .map(|(index, id)| (id.clone(), index))
3063 .collect::<BTreeMap<_, _>>();
3064 let closed_models = models.values().all(|model| {
3065 let declaration = model.declaration();
3066 let parent_is_valid = declaration.parent().is_none_or(|parent| {
3067 models.contains_key(parent) && shell_positions[parent] < shell_positions[model.id()]
3068 });
3069 let direct_sub_is_valid = match (declaration.parent(), declaration.direct_sub()) {
3070 (None, None) => true,
3071 (Some(parent), Some(sub)) => {
3072 sub.id().subtype() == model.id() && sub.id().supertype() == parent
3073 }
3074 (None, Some(_)) | (Some(_), None) => false,
3075 };
3076 let direct_fields_are_valid = declaration
3077 .direct_fields()
3078 .iter()
3079 .all(|id| model.query_tokens().fields().contains_key(id));
3080 let direct_roles_are_valid = declaration.direct_roles().iter().all(|(id, role)| {
3081 id == role.role()
3082 && model.query_tokens().roles().contains_key(id)
3083 && role.specializes().is_none_or(&role_exists)
3084 });
3085 let direct_plays_are_valid = declaration
3086 .direct_plays()
3087 .iter()
3088 .all(|id| id.player() == model.id() && playing_facts.contains_key(id));
3089 let fields_are_valid = model.query_tokens().fields().values().all(|field| {
3090 models.keys().any(|id| {
3091 id.kind() == TypeKind::Attribute && id.label() == field.id().attribute().label()
3092 })
3093 }) && model
3094 .create()
3095 .fields()
3096 .iter()
3097 .all(|field| type_ref_is_valid(field.value()))
3098 && model
3099 .complete_read()
3100 .fields()
3101 .iter()
3102 .all(|field| type_ref_is_valid(field.value()));
3103 let roles_are_valid = model
3104 .query_tokens()
3105 .roles()
3106 .values()
3107 .all(|role| role.specializes().is_none_or(&role_exists))
3108 && model
3109 .create()
3110 .roles()
3111 .values()
3112 .all(|role| role.players().iter().all(&model_use_is_valid))
3113 && model
3114 .complete_read()
3115 .roles()
3116 .values()
3117 .all(|role| role.players().iter().all(&read_model_use_is_valid));
3118 let role_upcasts_are_valid =
3119 model
3120 .complete_read()
3121 .role_upcasts()
3122 .iter()
3123 .all(|(active, ancestors)| {
3124 model.complete_read().roles().contains_key(active)
3125 && ancestors.iter().all(&role_exists)
3126 });
3127 let references_are_valid = model.reference_read().key_fields().iter().all(|id| {
3128 model
3129 .query_tokens()
3130 .fields()
3131 .get(id)
3132 .is_some_and(FieldTokenProjection::is_key)
3133 });
3134 let value_subject = model.id().kind() != TypeKind::Attribute
3135 || model.declaration().value_annotations().keys().all(|id| {
3136 id.subject()
3137 == &AnnotationSubjectId::Value(ValueFactId::new(
3138 AttributeId::new(model.id().label().as_str())
3139 .expect("projected attribute label is valid"),
3140 ))
3141 });
3142 parent_is_valid
3143 && direct_sub_is_valid
3144 && direct_fields_are_valid
3145 && direct_roles_are_valid
3146 && direct_plays_are_valid
3147 && fields_are_valid
3148 && roles_are_valid
3149 && role_upcasts_are_valid
3150 && references_are_valid
3151 && value_subject
3152 });
3153 let closed_playing = playing_facts.values().all(|playing| {
3154 models.contains_key(playing.id().player())
3155 && role_exists(playing.role())
3156 && (!matches!(
3157 target,
3158 BindingTarget::TypeScript | BindingTarget::Rust | BindingTarget::C
3159 ) || playing.target_name().is_some())
3160 });
3161 let closed_functions = functions.values().all(|function| {
3162 function
3163 .parameters()
3164 .iter()
3165 .all(|parameter| type_ref_is_valid(parameter.type_ref()))
3166 && match function.returns() {
3167 FunctionReturnProjection::Scalar(element) => {
3168 type_ref_is_valid(element.type_ref())
3169 }
3170 FunctionReturnProjection::Tuple(elements)
3171 | FunctionReturnProjection::Stream(elements) => elements
3172 .iter()
3173 .all(|element| type_ref_is_valid(element.type_ref())),
3174 }
3175 });
3176 if !closed_models || !closed_playing || !closed_functions {
3177 return Err(invalid_projection(
3178 "invalid_projection_reference",
3179 "projection graph contains an unavailable type, field, role, specialization, reference, or function dependency",
3180 ));
3181 }
3182 let content = to_canonical_json(&RuntimeProjectionContentView {
3183 models: &models,
3184 structs: &structs,
3185 functions: &functions,
3186 playing_facts: &playing_facts,
3187 emission: &emission,
3188 })?;
3189 let projection_fingerprint = BindingProjectionFingerprint::compute_with_projection(
3190 target,
3191 &semantic_fingerprint,
3192 &config,
3193 handlers,
3194 resources,
3195 &content,
3196 )?;
3197 let mut generator_handlers = handlers.to_vec();
3198 generator_handlers.sort_by(|left, right| left.id().cmp(right.id()));
3199 let mut code_resources = resources.to_vec();
3200 code_resources.sort_by(|left, right| left.id().cmp(right.id()));
3201 Ok(Self {
3202 target,
3203 config,
3204 semantic_fingerprint,
3205 projection_fingerprint,
3206 generator_handlers,
3207 code_resources,
3208 models,
3209 structs,
3210 functions,
3211 playing_facts,
3212 emission,
3213 })
3214 }
3215 #[must_use]
3217 pub const fn target(&self) -> BindingTarget {
3218 self.target
3219 }
3220 #[must_use]
3222 pub const fn config(&self) -> &ProjectionConfig {
3223 &self.config
3224 }
3225 #[must_use]
3227 pub const fn semantic_fingerprint(&self) -> &SemanticSchemaFingerprint {
3228 &self.semantic_fingerprint
3229 }
3230 #[must_use]
3232 pub const fn projection_fingerprint(&self) -> &BindingProjectionFingerprint {
3233 &self.projection_fingerprint
3234 }
3235 #[must_use]
3242 pub fn projected_token_identity(
3243 &self,
3244 kind: ProjectedTokenKind,
3245 ordinal: u32,
3246 ) -> Option<ProjectedTokenIdentity> {
3247 let index = usize::try_from(ordinal).ok()?;
3248 match kind {
3249 ProjectedTokenKind::Model => self
3250 .models
3251 .keys()
3252 .nth(index)
3253 .cloned()
3254 .map(ProjectedTokenIdentity::Model),
3255 ProjectedTokenKind::Field => self
3256 .models
3257 .iter()
3258 .flat_map(|(owner, model)| {
3259 model.query_tokens().fields().keys().map(move |field| {
3260 ProjectedTokenIdentity::Field {
3261 owner: owner.clone(),
3262 field: field.clone(),
3263 }
3264 })
3265 })
3266 .nth(index),
3267 ProjectedTokenKind::Role => self
3268 .models
3269 .iter()
3270 .flat_map(|(owner, model)| {
3271 model.query_tokens().roles().keys().map(move |role| {
3272 ProjectedTokenIdentity::Role {
3273 owner: owner.clone(),
3274 role: role.clone(),
3275 }
3276 })
3277 })
3278 .nth(index),
3279 ProjectedTokenKind::Function => self
3280 .functions
3281 .keys()
3282 .nth(index)
3283 .cloned()
3284 .map(ProjectedTokenIdentity::Function),
3285 ProjectedTokenKind::Struct => self
3286 .structs
3287 .keys()
3288 .nth(index)
3289 .cloned()
3290 .map(ProjectedTokenIdentity::Struct),
3291 ProjectedTokenKind::Attribute => self
3292 .models
3293 .keys()
3294 .filter(|model| model.kind() == TypeKind::Attribute)
3295 .nth(index)
3296 .and_then(|model| AttributeId::new(model.label().as_str()).ok())
3297 .map(ProjectedTokenIdentity::Attribute),
3298 }
3299 }
3300
3301 #[must_use]
3306 pub fn projected_token_ordinal(&self, identity: &ProjectedTokenIdentity) -> Option<u32> {
3307 let index = match identity {
3308 ProjectedTokenIdentity::Model(expected) => self
3309 .models
3310 .keys()
3311 .position(|candidate| candidate == expected),
3312 ProjectedTokenIdentity::Field {
3313 owner: expected_owner,
3314 field: expected_field,
3315 } => self
3316 .models
3317 .iter()
3318 .flat_map(|(owner, model)| {
3319 model
3320 .query_tokens()
3321 .fields()
3322 .keys()
3323 .map(move |field| (owner, field))
3324 })
3325 .position(|(owner, field)| owner == expected_owner && field == expected_field),
3326 ProjectedTokenIdentity::Role {
3327 owner: expected_owner,
3328 role: expected_role,
3329 } => self
3330 .models
3331 .iter()
3332 .flat_map(|(owner, model)| {
3333 model
3334 .query_tokens()
3335 .roles()
3336 .keys()
3337 .map(move |role| (owner, role))
3338 })
3339 .position(|(owner, role)| owner == expected_owner && role == expected_role),
3340 ProjectedTokenIdentity::Function(expected) => self
3341 .functions
3342 .keys()
3343 .position(|candidate| candidate == expected),
3344 ProjectedTokenIdentity::Struct(expected) => self
3345 .structs
3346 .keys()
3347 .position(|candidate| candidate == expected),
3348 ProjectedTokenIdentity::Attribute(expected) => self
3349 .models
3350 .keys()
3351 .filter(|model| model.kind() == TypeKind::Attribute)
3352 .position(|candidate| candidate.label() == expected.label()),
3353 }?;
3354 u32::try_from(index).ok()
3355 }
3356 #[must_use]
3358 pub fn generator_handlers(&self) -> &[ProjectionHandler] {
3359 &self.generator_handlers
3360 }
3361 #[must_use]
3363 pub fn code_resources(&self) -> &[CodeResourceDigest] {
3364 &self.code_resources
3365 }
3366 #[must_use]
3368 pub const fn models(&self) -> &BTreeMap<TypeId, ModelProjection> {
3369 &self.models
3370 }
3371 #[must_use]
3373 pub const fn structs(&self) -> &BTreeMap<StructId, StructProjection> {
3374 &self.structs
3375 }
3376 #[must_use]
3378 pub const fn functions(&self) -> &BTreeMap<FunctionId, FunctionProjection> {
3379 &self.functions
3380 }
3381 #[must_use]
3383 pub const fn playing_facts(&self) -> &BTreeMap<PlaysFactId, PlayingProjection> {
3384 &self.playing_facts
3385 }
3386 #[must_use]
3388 pub const fn emission(&self) -> &EmissionPlan {
3389 &self.emission
3390 }
3391}
3392
3393impl CodeResourceDigest {
3394 pub(crate) fn from_wire(
3396 id: impl Into<String>,
3397 content_fingerprint: Fingerprint,
3398 ) -> Result<Self, Diagnostic> {
3399 if content_fingerprint.domain().as_str() != CODE_RESOURCE_DOMAIN
3400 || content_fingerprint.canonicalization().as_str() != RAW_BYTES_CANONICALIZATION
3401 || content_fingerprint.semantic_profile().is_some()
3402 {
3403 return Err(Diagnostic::stable(
3404 DiagnosticCategory::Integrity,
3405 "invalid_code_resource_fingerprint",
3406 "code resource fingerprint wire metadata is inconsistent",
3407 ));
3408 }
3409 Ok(Self {
3410 id: CodeResourceId::new(id)?,
3411 content_fingerprint,
3412 })
3413 }
3414}
3415#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3417pub struct GeneratedManagerLookup<'a> {
3418 field_name: &'a str,
3419 lookup: &'a str,
3420}
3421
3422impl<'a> GeneratedManagerLookup<'a> {
3423 #[must_use]
3425 pub const fn field_name(self) -> &'a str {
3426 self.field_name
3427 }
3428
3429 #[must_use]
3431 pub const fn lookup(self) -> &'a str {
3432 self.lookup
3433 }
3434}
3435
3436#[must_use]
3442pub fn resolve_generated_manager_lookup<'a>(
3443 key: &'a str,
3444 has_field: impl Fn(&str) -> bool,
3445) -> GeneratedManagerLookup<'a> {
3446 let parsed = key.rsplit_once("__");
3447 match parsed {
3448 Some((field_name, lookup))
3449 if matches!(
3450 lookup,
3451 "eq" | "exact"
3452 | "ne"
3453 | "gt"
3454 | "gte"
3455 | "lt"
3456 | "lte"
3457 | "contains"
3458 | "startswith"
3459 | "endswith"
3460 | "regex"
3461 | "like"
3462 | "in"
3463 | "isnull"
3464 ) && has_field(field_name) =>
3465 {
3466 GeneratedManagerLookup { field_name, lookup }
3467 }
3468 _ if has_field(key) => GeneratedManagerLookup {
3469 field_name: key,
3470 lookup: "eq",
3471 },
3472 Some((field_name, lookup)) => GeneratedManagerLookup { field_name, lookup },
3473 None => GeneratedManagerLookup {
3474 field_name: key,
3475 lookup: "eq",
3476 },
3477 }
3478}