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 IntBig {
61 max_bytes: u32,
63 },
64 Principal,
66 Subaccount,
68 Text {
70 max_len: Option<u32>,
72 },
73 Timestamp,
75 Nat8,
77 Nat16,
79 Nat32,
81 Nat64,
83 Nat128,
85 NatBig {
87 max_bytes: u32,
89 },
90 U256,
92 Ulid,
94 Unit,
96}
97
98impl ScalarType {
99 #[must_use]
101 pub const fn kind(self) -> ScalarKind {
102 match self {
103 Self::Account => ScalarKind::Account,
104 Self::Blob { .. } => ScalarKind::Blob,
105 Self::Bool => ScalarKind::Bool,
106 Self::Date => ScalarKind::Date,
107 Self::Decimal { .. } => ScalarKind::Decimal,
108 Self::Duration => ScalarKind::Duration,
109 Self::Float32 => ScalarKind::Float32,
110 Self::Float64 => ScalarKind::Float64,
111 Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 => ScalarKind::Int,
112 Self::Int128 => ScalarKind::Int128,
113 Self::IntBig { .. } => ScalarKind::IntBig,
114 Self::Principal => ScalarKind::Principal,
115 Self::Subaccount => ScalarKind::Subaccount,
116 Self::Text { .. } => ScalarKind::Text,
117 Self::Timestamp => ScalarKind::Timestamp,
118 Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 => ScalarKind::Nat,
119 Self::Nat128 => ScalarKind::Nat128,
120 Self::NatBig { .. } => ScalarKind::NatBig,
121 Self::U256 => ScalarKind::U256,
122 Self::Ulid => ScalarKind::Ulid,
123 Self::Unit => ScalarKind::Unit,
124 }
125 }
126
127 pub(crate) const fn validate(self) -> Result<(), SchemaContractError> {
128 match self {
129 Self::Decimal { scale } if scale > Decimal::max_supported_scale() => {
130 Err(SchemaContractError::InvalidFieldType)
131 }
132 Self::IntBig { max_bytes: 0 } | Self::NatBig { max_bytes: 0 } => {
133 Err(SchemaContractError::InvalidFieldType)
134 }
135 _ => Ok(()),
136 }
137 }
138
139 pub(crate) fn accepts_literal(self, literal: &ScalarLiteral) -> bool {
140 match (self, literal) {
141 (Self::Account, ScalarLiteral::Account(_))
142 | (Self::Bool, ScalarLiteral::Bool(_))
143 | (Self::Date, ScalarLiteral::Date(_))
144 | (Self::Duration, ScalarLiteral::Duration(_))
145 | (Self::Float32, ScalarLiteral::Float32(_))
146 | (Self::Float64, ScalarLiteral::Float64(_))
147 | (Self::Int128, ScalarLiteral::Int(_))
148 | (Self::Principal, ScalarLiteral::Principal(_))
149 | (Self::Subaccount, ScalarLiteral::Subaccount(_))
150 | (Self::Timestamp, ScalarLiteral::Timestamp(_))
151 | (Self::Nat128, ScalarLiteral::Nat(_))
152 | (Self::U256, ScalarLiteral::U256(_))
153 | (Self::Ulid, ScalarLiteral::Ulid(_))
154 | (Self::Unit, ScalarLiteral::Unit(_)) => true,
155 (Self::Blob { max_len }, ScalarLiteral::Blob(value)) => {
156 max_len.is_none_or(|max| value.len() <= max as usize)
157 }
158 (Self::Text { max_len }, ScalarLiteral::Text(value)) => {
159 max_len.is_none_or(|max| value.chars().count() <= max as usize)
160 }
161 (Self::Int8, ScalarLiteral::Int(value)) => i8::try_from(*value).is_ok(),
162 (Self::Int16, ScalarLiteral::Int(value)) => i16::try_from(*value).is_ok(),
163 (Self::Int32, ScalarLiteral::Int(value)) => i32::try_from(*value).is_ok(),
164 (Self::Int64, ScalarLiteral::Int(value)) => i64::try_from(*value).is_ok(),
165 (Self::IntBig { max_bytes }, ScalarLiteral::IntBig(value)) => {
166 value.to_leb128().len() <= max_bytes as usize
167 }
168 (Self::Nat8, ScalarLiteral::Nat(value)) => u8::try_from(*value).is_ok(),
169 (Self::Nat16, ScalarLiteral::Nat(value)) => u16::try_from(*value).is_ok(),
170 (Self::Nat32, ScalarLiteral::Nat(value)) => u32::try_from(*value).is_ok(),
171 (Self::Nat64, ScalarLiteral::Nat(value)) => u64::try_from(*value).is_ok(),
172 (Self::NatBig { max_bytes }, ScalarLiteral::NatBig(value)) => {
173 value.to_leb128().len() <= max_bytes as usize
174 }
175 (Self::Decimal { scale }, ScalarLiteral::Decimal(value)) => {
176 decimal_fits_scale(*value, scale)
177 }
178 _ => false,
179 }
180 }
181}
182
183impl FieldType {
184 pub(crate) const fn validate(&self) -> Result<(), SchemaContractError> {
185 self.validate_at_depth(0)
186 }
187
188 const fn validate_at_depth(&self, depth: usize) -> Result<(), SchemaContractError> {
189 let Some(depth) = depth.checked_add(1) else {
190 return Err(SchemaContractError::FieldTypeDepthExceeded);
191 };
192 if depth > MAX_SCHEMA_FIELD_TYPE_DEPTH {
193 return Err(SchemaContractError::FieldTypeDepthExceeded);
194 }
195 match self {
196 Self::Scalar(scalar) => scalar.validate(),
197 Self::List(item) => item.validate_at_depth(depth),
198 Self::Named(_) => Ok(()),
199 }
200 }
201}
202
203#[derive(Clone, Debug, Eq, PartialEq)]
205pub enum FieldInsertPolicy {
206 Required,
208 Nullable,
210 Default(ScalarLiteral),
212 Generated,
214}
215
216#[derive(Clone, Copy, Debug, Eq, PartialEq)]
218pub enum FieldManagementPolicy {
219 CreatedAt,
221 UpdatedAt,
223}
224
225#[derive(Clone, Debug, Eq, PartialEq)]
227pub struct FieldFragment {
228 source_key: FieldSourceKey,
229 name: SchemaName,
230 field_type: FieldType,
231 nullable: bool,
232 insert_policy: FieldInsertPolicy,
233 management: Option<FieldManagementPolicy>,
234}
235
236impl FieldFragment {
237 #[must_use]
239 pub fn new(
240 name: SchemaName,
241 field_type: FieldType,
242 nullable: bool,
243 insert_policy: FieldInsertPolicy,
244 management: Option<FieldManagementPolicy>,
245 ) -> Self {
246 Self {
247 source_key: FieldSourceKey::from_name(&name),
248 name,
249 field_type,
250 nullable,
251 insert_policy,
252 management,
253 }
254 }
255
256 #[must_use]
258 pub const fn source_key(&self) -> &FieldSourceKey {
259 &self.source_key
260 }
261
262 #[must_use]
264 pub const fn name(&self) -> &SchemaName {
265 &self.name
266 }
267
268 #[must_use]
270 pub const fn field_type(&self) -> &FieldType {
271 &self.field_type
272 }
273
274 #[must_use]
276 pub const fn nullable(&self) -> bool {
277 self.nullable
278 }
279
280 #[must_use]
282 pub const fn insert_policy(&self) -> &FieldInsertPolicy {
283 &self.insert_policy
284 }
285
286 #[must_use]
288 pub const fn management(&self) -> Option<FieldManagementPolicy> {
289 self.management
290 }
291
292 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
293 ensure_current_name_key(self.source_key.as_str(), &self.name)?;
294 self.field_type.validate()?;
295 if let FieldInsertPolicy::Default(literal) = &self.insert_policy {
296 literal.validate()?;
297 match &self.field_type {
298 FieldType::Scalar(scalar) if scalar.accepts_literal(literal) => {}
299 FieldType::Named(_) if matches!(literal, ScalarLiteral::EnumUnit { .. }) => {}
300 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
301 return Err(SchemaContractError::LiteralTypeMismatch);
302 }
303 }
304 }
305 if matches!(self.insert_policy, FieldInsertPolicy::Nullable) && !self.nullable {
306 return Err(SchemaContractError::InvalidFieldPolicy);
307 }
308 if self.management.is_some()
309 && (!matches!(self.field_type, FieldType::Scalar(ScalarType::Timestamp))
310 || self.nullable
311 || !matches!(self.insert_policy, FieldInsertPolicy::Required))
312 {
313 return Err(SchemaContractError::InvalidFieldPolicy);
314 }
315 Ok(())
316 }
317}
318
319#[derive(Clone, Debug, Eq, PartialEq)]
321pub enum IndexKeyFragment {
322 Field(FieldSourceKey),
324 Lower(FieldSourceKey),
326 Upper(FieldSourceKey),
328 Trim(FieldSourceKey),
330 LowerTrim(FieldSourceKey),
332 Date(FieldSourceKey),
334 Year(FieldSourceKey),
336 Month(FieldSourceKey),
338 Day(FieldSourceKey),
340}
341
342impl IndexKeyFragment {
343 #[must_use]
345 pub const fn field(&self) -> &FieldSourceKey {
346 match self {
347 Self::Field(field)
348 | Self::Lower(field)
349 | Self::Upper(field)
350 | Self::Trim(field)
351 | Self::LowerTrim(field)
352 | Self::Date(field)
353 | Self::Year(field)
354 | Self::Month(field)
355 | Self::Day(field) => field,
356 }
357 }
358}
359
360#[derive(Clone, Debug, Eq, PartialEq)]
362pub struct IndexFragment {
363 source_key: IndexSourceKey,
364 name: SchemaName,
365 key: Vec<IndexKeyFragment>,
366 unique: bool,
367 predicate: Option<SourceCheckExpr>,
368}
369
370impl IndexFragment {
371 pub fn try_new(
377 name: SchemaName,
378 key: Vec<IndexKeyFragment>,
379 unique: bool,
380 predicate: Option<SourceCheckExpr>,
381 ) -> Result<Self, SchemaContractError> {
382 if key.is_empty() {
383 return Err(SchemaContractError::InvalidReferenceList);
384 }
385 if let Some(predicate) = &predicate {
386 predicate.validate()?;
387 }
388 Ok(Self {
389 source_key: IndexSourceKey::from_name(&name),
390 name,
391 key,
392 unique,
393 predicate,
394 })
395 }
396
397 #[must_use]
399 pub const fn source_key(&self) -> &IndexSourceKey {
400 &self.source_key
401 }
402
403 #[must_use]
405 pub const fn name(&self) -> &SchemaName {
406 &self.name
407 }
408
409 #[must_use]
411 pub fn key(&self) -> &[IndexKeyFragment] {
412 &self.key
413 }
414
415 #[must_use]
417 pub const fn unique(&self) -> bool {
418 self.unique
419 }
420
421 #[must_use]
423 pub const fn predicate(&self) -> Option<&SourceCheckExpr> {
424 self.predicate.as_ref()
425 }
426
427 fn validate(&self) -> Result<(), SchemaContractError> {
428 let rebuilt = Self::try_new(
429 self.name.clone(),
430 self.key.clone(),
431 self.unique,
432 self.predicate.clone(),
433 )?;
434 ensure_canonical_rebuild(self, &rebuilt)
435 }
436}
437
438#[derive(Clone, Copy, Debug, Eq, PartialEq)]
440pub enum RelationDeleteAction {
441 Restrict,
443}
444
445#[derive(Clone, Debug, Eq, PartialEq)]
447pub enum RelationPathStepFragment {
448 EnterNamed { r#type: TypeSourceKey },
450 OptionalSome,
452 RecordMember {
454 record: TypeSourceKey,
456 field: FieldSourceKey,
458 },
459 EnumVariantPayload {
461 r#enum: TypeSourceKey,
463 variant: TypeSourceKey,
465 },
466 ListItems,
468 SetItems,
470 MapValues,
472}
473
474#[derive(Clone, Debug, Eq, PartialEq)]
476pub enum RelationSourceFragment {
477 Direct { fields: Vec<FieldSourceKey> },
479 Nested {
481 root: FieldSourceKey,
483 steps: Vec<RelationPathStepFragment>,
485 },
486}
487
488impl RelationSourceFragment {
489 #[must_use]
491 pub const fn direct(fields: Vec<FieldSourceKey>) -> Self {
492 Self::Direct { fields }
493 }
494
495 pub fn root_fields(&self) -> impl Iterator<Item = &FieldSourceKey> {
497 let direct = match self {
498 Self::Direct { fields } => fields.as_slice(),
499 Self::Nested { .. } => &[],
500 };
501 let nested = match self {
502 Self::Nested { root, .. } => Some(root),
503 Self::Direct { .. } => None,
504 };
505 direct.iter().chain(nested)
506 }
507
508 fn validate(&self, target_field_count: usize) -> Result<(), SchemaContractError> {
509 match self {
510 Self::Direct { fields } => {
511 if fields.is_empty() || fields.len() != target_field_count {
512 return Err(SchemaContractError::InvalidReferenceList);
513 }
514 ensure_unique(fields)
515 }
516 Self::Nested { steps, .. } => {
517 if steps.is_empty()
518 || steps.len() > MAX_RELATION_PATH_STEPS
519 || target_field_count != 1
520 {
521 return Err(SchemaContractError::InvalidReferenceList);
522 }
523 Ok(())
524 }
525 }
526 }
527}
528
529#[derive(Clone, Debug, Eq, PartialEq)]
531pub struct RelationFragment {
532 source_key: RelationSourceKey,
533 name: SchemaName,
534 source: RelationSourceFragment,
535 target_entity: EntitySourceKey,
536 target_fields: Vec<FieldSourceKey>,
537 on_delete: RelationDeleteAction,
538}
539
540impl RelationFragment {
541 pub fn try_new(
548 name: SchemaName,
549 source: RelationSourceFragment,
550 target_entity: EntitySourceKey,
551 target_fields: Vec<FieldSourceKey>,
552 on_delete: RelationDeleteAction,
553 ) -> Result<Self, SchemaContractError> {
554 if target_fields.is_empty() {
555 return Err(SchemaContractError::InvalidReferenceList);
556 }
557 source.validate(target_fields.len())?;
558 ensure_unique(&target_fields)?;
559 Ok(Self {
560 source_key: RelationSourceKey::from_name(&name),
561 name,
562 source,
563 target_entity,
564 target_fields,
565 on_delete,
566 })
567 }
568
569 #[must_use]
571 pub const fn source_key(&self) -> &RelationSourceKey {
572 &self.source_key
573 }
574
575 #[must_use]
577 pub const fn name(&self) -> &SchemaName {
578 &self.name
579 }
580
581 #[must_use]
583 pub const fn source(&self) -> &RelationSourceFragment {
584 &self.source
585 }
586
587 #[must_use]
589 pub const fn target_entity(&self) -> &EntitySourceKey {
590 &self.target_entity
591 }
592
593 #[must_use]
595 pub fn target_fields(&self) -> &[FieldSourceKey] {
596 &self.target_fields
597 }
598
599 #[must_use]
601 pub const fn on_delete(&self) -> RelationDeleteAction {
602 self.on_delete
603 }
604
605 fn validate(&self) -> Result<(), SchemaContractError> {
606 let rebuilt = Self::try_new(
607 self.name.clone(),
608 self.source.clone(),
609 self.target_entity.clone(),
610 self.target_fields.clone(),
611 self.on_delete,
612 )?;
613 ensure_canonical_rebuild(self, &rebuilt)
614 }
615}
616
617#[derive(Clone, Debug, Eq, PartialEq)]
619pub enum ConstraintFragmentKind {
620 Check(SourceCheckExpr),
622 TargetedRule(TargetedRuleFragment),
624}
625
626impl ConstraintFragmentKind {
627 fn validate(&self) -> Result<(), SchemaContractError> {
628 match self {
629 Self::Check(expression) => expression.validate(),
630 Self::TargetedRule(rule) => rule.validate(),
631 }
632 }
633}
634
635#[derive(Clone, Debug, Eq, PartialEq)]
637pub struct TargetedRuleFragment {
638 root: FieldSourceKey,
639 target_type: TypeSourceKey,
640 rule: RuleSourceKey,
641 operation: SourceRuleOperation,
642}
643
644impl TargetedRuleFragment {
645 #[must_use]
647 pub fn new(
648 root: FieldSourceKey,
649 target_type: TypeSourceKey,
650 rule: SchemaName,
651 operation: SourceRuleOperation,
652 ) -> Self {
653 Self {
654 root,
655 target_type,
656 rule: RuleSourceKey::from_name(&rule),
657 operation,
658 }
659 }
660
661 #[must_use]
663 pub const fn root(&self) -> &FieldSourceKey {
664 &self.root
665 }
666
667 #[must_use]
669 pub const fn target_type(&self) -> &TypeSourceKey {
670 &self.target_type
671 }
672
673 #[must_use]
675 pub const fn rule(&self) -> &RuleSourceKey {
676 &self.rule
677 }
678
679 #[must_use]
681 pub const fn operation(&self) -> &SourceRuleOperation {
682 &self.operation
683 }
684
685 fn validate(&self) -> Result<(), SchemaContractError> {
686 self.operation.validate()
687 }
688}
689
690#[derive(Clone, Debug, Eq, PartialEq)]
692pub struct ConstraintFragment {
693 source_key: ConstraintSourceKey,
694 name: SchemaName,
695 kind: ConstraintFragmentKind,
696}
697
698impl ConstraintFragment {
699 #[must_use]
701 pub fn check(name: SchemaName, expression: SourceCheckExpr) -> Self {
702 Self {
703 source_key: ConstraintSourceKey::from_name(&name),
704 name,
705 kind: ConstraintFragmentKind::Check(expression),
706 }
707 }
708
709 #[must_use]
711 pub fn targeted_rule(rule: TargetedRuleFragment) -> Self {
712 let source_key = ConstraintSourceKey::for_targeted_field_rule(
713 rule.root(),
714 rule.target_type(),
715 rule.rule(),
716 );
717 let name = SchemaName::for_targeted_rule(&source_key);
718 Self {
719 source_key,
720 name,
721 kind: ConstraintFragmentKind::TargetedRule(rule),
722 }
723 }
724
725 #[must_use]
727 pub const fn source_key(&self) -> &ConstraintSourceKey {
728 &self.source_key
729 }
730
731 #[must_use]
733 pub const fn name(&self) -> &SchemaName {
734 &self.name
735 }
736
737 #[must_use]
739 pub const fn kind(&self) -> &ConstraintFragmentKind {
740 &self.kind
741 }
742
743 fn validate(&self) -> Result<(), SchemaContractError> {
744 self.kind.validate()?;
745 let rebuilt = match &self.kind {
746 ConstraintFragmentKind::Check(expression) => {
747 Self::check(self.name.clone(), expression.clone())
748 }
749 ConstraintFragmentKind::TargetedRule(rule) => Self::targeted_rule(rule.clone()),
750 };
751 ensure_canonical_rebuild(self, &rebuilt)
752 }
753}
754
755#[derive(Clone, Debug, Eq, PartialEq)]
757pub struct EntityFragment {
758 source_key: EntitySourceKey,
759 name: SchemaName,
760 version: DeclaredEntityVersion,
761 fields: Vec<FieldFragment>,
762 primary_key: Vec<FieldSourceKey>,
763 indexes: Vec<IndexFragment>,
764 relations: Vec<RelationFragment>,
765 constraints: Vec<ConstraintFragment>,
766}
767
768impl EntityFragment {
769 pub fn try_new(
776 name: SchemaName,
777 version: DeclaredEntityVersion,
778 mut fields: Vec<FieldFragment>,
779 primary_key: Vec<FieldSourceKey>,
780 mut indexes: Vec<IndexFragment>,
781 mut relations: Vec<RelationFragment>,
782 mut constraints: Vec<ConstraintFragment>,
783 ) -> Result<Self, SchemaContractError> {
784 let source_key = EntitySourceKey::from_name(&name);
785 check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
786 check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
787 check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
788 check_len(
789 "entity constraints",
790 constraints.len(),
791 MAX_FRAGMENT_CONSTRAINTS,
792 )?;
793 if primary_key.is_empty() {
794 return Err(SchemaContractError::InvalidReferenceList);
795 }
796 ensure_unique(&primary_key)?;
797 crate::compact_sort_unstable_by(&mut fields, |a, b| a.source_key.cmp(&b.source_key));
800 crate::compact_sort_unstable_by(&mut indexes, |a, b| a.source_key.cmp(&b.source_key));
801 crate::compact_sort_unstable_by(&mut relations, |a, b| a.source_key.cmp(&b.source_key));
802 crate::compact_sort_unstable_by(&mut constraints, |a, b| a.source_key.cmp(&b.source_key));
803 ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
804 ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
805 ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
806 ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
807 ensure_unique_names(fields.iter().map(FieldFragment::name))?;
808 ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
809 ensure_unique_names(relations.iter().map(RelationFragment::name))?;
810 ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
811 for field in &fields {
812 field.validate()?;
813 }
814 validate_management_cardinality(&fields)?;
815 for index in &indexes {
816 index.validate()?;
817 }
818 for relation in &relations {
819 relation.validate()?;
820 }
821 for constraint in &constraints {
822 constraint.validate()?;
823 }
824 let field_keys = fields
825 .iter()
826 .map(|field| field.source_key.clone())
827 .collect::<BTreeSet<_>>();
828 if primary_key.iter().any(|field| !field_keys.contains(field)) {
829 return Err(SchemaContractError::InvalidLocalReference);
830 }
831 validate_insert_generation(&fields, &primary_key)?;
832 for index in &indexes {
833 if index
834 .key()
835 .iter()
836 .any(|component| !field_keys.contains(component.field()))
837 || index.predicate().is_some_and(|predicate| {
838 predicate
839 .dependencies()
840 .iter()
841 .any(|field| !field_keys.contains(field))
842 })
843 {
844 return Err(SchemaContractError::InvalidLocalReference);
845 }
846 }
847 for relation in &relations {
848 if relation
849 .source()
850 .root_fields()
851 .any(|field| !field_keys.contains(field))
852 || (relation.target_entity() == &source_key
853 && relation
854 .target_fields()
855 .iter()
856 .any(|field| !field_keys.contains(field)))
857 {
858 return Err(SchemaContractError::InvalidLocalReference);
859 }
860 }
861 for constraint in &constraints {
862 let invalid = match constraint.kind() {
863 ConstraintFragmentKind::Check(expression) => expression
864 .dependencies()
865 .iter()
866 .any(|field| !field_keys.contains(field)),
867 ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
868 };
869 if invalid {
870 return Err(SchemaContractError::InvalidLocalReference);
871 }
872 }
873 Ok(Self {
874 source_key,
875 name,
876 version,
877 fields,
878 primary_key,
879 indexes,
880 relations,
881 constraints,
882 })
883 }
884
885 #[must_use]
887 pub const fn source_key(&self) -> &EntitySourceKey {
888 &self.source_key
889 }
890
891 #[must_use]
893 pub const fn name(&self) -> &SchemaName {
894 &self.name
895 }
896
897 #[must_use]
899 pub const fn version(&self) -> DeclaredEntityVersion {
900 self.version
901 }
902
903 #[must_use]
905 pub fn fields(&self) -> &[FieldFragment] {
906 &self.fields
907 }
908
909 #[must_use]
911 pub fn primary_key(&self) -> &[FieldSourceKey] {
912 &self.primary_key
913 }
914
915 #[must_use]
917 pub fn indexes(&self) -> &[IndexFragment] {
918 &self.indexes
919 }
920
921 #[must_use]
923 pub fn relations(&self) -> &[RelationFragment] {
924 &self.relations
925 }
926
927 #[must_use]
929 pub fn constraints(&self) -> &[ConstraintFragment] {
930 &self.constraints
931 }
932
933 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
934 let rebuilt = Self::try_new(
935 self.name.clone(),
936 self.version,
937 self.fields.clone(),
938 self.primary_key.clone(),
939 self.indexes.clone(),
940 self.relations.clone(),
941 self.constraints.clone(),
942 )?;
943 ensure_canonical_rebuild(self, &rebuilt)
944 }
945}
946
947fn validate_insert_generation(
951 fields: &[FieldFragment],
952 primary_key: &[FieldSourceKey],
953) -> Result<(), SchemaContractError> {
954 for field in fields {
955 if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
956 continue;
957 }
958 if field.nullable() || field.management().is_some() {
959 return Err(SchemaContractError::InvalidFieldPolicy);
960 }
961 match field.field_type() {
962 FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
963 FieldType::Scalar(
964 ScalarType::Nat8
965 | ScalarType::Nat16
966 | ScalarType::Nat32
967 | ScalarType::Nat64
968 | ScalarType::Nat128,
969 ) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
970 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
971 return Err(SchemaContractError::InvalidFieldPolicy);
972 }
973 }
974 }
975 Ok(())
976}
977
978#[derive(Clone, Debug, Eq, PartialEq)]
983pub struct RecordFieldFragment {
984 source_key: FieldSourceKey,
985 name: SchemaName,
986 field_type: FieldType,
987 nullable: bool,
988}
989
990impl RecordFieldFragment {
991 #[must_use]
993 pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
994 Self {
995 source_key: FieldSourceKey::from_name(&name),
996 name,
997 field_type,
998 nullable,
999 }
1000 }
1001
1002 #[must_use]
1004 pub const fn source_key(&self) -> &FieldSourceKey {
1005 &self.source_key
1006 }
1007
1008 #[must_use]
1010 pub const fn name(&self) -> &SchemaName {
1011 &self.name
1012 }
1013
1014 #[must_use]
1016 pub const fn field_type(&self) -> &FieldType {
1017 &self.field_type
1018 }
1019
1020 #[must_use]
1022 pub const fn nullable(&self) -> bool {
1023 self.nullable
1024 }
1025
1026 fn validate(&self) -> Result<(), SchemaContractError> {
1027 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1028 return Err(SchemaContractError::NonCanonical);
1029 }
1030 self.field_type.validate()
1031 }
1032}
1033
1034#[derive(Clone, Debug, Eq, PartialEq)]
1037pub struct TupleElementFragment {
1038 field_type: FieldType,
1039 nullable: bool,
1040}
1041
1042impl TupleElementFragment {
1043 #[must_use]
1045 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
1046 Self {
1047 field_type,
1048 nullable,
1049 }
1050 }
1051
1052 #[must_use]
1054 pub const fn field_type(&self) -> &FieldType {
1055 &self.field_type
1056 }
1057
1058 #[must_use]
1060 pub const fn nullable(&self) -> bool {
1061 self.nullable
1062 }
1063
1064 const fn validate(&self) -> Result<(), SchemaContractError> {
1065 self.field_type.validate()
1066 }
1067}
1068
1069#[derive(Clone, Debug, Eq, PartialEq)]
1071pub struct RecordTypeFragment {
1072 source_key: TypeSourceKey,
1073 name: SchemaName,
1074 fields: Vec<RecordFieldFragment>,
1075}
1076
1077impl RecordTypeFragment {
1078 pub fn try_new(
1085 name: SchemaName,
1086 mut fields: Vec<RecordFieldFragment>,
1087 ) -> Result<Self, SchemaContractError> {
1088 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
1089 crate::compact_sort_unstable_by(&mut fields, |left, right| {
1090 left.source_key.cmp(&right.source_key)
1091 });
1092 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
1093 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
1094 for field in &fields {
1095 field.validate()?;
1096 }
1097 Ok(Self {
1098 source_key: TypeSourceKey::from_name(&name),
1099 name,
1100 fields,
1101 })
1102 }
1103
1104 #[must_use]
1106 pub const fn source_key(&self) -> &TypeSourceKey {
1107 &self.source_key
1108 }
1109
1110 #[must_use]
1112 pub const fn name(&self) -> &SchemaName {
1113 &self.name
1114 }
1115
1116 #[must_use]
1118 pub fn fields(&self) -> &[RecordFieldFragment] {
1119 &self.fields
1120 }
1121
1122 fn validate(&self) -> Result<(), SchemaContractError> {
1123 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1124 if rebuilt != *self {
1125 return Err(SchemaContractError::NonCanonical);
1126 }
1127 Ok(())
1128 }
1129}
1130
1131#[derive(Clone, Debug, Eq, PartialEq)]
1133pub struct EnumVariantFragment {
1134 source_key: TypeSourceKey,
1135 name: SchemaName,
1136 payload: Option<FieldType>,
1137}
1138
1139impl EnumVariantFragment {
1140 #[must_use]
1142 pub fn new(name: SchemaName) -> Self {
1143 Self {
1144 source_key: TypeSourceKey::from_name(&name),
1145 name,
1146 payload: None,
1147 }
1148 }
1149
1150 #[must_use]
1152 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1153 Self {
1154 source_key: TypeSourceKey::from_name(&name),
1155 name,
1156 payload: Some(payload),
1157 }
1158 }
1159
1160 #[must_use]
1162 pub const fn source_key(&self) -> &TypeSourceKey {
1163 &self.source_key
1164 }
1165
1166 #[must_use]
1168 pub const fn name(&self) -> &SchemaName {
1169 &self.name
1170 }
1171
1172 #[must_use]
1174 pub const fn payload(&self) -> Option<&FieldType> {
1175 self.payload.as_ref()
1176 }
1177
1178 fn validate(&self) -> Result<(), SchemaContractError> {
1179 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1180 return Err(SchemaContractError::NonCanonical);
1181 }
1182 match &self.payload {
1183 Some(payload) => payload.validate(),
1184 None => Ok(()),
1185 }
1186 }
1187}
1188
1189#[derive(Clone, Debug, Eq, PartialEq)]
1191pub struct EnumTypeFragment {
1192 source_key: TypeSourceKey,
1193 name: SchemaName,
1194 variants: Vec<EnumVariantFragment>,
1195}
1196
1197impl EnumTypeFragment {
1198 pub fn try_new(
1205 name: SchemaName,
1206 mut variants: Vec<EnumVariantFragment>,
1207 ) -> Result<Self, SchemaContractError> {
1208 if variants.is_empty() {
1209 return Err(SchemaContractError::InvalidReferenceList);
1210 }
1211 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1212 crate::compact_sort_unstable_by(&mut variants, |left, right| {
1213 left.source_key.cmp(&right.source_key)
1214 });
1215 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1216 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1217 for variant in &variants {
1218 variant.validate()?;
1219 }
1220 Ok(Self {
1221 source_key: TypeSourceKey::from_name(&name),
1222 name,
1223 variants,
1224 })
1225 }
1226
1227 #[must_use]
1229 pub const fn source_key(&self) -> &TypeSourceKey {
1230 &self.source_key
1231 }
1232
1233 #[must_use]
1235 pub const fn name(&self) -> &SchemaName {
1236 &self.name
1237 }
1238
1239 #[must_use]
1241 pub fn variants(&self) -> &[EnumVariantFragment] {
1242 &self.variants
1243 }
1244
1245 fn validate(&self) -> Result<(), SchemaContractError> {
1246 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1247 if rebuilt != *self {
1248 return Err(SchemaContractError::NonCanonical);
1249 }
1250 Ok(())
1251 }
1252}
1253
1254#[derive(Clone, Debug, Eq, PartialEq)]
1256pub enum NamedTypeFragment {
1257 Record(RecordTypeFragment),
1259 Enum(EnumTypeFragment),
1261 Newtype {
1263 source_key: TypeSourceKey,
1265 name: SchemaName,
1267 inner: FieldType,
1269 },
1270 List {
1272 source_key: TypeSourceKey,
1274 name: SchemaName,
1276 item: FieldType,
1278 },
1279 Set {
1281 source_key: TypeSourceKey,
1283 name: SchemaName,
1285 item: FieldType,
1287 },
1288 Map {
1290 source_key: TypeSourceKey,
1292 name: SchemaName,
1294 key: FieldType,
1296 value: FieldType,
1298 },
1299 Tuple {
1301 source_key: TypeSourceKey,
1303 name: SchemaName,
1305 members: Vec<TupleElementFragment>,
1307 },
1308}
1309
1310impl NamedTypeFragment {
1311 #[must_use]
1313 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1314 Self::Newtype {
1315 source_key: TypeSourceKey::from_name(&name),
1316 name,
1317 inner,
1318 }
1319 }
1320
1321 #[must_use]
1323 pub fn list(name: SchemaName, item: FieldType) -> Self {
1324 Self::List {
1325 source_key: TypeSourceKey::from_name(&name),
1326 name,
1327 item,
1328 }
1329 }
1330
1331 #[must_use]
1333 pub fn set(name: SchemaName, item: FieldType) -> Self {
1334 Self::Set {
1335 source_key: TypeSourceKey::from_name(&name),
1336 name,
1337 item,
1338 }
1339 }
1340
1341 #[must_use]
1343 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1344 Self::Map {
1345 source_key: TypeSourceKey::from_name(&name),
1346 name,
1347 key,
1348 value,
1349 }
1350 }
1351
1352 #[must_use]
1354 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1355 Self::Tuple {
1356 source_key: TypeSourceKey::from_name(&name),
1357 name,
1358 members,
1359 }
1360 }
1361
1362 #[must_use]
1364 pub const fn source_key(&self) -> &TypeSourceKey {
1365 match self {
1366 Self::Record(record) => record.source_key(),
1367 Self::Enum(r#enum) => r#enum.source_key(),
1368 Self::Newtype { source_key, .. }
1369 | Self::List { source_key, .. }
1370 | Self::Set { source_key, .. }
1371 | Self::Map { source_key, .. }
1372 | Self::Tuple { source_key, .. } => source_key,
1373 }
1374 }
1375
1376 #[must_use]
1378 pub const fn name(&self) -> &SchemaName {
1379 match self {
1380 Self::Record(record) => record.name(),
1381 Self::Enum(r#enum) => r#enum.name(),
1382 Self::Newtype { name, .. }
1383 | Self::List { name, .. }
1384 | Self::Set { name, .. }
1385 | Self::Map { name, .. }
1386 | Self::Tuple { name, .. } => name,
1387 }
1388 }
1389
1390 fn validate(&self) -> Result<(), SchemaContractError> {
1391 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1392 match self {
1393 Self::Record(record) => record.validate(),
1394 Self::Enum(r#enum) => r#enum.validate(),
1395 Self::Newtype { inner, .. }
1396 | Self::List { item: inner, .. }
1397 | Self::Set { item: inner, .. } => inner.validate(),
1398 Self::Map { key, value, .. } => {
1399 key.validate()?;
1400 value.validate()
1401 }
1402 Self::Tuple { members, .. } => {
1403 if members.is_empty() {
1404 return Err(SchemaContractError::InvalidReferenceList);
1405 }
1406 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1407 members.iter().try_for_each(TupleElementFragment::validate)
1408 }
1409 }
1410 }
1411}
1412
1413#[derive(Clone, Debug, Eq, PartialEq)]
1415pub struct SchemaFragment {
1416 entities: Vec<EntityFragment>,
1417 types: Vec<NamedTypeFragment>,
1418}
1419
1420impl SchemaFragment {
1421 pub fn try_new(
1428 mut entities: Vec<EntityFragment>,
1429 mut types: Vec<NamedTypeFragment>,
1430 ) -> Result<Self, SchemaContractError> {
1431 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1432 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1433 crate::compact_sort_unstable_by(&mut entities, |left, right| {
1434 left.source_key.cmp(&right.source_key)
1435 });
1436 crate::compact_sort_unstable_by(&mut types, |left, right| {
1437 left.source_key().cmp(right.source_key())
1438 });
1439 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1440 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1441 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1442 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1443 for entity in &entities {
1444 entity.validate()?;
1445 }
1446 for r#type in &types {
1447 r#type.validate()?;
1448 }
1449 Ok(Self { entities, types })
1450 }
1451
1452 #[must_use]
1454 pub fn entities(&self) -> &[EntityFragment] {
1455 &self.entities
1456 }
1457
1458 #[must_use]
1460 pub fn types(&self) -> &[NamedTypeFragment] {
1461 &self.types
1462 }
1463
1464 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1465 for r#type in &self.types {
1466 r#type.validate()?;
1467 }
1468 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1469 if rebuilt != *self {
1470 return Err(SchemaContractError::NonCanonical);
1471 }
1472 Ok(())
1473 }
1474}
1475
1476pub(crate) const fn check_len(
1477 kind: &'static str,
1478 len: usize,
1479 max: usize,
1480) -> Result<(), SchemaContractError> {
1481 if len > max {
1482 return Err(SchemaContractError::TooManyItems { kind, len, max });
1483 }
1484 Ok(())
1485}
1486
1487fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1488 source_key == name.as_str()
1489}
1490
1491fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1492 if !current_name_key_matches(source_key, name) {
1493 return Err(SchemaContractError::NonCanonical);
1494 }
1495 Ok(())
1496}
1497
1498fn ensure_canonical_rebuild<T: PartialEq>(
1499 current: &T,
1500 rebuilt: &T,
1501) -> Result<(), SchemaContractError> {
1502 if current != rebuilt {
1503 return Err(SchemaContractError::NonCanonical);
1504 }
1505 Ok(())
1506}
1507
1508fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1509where
1510 T: Ord,
1511{
1512 let mut seen = BTreeSet::new();
1513 if values.iter().any(|value| !seen.insert(value)) {
1514 return Err(SchemaContractError::InvalidReferenceList);
1515 }
1516 Ok(())
1517}
1518
1519fn ensure_unique_sorted_by<T, K>(
1520 values: &[T],
1521 key: impl Fn(&T) -> &K,
1522) -> Result<(), SchemaContractError>
1523where
1524 K: Eq,
1525{
1526 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1527 return Err(SchemaContractError::DuplicateSourceKey);
1528 }
1529 Ok(())
1530}
1531
1532fn ensure_unique_names<'a>(
1533 names: impl IntoIterator<Item = &'a SchemaName>,
1534) -> Result<(), SchemaContractError> {
1535 let mut seen = BTreeSet::new();
1536 if names.into_iter().any(|name| !seen.insert(name)) {
1537 return Err(SchemaContractError::DuplicateName);
1538 }
1539 Ok(())
1540}
1541
1542fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1543 for policy in [
1544 FieldManagementPolicy::CreatedAt,
1545 FieldManagementPolicy::UpdatedAt,
1546 ] {
1547 if fields
1548 .iter()
1549 .filter(|field| field.management() == Some(policy))
1550 .count()
1551 > 1
1552 {
1553 return Err(SchemaContractError::InvalidFieldPolicy);
1554 }
1555 }
1556 Ok(())
1557}
1558
1559fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1560 match value.scale().cmp(&scale) {
1561 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1562 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1563 }
1564}
1565
1566#[cfg(test)]
1567mod tests {
1568 use super::{
1569 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1570 SchemaContractError, SchemaName,
1571 };
1572
1573 #[test]
1574 fn independently_decoded_field_key_and_name_must_match() {
1575 let field = FieldFragment {
1576 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1577 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1578 field_type: FieldType::Scalar(ScalarType::Nat64),
1579 nullable: false,
1580 insert_policy: FieldInsertPolicy::Required,
1581 management: None,
1582 };
1583
1584 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1585 }
1586}