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