1use std::collections::BTreeSet;
4
5use crate::{
6 ConstraintSourceKey, Decimal, DeclaredEntityVersion, EntitySourceKey, FieldSourceKey,
7 IndexSourceKey, MAX_FRAGMENT_CONSTRAINTS, MAX_FRAGMENT_ENTITIES, MAX_FRAGMENT_FIELDS,
8 MAX_FRAGMENT_INDEXES, MAX_FRAGMENT_RELATIONS, MAX_FRAGMENT_TYPES, MAX_RELATION_PATH_STEPS,
9 MAX_SCHEMA_FIELD_TYPE_DEPTH, RelationSourceKey, RuleSourceKey, ScalarKind, ScalarLiteral,
10 SchemaContractError, SchemaName, SourceCheckExpr, SourceRuleOperation, TypeSourceKey,
11};
12
13#[derive(Clone, Debug, Eq, PartialEq)]
15pub enum FieldType {
16 Scalar(ScalarType),
18 List(Box<Self>),
20 Named(TypeSourceKey),
22}
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum ScalarType {
27 Account,
29 Blob {
31 max_len: Option<u32>,
33 },
34 Bool,
36 Date,
38 Decimal {
40 scale: u32,
42 },
43 Duration,
45 Float32,
47 Float64,
49 Int8,
51 Int16,
53 Int32,
55 Int64,
57 Int128,
59 Principal,
61 Subaccount,
63 Text {
65 max_len: Option<u32>,
67 },
68 Timestamp,
70 Nat8,
72 Nat16,
74 Nat32,
76 Nat64,
78 Nat128,
80 U256,
82 Ulid,
84 Unit,
86}
87
88impl ScalarType {
89 #[must_use]
91 pub const fn kind(self) -> ScalarKind {
92 match self {
93 Self::Account => ScalarKind::Account,
94 Self::Blob { .. } => ScalarKind::Blob,
95 Self::Bool => ScalarKind::Bool,
96 Self::Date => ScalarKind::Date,
97 Self::Decimal { .. } => ScalarKind::Decimal,
98 Self::Duration => ScalarKind::Duration,
99 Self::Float32 => ScalarKind::Float32,
100 Self::Float64 => ScalarKind::Float64,
101 Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 => ScalarKind::Int,
102 Self::Int128 => ScalarKind::Int128,
103 Self::Principal => ScalarKind::Principal,
104 Self::Subaccount => ScalarKind::Subaccount,
105 Self::Text { .. } => ScalarKind::Text,
106 Self::Timestamp => ScalarKind::Timestamp,
107 Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 => ScalarKind::Nat,
108 Self::Nat128 => ScalarKind::Nat128,
109 Self::U256 => ScalarKind::U256,
110 Self::Ulid => ScalarKind::Ulid,
111 Self::Unit => ScalarKind::Unit,
112 }
113 }
114
115 pub(crate) const fn validate(self) -> Result<(), SchemaContractError> {
116 match self {
117 Self::Decimal { scale } if scale > Decimal::max_supported_scale() => {
118 Err(SchemaContractError::InvalidFieldType)
119 }
120 _ => Ok(()),
121 }
122 }
123
124 pub(crate) fn accepts_literal(self, literal: &ScalarLiteral) -> bool {
125 match (self, literal) {
126 (Self::Account, ScalarLiteral::Account(_))
127 | (Self::Bool, ScalarLiteral::Bool(_))
128 | (Self::Date, ScalarLiteral::Date(_))
129 | (Self::Duration, ScalarLiteral::Duration(_))
130 | (Self::Float32, ScalarLiteral::Float32(_))
131 | (Self::Float64, ScalarLiteral::Float64(_))
132 | (Self::Int128, ScalarLiteral::Int(_))
133 | (Self::Principal, ScalarLiteral::Principal(_))
134 | (Self::Subaccount, ScalarLiteral::Subaccount(_))
135 | (Self::Timestamp, ScalarLiteral::Timestamp(_))
136 | (Self::Nat128, ScalarLiteral::Nat(_))
137 | (Self::U256, ScalarLiteral::U256(_))
138 | (Self::Ulid, ScalarLiteral::Ulid(_))
139 | (Self::Unit, ScalarLiteral::Unit(_)) => true,
140 (Self::Blob { max_len }, ScalarLiteral::Blob(value)) => {
141 max_len.is_none_or(|max| value.len() <= max as usize)
142 }
143 (Self::Text { max_len }, ScalarLiteral::Text(value)) => {
144 max_len.is_none_or(|max| value.chars().count() <= max as usize)
145 }
146 (Self::Int8, ScalarLiteral::Int(value)) => i8::try_from(*value).is_ok(),
147 (Self::Int16, ScalarLiteral::Int(value)) => i16::try_from(*value).is_ok(),
148 (Self::Int32, ScalarLiteral::Int(value)) => i32::try_from(*value).is_ok(),
149 (Self::Int64, ScalarLiteral::Int(value)) => i64::try_from(*value).is_ok(),
150 (Self::Nat8, ScalarLiteral::Nat(value)) => u8::try_from(*value).is_ok(),
151 (Self::Nat16, ScalarLiteral::Nat(value)) => u16::try_from(*value).is_ok(),
152 (Self::Nat32, ScalarLiteral::Nat(value)) => u32::try_from(*value).is_ok(),
153 (Self::Nat64, ScalarLiteral::Nat(value)) => u64::try_from(*value).is_ok(),
154 (Self::Decimal { scale }, ScalarLiteral::Decimal(value)) => {
155 decimal_fits_scale(*value, scale)
156 }
157 _ => false,
158 }
159 }
160}
161
162impl FieldType {
163 pub fn append_named_type_dependencies(&self, pending: &mut Vec<TypeSourceKey>) {
168 let mut field_type = self;
169 while let Self::List(item) = field_type {
170 field_type = item;
171 }
172 if let Self::Named(source) = field_type {
173 pending.push(source.clone());
174 }
175 }
176
177 pub(crate) const fn validate(&self) -> Result<(), SchemaContractError> {
178 self.validate_at_depth(0)
179 }
180
181 const fn validate_at_depth(&self, depth: usize) -> Result<(), SchemaContractError> {
182 let Some(depth) = depth.checked_add(1) else {
183 return Err(SchemaContractError::FieldTypeDepthExceeded);
184 };
185 if depth > MAX_SCHEMA_FIELD_TYPE_DEPTH {
186 return Err(SchemaContractError::FieldTypeDepthExceeded);
187 }
188 match self {
189 Self::Scalar(scalar) => scalar.validate(),
190 Self::List(item) => item.validate_at_depth(depth),
191 Self::Named(_) => Ok(()),
192 }
193 }
194}
195
196#[derive(Clone, Debug, Eq, PartialEq)]
198pub enum FieldInsertPolicy {
199 Required,
201 Nullable,
203 Default(ScalarLiteral),
205 Generated,
207}
208
209#[derive(Clone, Copy, Debug, Eq, PartialEq)]
211pub enum FieldManagementPolicy {
212 CreatedAt,
214 UpdatedAt,
216}
217
218#[derive(Clone, Debug, Eq, PartialEq)]
220pub struct FieldFragment {
221 source_key: FieldSourceKey,
222 name: SchemaName,
223 field_type: FieldType,
224 nullable: bool,
225 insert_policy: FieldInsertPolicy,
226 management: Option<FieldManagementPolicy>,
227}
228
229impl FieldFragment {
230 #[must_use]
232 pub fn new(
233 name: SchemaName,
234 field_type: FieldType,
235 nullable: bool,
236 insert_policy: FieldInsertPolicy,
237 management: Option<FieldManagementPolicy>,
238 ) -> Self {
239 Self {
240 source_key: FieldSourceKey::from_name(&name),
241 name,
242 field_type,
243 nullable,
244 insert_policy,
245 management,
246 }
247 }
248
249 #[must_use]
251 pub const fn source_key(&self) -> &FieldSourceKey {
252 &self.source_key
253 }
254
255 #[must_use]
257 pub const fn name(&self) -> &SchemaName {
258 &self.name
259 }
260
261 #[must_use]
263 pub const fn field_type(&self) -> &FieldType {
264 &self.field_type
265 }
266
267 #[must_use]
269 pub const fn nullable(&self) -> bool {
270 self.nullable
271 }
272
273 #[must_use]
275 pub const fn insert_policy(&self) -> &FieldInsertPolicy {
276 &self.insert_policy
277 }
278
279 #[must_use]
281 pub const fn management(&self) -> Option<FieldManagementPolicy> {
282 self.management
283 }
284
285 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
286 ensure_current_name_key(self.source_key.as_str(), &self.name)?;
287 self.field_type.validate()?;
288 if let FieldInsertPolicy::Default(literal) = &self.insert_policy {
289 literal.validate()?;
290 match &self.field_type {
291 FieldType::Scalar(scalar) if scalar.accepts_literal(literal) => {}
292 FieldType::Named(_) if matches!(literal, ScalarLiteral::EnumUnit { .. }) => {}
293 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
294 return Err(SchemaContractError::LiteralTypeMismatch);
295 }
296 }
297 }
298 if matches!(self.insert_policy, FieldInsertPolicy::Nullable) && !self.nullable {
299 return Err(SchemaContractError::InvalidFieldPolicy);
300 }
301 if self.management.is_some()
302 && (!matches!(self.field_type, FieldType::Scalar(ScalarType::Timestamp))
303 || self.nullable
304 || !matches!(self.insert_policy, FieldInsertPolicy::Required))
305 {
306 return Err(SchemaContractError::InvalidFieldPolicy);
307 }
308 Ok(())
309 }
310}
311
312#[derive(Clone, Debug, Eq, PartialEq)]
314pub enum IndexKeyFragment {
315 Field(FieldSourceKey),
317 Lower(FieldSourceKey),
319 Upper(FieldSourceKey),
321 Trim(FieldSourceKey),
323 LowerTrim(FieldSourceKey),
325 Date(FieldSourceKey),
327 Year(FieldSourceKey),
329 Month(FieldSourceKey),
331 Day(FieldSourceKey),
333}
334
335impl IndexKeyFragment {
336 #[must_use]
338 pub const fn field(&self) -> &FieldSourceKey {
339 match self {
340 Self::Field(field)
341 | Self::Lower(field)
342 | Self::Upper(field)
343 | Self::Trim(field)
344 | Self::LowerTrim(field)
345 | Self::Date(field)
346 | Self::Year(field)
347 | Self::Month(field)
348 | Self::Day(field) => field,
349 }
350 }
351}
352
353#[derive(Clone, Debug, Eq, PartialEq)]
355pub struct IndexFragment {
356 source_key: IndexSourceKey,
357 name: SchemaName,
358 key: Vec<IndexKeyFragment>,
359 unique: bool,
360 predicate: Option<SourceCheckExpr>,
361}
362
363impl IndexFragment {
364 pub fn try_new(
370 name: SchemaName,
371 key: Vec<IndexKeyFragment>,
372 unique: bool,
373 predicate: Option<SourceCheckExpr>,
374 ) -> Result<Self, SchemaContractError> {
375 if crate::is_engine_reserved_schema_name(name.as_str()) {
376 return Err(SchemaContractError::InvalidSourceKey);
377 }
378 if key.is_empty() {
379 return Err(SchemaContractError::InvalidReferenceList);
380 }
381 if let Some(predicate) = &predicate {
382 predicate.validate()?;
383 }
384 Ok(Self {
385 source_key: IndexSourceKey::from_name(&name),
386 name,
387 key,
388 unique,
389 predicate,
390 })
391 }
392
393 #[must_use]
395 pub const fn source_key(&self) -> &IndexSourceKey {
396 &self.source_key
397 }
398
399 #[must_use]
401 pub const fn name(&self) -> &SchemaName {
402 &self.name
403 }
404
405 #[must_use]
407 pub fn key(&self) -> &[IndexKeyFragment] {
408 &self.key
409 }
410
411 #[must_use]
413 pub const fn unique(&self) -> bool {
414 self.unique
415 }
416
417 #[must_use]
419 pub const fn predicate(&self) -> Option<&SourceCheckExpr> {
420 self.predicate.as_ref()
421 }
422
423 fn validate(&self) -> Result<(), SchemaContractError> {
424 let rebuilt = Self::try_new(
425 self.name.clone(),
426 self.key.clone(),
427 self.unique,
428 self.predicate.clone(),
429 )?;
430 ensure_canonical_rebuild(self, &rebuilt)
431 }
432}
433
434#[derive(Clone, Copy, Debug, Eq, PartialEq)]
436pub enum RelationDeleteAction {
437 Restrict,
439}
440
441#[derive(Clone, Debug, Eq, PartialEq)]
443pub enum RelationPathStepFragment {
444 EnterNamed { r#type: TypeSourceKey },
446 OptionalSome,
448 RecordMember {
450 record: TypeSourceKey,
452 field: FieldSourceKey,
454 },
455 EnumVariantPayload {
457 r#enum: TypeSourceKey,
459 variant: TypeSourceKey,
461 },
462 ListItems,
464 SetItems,
466 MapValues,
468}
469
470#[derive(Clone, Debug, Eq, PartialEq)]
472pub enum RelationSourceFragment {
473 Direct { fields: Vec<FieldSourceKey> },
475 Nested {
477 root: FieldSourceKey,
479 steps: Vec<RelationPathStepFragment>,
481 },
482}
483
484impl RelationSourceFragment {
485 #[must_use]
487 pub const fn direct(fields: Vec<FieldSourceKey>) -> Self {
488 Self::Direct { fields }
489 }
490
491 pub fn root_fields(&self) -> impl Iterator<Item = &FieldSourceKey> {
493 let direct = match self {
494 Self::Direct { fields } => fields.as_slice(),
495 Self::Nested { .. } => &[],
496 };
497 let nested = match self {
498 Self::Nested { root, .. } => Some(root),
499 Self::Direct { .. } => None,
500 };
501 direct.iter().chain(nested)
502 }
503
504 fn validate(&self, target_field_count: usize) -> Result<(), SchemaContractError> {
505 match self {
506 Self::Direct { fields } => {
507 if fields.is_empty() || fields.len() != target_field_count {
508 return Err(SchemaContractError::InvalidReferenceList);
509 }
510 ensure_unique(fields)
511 }
512 Self::Nested { steps, .. } => {
513 if steps.is_empty()
514 || steps.len() > MAX_RELATION_PATH_STEPS
515 || target_field_count != 1
516 {
517 return Err(SchemaContractError::InvalidReferenceList);
518 }
519 Ok(())
520 }
521 }
522 }
523}
524
525#[derive(Clone, Debug, Eq, PartialEq)]
527pub struct RelationFragment {
528 source_key: RelationSourceKey,
529 name: SchemaName,
530 source: RelationSourceFragment,
531 target_entity: EntitySourceKey,
532 target_fields: Vec<FieldSourceKey>,
533 on_delete: RelationDeleteAction,
534}
535
536impl RelationFragment {
537 pub fn try_new(
544 name: SchemaName,
545 source: RelationSourceFragment,
546 target_entity: EntitySourceKey,
547 target_fields: Vec<FieldSourceKey>,
548 on_delete: RelationDeleteAction,
549 ) -> Result<Self, SchemaContractError> {
550 if target_fields.is_empty() {
551 return Err(SchemaContractError::InvalidReferenceList);
552 }
553 source.validate(target_fields.len())?;
554 ensure_unique(&target_fields)?;
555 Ok(Self {
556 source_key: RelationSourceKey::from_name(&name),
557 name,
558 source,
559 target_entity,
560 target_fields,
561 on_delete,
562 })
563 }
564
565 #[must_use]
567 pub const fn source_key(&self) -> &RelationSourceKey {
568 &self.source_key
569 }
570
571 #[must_use]
573 pub const fn name(&self) -> &SchemaName {
574 &self.name
575 }
576
577 #[must_use]
579 pub const fn source(&self) -> &RelationSourceFragment {
580 &self.source
581 }
582
583 #[must_use]
585 pub const fn target_entity(&self) -> &EntitySourceKey {
586 &self.target_entity
587 }
588
589 #[must_use]
591 pub fn target_fields(&self) -> &[FieldSourceKey] {
592 &self.target_fields
593 }
594
595 #[must_use]
597 pub const fn on_delete(&self) -> RelationDeleteAction {
598 self.on_delete
599 }
600
601 fn validate(&self) -> Result<(), SchemaContractError> {
602 let rebuilt = Self::try_new(
603 self.name.clone(),
604 self.source.clone(),
605 self.target_entity.clone(),
606 self.target_fields.clone(),
607 self.on_delete,
608 )?;
609 ensure_canonical_rebuild(self, &rebuilt)
610 }
611}
612
613#[derive(Clone, Debug, Eq, PartialEq)]
615pub enum ConstraintFragmentKind {
616 Check(SourceCheckExpr),
618 TargetedRule(TargetedRuleFragment),
620}
621
622impl ConstraintFragmentKind {
623 fn validate(&self) -> Result<(), SchemaContractError> {
624 match self {
625 Self::Check(expression) => expression.validate(),
626 Self::TargetedRule(rule) => rule.validate(),
627 }
628 }
629}
630
631#[derive(Clone, Debug, Eq, PartialEq)]
633pub struct TargetedRuleFragment {
634 root: FieldSourceKey,
635 target_type: TypeSourceKey,
636 rule: RuleSourceKey,
637 operation: SourceRuleOperation,
638}
639
640impl TargetedRuleFragment {
641 #[must_use]
643 pub fn new(
644 root: FieldSourceKey,
645 target_type: TypeSourceKey,
646 rule: SchemaName,
647 operation: SourceRuleOperation,
648 ) -> Self {
649 Self {
650 root,
651 target_type,
652 rule: RuleSourceKey::from_name(&rule),
653 operation,
654 }
655 }
656
657 #[must_use]
659 pub const fn root(&self) -> &FieldSourceKey {
660 &self.root
661 }
662
663 #[must_use]
665 pub const fn target_type(&self) -> &TypeSourceKey {
666 &self.target_type
667 }
668
669 #[must_use]
671 pub const fn rule(&self) -> &RuleSourceKey {
672 &self.rule
673 }
674
675 #[must_use]
677 pub const fn operation(&self) -> &SourceRuleOperation {
678 &self.operation
679 }
680
681 fn validate(&self) -> Result<(), SchemaContractError> {
682 self.operation.validate()
683 }
684}
685
686#[derive(Clone, Debug, Eq, PartialEq)]
688pub struct ConstraintFragment {
689 source_key: ConstraintSourceKey,
690 name: SchemaName,
691 kind: ConstraintFragmentKind,
692}
693
694impl ConstraintFragment {
695 #[must_use]
697 pub fn check(name: SchemaName, expression: SourceCheckExpr) -> Self {
698 Self {
699 source_key: ConstraintSourceKey::from_name(&name),
700 name,
701 kind: ConstraintFragmentKind::Check(expression),
702 }
703 }
704
705 #[must_use]
707 pub fn targeted_rule(rule: TargetedRuleFragment) -> Self {
708 let source_key = ConstraintSourceKey::for_targeted_field_rule(
709 rule.root(),
710 rule.target_type(),
711 rule.rule(),
712 );
713 let name = SchemaName::for_targeted_rule(&source_key);
714 Self {
715 source_key,
716 name,
717 kind: ConstraintFragmentKind::TargetedRule(rule),
718 }
719 }
720
721 #[must_use]
723 pub const fn source_key(&self) -> &ConstraintSourceKey {
724 &self.source_key
725 }
726
727 #[must_use]
729 pub const fn name(&self) -> &SchemaName {
730 &self.name
731 }
732
733 #[must_use]
735 pub const fn kind(&self) -> &ConstraintFragmentKind {
736 &self.kind
737 }
738
739 fn validate(&self) -> Result<(), SchemaContractError> {
740 self.kind.validate()?;
741 let rebuilt = match &self.kind {
742 ConstraintFragmentKind::Check(expression) => {
743 if crate::is_engine_reserved_schema_name(self.name.as_str()) {
744 return Err(SchemaContractError::InvalidSourceKey);
745 }
746 Self::check(self.name.clone(), expression.clone())
747 }
748 ConstraintFragmentKind::TargetedRule(rule) => Self::targeted_rule(rule.clone()),
749 };
750 ensure_canonical_rebuild(self, &rebuilt)
751 }
752}
753
754#[derive(Clone, Debug, Eq, PartialEq)]
756pub struct EntityFragment {
757 source_key: EntitySourceKey,
758 name: SchemaName,
759 version: DeclaredEntityVersion,
760 fields: Vec<FieldFragment>,
761 primary_key: Vec<FieldSourceKey>,
762 indexes: Vec<IndexFragment>,
763 relations: Vec<RelationFragment>,
764 constraints: Vec<ConstraintFragment>,
765}
766
767impl EntityFragment {
768 pub fn try_new(
777 name: SchemaName,
778 version: DeclaredEntityVersion,
779 mut fields: Vec<FieldFragment>,
780 primary_key: Vec<FieldSourceKey>,
781 mut indexes: Vec<IndexFragment>,
782 mut relations: Vec<RelationFragment>,
783 mut constraints: Vec<ConstraintFragment>,
784 ) -> Result<Self, SchemaContractError> {
785 let source_key = EntitySourceKey::from_name(&name);
786 check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
787 check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
788 check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
789 check_len(
790 "entity constraints",
791 constraints.len(),
792 MAX_FRAGMENT_CONSTRAINTS,
793 )?;
794 if primary_key.is_empty() {
795 return Err(SchemaContractError::InvalidReferenceList);
796 }
797 ensure_unique(&primary_key)?;
798 crate::compact_sort_unstable_by(&mut fields, |a, b| a.source_key.cmp(&b.source_key));
801 crate::compact_sort_unstable_by(&mut indexes, |a, b| a.source_key.cmp(&b.source_key));
802 crate::compact_sort_unstable_by(&mut relations, |a, b| a.source_key.cmp(&b.source_key));
803 crate::compact_sort_unstable_by(&mut constraints, |a, b| a.source_key.cmp(&b.source_key));
804 ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
805 ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
806 ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
807 ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
808 ensure_unique_names(fields.iter().map(FieldFragment::name))?;
809 ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
810 ensure_unique_names(relations.iter().map(RelationFragment::name))?;
811 ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
812 for field in &fields {
813 field.validate()?;
814 }
815 validate_management_cardinality(&fields)?;
816 for index in &indexes {
817 index.validate()?;
818 }
819 for relation in &relations {
820 relation.validate()?;
821 }
822 for constraint in &constraints {
823 constraint.validate()?;
824 }
825 let field_keys = fields
826 .iter()
827 .map(|field| field.source_key.clone())
828 .collect::<BTreeSet<_>>();
829 if primary_key.iter().any(|field| !field_keys.contains(field)) {
830 return Err(SchemaContractError::InvalidLocalReference);
831 }
832 validate_insert_generation(&fields, &primary_key)?;
833 validate_primary_key_field_shapes(&fields, &primary_key)?;
834 for index in &indexes {
835 if index
836 .key()
837 .iter()
838 .any(|component| !field_keys.contains(component.field()))
839 || index.predicate().is_some_and(|predicate| {
840 predicate
841 .dependencies()
842 .iter()
843 .any(|field| !field_keys.contains(field))
844 })
845 {
846 return Err(SchemaContractError::InvalidLocalReference);
847 }
848 }
849 for relation in &relations {
850 if relation
851 .source()
852 .root_fields()
853 .any(|field| !field_keys.contains(field))
854 || (relation.target_entity() == &source_key
855 && relation
856 .target_fields()
857 .iter()
858 .any(|field| !field_keys.contains(field)))
859 {
860 return Err(SchemaContractError::InvalidLocalReference);
861 }
862 }
863 for constraint in &constraints {
864 let invalid = match constraint.kind() {
865 ConstraintFragmentKind::Check(expression) => expression
866 .dependencies()
867 .iter()
868 .any(|field| !field_keys.contains(field)),
869 ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
870 };
871 if invalid {
872 return Err(SchemaContractError::InvalidLocalReference);
873 }
874 }
875 Ok(Self {
876 source_key,
877 name,
878 version,
879 fields,
880 primary_key,
881 indexes,
882 relations,
883 constraints,
884 })
885 }
886
887 #[must_use]
889 pub const fn source_key(&self) -> &EntitySourceKey {
890 &self.source_key
891 }
892
893 #[must_use]
895 pub const fn name(&self) -> &SchemaName {
896 &self.name
897 }
898
899 #[must_use]
901 pub const fn version(&self) -> DeclaredEntityVersion {
902 self.version
903 }
904
905 #[must_use]
907 pub fn fields(&self) -> &[FieldFragment] {
908 &self.fields
909 }
910
911 #[must_use]
913 pub fn primary_key(&self) -> &[FieldSourceKey] {
914 &self.primary_key
915 }
916
917 #[must_use]
919 pub fn indexes(&self) -> &[IndexFragment] {
920 &self.indexes
921 }
922
923 #[must_use]
925 pub fn relations(&self) -> &[RelationFragment] {
926 &self.relations
927 }
928
929 #[must_use]
931 pub fn constraints(&self) -> &[ConstraintFragment] {
932 &self.constraints
933 }
934
935 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
936 let rebuilt = Self::try_new(
937 self.name.clone(),
938 self.version,
939 self.fields.clone(),
940 self.primary_key.clone(),
941 self.indexes.clone(),
942 self.relations.clone(),
943 self.constraints.clone(),
944 )?;
945 ensure_canonical_rebuild(self, &rebuilt)
946 }
947}
948
949fn validate_primary_key_field_shapes(
952 fields: &[FieldFragment],
953 primary_key: &[FieldSourceKey],
954) -> Result<(), SchemaContractError> {
955 if fields.iter().any(|field| {
956 primary_key.contains(&field.source_key)
957 && (field.nullable
958 || !matches!(field.field_type, FieldType::Scalar(scalar)
959 if scalar.kind().is_primary_key_component_encodable()
960 && (primary_key.len() == 1 || scalar != ScalarType::Unit)))
961 }) {
962 return Err(SchemaContractError::InvalidFieldPolicy);
963 }
964 Ok(())
965}
966
967fn validate_insert_generation(
971 fields: &[FieldFragment],
972 primary_key: &[FieldSourceKey],
973) -> Result<(), SchemaContractError> {
974 for field in fields {
975 if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
976 continue;
977 }
978 if field.nullable() || field.management().is_some() {
979 return Err(SchemaContractError::InvalidFieldPolicy);
980 }
981 match field.field_type() {
982 FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
983 FieldType::Scalar(
984 ScalarType::Nat8
985 | ScalarType::Nat16
986 | ScalarType::Nat32
987 | ScalarType::Nat64
988 | ScalarType::Nat128,
989 ) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
990 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
991 return Err(SchemaContractError::InvalidFieldPolicy);
992 }
993 }
994 }
995 Ok(())
996}
997
998#[derive(Clone, Debug, Eq, PartialEq)]
1003pub struct RecordFieldFragment {
1004 source_key: FieldSourceKey,
1005 name: SchemaName,
1006 field_type: FieldType,
1007 nullable: bool,
1008}
1009
1010impl RecordFieldFragment {
1011 #[must_use]
1013 pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
1014 Self {
1015 source_key: FieldSourceKey::from_name(&name),
1016 name,
1017 field_type,
1018 nullable,
1019 }
1020 }
1021
1022 #[must_use]
1024 pub const fn source_key(&self) -> &FieldSourceKey {
1025 &self.source_key
1026 }
1027
1028 #[must_use]
1030 pub const fn name(&self) -> &SchemaName {
1031 &self.name
1032 }
1033
1034 #[must_use]
1036 pub const fn field_type(&self) -> &FieldType {
1037 &self.field_type
1038 }
1039
1040 #[must_use]
1042 pub const fn nullable(&self) -> bool {
1043 self.nullable
1044 }
1045
1046 fn validate(&self) -> Result<(), SchemaContractError> {
1047 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1048 return Err(SchemaContractError::NonCanonical);
1049 }
1050 self.field_type.validate()
1051 }
1052}
1053
1054#[derive(Clone, Debug, Eq, PartialEq)]
1057pub struct TupleElementFragment {
1058 field_type: FieldType,
1059 nullable: bool,
1060}
1061
1062impl TupleElementFragment {
1063 #[must_use]
1065 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
1066 Self {
1067 field_type,
1068 nullable,
1069 }
1070 }
1071
1072 #[must_use]
1074 pub const fn field_type(&self) -> &FieldType {
1075 &self.field_type
1076 }
1077
1078 #[must_use]
1080 pub const fn nullable(&self) -> bool {
1081 self.nullable
1082 }
1083
1084 const fn validate(&self) -> Result<(), SchemaContractError> {
1085 self.field_type.validate()
1086 }
1087}
1088
1089#[derive(Clone, Debug, Eq, PartialEq)]
1091pub struct RecordTypeFragment {
1092 source_key: TypeSourceKey,
1093 name: SchemaName,
1094 fields: Vec<RecordFieldFragment>,
1095}
1096
1097impl RecordTypeFragment {
1098 pub fn try_new(
1105 name: SchemaName,
1106 mut fields: Vec<RecordFieldFragment>,
1107 ) -> Result<Self, SchemaContractError> {
1108 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
1109 crate::compact_sort_unstable_by(&mut fields, |left, right| {
1110 left.source_key.cmp(&right.source_key)
1111 });
1112 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
1113 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
1114 for field in &fields {
1115 field.validate()?;
1116 }
1117 Ok(Self {
1118 source_key: TypeSourceKey::from_name(&name),
1119 name,
1120 fields,
1121 })
1122 }
1123
1124 #[must_use]
1126 pub const fn source_key(&self) -> &TypeSourceKey {
1127 &self.source_key
1128 }
1129
1130 #[must_use]
1132 pub const fn name(&self) -> &SchemaName {
1133 &self.name
1134 }
1135
1136 #[must_use]
1138 pub fn fields(&self) -> &[RecordFieldFragment] {
1139 &self.fields
1140 }
1141
1142 fn validate(&self) -> Result<(), SchemaContractError> {
1143 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1144 if rebuilt != *self {
1145 return Err(SchemaContractError::NonCanonical);
1146 }
1147 Ok(())
1148 }
1149}
1150
1151#[derive(Clone, Debug, Eq, PartialEq)]
1153pub struct EnumVariantFragment {
1154 source_key: TypeSourceKey,
1155 name: SchemaName,
1156 payload: Option<FieldType>,
1157}
1158
1159impl EnumVariantFragment {
1160 #[must_use]
1162 pub fn new(name: SchemaName) -> Self {
1163 Self {
1164 source_key: TypeSourceKey::from_name(&name),
1165 name,
1166 payload: None,
1167 }
1168 }
1169
1170 #[must_use]
1172 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1173 Self {
1174 source_key: TypeSourceKey::from_name(&name),
1175 name,
1176 payload: Some(payload),
1177 }
1178 }
1179
1180 #[must_use]
1182 pub const fn source_key(&self) -> &TypeSourceKey {
1183 &self.source_key
1184 }
1185
1186 #[must_use]
1188 pub const fn name(&self) -> &SchemaName {
1189 &self.name
1190 }
1191
1192 #[must_use]
1194 pub const fn payload(&self) -> Option<&FieldType> {
1195 self.payload.as_ref()
1196 }
1197
1198 fn validate(&self) -> Result<(), SchemaContractError> {
1199 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1200 return Err(SchemaContractError::NonCanonical);
1201 }
1202 match &self.payload {
1203 Some(payload) => payload.validate(),
1204 None => Ok(()),
1205 }
1206 }
1207}
1208
1209#[derive(Clone, Debug, Eq, PartialEq)]
1211pub struct EnumTypeFragment {
1212 source_key: TypeSourceKey,
1213 name: SchemaName,
1214 variants: Vec<EnumVariantFragment>,
1215}
1216
1217impl EnumTypeFragment {
1218 pub fn try_new(
1225 name: SchemaName,
1226 mut variants: Vec<EnumVariantFragment>,
1227 ) -> Result<Self, SchemaContractError> {
1228 if variants.is_empty() {
1229 return Err(SchemaContractError::InvalidReferenceList);
1230 }
1231 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1232 crate::compact_sort_unstable_by(&mut variants, |left, right| {
1233 left.source_key.cmp(&right.source_key)
1234 });
1235 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1236 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1237 for variant in &variants {
1238 variant.validate()?;
1239 }
1240 Ok(Self {
1241 source_key: TypeSourceKey::from_name(&name),
1242 name,
1243 variants,
1244 })
1245 }
1246
1247 #[must_use]
1249 pub const fn source_key(&self) -> &TypeSourceKey {
1250 &self.source_key
1251 }
1252
1253 #[must_use]
1255 pub const fn name(&self) -> &SchemaName {
1256 &self.name
1257 }
1258
1259 #[must_use]
1261 pub fn variants(&self) -> &[EnumVariantFragment] {
1262 &self.variants
1263 }
1264
1265 fn validate(&self) -> Result<(), SchemaContractError> {
1266 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1267 if rebuilt != *self {
1268 return Err(SchemaContractError::NonCanonical);
1269 }
1270 Ok(())
1271 }
1272}
1273
1274#[derive(Clone, Debug, Eq, PartialEq)]
1276pub enum NamedTypeFragment {
1277 Record(RecordTypeFragment),
1279 Enum(EnumTypeFragment),
1281 Newtype {
1283 source_key: TypeSourceKey,
1285 name: SchemaName,
1287 inner: FieldType,
1289 },
1290 List {
1292 source_key: TypeSourceKey,
1294 name: SchemaName,
1296 item: FieldType,
1298 },
1299 Set {
1301 source_key: TypeSourceKey,
1303 name: SchemaName,
1305 item: FieldType,
1307 },
1308 Map {
1310 source_key: TypeSourceKey,
1312 name: SchemaName,
1314 key: FieldType,
1316 value: FieldType,
1318 },
1319 Tuple {
1321 source_key: TypeSourceKey,
1323 name: SchemaName,
1325 members: Vec<TupleElementFragment>,
1327 },
1328}
1329
1330impl NamedTypeFragment {
1331 pub fn append_named_type_dependencies(&self, pending: &mut Vec<TypeSourceKey>) {
1337 match self {
1338 Self::Record(record) => {
1339 for field in record.fields() {
1340 field.field_type().append_named_type_dependencies(pending);
1341 }
1342 }
1343 Self::Enum(r#enum) => {
1344 for variant in r#enum.variants() {
1345 if let Some(payload) = variant.payload() {
1346 payload.append_named_type_dependencies(pending);
1347 }
1348 }
1349 }
1350 Self::Newtype { inner, .. }
1351 | Self::List { item: inner, .. }
1352 | Self::Set { item: inner, .. } => {
1353 inner.append_named_type_dependencies(pending);
1354 }
1355 Self::Map { key, value, .. } => {
1356 key.append_named_type_dependencies(pending);
1357 value.append_named_type_dependencies(pending);
1358 }
1359 Self::Tuple { members, .. } => {
1360 for member in members {
1361 member.field_type().append_named_type_dependencies(pending);
1362 }
1363 }
1364 }
1365 }
1366
1367 #[must_use]
1369 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1370 Self::Newtype {
1371 source_key: TypeSourceKey::from_name(&name),
1372 name,
1373 inner,
1374 }
1375 }
1376
1377 #[must_use]
1379 pub fn list(name: SchemaName, item: FieldType) -> Self {
1380 Self::List {
1381 source_key: TypeSourceKey::from_name(&name),
1382 name,
1383 item,
1384 }
1385 }
1386
1387 #[must_use]
1389 pub fn set(name: SchemaName, item: FieldType) -> Self {
1390 Self::Set {
1391 source_key: TypeSourceKey::from_name(&name),
1392 name,
1393 item,
1394 }
1395 }
1396
1397 #[must_use]
1399 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1400 Self::Map {
1401 source_key: TypeSourceKey::from_name(&name),
1402 name,
1403 key,
1404 value,
1405 }
1406 }
1407
1408 #[must_use]
1410 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1411 Self::Tuple {
1412 source_key: TypeSourceKey::from_name(&name),
1413 name,
1414 members,
1415 }
1416 }
1417
1418 #[must_use]
1420 pub const fn source_key(&self) -> &TypeSourceKey {
1421 match self {
1422 Self::Record(record) => record.source_key(),
1423 Self::Enum(r#enum) => r#enum.source_key(),
1424 Self::Newtype { source_key, .. }
1425 | Self::List { source_key, .. }
1426 | Self::Set { source_key, .. }
1427 | Self::Map { source_key, .. }
1428 | Self::Tuple { source_key, .. } => source_key,
1429 }
1430 }
1431
1432 #[must_use]
1434 pub const fn name(&self) -> &SchemaName {
1435 match self {
1436 Self::Record(record) => record.name(),
1437 Self::Enum(r#enum) => r#enum.name(),
1438 Self::Newtype { name, .. }
1439 | Self::List { name, .. }
1440 | Self::Set { name, .. }
1441 | Self::Map { name, .. }
1442 | Self::Tuple { name, .. } => name,
1443 }
1444 }
1445
1446 fn validate(&self) -> Result<(), SchemaContractError> {
1447 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1448 match self {
1449 Self::Record(record) => record.validate(),
1450 Self::Enum(r#enum) => r#enum.validate(),
1451 Self::Newtype { inner, .. }
1452 | Self::List { item: inner, .. }
1453 | Self::Set { item: inner, .. } => inner.validate(),
1454 Self::Map { key, value, .. } => {
1455 key.validate()?;
1456 value.validate()
1457 }
1458 Self::Tuple { members, .. } => {
1459 if members.is_empty() {
1460 return Err(SchemaContractError::InvalidReferenceList);
1461 }
1462 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1463 members.iter().try_for_each(TupleElementFragment::validate)
1464 }
1465 }
1466 }
1467}
1468
1469#[derive(Clone, Debug, Eq, PartialEq)]
1471pub struct SchemaFragment {
1472 entities: Vec<EntityFragment>,
1473 types: Vec<NamedTypeFragment>,
1474}
1475
1476impl SchemaFragment {
1477 pub fn try_new(
1484 mut entities: Vec<EntityFragment>,
1485 mut types: Vec<NamedTypeFragment>,
1486 ) -> Result<Self, SchemaContractError> {
1487 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1488 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1489 crate::compact_sort_unstable_by(&mut entities, |left, right| {
1490 left.source_key.cmp(&right.source_key)
1491 });
1492 crate::compact_sort_unstable_by(&mut types, |left, right| {
1493 left.source_key().cmp(right.source_key())
1494 });
1495 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1496 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1497 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1498 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1499 for entity in &entities {
1500 entity.validate()?;
1501 }
1502 for r#type in &types {
1503 r#type.validate()?;
1504 }
1505 Ok(Self { entities, types })
1506 }
1507
1508 #[must_use]
1510 pub fn entities(&self) -> &[EntityFragment] {
1511 &self.entities
1512 }
1513
1514 #[must_use]
1516 pub fn types(&self) -> &[NamedTypeFragment] {
1517 &self.types
1518 }
1519
1520 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1521 for r#type in &self.types {
1522 r#type.validate()?;
1523 }
1524 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1525 if rebuilt != *self {
1526 return Err(SchemaContractError::NonCanonical);
1527 }
1528 Ok(())
1529 }
1530}
1531
1532pub(crate) const fn check_len(
1533 kind: &'static str,
1534 len: usize,
1535 max: usize,
1536) -> Result<(), SchemaContractError> {
1537 if len > max {
1538 return Err(SchemaContractError::TooManyItems { kind, len, max });
1539 }
1540 Ok(())
1541}
1542
1543fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1544 source_key == name.as_str()
1545}
1546
1547fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1548 if !current_name_key_matches(source_key, name) {
1549 return Err(SchemaContractError::NonCanonical);
1550 }
1551 Ok(())
1552}
1553
1554fn ensure_canonical_rebuild<T: PartialEq>(
1555 current: &T,
1556 rebuilt: &T,
1557) -> Result<(), SchemaContractError> {
1558 if current != rebuilt {
1559 return Err(SchemaContractError::NonCanonical);
1560 }
1561 Ok(())
1562}
1563
1564fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1565where
1566 T: Ord,
1567{
1568 let mut seen = BTreeSet::new();
1569 if values.iter().any(|value| !seen.insert(value)) {
1570 return Err(SchemaContractError::InvalidReferenceList);
1571 }
1572 Ok(())
1573}
1574
1575fn ensure_unique_sorted_by<T, K>(
1576 values: &[T],
1577 key: impl Fn(&T) -> &K,
1578) -> Result<(), SchemaContractError>
1579where
1580 K: Eq,
1581{
1582 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1583 return Err(SchemaContractError::DuplicateSourceKey);
1584 }
1585 Ok(())
1586}
1587
1588fn ensure_unique_names<'a>(
1589 names: impl IntoIterator<Item = &'a SchemaName>,
1590) -> Result<(), SchemaContractError> {
1591 let mut seen = BTreeSet::new();
1592 if names.into_iter().any(|name| !seen.insert(name)) {
1593 return Err(SchemaContractError::DuplicateName);
1594 }
1595 Ok(())
1596}
1597
1598fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1599 for policy in [
1600 FieldManagementPolicy::CreatedAt,
1601 FieldManagementPolicy::UpdatedAt,
1602 ] {
1603 if fields
1604 .iter()
1605 .filter(|field| field.management() == Some(policy))
1606 .count()
1607 > 1
1608 {
1609 return Err(SchemaContractError::InvalidFieldPolicy);
1610 }
1611 }
1612 Ok(())
1613}
1614
1615fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1616 match value.scale().cmp(&scale) {
1617 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1618 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1619 }
1620}
1621
1622#[cfg(test)]
1623mod tests {
1624 use super::{
1625 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1626 SchemaContractError, SchemaName,
1627 };
1628
1629 #[test]
1630 fn independently_decoded_field_key_and_name_must_match() {
1631 let field = FieldFragment {
1632 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1633 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1634 field_type: FieldType::Scalar(ScalarType::Nat64),
1635 nullable: false,
1636 insert_policy: FieldInsertPolicy::Required,
1637 management: None,
1638 };
1639
1640 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1641 }
1642}