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_SCHEMA_FIELD_TYPE_DEPTH,
9 RelationSourceKey, RuleSourceKey, ScalarKind, ScalarLiteral, SchemaContractError, SchemaName,
10 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 struct RelationFragment {
448 source_key: RelationSourceKey,
449 name: SchemaName,
450 local_fields: Vec<FieldSourceKey>,
451 target_entity: EntitySourceKey,
452 target_fields: Vec<FieldSourceKey>,
453 on_delete: RelationDeleteAction,
454}
455
456impl RelationFragment {
457 pub fn try_new(
464 name: SchemaName,
465 local_fields: Vec<FieldSourceKey>,
466 target_entity: EntitySourceKey,
467 target_fields: Vec<FieldSourceKey>,
468 on_delete: RelationDeleteAction,
469 ) -> Result<Self, SchemaContractError> {
470 if local_fields.is_empty() || local_fields.len() != target_fields.len() {
471 return Err(SchemaContractError::InvalidReferenceList);
472 }
473 ensure_unique(&local_fields)?;
474 ensure_unique(&target_fields)?;
475 Ok(Self {
476 source_key: RelationSourceKey::from_name(&name),
477 name,
478 local_fields,
479 target_entity,
480 target_fields,
481 on_delete,
482 })
483 }
484
485 #[must_use]
487 pub const fn source_key(&self) -> &RelationSourceKey {
488 &self.source_key
489 }
490
491 #[must_use]
493 pub const fn name(&self) -> &SchemaName {
494 &self.name
495 }
496
497 #[must_use]
499 pub fn local_fields(&self) -> &[FieldSourceKey] {
500 &self.local_fields
501 }
502
503 #[must_use]
505 pub const fn target_entity(&self) -> &EntitySourceKey {
506 &self.target_entity
507 }
508
509 #[must_use]
511 pub fn target_fields(&self) -> &[FieldSourceKey] {
512 &self.target_fields
513 }
514
515 #[must_use]
517 pub const fn on_delete(&self) -> RelationDeleteAction {
518 self.on_delete
519 }
520
521 fn validate(&self) -> Result<(), SchemaContractError> {
522 let rebuilt = Self::try_new(
523 self.name.clone(),
524 self.local_fields.clone(),
525 self.target_entity.clone(),
526 self.target_fields.clone(),
527 self.on_delete,
528 )?;
529 ensure_canonical_rebuild(self, &rebuilt)
530 }
531}
532
533#[derive(Clone, Debug, Eq, PartialEq)]
535pub enum ConstraintFragmentKind {
536 Check(SourceCheckExpr),
538 TargetedRule(TargetedRuleFragment),
540}
541
542impl ConstraintFragmentKind {
543 fn validate(&self) -> Result<(), SchemaContractError> {
544 match self {
545 Self::Check(expression) => expression.validate(),
546 Self::TargetedRule(rule) => rule.validate(),
547 }
548 }
549}
550
551#[derive(Clone, Debug, Eq, PartialEq)]
553pub struct TargetedRuleFragment {
554 root: FieldSourceKey,
555 target_type: TypeSourceKey,
556 rule: RuleSourceKey,
557 operation: SourceRuleOperation,
558}
559
560impl TargetedRuleFragment {
561 #[must_use]
563 pub fn new(
564 root: FieldSourceKey,
565 target_type: TypeSourceKey,
566 rule: SchemaName,
567 operation: SourceRuleOperation,
568 ) -> Self {
569 Self {
570 root,
571 target_type,
572 rule: RuleSourceKey::from_name(&rule),
573 operation,
574 }
575 }
576
577 #[must_use]
579 pub const fn root(&self) -> &FieldSourceKey {
580 &self.root
581 }
582
583 #[must_use]
585 pub const fn target_type(&self) -> &TypeSourceKey {
586 &self.target_type
587 }
588
589 #[must_use]
591 pub const fn rule(&self) -> &RuleSourceKey {
592 &self.rule
593 }
594
595 #[must_use]
597 pub const fn operation(&self) -> &SourceRuleOperation {
598 &self.operation
599 }
600
601 fn validate(&self) -> Result<(), SchemaContractError> {
602 self.operation.validate()
603 }
604}
605
606#[derive(Clone, Debug, Eq, PartialEq)]
608pub struct ConstraintFragment {
609 source_key: ConstraintSourceKey,
610 name: SchemaName,
611 kind: ConstraintFragmentKind,
612}
613
614impl ConstraintFragment {
615 #[must_use]
617 pub fn check(name: SchemaName, expression: SourceCheckExpr) -> Self {
618 Self {
619 source_key: ConstraintSourceKey::from_name(&name),
620 name,
621 kind: ConstraintFragmentKind::Check(expression),
622 }
623 }
624
625 #[must_use]
627 pub fn targeted_rule(rule: TargetedRuleFragment) -> Self {
628 let source_key = ConstraintSourceKey::for_targeted_field_rule(
629 rule.root(),
630 rule.target_type(),
631 rule.rule(),
632 );
633 let name = SchemaName::for_targeted_rule(&source_key);
634 Self {
635 source_key,
636 name,
637 kind: ConstraintFragmentKind::TargetedRule(rule),
638 }
639 }
640
641 #[must_use]
643 pub const fn source_key(&self) -> &ConstraintSourceKey {
644 &self.source_key
645 }
646
647 #[must_use]
649 pub const fn name(&self) -> &SchemaName {
650 &self.name
651 }
652
653 #[must_use]
655 pub const fn kind(&self) -> &ConstraintFragmentKind {
656 &self.kind
657 }
658
659 fn validate(&self) -> Result<(), SchemaContractError> {
660 self.kind.validate()?;
661 let rebuilt = match &self.kind {
662 ConstraintFragmentKind::Check(expression) => {
663 Self::check(self.name.clone(), expression.clone())
664 }
665 ConstraintFragmentKind::TargetedRule(rule) => Self::targeted_rule(rule.clone()),
666 };
667 ensure_canonical_rebuild(self, &rebuilt)
668 }
669}
670
671#[derive(Clone, Debug, Eq, PartialEq)]
673pub struct EntityFragment {
674 source_key: EntitySourceKey,
675 name: SchemaName,
676 version: DeclaredEntityVersion,
677 fields: Vec<FieldFragment>,
678 primary_key: Vec<FieldSourceKey>,
679 indexes: Vec<IndexFragment>,
680 relations: Vec<RelationFragment>,
681 constraints: Vec<ConstraintFragment>,
682}
683
684impl EntityFragment {
685 pub fn try_new(
692 name: SchemaName,
693 version: DeclaredEntityVersion,
694 mut fields: Vec<FieldFragment>,
695 primary_key: Vec<FieldSourceKey>,
696 mut indexes: Vec<IndexFragment>,
697 mut relations: Vec<RelationFragment>,
698 mut constraints: Vec<ConstraintFragment>,
699 ) -> Result<Self, SchemaContractError> {
700 let source_key = EntitySourceKey::from_name(&name);
701 check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
702 check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
703 check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
704 check_len(
705 "entity constraints",
706 constraints.len(),
707 MAX_FRAGMENT_CONSTRAINTS,
708 )?;
709 if primary_key.is_empty() {
710 return Err(SchemaContractError::InvalidReferenceList);
711 }
712 ensure_unique(&primary_key)?;
713 crate::compact_sort_unstable_by(&mut fields, |a, b| a.source_key.cmp(&b.source_key));
716 crate::compact_sort_unstable_by(&mut indexes, |a, b| a.source_key.cmp(&b.source_key));
717 crate::compact_sort_unstable_by(&mut relations, |a, b| a.source_key.cmp(&b.source_key));
718 crate::compact_sort_unstable_by(&mut constraints, |a, b| a.source_key.cmp(&b.source_key));
719 ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
720 ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
721 ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
722 ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
723 ensure_unique_names(fields.iter().map(FieldFragment::name))?;
724 ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
725 ensure_unique_names(relations.iter().map(RelationFragment::name))?;
726 ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
727 for field in &fields {
728 field.validate()?;
729 }
730 validate_management_cardinality(&fields)?;
731 for index in &indexes {
732 index.validate()?;
733 }
734 for relation in &relations {
735 relation.validate()?;
736 }
737 for constraint in &constraints {
738 constraint.validate()?;
739 }
740 let field_keys = fields
741 .iter()
742 .map(|field| field.source_key.clone())
743 .collect::<BTreeSet<_>>();
744 if primary_key.iter().any(|field| !field_keys.contains(field)) {
745 return Err(SchemaContractError::InvalidLocalReference);
746 }
747 validate_insert_generation(&fields, &primary_key)?;
748 for index in &indexes {
749 if index
750 .key()
751 .iter()
752 .any(|component| !field_keys.contains(component.field()))
753 || index.predicate().is_some_and(|predicate| {
754 predicate
755 .dependencies()
756 .iter()
757 .any(|field| !field_keys.contains(field))
758 })
759 {
760 return Err(SchemaContractError::InvalidLocalReference);
761 }
762 }
763 for relation in &relations {
764 if relation
765 .local_fields()
766 .iter()
767 .any(|field| !field_keys.contains(field))
768 || (relation.target_entity() == &source_key
769 && relation
770 .target_fields()
771 .iter()
772 .any(|field| !field_keys.contains(field)))
773 {
774 return Err(SchemaContractError::InvalidLocalReference);
775 }
776 }
777 for constraint in &constraints {
778 let invalid = match constraint.kind() {
779 ConstraintFragmentKind::Check(expression) => expression
780 .dependencies()
781 .iter()
782 .any(|field| !field_keys.contains(field)),
783 ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
784 };
785 if invalid {
786 return Err(SchemaContractError::InvalidLocalReference);
787 }
788 }
789 Ok(Self {
790 source_key,
791 name,
792 version,
793 fields,
794 primary_key,
795 indexes,
796 relations,
797 constraints,
798 })
799 }
800
801 #[must_use]
803 pub const fn source_key(&self) -> &EntitySourceKey {
804 &self.source_key
805 }
806
807 #[must_use]
809 pub const fn name(&self) -> &SchemaName {
810 &self.name
811 }
812
813 #[must_use]
815 pub const fn version(&self) -> DeclaredEntityVersion {
816 self.version
817 }
818
819 #[must_use]
821 pub fn fields(&self) -> &[FieldFragment] {
822 &self.fields
823 }
824
825 #[must_use]
827 pub fn primary_key(&self) -> &[FieldSourceKey] {
828 &self.primary_key
829 }
830
831 #[must_use]
833 pub fn indexes(&self) -> &[IndexFragment] {
834 &self.indexes
835 }
836
837 #[must_use]
839 pub fn relations(&self) -> &[RelationFragment] {
840 &self.relations
841 }
842
843 #[must_use]
845 pub fn constraints(&self) -> &[ConstraintFragment] {
846 &self.constraints
847 }
848
849 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
850 let rebuilt = Self::try_new(
851 self.name.clone(),
852 self.version,
853 self.fields.clone(),
854 self.primary_key.clone(),
855 self.indexes.clone(),
856 self.relations.clone(),
857 self.constraints.clone(),
858 )?;
859 ensure_canonical_rebuild(self, &rebuilt)
860 }
861}
862
863fn validate_insert_generation(
867 fields: &[FieldFragment],
868 primary_key: &[FieldSourceKey],
869) -> Result<(), SchemaContractError> {
870 for field in fields {
871 if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
872 continue;
873 }
874 if field.nullable() || field.management().is_some() {
875 return Err(SchemaContractError::InvalidFieldPolicy);
876 }
877 match field.field_type() {
878 FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
879 FieldType::Scalar(
880 ScalarType::Nat8
881 | ScalarType::Nat16
882 | ScalarType::Nat32
883 | ScalarType::Nat64
884 | ScalarType::Nat128,
885 ) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
886 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
887 return Err(SchemaContractError::InvalidFieldPolicy);
888 }
889 }
890 }
891 Ok(())
892}
893
894#[derive(Clone, Debug, Eq, PartialEq)]
899pub struct RecordFieldFragment {
900 source_key: FieldSourceKey,
901 name: SchemaName,
902 field_type: FieldType,
903 nullable: bool,
904}
905
906impl RecordFieldFragment {
907 #[must_use]
909 pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
910 Self {
911 source_key: FieldSourceKey::from_name(&name),
912 name,
913 field_type,
914 nullable,
915 }
916 }
917
918 #[must_use]
920 pub const fn source_key(&self) -> &FieldSourceKey {
921 &self.source_key
922 }
923
924 #[must_use]
926 pub const fn name(&self) -> &SchemaName {
927 &self.name
928 }
929
930 #[must_use]
932 pub const fn field_type(&self) -> &FieldType {
933 &self.field_type
934 }
935
936 #[must_use]
938 pub const fn nullable(&self) -> bool {
939 self.nullable
940 }
941
942 fn validate(&self) -> Result<(), SchemaContractError> {
943 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
944 return Err(SchemaContractError::NonCanonical);
945 }
946 self.field_type.validate()
947 }
948}
949
950#[derive(Clone, Debug, Eq, PartialEq)]
953pub struct TupleElementFragment {
954 field_type: FieldType,
955 nullable: bool,
956}
957
958impl TupleElementFragment {
959 #[must_use]
961 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
962 Self {
963 field_type,
964 nullable,
965 }
966 }
967
968 #[must_use]
970 pub const fn field_type(&self) -> &FieldType {
971 &self.field_type
972 }
973
974 #[must_use]
976 pub const fn nullable(&self) -> bool {
977 self.nullable
978 }
979
980 const fn validate(&self) -> Result<(), SchemaContractError> {
981 self.field_type.validate()
982 }
983}
984
985#[derive(Clone, Debug, Eq, PartialEq)]
987pub struct RecordTypeFragment {
988 source_key: TypeSourceKey,
989 name: SchemaName,
990 fields: Vec<RecordFieldFragment>,
991}
992
993impl RecordTypeFragment {
994 pub fn try_new(
1001 name: SchemaName,
1002 mut fields: Vec<RecordFieldFragment>,
1003 ) -> Result<Self, SchemaContractError> {
1004 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
1005 crate::compact_sort_unstable_by(&mut fields, |left, right| {
1006 left.source_key.cmp(&right.source_key)
1007 });
1008 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
1009 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
1010 for field in &fields {
1011 field.validate()?;
1012 }
1013 Ok(Self {
1014 source_key: TypeSourceKey::from_name(&name),
1015 name,
1016 fields,
1017 })
1018 }
1019
1020 #[must_use]
1022 pub const fn source_key(&self) -> &TypeSourceKey {
1023 &self.source_key
1024 }
1025
1026 #[must_use]
1028 pub const fn name(&self) -> &SchemaName {
1029 &self.name
1030 }
1031
1032 #[must_use]
1034 pub fn fields(&self) -> &[RecordFieldFragment] {
1035 &self.fields
1036 }
1037
1038 fn validate(&self) -> Result<(), SchemaContractError> {
1039 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1040 if rebuilt != *self {
1041 return Err(SchemaContractError::NonCanonical);
1042 }
1043 Ok(())
1044 }
1045}
1046
1047#[derive(Clone, Debug, Eq, PartialEq)]
1049pub struct EnumVariantFragment {
1050 source_key: TypeSourceKey,
1051 name: SchemaName,
1052 payload: Option<FieldType>,
1053}
1054
1055impl EnumVariantFragment {
1056 #[must_use]
1058 pub fn new(name: SchemaName) -> Self {
1059 Self {
1060 source_key: TypeSourceKey::from_name(&name),
1061 name,
1062 payload: None,
1063 }
1064 }
1065
1066 #[must_use]
1068 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1069 Self {
1070 source_key: TypeSourceKey::from_name(&name),
1071 name,
1072 payload: Some(payload),
1073 }
1074 }
1075
1076 #[must_use]
1078 pub const fn source_key(&self) -> &TypeSourceKey {
1079 &self.source_key
1080 }
1081
1082 #[must_use]
1084 pub const fn name(&self) -> &SchemaName {
1085 &self.name
1086 }
1087
1088 #[must_use]
1090 pub const fn payload(&self) -> Option<&FieldType> {
1091 self.payload.as_ref()
1092 }
1093
1094 fn validate(&self) -> Result<(), SchemaContractError> {
1095 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1096 return Err(SchemaContractError::NonCanonical);
1097 }
1098 match &self.payload {
1099 Some(payload) => payload.validate(),
1100 None => Ok(()),
1101 }
1102 }
1103}
1104
1105#[derive(Clone, Debug, Eq, PartialEq)]
1107pub struct EnumTypeFragment {
1108 source_key: TypeSourceKey,
1109 name: SchemaName,
1110 variants: Vec<EnumVariantFragment>,
1111}
1112
1113impl EnumTypeFragment {
1114 pub fn try_new(
1121 name: SchemaName,
1122 mut variants: Vec<EnumVariantFragment>,
1123 ) -> Result<Self, SchemaContractError> {
1124 if variants.is_empty() {
1125 return Err(SchemaContractError::InvalidReferenceList);
1126 }
1127 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1128 crate::compact_sort_unstable_by(&mut variants, |left, right| {
1129 left.source_key.cmp(&right.source_key)
1130 });
1131 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1132 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1133 for variant in &variants {
1134 variant.validate()?;
1135 }
1136 Ok(Self {
1137 source_key: TypeSourceKey::from_name(&name),
1138 name,
1139 variants,
1140 })
1141 }
1142
1143 #[must_use]
1145 pub const fn source_key(&self) -> &TypeSourceKey {
1146 &self.source_key
1147 }
1148
1149 #[must_use]
1151 pub const fn name(&self) -> &SchemaName {
1152 &self.name
1153 }
1154
1155 #[must_use]
1157 pub fn variants(&self) -> &[EnumVariantFragment] {
1158 &self.variants
1159 }
1160
1161 fn validate(&self) -> Result<(), SchemaContractError> {
1162 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1163 if rebuilt != *self {
1164 return Err(SchemaContractError::NonCanonical);
1165 }
1166 Ok(())
1167 }
1168}
1169
1170#[derive(Clone, Debug, Eq, PartialEq)]
1172pub enum NamedTypeFragment {
1173 Record(RecordTypeFragment),
1175 Enum(EnumTypeFragment),
1177 Newtype {
1179 source_key: TypeSourceKey,
1181 name: SchemaName,
1183 inner: FieldType,
1185 },
1186 List {
1188 source_key: TypeSourceKey,
1190 name: SchemaName,
1192 item: FieldType,
1194 },
1195 Set {
1197 source_key: TypeSourceKey,
1199 name: SchemaName,
1201 item: FieldType,
1203 },
1204 Map {
1206 source_key: TypeSourceKey,
1208 name: SchemaName,
1210 key: FieldType,
1212 value: FieldType,
1214 },
1215 Tuple {
1217 source_key: TypeSourceKey,
1219 name: SchemaName,
1221 members: Vec<TupleElementFragment>,
1223 },
1224}
1225
1226impl NamedTypeFragment {
1227 #[must_use]
1229 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1230 Self::Newtype {
1231 source_key: TypeSourceKey::from_name(&name),
1232 name,
1233 inner,
1234 }
1235 }
1236
1237 #[must_use]
1239 pub fn list(name: SchemaName, item: FieldType) -> Self {
1240 Self::List {
1241 source_key: TypeSourceKey::from_name(&name),
1242 name,
1243 item,
1244 }
1245 }
1246
1247 #[must_use]
1249 pub fn set(name: SchemaName, item: FieldType) -> Self {
1250 Self::Set {
1251 source_key: TypeSourceKey::from_name(&name),
1252 name,
1253 item,
1254 }
1255 }
1256
1257 #[must_use]
1259 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1260 Self::Map {
1261 source_key: TypeSourceKey::from_name(&name),
1262 name,
1263 key,
1264 value,
1265 }
1266 }
1267
1268 #[must_use]
1270 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1271 Self::Tuple {
1272 source_key: TypeSourceKey::from_name(&name),
1273 name,
1274 members,
1275 }
1276 }
1277
1278 #[must_use]
1280 pub const fn source_key(&self) -> &TypeSourceKey {
1281 match self {
1282 Self::Record(record) => record.source_key(),
1283 Self::Enum(r#enum) => r#enum.source_key(),
1284 Self::Newtype { source_key, .. }
1285 | Self::List { source_key, .. }
1286 | Self::Set { source_key, .. }
1287 | Self::Map { source_key, .. }
1288 | Self::Tuple { source_key, .. } => source_key,
1289 }
1290 }
1291
1292 #[must_use]
1294 pub const fn name(&self) -> &SchemaName {
1295 match self {
1296 Self::Record(record) => record.name(),
1297 Self::Enum(r#enum) => r#enum.name(),
1298 Self::Newtype { name, .. }
1299 | Self::List { name, .. }
1300 | Self::Set { name, .. }
1301 | Self::Map { name, .. }
1302 | Self::Tuple { name, .. } => name,
1303 }
1304 }
1305
1306 fn validate(&self) -> Result<(), SchemaContractError> {
1307 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1308 match self {
1309 Self::Record(record) => record.validate(),
1310 Self::Enum(r#enum) => r#enum.validate(),
1311 Self::Newtype { inner, .. }
1312 | Self::List { item: inner, .. }
1313 | Self::Set { item: inner, .. } => inner.validate(),
1314 Self::Map { key, value, .. } => {
1315 key.validate()?;
1316 value.validate()
1317 }
1318 Self::Tuple { members, .. } => {
1319 if members.is_empty() {
1320 return Err(SchemaContractError::InvalidReferenceList);
1321 }
1322 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1323 members.iter().try_for_each(TupleElementFragment::validate)
1324 }
1325 }
1326 }
1327}
1328
1329#[derive(Clone, Debug, Eq, PartialEq)]
1331pub struct SchemaFragment {
1332 entities: Vec<EntityFragment>,
1333 types: Vec<NamedTypeFragment>,
1334}
1335
1336impl SchemaFragment {
1337 pub fn try_new(
1344 mut entities: Vec<EntityFragment>,
1345 mut types: Vec<NamedTypeFragment>,
1346 ) -> Result<Self, SchemaContractError> {
1347 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1348 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1349 crate::compact_sort_unstable_by(&mut entities, |left, right| {
1350 left.source_key.cmp(&right.source_key)
1351 });
1352 crate::compact_sort_unstable_by(&mut types, |left, right| {
1353 left.source_key().cmp(right.source_key())
1354 });
1355 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1356 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1357 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1358 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1359 for entity in &entities {
1360 entity.validate()?;
1361 }
1362 for r#type in &types {
1363 r#type.validate()?;
1364 }
1365 Ok(Self { entities, types })
1366 }
1367
1368 #[must_use]
1370 pub fn entities(&self) -> &[EntityFragment] {
1371 &self.entities
1372 }
1373
1374 #[must_use]
1376 pub fn types(&self) -> &[NamedTypeFragment] {
1377 &self.types
1378 }
1379
1380 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1381 for r#type in &self.types {
1382 r#type.validate()?;
1383 }
1384 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1385 if rebuilt != *self {
1386 return Err(SchemaContractError::NonCanonical);
1387 }
1388 Ok(())
1389 }
1390}
1391
1392pub(crate) const fn check_len(
1393 kind: &'static str,
1394 len: usize,
1395 max: usize,
1396) -> Result<(), SchemaContractError> {
1397 if len > max {
1398 return Err(SchemaContractError::TooManyItems { kind, len, max });
1399 }
1400 Ok(())
1401}
1402
1403fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1404 source_key == name.as_str()
1405}
1406
1407fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1408 if !current_name_key_matches(source_key, name) {
1409 return Err(SchemaContractError::NonCanonical);
1410 }
1411 Ok(())
1412}
1413
1414fn ensure_canonical_rebuild<T: PartialEq>(
1415 current: &T,
1416 rebuilt: &T,
1417) -> Result<(), SchemaContractError> {
1418 if current != rebuilt {
1419 return Err(SchemaContractError::NonCanonical);
1420 }
1421 Ok(())
1422}
1423
1424fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1425where
1426 T: Ord,
1427{
1428 let mut seen = BTreeSet::new();
1429 if values.iter().any(|value| !seen.insert(value)) {
1430 return Err(SchemaContractError::InvalidReferenceList);
1431 }
1432 Ok(())
1433}
1434
1435fn ensure_unique_sorted_by<T, K>(
1436 values: &[T],
1437 key: impl Fn(&T) -> &K,
1438) -> Result<(), SchemaContractError>
1439where
1440 K: Eq,
1441{
1442 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1443 return Err(SchemaContractError::DuplicateSourceKey);
1444 }
1445 Ok(())
1446}
1447
1448fn ensure_unique_names<'a>(
1449 names: impl IntoIterator<Item = &'a SchemaName>,
1450) -> Result<(), SchemaContractError> {
1451 let mut seen = BTreeSet::new();
1452 if names.into_iter().any(|name| !seen.insert(name)) {
1453 return Err(SchemaContractError::DuplicateName);
1454 }
1455 Ok(())
1456}
1457
1458fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1459 for policy in [
1460 FieldManagementPolicy::CreatedAt,
1461 FieldManagementPolicy::UpdatedAt,
1462 ] {
1463 if fields
1464 .iter()
1465 .filter(|field| field.management() == Some(policy))
1466 .count()
1467 > 1
1468 {
1469 return Err(SchemaContractError::InvalidFieldPolicy);
1470 }
1471 }
1472 Ok(())
1473}
1474
1475fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1476 match value.scale().cmp(&scale) {
1477 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1478 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1479 }
1480}
1481
1482#[cfg(test)]
1483mod tests {
1484 use super::{
1485 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1486 SchemaContractError, SchemaName,
1487 };
1488
1489 #[test]
1490 fn independently_decoded_field_key_and_name_must_match() {
1491 let field = FieldFragment {
1492 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1493 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1494 field_type: FieldType::Scalar(ScalarType::Nat64),
1495 nullable: false,
1496 insert_policy: FieldInsertPolicy::Required,
1497 management: None,
1498 };
1499
1500 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1501 }
1502}