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.leb128_len() <= u64::from(max_bytes)
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.leb128_len() <= u64::from(max_bytes)
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 fn append_named_type_dependencies(&self, pending: &mut Vec<TypeSourceKey>) {
189 let mut field_type = self;
190 while let Self::List(item) = field_type {
191 field_type = item;
192 }
193 if let Self::Named(source) = field_type {
194 pending.push(source.clone());
195 }
196 }
197
198 pub(crate) const fn validate(&self) -> Result<(), SchemaContractError> {
199 self.validate_at_depth(0)
200 }
201
202 const fn validate_at_depth(&self, depth: usize) -> Result<(), SchemaContractError> {
203 let Some(depth) = depth.checked_add(1) else {
204 return Err(SchemaContractError::FieldTypeDepthExceeded);
205 };
206 if depth > MAX_SCHEMA_FIELD_TYPE_DEPTH {
207 return Err(SchemaContractError::FieldTypeDepthExceeded);
208 }
209 match self {
210 Self::Scalar(scalar) => scalar.validate(),
211 Self::List(item) => item.validate_at_depth(depth),
212 Self::Named(_) => Ok(()),
213 }
214 }
215}
216
217#[derive(Clone, Debug, Eq, PartialEq)]
219pub enum FieldInsertPolicy {
220 Required,
222 Nullable,
224 Default(ScalarLiteral),
226 Generated,
228}
229
230#[derive(Clone, Copy, Debug, Eq, PartialEq)]
232pub enum FieldManagementPolicy {
233 CreatedAt,
235 UpdatedAt,
237}
238
239#[derive(Clone, Debug, Eq, PartialEq)]
241pub struct FieldFragment {
242 source_key: FieldSourceKey,
243 name: SchemaName,
244 field_type: FieldType,
245 nullable: bool,
246 insert_policy: FieldInsertPolicy,
247 management: Option<FieldManagementPolicy>,
248}
249
250impl FieldFragment {
251 #[must_use]
253 pub fn new(
254 name: SchemaName,
255 field_type: FieldType,
256 nullable: bool,
257 insert_policy: FieldInsertPolicy,
258 management: Option<FieldManagementPolicy>,
259 ) -> Self {
260 Self {
261 source_key: FieldSourceKey::from_name(&name),
262 name,
263 field_type,
264 nullable,
265 insert_policy,
266 management,
267 }
268 }
269
270 #[must_use]
272 pub const fn source_key(&self) -> &FieldSourceKey {
273 &self.source_key
274 }
275
276 #[must_use]
278 pub const fn name(&self) -> &SchemaName {
279 &self.name
280 }
281
282 #[must_use]
284 pub const fn field_type(&self) -> &FieldType {
285 &self.field_type
286 }
287
288 #[must_use]
290 pub const fn nullable(&self) -> bool {
291 self.nullable
292 }
293
294 #[must_use]
296 pub const fn insert_policy(&self) -> &FieldInsertPolicy {
297 &self.insert_policy
298 }
299
300 #[must_use]
302 pub const fn management(&self) -> Option<FieldManagementPolicy> {
303 self.management
304 }
305
306 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
307 ensure_current_name_key(self.source_key.as_str(), &self.name)?;
308 self.field_type.validate()?;
309 if let FieldInsertPolicy::Default(literal) = &self.insert_policy {
310 literal.validate()?;
311 match &self.field_type {
312 FieldType::Scalar(scalar) if scalar.accepts_literal(literal) => {}
313 FieldType::Named(_) if matches!(literal, ScalarLiteral::EnumUnit { .. }) => {}
314 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
315 return Err(SchemaContractError::LiteralTypeMismatch);
316 }
317 }
318 }
319 if matches!(self.insert_policy, FieldInsertPolicy::Nullable) && !self.nullable {
320 return Err(SchemaContractError::InvalidFieldPolicy);
321 }
322 if self.management.is_some()
323 && (!matches!(self.field_type, FieldType::Scalar(ScalarType::Timestamp))
324 || self.nullable
325 || !matches!(self.insert_policy, FieldInsertPolicy::Required))
326 {
327 return Err(SchemaContractError::InvalidFieldPolicy);
328 }
329 Ok(())
330 }
331}
332
333#[derive(Clone, Debug, Eq, PartialEq)]
335pub enum IndexKeyFragment {
336 Field(FieldSourceKey),
338 Lower(FieldSourceKey),
340 Upper(FieldSourceKey),
342 Trim(FieldSourceKey),
344 LowerTrim(FieldSourceKey),
346 Date(FieldSourceKey),
348 Year(FieldSourceKey),
350 Month(FieldSourceKey),
352 Day(FieldSourceKey),
354}
355
356impl IndexKeyFragment {
357 #[must_use]
359 pub const fn field(&self) -> &FieldSourceKey {
360 match self {
361 Self::Field(field)
362 | Self::Lower(field)
363 | Self::Upper(field)
364 | Self::Trim(field)
365 | Self::LowerTrim(field)
366 | Self::Date(field)
367 | Self::Year(field)
368 | Self::Month(field)
369 | Self::Day(field) => field,
370 }
371 }
372}
373
374#[derive(Clone, Debug, Eq, PartialEq)]
376pub struct IndexFragment {
377 source_key: IndexSourceKey,
378 name: SchemaName,
379 key: Vec<IndexKeyFragment>,
380 unique: bool,
381 predicate: Option<SourceCheckExpr>,
382}
383
384impl IndexFragment {
385 pub fn try_new(
391 name: SchemaName,
392 key: Vec<IndexKeyFragment>,
393 unique: bool,
394 predicate: Option<SourceCheckExpr>,
395 ) -> Result<Self, SchemaContractError> {
396 if key.is_empty() {
397 return Err(SchemaContractError::InvalidReferenceList);
398 }
399 if let Some(predicate) = &predicate {
400 predicate.validate()?;
401 }
402 Ok(Self {
403 source_key: IndexSourceKey::from_name(&name),
404 name,
405 key,
406 unique,
407 predicate,
408 })
409 }
410
411 #[must_use]
413 pub const fn source_key(&self) -> &IndexSourceKey {
414 &self.source_key
415 }
416
417 #[must_use]
419 pub const fn name(&self) -> &SchemaName {
420 &self.name
421 }
422
423 #[must_use]
425 pub fn key(&self) -> &[IndexKeyFragment] {
426 &self.key
427 }
428
429 #[must_use]
431 pub const fn unique(&self) -> bool {
432 self.unique
433 }
434
435 #[must_use]
437 pub const fn predicate(&self) -> Option<&SourceCheckExpr> {
438 self.predicate.as_ref()
439 }
440
441 fn validate(&self) -> Result<(), SchemaContractError> {
442 let rebuilt = Self::try_new(
443 self.name.clone(),
444 self.key.clone(),
445 self.unique,
446 self.predicate.clone(),
447 )?;
448 ensure_canonical_rebuild(self, &rebuilt)
449 }
450}
451
452#[derive(Clone, Copy, Debug, Eq, PartialEq)]
454pub enum RelationDeleteAction {
455 Restrict,
457}
458
459#[derive(Clone, Debug, Eq, PartialEq)]
461pub enum RelationPathStepFragment {
462 EnterNamed { r#type: TypeSourceKey },
464 OptionalSome,
466 RecordMember {
468 record: TypeSourceKey,
470 field: FieldSourceKey,
472 },
473 EnumVariantPayload {
475 r#enum: TypeSourceKey,
477 variant: TypeSourceKey,
479 },
480 ListItems,
482 SetItems,
484 MapValues,
486}
487
488#[derive(Clone, Debug, Eq, PartialEq)]
490pub enum RelationSourceFragment {
491 Direct { fields: Vec<FieldSourceKey> },
493 Nested {
495 root: FieldSourceKey,
497 steps: Vec<RelationPathStepFragment>,
499 },
500}
501
502impl RelationSourceFragment {
503 #[must_use]
505 pub const fn direct(fields: Vec<FieldSourceKey>) -> Self {
506 Self::Direct { fields }
507 }
508
509 pub fn root_fields(&self) -> impl Iterator<Item = &FieldSourceKey> {
511 let direct = match self {
512 Self::Direct { fields } => fields.as_slice(),
513 Self::Nested { .. } => &[],
514 };
515 let nested = match self {
516 Self::Nested { root, .. } => Some(root),
517 Self::Direct { .. } => None,
518 };
519 direct.iter().chain(nested)
520 }
521
522 fn validate(&self, target_field_count: usize) -> Result<(), SchemaContractError> {
523 match self {
524 Self::Direct { fields } => {
525 if fields.is_empty() || fields.len() != target_field_count {
526 return Err(SchemaContractError::InvalidReferenceList);
527 }
528 ensure_unique(fields)
529 }
530 Self::Nested { steps, .. } => {
531 if steps.is_empty()
532 || steps.len() > MAX_RELATION_PATH_STEPS
533 || target_field_count != 1
534 {
535 return Err(SchemaContractError::InvalidReferenceList);
536 }
537 Ok(())
538 }
539 }
540 }
541}
542
543#[derive(Clone, Debug, Eq, PartialEq)]
545pub struct RelationFragment {
546 source_key: RelationSourceKey,
547 name: SchemaName,
548 source: RelationSourceFragment,
549 target_entity: EntitySourceKey,
550 target_fields: Vec<FieldSourceKey>,
551 on_delete: RelationDeleteAction,
552}
553
554impl RelationFragment {
555 pub fn try_new(
562 name: SchemaName,
563 source: RelationSourceFragment,
564 target_entity: EntitySourceKey,
565 target_fields: Vec<FieldSourceKey>,
566 on_delete: RelationDeleteAction,
567 ) -> Result<Self, SchemaContractError> {
568 if target_fields.is_empty() {
569 return Err(SchemaContractError::InvalidReferenceList);
570 }
571 source.validate(target_fields.len())?;
572 ensure_unique(&target_fields)?;
573 Ok(Self {
574 source_key: RelationSourceKey::from_name(&name),
575 name,
576 source,
577 target_entity,
578 target_fields,
579 on_delete,
580 })
581 }
582
583 #[must_use]
585 pub const fn source_key(&self) -> &RelationSourceKey {
586 &self.source_key
587 }
588
589 #[must_use]
591 pub const fn name(&self) -> &SchemaName {
592 &self.name
593 }
594
595 #[must_use]
597 pub const fn source(&self) -> &RelationSourceFragment {
598 &self.source
599 }
600
601 #[must_use]
603 pub const fn target_entity(&self) -> &EntitySourceKey {
604 &self.target_entity
605 }
606
607 #[must_use]
609 pub fn target_fields(&self) -> &[FieldSourceKey] {
610 &self.target_fields
611 }
612
613 #[must_use]
615 pub const fn on_delete(&self) -> RelationDeleteAction {
616 self.on_delete
617 }
618
619 fn validate(&self) -> Result<(), SchemaContractError> {
620 let rebuilt = Self::try_new(
621 self.name.clone(),
622 self.source.clone(),
623 self.target_entity.clone(),
624 self.target_fields.clone(),
625 self.on_delete,
626 )?;
627 ensure_canonical_rebuild(self, &rebuilt)
628 }
629}
630
631#[derive(Clone, Debug, Eq, PartialEq)]
633pub enum ConstraintFragmentKind {
634 Check(SourceCheckExpr),
636 TargetedRule(TargetedRuleFragment),
638}
639
640impl ConstraintFragmentKind {
641 fn validate(&self) -> Result<(), SchemaContractError> {
642 match self {
643 Self::Check(expression) => expression.validate(),
644 Self::TargetedRule(rule) => rule.validate(),
645 }
646 }
647}
648
649#[derive(Clone, Debug, Eq, PartialEq)]
651pub struct TargetedRuleFragment {
652 root: FieldSourceKey,
653 target_type: TypeSourceKey,
654 rule: RuleSourceKey,
655 operation: SourceRuleOperation,
656}
657
658impl TargetedRuleFragment {
659 #[must_use]
661 pub fn new(
662 root: FieldSourceKey,
663 target_type: TypeSourceKey,
664 rule: SchemaName,
665 operation: SourceRuleOperation,
666 ) -> Self {
667 Self {
668 root,
669 target_type,
670 rule: RuleSourceKey::from_name(&rule),
671 operation,
672 }
673 }
674
675 #[must_use]
677 pub const fn root(&self) -> &FieldSourceKey {
678 &self.root
679 }
680
681 #[must_use]
683 pub const fn target_type(&self) -> &TypeSourceKey {
684 &self.target_type
685 }
686
687 #[must_use]
689 pub const fn rule(&self) -> &RuleSourceKey {
690 &self.rule
691 }
692
693 #[must_use]
695 pub const fn operation(&self) -> &SourceRuleOperation {
696 &self.operation
697 }
698
699 fn validate(&self) -> Result<(), SchemaContractError> {
700 self.operation.validate()
701 }
702}
703
704#[derive(Clone, Debug, Eq, PartialEq)]
706pub struct ConstraintFragment {
707 source_key: ConstraintSourceKey,
708 name: SchemaName,
709 kind: ConstraintFragmentKind,
710}
711
712impl ConstraintFragment {
713 #[must_use]
715 pub fn check(name: SchemaName, expression: SourceCheckExpr) -> Self {
716 Self {
717 source_key: ConstraintSourceKey::from_name(&name),
718 name,
719 kind: ConstraintFragmentKind::Check(expression),
720 }
721 }
722
723 #[must_use]
725 pub fn targeted_rule(rule: TargetedRuleFragment) -> Self {
726 let source_key = ConstraintSourceKey::for_targeted_field_rule(
727 rule.root(),
728 rule.target_type(),
729 rule.rule(),
730 );
731 let name = SchemaName::for_targeted_rule(&source_key);
732 Self {
733 source_key,
734 name,
735 kind: ConstraintFragmentKind::TargetedRule(rule),
736 }
737 }
738
739 #[must_use]
741 pub const fn source_key(&self) -> &ConstraintSourceKey {
742 &self.source_key
743 }
744
745 #[must_use]
747 pub const fn name(&self) -> &SchemaName {
748 &self.name
749 }
750
751 #[must_use]
753 pub const fn kind(&self) -> &ConstraintFragmentKind {
754 &self.kind
755 }
756
757 fn validate(&self) -> Result<(), SchemaContractError> {
758 self.kind.validate()?;
759 let rebuilt = match &self.kind {
760 ConstraintFragmentKind::Check(expression) => {
761 Self::check(self.name.clone(), expression.clone())
762 }
763 ConstraintFragmentKind::TargetedRule(rule) => Self::targeted_rule(rule.clone()),
764 };
765 ensure_canonical_rebuild(self, &rebuilt)
766 }
767}
768
769#[derive(Clone, Debug, Eq, PartialEq)]
771pub struct EntityFragment {
772 source_key: EntitySourceKey,
773 name: SchemaName,
774 version: DeclaredEntityVersion,
775 fields: Vec<FieldFragment>,
776 primary_key: Vec<FieldSourceKey>,
777 indexes: Vec<IndexFragment>,
778 relations: Vec<RelationFragment>,
779 constraints: Vec<ConstraintFragment>,
780}
781
782impl EntityFragment {
783 pub fn try_new(
790 name: SchemaName,
791 version: DeclaredEntityVersion,
792 mut fields: Vec<FieldFragment>,
793 primary_key: Vec<FieldSourceKey>,
794 mut indexes: Vec<IndexFragment>,
795 mut relations: Vec<RelationFragment>,
796 mut constraints: Vec<ConstraintFragment>,
797 ) -> Result<Self, SchemaContractError> {
798 let source_key = EntitySourceKey::from_name(&name);
799 check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
800 check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
801 check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
802 check_len(
803 "entity constraints",
804 constraints.len(),
805 MAX_FRAGMENT_CONSTRAINTS,
806 )?;
807 if primary_key.is_empty() {
808 return Err(SchemaContractError::InvalidReferenceList);
809 }
810 ensure_unique(&primary_key)?;
811 crate::compact_sort_unstable_by(&mut fields, |a, b| a.source_key.cmp(&b.source_key));
814 crate::compact_sort_unstable_by(&mut indexes, |a, b| a.source_key.cmp(&b.source_key));
815 crate::compact_sort_unstable_by(&mut relations, |a, b| a.source_key.cmp(&b.source_key));
816 crate::compact_sort_unstable_by(&mut constraints, |a, b| a.source_key.cmp(&b.source_key));
817 ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
818 ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
819 ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
820 ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
821 ensure_unique_names(fields.iter().map(FieldFragment::name))?;
822 ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
823 ensure_unique_names(relations.iter().map(RelationFragment::name))?;
824 ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
825 for field in &fields {
826 field.validate()?;
827 }
828 validate_management_cardinality(&fields)?;
829 for index in &indexes {
830 index.validate()?;
831 }
832 for relation in &relations {
833 relation.validate()?;
834 }
835 for constraint in &constraints {
836 constraint.validate()?;
837 }
838 let field_keys = fields
839 .iter()
840 .map(|field| field.source_key.clone())
841 .collect::<BTreeSet<_>>();
842 if primary_key.iter().any(|field| !field_keys.contains(field)) {
843 return Err(SchemaContractError::InvalidLocalReference);
844 }
845 validate_insert_generation(&fields, &primary_key)?;
846 for index in &indexes {
847 if index
848 .key()
849 .iter()
850 .any(|component| !field_keys.contains(component.field()))
851 || index.predicate().is_some_and(|predicate| {
852 predicate
853 .dependencies()
854 .iter()
855 .any(|field| !field_keys.contains(field))
856 })
857 {
858 return Err(SchemaContractError::InvalidLocalReference);
859 }
860 }
861 for relation in &relations {
862 if relation
863 .source()
864 .root_fields()
865 .any(|field| !field_keys.contains(field))
866 || (relation.target_entity() == &source_key
867 && relation
868 .target_fields()
869 .iter()
870 .any(|field| !field_keys.contains(field)))
871 {
872 return Err(SchemaContractError::InvalidLocalReference);
873 }
874 }
875 for constraint in &constraints {
876 let invalid = match constraint.kind() {
877 ConstraintFragmentKind::Check(expression) => expression
878 .dependencies()
879 .iter()
880 .any(|field| !field_keys.contains(field)),
881 ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
882 };
883 if invalid {
884 return Err(SchemaContractError::InvalidLocalReference);
885 }
886 }
887 Ok(Self {
888 source_key,
889 name,
890 version,
891 fields,
892 primary_key,
893 indexes,
894 relations,
895 constraints,
896 })
897 }
898
899 #[must_use]
901 pub const fn source_key(&self) -> &EntitySourceKey {
902 &self.source_key
903 }
904
905 #[must_use]
907 pub const fn name(&self) -> &SchemaName {
908 &self.name
909 }
910
911 #[must_use]
913 pub const fn version(&self) -> DeclaredEntityVersion {
914 self.version
915 }
916
917 #[must_use]
919 pub fn fields(&self) -> &[FieldFragment] {
920 &self.fields
921 }
922
923 #[must_use]
925 pub fn primary_key(&self) -> &[FieldSourceKey] {
926 &self.primary_key
927 }
928
929 #[must_use]
931 pub fn indexes(&self) -> &[IndexFragment] {
932 &self.indexes
933 }
934
935 #[must_use]
937 pub fn relations(&self) -> &[RelationFragment] {
938 &self.relations
939 }
940
941 #[must_use]
943 pub fn constraints(&self) -> &[ConstraintFragment] {
944 &self.constraints
945 }
946
947 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
948 let rebuilt = Self::try_new(
949 self.name.clone(),
950 self.version,
951 self.fields.clone(),
952 self.primary_key.clone(),
953 self.indexes.clone(),
954 self.relations.clone(),
955 self.constraints.clone(),
956 )?;
957 ensure_canonical_rebuild(self, &rebuilt)
958 }
959}
960
961fn validate_insert_generation(
965 fields: &[FieldFragment],
966 primary_key: &[FieldSourceKey],
967) -> Result<(), SchemaContractError> {
968 for field in fields {
969 if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
970 continue;
971 }
972 if field.nullable() || field.management().is_some() {
973 return Err(SchemaContractError::InvalidFieldPolicy);
974 }
975 match field.field_type() {
976 FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
977 FieldType::Scalar(
978 ScalarType::Nat8
979 | ScalarType::Nat16
980 | ScalarType::Nat32
981 | ScalarType::Nat64
982 | ScalarType::Nat128,
983 ) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
984 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
985 return Err(SchemaContractError::InvalidFieldPolicy);
986 }
987 }
988 }
989 Ok(())
990}
991
992#[derive(Clone, Debug, Eq, PartialEq)]
997pub struct RecordFieldFragment {
998 source_key: FieldSourceKey,
999 name: SchemaName,
1000 field_type: FieldType,
1001 nullable: bool,
1002}
1003
1004impl RecordFieldFragment {
1005 #[must_use]
1007 pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
1008 Self {
1009 source_key: FieldSourceKey::from_name(&name),
1010 name,
1011 field_type,
1012 nullable,
1013 }
1014 }
1015
1016 #[must_use]
1018 pub const fn source_key(&self) -> &FieldSourceKey {
1019 &self.source_key
1020 }
1021
1022 #[must_use]
1024 pub const fn name(&self) -> &SchemaName {
1025 &self.name
1026 }
1027
1028 #[must_use]
1030 pub const fn field_type(&self) -> &FieldType {
1031 &self.field_type
1032 }
1033
1034 #[must_use]
1036 pub const fn nullable(&self) -> bool {
1037 self.nullable
1038 }
1039
1040 fn validate(&self) -> Result<(), SchemaContractError> {
1041 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1042 return Err(SchemaContractError::NonCanonical);
1043 }
1044 self.field_type.validate()
1045 }
1046}
1047
1048#[derive(Clone, Debug, Eq, PartialEq)]
1051pub struct TupleElementFragment {
1052 field_type: FieldType,
1053 nullable: bool,
1054}
1055
1056impl TupleElementFragment {
1057 #[must_use]
1059 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
1060 Self {
1061 field_type,
1062 nullable,
1063 }
1064 }
1065
1066 #[must_use]
1068 pub const fn field_type(&self) -> &FieldType {
1069 &self.field_type
1070 }
1071
1072 #[must_use]
1074 pub const fn nullable(&self) -> bool {
1075 self.nullable
1076 }
1077
1078 const fn validate(&self) -> Result<(), SchemaContractError> {
1079 self.field_type.validate()
1080 }
1081}
1082
1083#[derive(Clone, Debug, Eq, PartialEq)]
1085pub struct RecordTypeFragment {
1086 source_key: TypeSourceKey,
1087 name: SchemaName,
1088 fields: Vec<RecordFieldFragment>,
1089}
1090
1091impl RecordTypeFragment {
1092 pub fn try_new(
1099 name: SchemaName,
1100 mut fields: Vec<RecordFieldFragment>,
1101 ) -> Result<Self, SchemaContractError> {
1102 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
1103 crate::compact_sort_unstable_by(&mut fields, |left, right| {
1104 left.source_key.cmp(&right.source_key)
1105 });
1106 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
1107 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
1108 for field in &fields {
1109 field.validate()?;
1110 }
1111 Ok(Self {
1112 source_key: TypeSourceKey::from_name(&name),
1113 name,
1114 fields,
1115 })
1116 }
1117
1118 #[must_use]
1120 pub const fn source_key(&self) -> &TypeSourceKey {
1121 &self.source_key
1122 }
1123
1124 #[must_use]
1126 pub const fn name(&self) -> &SchemaName {
1127 &self.name
1128 }
1129
1130 #[must_use]
1132 pub fn fields(&self) -> &[RecordFieldFragment] {
1133 &self.fields
1134 }
1135
1136 fn validate(&self) -> Result<(), SchemaContractError> {
1137 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1138 if rebuilt != *self {
1139 return Err(SchemaContractError::NonCanonical);
1140 }
1141 Ok(())
1142 }
1143}
1144
1145#[derive(Clone, Debug, Eq, PartialEq)]
1147pub struct EnumVariantFragment {
1148 source_key: TypeSourceKey,
1149 name: SchemaName,
1150 payload: Option<FieldType>,
1151}
1152
1153impl EnumVariantFragment {
1154 #[must_use]
1156 pub fn new(name: SchemaName) -> Self {
1157 Self {
1158 source_key: TypeSourceKey::from_name(&name),
1159 name,
1160 payload: None,
1161 }
1162 }
1163
1164 #[must_use]
1166 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1167 Self {
1168 source_key: TypeSourceKey::from_name(&name),
1169 name,
1170 payload: Some(payload),
1171 }
1172 }
1173
1174 #[must_use]
1176 pub const fn source_key(&self) -> &TypeSourceKey {
1177 &self.source_key
1178 }
1179
1180 #[must_use]
1182 pub const fn name(&self) -> &SchemaName {
1183 &self.name
1184 }
1185
1186 #[must_use]
1188 pub const fn payload(&self) -> Option<&FieldType> {
1189 self.payload.as_ref()
1190 }
1191
1192 fn validate(&self) -> Result<(), SchemaContractError> {
1193 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1194 return Err(SchemaContractError::NonCanonical);
1195 }
1196 match &self.payload {
1197 Some(payload) => payload.validate(),
1198 None => Ok(()),
1199 }
1200 }
1201}
1202
1203#[derive(Clone, Debug, Eq, PartialEq)]
1205pub struct EnumTypeFragment {
1206 source_key: TypeSourceKey,
1207 name: SchemaName,
1208 variants: Vec<EnumVariantFragment>,
1209}
1210
1211impl EnumTypeFragment {
1212 pub fn try_new(
1219 name: SchemaName,
1220 mut variants: Vec<EnumVariantFragment>,
1221 ) -> Result<Self, SchemaContractError> {
1222 if variants.is_empty() {
1223 return Err(SchemaContractError::InvalidReferenceList);
1224 }
1225 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1226 crate::compact_sort_unstable_by(&mut variants, |left, right| {
1227 left.source_key.cmp(&right.source_key)
1228 });
1229 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1230 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1231 for variant in &variants {
1232 variant.validate()?;
1233 }
1234 Ok(Self {
1235 source_key: TypeSourceKey::from_name(&name),
1236 name,
1237 variants,
1238 })
1239 }
1240
1241 #[must_use]
1243 pub const fn source_key(&self) -> &TypeSourceKey {
1244 &self.source_key
1245 }
1246
1247 #[must_use]
1249 pub const fn name(&self) -> &SchemaName {
1250 &self.name
1251 }
1252
1253 #[must_use]
1255 pub fn variants(&self) -> &[EnumVariantFragment] {
1256 &self.variants
1257 }
1258
1259 fn validate(&self) -> Result<(), SchemaContractError> {
1260 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1261 if rebuilt != *self {
1262 return Err(SchemaContractError::NonCanonical);
1263 }
1264 Ok(())
1265 }
1266}
1267
1268#[derive(Clone, Debug, Eq, PartialEq)]
1270pub enum NamedTypeFragment {
1271 Record(RecordTypeFragment),
1273 Enum(EnumTypeFragment),
1275 Newtype {
1277 source_key: TypeSourceKey,
1279 name: SchemaName,
1281 inner: FieldType,
1283 },
1284 List {
1286 source_key: TypeSourceKey,
1288 name: SchemaName,
1290 item: FieldType,
1292 },
1293 Set {
1295 source_key: TypeSourceKey,
1297 name: SchemaName,
1299 item: FieldType,
1301 },
1302 Map {
1304 source_key: TypeSourceKey,
1306 name: SchemaName,
1308 key: FieldType,
1310 value: FieldType,
1312 },
1313 Tuple {
1315 source_key: TypeSourceKey,
1317 name: SchemaName,
1319 members: Vec<TupleElementFragment>,
1321 },
1322}
1323
1324impl NamedTypeFragment {
1325 pub fn append_named_type_dependencies(&self, pending: &mut Vec<TypeSourceKey>) {
1331 match self {
1332 Self::Record(record) => {
1333 for field in record.fields() {
1334 field.field_type().append_named_type_dependencies(pending);
1335 }
1336 }
1337 Self::Enum(r#enum) => {
1338 for variant in r#enum.variants() {
1339 if let Some(payload) = variant.payload() {
1340 payload.append_named_type_dependencies(pending);
1341 }
1342 }
1343 }
1344 Self::Newtype { inner, .. }
1345 | Self::List { item: inner, .. }
1346 | Self::Set { item: inner, .. } => {
1347 inner.append_named_type_dependencies(pending);
1348 }
1349 Self::Map { key, value, .. } => {
1350 key.append_named_type_dependencies(pending);
1351 value.append_named_type_dependencies(pending);
1352 }
1353 Self::Tuple { members, .. } => {
1354 for member in members {
1355 member.field_type().append_named_type_dependencies(pending);
1356 }
1357 }
1358 }
1359 }
1360
1361 #[must_use]
1363 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1364 Self::Newtype {
1365 source_key: TypeSourceKey::from_name(&name),
1366 name,
1367 inner,
1368 }
1369 }
1370
1371 #[must_use]
1373 pub fn list(name: SchemaName, item: FieldType) -> Self {
1374 Self::List {
1375 source_key: TypeSourceKey::from_name(&name),
1376 name,
1377 item,
1378 }
1379 }
1380
1381 #[must_use]
1383 pub fn set(name: SchemaName, item: FieldType) -> Self {
1384 Self::Set {
1385 source_key: TypeSourceKey::from_name(&name),
1386 name,
1387 item,
1388 }
1389 }
1390
1391 #[must_use]
1393 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1394 Self::Map {
1395 source_key: TypeSourceKey::from_name(&name),
1396 name,
1397 key,
1398 value,
1399 }
1400 }
1401
1402 #[must_use]
1404 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1405 Self::Tuple {
1406 source_key: TypeSourceKey::from_name(&name),
1407 name,
1408 members,
1409 }
1410 }
1411
1412 #[must_use]
1414 pub const fn source_key(&self) -> &TypeSourceKey {
1415 match self {
1416 Self::Record(record) => record.source_key(),
1417 Self::Enum(r#enum) => r#enum.source_key(),
1418 Self::Newtype { source_key, .. }
1419 | Self::List { source_key, .. }
1420 | Self::Set { source_key, .. }
1421 | Self::Map { source_key, .. }
1422 | Self::Tuple { source_key, .. } => source_key,
1423 }
1424 }
1425
1426 #[must_use]
1428 pub const fn name(&self) -> &SchemaName {
1429 match self {
1430 Self::Record(record) => record.name(),
1431 Self::Enum(r#enum) => r#enum.name(),
1432 Self::Newtype { name, .. }
1433 | Self::List { name, .. }
1434 | Self::Set { name, .. }
1435 | Self::Map { name, .. }
1436 | Self::Tuple { name, .. } => name,
1437 }
1438 }
1439
1440 fn validate(&self) -> Result<(), SchemaContractError> {
1441 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1442 match self {
1443 Self::Record(record) => record.validate(),
1444 Self::Enum(r#enum) => r#enum.validate(),
1445 Self::Newtype { inner, .. }
1446 | Self::List { item: inner, .. }
1447 | Self::Set { item: inner, .. } => inner.validate(),
1448 Self::Map { key, value, .. } => {
1449 key.validate()?;
1450 value.validate()
1451 }
1452 Self::Tuple { members, .. } => {
1453 if members.is_empty() {
1454 return Err(SchemaContractError::InvalidReferenceList);
1455 }
1456 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1457 members.iter().try_for_each(TupleElementFragment::validate)
1458 }
1459 }
1460 }
1461}
1462
1463#[derive(Clone, Debug, Eq, PartialEq)]
1465pub struct SchemaFragment {
1466 entities: Vec<EntityFragment>,
1467 types: Vec<NamedTypeFragment>,
1468}
1469
1470impl SchemaFragment {
1471 pub fn try_new(
1478 mut entities: Vec<EntityFragment>,
1479 mut types: Vec<NamedTypeFragment>,
1480 ) -> Result<Self, SchemaContractError> {
1481 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1482 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1483 crate::compact_sort_unstable_by(&mut entities, |left, right| {
1484 left.source_key.cmp(&right.source_key)
1485 });
1486 crate::compact_sort_unstable_by(&mut types, |left, right| {
1487 left.source_key().cmp(right.source_key())
1488 });
1489 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1490 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1491 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1492 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1493 for entity in &entities {
1494 entity.validate()?;
1495 }
1496 for r#type in &types {
1497 r#type.validate()?;
1498 }
1499 Ok(Self { entities, types })
1500 }
1501
1502 #[must_use]
1504 pub fn entities(&self) -> &[EntityFragment] {
1505 &self.entities
1506 }
1507
1508 #[must_use]
1510 pub fn types(&self) -> &[NamedTypeFragment] {
1511 &self.types
1512 }
1513
1514 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1515 for r#type in &self.types {
1516 r#type.validate()?;
1517 }
1518 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1519 if rebuilt != *self {
1520 return Err(SchemaContractError::NonCanonical);
1521 }
1522 Ok(())
1523 }
1524}
1525
1526pub(crate) const fn check_len(
1527 kind: &'static str,
1528 len: usize,
1529 max: usize,
1530) -> Result<(), SchemaContractError> {
1531 if len > max {
1532 return Err(SchemaContractError::TooManyItems { kind, len, max });
1533 }
1534 Ok(())
1535}
1536
1537fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1538 source_key == name.as_str()
1539}
1540
1541fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1542 if !current_name_key_matches(source_key, name) {
1543 return Err(SchemaContractError::NonCanonical);
1544 }
1545 Ok(())
1546}
1547
1548fn ensure_canonical_rebuild<T: PartialEq>(
1549 current: &T,
1550 rebuilt: &T,
1551) -> Result<(), SchemaContractError> {
1552 if current != rebuilt {
1553 return Err(SchemaContractError::NonCanonical);
1554 }
1555 Ok(())
1556}
1557
1558fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1559where
1560 T: Ord,
1561{
1562 let mut seen = BTreeSet::new();
1563 if values.iter().any(|value| !seen.insert(value)) {
1564 return Err(SchemaContractError::InvalidReferenceList);
1565 }
1566 Ok(())
1567}
1568
1569fn ensure_unique_sorted_by<T, K>(
1570 values: &[T],
1571 key: impl Fn(&T) -> &K,
1572) -> Result<(), SchemaContractError>
1573where
1574 K: Eq,
1575{
1576 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1577 return Err(SchemaContractError::DuplicateSourceKey);
1578 }
1579 Ok(())
1580}
1581
1582fn ensure_unique_names<'a>(
1583 names: impl IntoIterator<Item = &'a SchemaName>,
1584) -> Result<(), SchemaContractError> {
1585 let mut seen = BTreeSet::new();
1586 if names.into_iter().any(|name| !seen.insert(name)) {
1587 return Err(SchemaContractError::DuplicateName);
1588 }
1589 Ok(())
1590}
1591
1592fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1593 for policy in [
1594 FieldManagementPolicy::CreatedAt,
1595 FieldManagementPolicy::UpdatedAt,
1596 ] {
1597 if fields
1598 .iter()
1599 .filter(|field| field.management() == Some(policy))
1600 .count()
1601 > 1
1602 {
1603 return Err(SchemaContractError::InvalidFieldPolicy);
1604 }
1605 }
1606 Ok(())
1607}
1608
1609fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1610 match value.scale().cmp(&scale) {
1611 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1612 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1613 }
1614}
1615
1616#[cfg(test)]
1617mod tests {
1618 use super::{
1619 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1620 SchemaContractError, SchemaName,
1621 };
1622
1623 #[test]
1624 fn independently_decoded_field_key_and_name_must_match() {
1625 let field = FieldFragment {
1626 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1627 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1628 field_type: FieldType::Scalar(ScalarType::Nat64),
1629 nullable: false,
1630 insert_policy: FieldInsertPolicy::Required,
1631 management: None,
1632 };
1633
1634 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1635 }
1636}