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 Ulid,
92 Unit,
94}
95
96impl ScalarType {
97 #[must_use]
99 pub const fn kind(self) -> ScalarKind {
100 match self {
101 Self::Account => ScalarKind::Account,
102 Self::Blob { .. } => ScalarKind::Blob,
103 Self::Bool => ScalarKind::Bool,
104 Self::Date => ScalarKind::Date,
105 Self::Decimal { .. } => ScalarKind::Decimal,
106 Self::Duration => ScalarKind::Duration,
107 Self::Float32 => ScalarKind::Float32,
108 Self::Float64 => ScalarKind::Float64,
109 Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 => ScalarKind::Int,
110 Self::Int128 => ScalarKind::Int128,
111 Self::IntBig { .. } => ScalarKind::IntBig,
112 Self::Principal => ScalarKind::Principal,
113 Self::Subaccount => ScalarKind::Subaccount,
114 Self::Text { .. } => ScalarKind::Text,
115 Self::Timestamp => ScalarKind::Timestamp,
116 Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 => ScalarKind::Nat,
117 Self::Nat128 => ScalarKind::Nat128,
118 Self::NatBig { .. } => ScalarKind::NatBig,
119 Self::Ulid => ScalarKind::Ulid,
120 Self::Unit => ScalarKind::Unit,
121 }
122 }
123
124 pub(crate) const fn validate(self) -> Result<(), SchemaContractError> {
125 match self {
126 Self::Decimal { scale } if scale > Decimal::max_supported_scale() => {
127 Err(SchemaContractError::InvalidFieldType)
128 }
129 Self::IntBig { max_bytes: 0 } | Self::NatBig { max_bytes: 0 } => {
130 Err(SchemaContractError::InvalidFieldType)
131 }
132 _ => Ok(()),
133 }
134 }
135
136 pub(crate) fn accepts_literal(self, literal: &ScalarLiteral) -> bool {
137 match (self, literal) {
138 (Self::Account, ScalarLiteral::Account(_))
139 | (Self::Bool, ScalarLiteral::Bool(_))
140 | (Self::Date, ScalarLiteral::Date(_))
141 | (Self::Duration, ScalarLiteral::Duration(_))
142 | (Self::Float32, ScalarLiteral::Float32(_))
143 | (Self::Float64, ScalarLiteral::Float64(_))
144 | (Self::Int128, ScalarLiteral::Int(_))
145 | (Self::Principal, ScalarLiteral::Principal(_))
146 | (Self::Subaccount, ScalarLiteral::Subaccount(_))
147 | (Self::Timestamp, ScalarLiteral::Timestamp(_))
148 | (Self::Nat128, ScalarLiteral::Nat(_))
149 | (Self::Ulid, ScalarLiteral::Ulid(_))
150 | (Self::Unit, ScalarLiteral::Unit(_)) => true,
151 (Self::Blob { max_len }, ScalarLiteral::Blob(value)) => {
152 max_len.is_none_or(|max| value.len() <= max as usize)
153 }
154 (Self::Text { max_len }, ScalarLiteral::Text(value)) => {
155 max_len.is_none_or(|max| value.chars().count() <= max as usize)
156 }
157 (Self::Int8, ScalarLiteral::Int(value)) => i8::try_from(*value).is_ok(),
158 (Self::Int16, ScalarLiteral::Int(value)) => i16::try_from(*value).is_ok(),
159 (Self::Int32, ScalarLiteral::Int(value)) => i32::try_from(*value).is_ok(),
160 (Self::Int64, ScalarLiteral::Int(value)) => i64::try_from(*value).is_ok(),
161 (Self::IntBig { max_bytes }, ScalarLiteral::IntBig(value)) => {
162 value.to_leb128().len() <= max_bytes as usize
163 }
164 (Self::Nat8, ScalarLiteral::Nat(value)) => u8::try_from(*value).is_ok(),
165 (Self::Nat16, ScalarLiteral::Nat(value)) => u16::try_from(*value).is_ok(),
166 (Self::Nat32, ScalarLiteral::Nat(value)) => u32::try_from(*value).is_ok(),
167 (Self::Nat64, ScalarLiteral::Nat(value)) => u64::try_from(*value).is_ok(),
168 (Self::NatBig { max_bytes }, ScalarLiteral::NatBig(value)) => {
169 value.to_leb128().len() <= max_bytes as usize
170 }
171 (Self::Decimal { scale }, ScalarLiteral::Decimal(value)) => {
172 decimal_fits_scale(*value, scale)
173 }
174 _ => false,
175 }
176 }
177}
178
179impl FieldType {
180 pub(crate) const fn validate(&self) -> Result<(), SchemaContractError> {
181 self.validate_at_depth(0)
182 }
183
184 const fn validate_at_depth(&self, depth: usize) -> Result<(), SchemaContractError> {
185 let Some(depth) = depth.checked_add(1) else {
186 return Err(SchemaContractError::FieldTypeDepthExceeded);
187 };
188 if depth > MAX_SCHEMA_FIELD_TYPE_DEPTH {
189 return Err(SchemaContractError::FieldTypeDepthExceeded);
190 }
191 match self {
192 Self::Scalar(scalar) => scalar.validate(),
193 Self::List(item) => item.validate_at_depth(depth),
194 Self::Named(_) => Ok(()),
195 }
196 }
197}
198
199#[derive(Clone, Debug, Eq, PartialEq)]
201pub enum FieldInsertPolicy {
202 Required,
204 Nullable,
206 Default(ScalarLiteral),
208 Generated,
210}
211
212#[derive(Clone, Copy, Debug, Eq, PartialEq)]
214pub enum FieldManagementPolicy {
215 CreatedAt,
217 UpdatedAt,
219}
220
221#[derive(Clone, Debug, Eq, PartialEq)]
223pub struct FieldFragment {
224 source_key: FieldSourceKey,
225 name: SchemaName,
226 field_type: FieldType,
227 nullable: bool,
228 insert_policy: FieldInsertPolicy,
229 management: Option<FieldManagementPolicy>,
230}
231
232impl FieldFragment {
233 #[must_use]
235 pub fn new(
236 name: SchemaName,
237 field_type: FieldType,
238 nullable: bool,
239 insert_policy: FieldInsertPolicy,
240 management: Option<FieldManagementPolicy>,
241 ) -> Self {
242 Self {
243 source_key: FieldSourceKey::from_name(&name),
244 name,
245 field_type,
246 nullable,
247 insert_policy,
248 management,
249 }
250 }
251
252 #[must_use]
254 pub const fn source_key(&self) -> &FieldSourceKey {
255 &self.source_key
256 }
257
258 #[must_use]
260 pub const fn name(&self) -> &SchemaName {
261 &self.name
262 }
263
264 #[must_use]
266 pub const fn field_type(&self) -> &FieldType {
267 &self.field_type
268 }
269
270 #[must_use]
272 pub const fn nullable(&self) -> bool {
273 self.nullable
274 }
275
276 #[must_use]
278 pub const fn insert_policy(&self) -> &FieldInsertPolicy {
279 &self.insert_policy
280 }
281
282 #[must_use]
284 pub const fn management(&self) -> Option<FieldManagementPolicy> {
285 self.management
286 }
287
288 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
289 ensure_current_name_key(self.source_key.as_str(), &self.name)?;
290 self.field_type.validate()?;
291 if let FieldInsertPolicy::Default(literal) = &self.insert_policy {
292 literal.validate()?;
293 match &self.field_type {
294 FieldType::Scalar(scalar) if scalar.accepts_literal(literal) => {}
295 FieldType::Named(_) if matches!(literal, ScalarLiteral::EnumUnit { .. }) => {}
296 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
297 return Err(SchemaContractError::LiteralTypeMismatch);
298 }
299 }
300 }
301 if matches!(self.insert_policy, FieldInsertPolicy::Nullable) && !self.nullable {
302 return Err(SchemaContractError::InvalidFieldPolicy);
303 }
304 if self.management.is_some()
305 && (!matches!(self.field_type, FieldType::Scalar(ScalarType::Timestamp))
306 || self.nullable
307 || !matches!(self.insert_policy, FieldInsertPolicy::Required))
308 {
309 return Err(SchemaContractError::InvalidFieldPolicy);
310 }
311 Ok(())
312 }
313}
314
315#[derive(Clone, Debug, Eq, PartialEq)]
317pub enum IndexKeyFragment {
318 Field(FieldSourceKey),
320 Lower(FieldSourceKey),
322 Upper(FieldSourceKey),
324 Trim(FieldSourceKey),
326 LowerTrim(FieldSourceKey),
328 Date(FieldSourceKey),
330 Year(FieldSourceKey),
332 Month(FieldSourceKey),
334 Day(FieldSourceKey),
336}
337
338impl IndexKeyFragment {
339 #[must_use]
341 pub const fn field(&self) -> &FieldSourceKey {
342 match self {
343 Self::Field(field)
344 | Self::Lower(field)
345 | Self::Upper(field)
346 | Self::Trim(field)
347 | Self::LowerTrim(field)
348 | Self::Date(field)
349 | Self::Year(field)
350 | Self::Month(field)
351 | Self::Day(field) => field,
352 }
353 }
354}
355
356#[derive(Clone, Debug, Eq, PartialEq)]
358pub struct IndexFragment {
359 source_key: IndexSourceKey,
360 name: SchemaName,
361 key: Vec<IndexKeyFragment>,
362 unique: bool,
363 predicate: Option<SourceCheckExpr>,
364}
365
366impl IndexFragment {
367 pub fn try_new(
373 name: SchemaName,
374 key: Vec<IndexKeyFragment>,
375 unique: bool,
376 predicate: Option<SourceCheckExpr>,
377 ) -> Result<Self, SchemaContractError> {
378 if key.is_empty() {
379 return Err(SchemaContractError::InvalidReferenceList);
380 }
381 if let Some(predicate) = &predicate {
382 predicate.validate()?;
383 }
384 Ok(Self {
385 source_key: IndexSourceKey::from_name(&name),
386 name,
387 key,
388 unique,
389 predicate,
390 })
391 }
392
393 #[must_use]
395 pub const fn source_key(&self) -> &IndexSourceKey {
396 &self.source_key
397 }
398
399 #[must_use]
401 pub const fn name(&self) -> &SchemaName {
402 &self.name
403 }
404
405 #[must_use]
407 pub fn key(&self) -> &[IndexKeyFragment] {
408 &self.key
409 }
410
411 #[must_use]
413 pub const fn unique(&self) -> bool {
414 self.unique
415 }
416
417 #[must_use]
419 pub const fn predicate(&self) -> Option<&SourceCheckExpr> {
420 self.predicate.as_ref()
421 }
422
423 fn validate(&self) -> Result<(), SchemaContractError> {
424 let rebuilt = Self::try_new(
425 self.name.clone(),
426 self.key.clone(),
427 self.unique,
428 self.predicate.clone(),
429 )?;
430 ensure_canonical_rebuild(self, &rebuilt)
431 }
432}
433
434#[derive(Clone, Copy, Debug, Eq, PartialEq)]
436pub enum RelationDeleteAction {
437 Restrict,
439}
440
441#[derive(Clone, Debug, Eq, PartialEq)]
443pub struct RelationFragment {
444 source_key: RelationSourceKey,
445 name: SchemaName,
446 local_fields: Vec<FieldSourceKey>,
447 target_entity: EntitySourceKey,
448 target_fields: Vec<FieldSourceKey>,
449 on_delete: RelationDeleteAction,
450}
451
452impl RelationFragment {
453 pub fn try_new(
460 name: SchemaName,
461 local_fields: Vec<FieldSourceKey>,
462 target_entity: EntitySourceKey,
463 target_fields: Vec<FieldSourceKey>,
464 on_delete: RelationDeleteAction,
465 ) -> Result<Self, SchemaContractError> {
466 if local_fields.is_empty() || local_fields.len() != target_fields.len() {
467 return Err(SchemaContractError::InvalidReferenceList);
468 }
469 ensure_unique(&local_fields)?;
470 ensure_unique(&target_fields)?;
471 Ok(Self {
472 source_key: RelationSourceKey::from_name(&name),
473 name,
474 local_fields,
475 target_entity,
476 target_fields,
477 on_delete,
478 })
479 }
480
481 #[must_use]
483 pub const fn source_key(&self) -> &RelationSourceKey {
484 &self.source_key
485 }
486
487 #[must_use]
489 pub const fn name(&self) -> &SchemaName {
490 &self.name
491 }
492
493 #[must_use]
495 pub fn local_fields(&self) -> &[FieldSourceKey] {
496 &self.local_fields
497 }
498
499 #[must_use]
501 pub const fn target_entity(&self) -> &EntitySourceKey {
502 &self.target_entity
503 }
504
505 #[must_use]
507 pub fn target_fields(&self) -> &[FieldSourceKey] {
508 &self.target_fields
509 }
510
511 #[must_use]
513 pub const fn on_delete(&self) -> RelationDeleteAction {
514 self.on_delete
515 }
516
517 fn validate(&self) -> Result<(), SchemaContractError> {
518 let rebuilt = Self::try_new(
519 self.name.clone(),
520 self.local_fields.clone(),
521 self.target_entity.clone(),
522 self.target_fields.clone(),
523 self.on_delete,
524 )?;
525 ensure_canonical_rebuild(self, &rebuilt)
526 }
527}
528
529#[derive(Clone, Debug, Eq, PartialEq)]
531pub enum ConstraintFragmentKind {
532 Check(SourceCheckExpr),
534 TargetedRule(TargetedRuleFragment),
536}
537
538impl ConstraintFragmentKind {
539 fn validate(&self) -> Result<(), SchemaContractError> {
540 match self {
541 Self::Check(expression) => expression.validate(),
542 Self::TargetedRule(rule) => rule.validate(),
543 }
544 }
545}
546
547#[derive(Clone, Debug, Eq, PartialEq)]
549pub struct TargetedRuleFragment {
550 root: FieldSourceKey,
551 target_type: TypeSourceKey,
552 rule: RuleSourceKey,
553 operation: SourceRuleOperation,
554}
555
556impl TargetedRuleFragment {
557 #[must_use]
559 pub fn new(
560 root: FieldSourceKey,
561 target_type: TypeSourceKey,
562 rule: SchemaName,
563 operation: SourceRuleOperation,
564 ) -> Self {
565 Self {
566 root,
567 target_type,
568 rule: RuleSourceKey::from_name(&rule),
569 operation,
570 }
571 }
572
573 #[must_use]
575 pub const fn root(&self) -> &FieldSourceKey {
576 &self.root
577 }
578
579 #[must_use]
581 pub const fn target_type(&self) -> &TypeSourceKey {
582 &self.target_type
583 }
584
585 #[must_use]
587 pub const fn rule(&self) -> &RuleSourceKey {
588 &self.rule
589 }
590
591 #[must_use]
593 pub const fn operation(&self) -> &SourceRuleOperation {
594 &self.operation
595 }
596
597 fn validate(&self) -> Result<(), SchemaContractError> {
598 self.operation.validate()
599 }
600}
601
602#[derive(Clone, Debug, Eq, PartialEq)]
604pub struct ConstraintFragment {
605 source_key: ConstraintSourceKey,
606 name: SchemaName,
607 kind: ConstraintFragmentKind,
608}
609
610impl ConstraintFragment {
611 #[must_use]
613 pub fn check(name: SchemaName, expression: SourceCheckExpr) -> Self {
614 Self {
615 source_key: ConstraintSourceKey::from_name(&name),
616 name,
617 kind: ConstraintFragmentKind::Check(expression),
618 }
619 }
620
621 #[must_use]
623 pub fn targeted_rule(rule: TargetedRuleFragment) -> Self {
624 let source_key = ConstraintSourceKey::for_targeted_field_rule(
625 rule.root(),
626 rule.target_type(),
627 rule.rule(),
628 );
629 let name = SchemaName::for_targeted_rule(&source_key);
630 Self {
631 source_key,
632 name,
633 kind: ConstraintFragmentKind::TargetedRule(rule),
634 }
635 }
636
637 #[must_use]
639 pub const fn source_key(&self) -> &ConstraintSourceKey {
640 &self.source_key
641 }
642
643 #[must_use]
645 pub const fn name(&self) -> &SchemaName {
646 &self.name
647 }
648
649 #[must_use]
651 pub const fn kind(&self) -> &ConstraintFragmentKind {
652 &self.kind
653 }
654
655 fn validate(&self) -> Result<(), SchemaContractError> {
656 self.kind.validate()?;
657 let rebuilt = match &self.kind {
658 ConstraintFragmentKind::Check(expression) => {
659 Self::check(self.name.clone(), expression.clone())
660 }
661 ConstraintFragmentKind::TargetedRule(rule) => Self::targeted_rule(rule.clone()),
662 };
663 ensure_canonical_rebuild(self, &rebuilt)
664 }
665}
666
667#[derive(Clone, Debug, Eq, PartialEq)]
669pub struct EntityFragment {
670 source_key: EntitySourceKey,
671 name: SchemaName,
672 version: DeclaredEntityVersion,
673 fields: Vec<FieldFragment>,
674 primary_key: Vec<FieldSourceKey>,
675 indexes: Vec<IndexFragment>,
676 relations: Vec<RelationFragment>,
677 constraints: Vec<ConstraintFragment>,
678}
679
680impl EntityFragment {
681 pub fn try_new(
688 name: SchemaName,
689 version: DeclaredEntityVersion,
690 mut fields: Vec<FieldFragment>,
691 primary_key: Vec<FieldSourceKey>,
692 mut indexes: Vec<IndexFragment>,
693 mut relations: Vec<RelationFragment>,
694 mut constraints: Vec<ConstraintFragment>,
695 ) -> Result<Self, SchemaContractError> {
696 let source_key = EntitySourceKey::from_name(&name);
697 check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
698 check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
699 check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
700 check_len(
701 "entity constraints",
702 constraints.len(),
703 MAX_FRAGMENT_CONSTRAINTS,
704 )?;
705 if primary_key.is_empty() {
706 return Err(SchemaContractError::InvalidReferenceList);
707 }
708 ensure_unique(&primary_key)?;
709 crate::compact_sort_unstable_by(&mut fields, |a, b| a.source_key.cmp(&b.source_key));
712 crate::compact_sort_unstable_by(&mut indexes, |a, b| a.source_key.cmp(&b.source_key));
713 crate::compact_sort_unstable_by(&mut relations, |a, b| a.source_key.cmp(&b.source_key));
714 crate::compact_sort_unstable_by(&mut constraints, |a, b| a.source_key.cmp(&b.source_key));
715 ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
716 ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
717 ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
718 ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
719 ensure_unique_names(fields.iter().map(FieldFragment::name))?;
720 ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
721 ensure_unique_names(relations.iter().map(RelationFragment::name))?;
722 ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
723 for field in &fields {
724 field.validate()?;
725 }
726 validate_management_cardinality(&fields)?;
727 for index in &indexes {
728 index.validate()?;
729 }
730 for relation in &relations {
731 relation.validate()?;
732 }
733 for constraint in &constraints {
734 constraint.validate()?;
735 }
736 let field_keys = fields
737 .iter()
738 .map(|field| field.source_key.clone())
739 .collect::<BTreeSet<_>>();
740 if primary_key.iter().any(|field| !field_keys.contains(field)) {
741 return Err(SchemaContractError::InvalidLocalReference);
742 }
743 validate_insert_generation(&fields, &primary_key)?;
744 for index in &indexes {
745 if index
746 .key()
747 .iter()
748 .any(|component| !field_keys.contains(component.field()))
749 || index.predicate().is_some_and(|predicate| {
750 predicate
751 .dependencies()
752 .iter()
753 .any(|field| !field_keys.contains(field))
754 })
755 {
756 return Err(SchemaContractError::InvalidLocalReference);
757 }
758 }
759 for relation in &relations {
760 if relation
761 .local_fields()
762 .iter()
763 .any(|field| !field_keys.contains(field))
764 || (relation.target_entity() == &source_key
765 && relation
766 .target_fields()
767 .iter()
768 .any(|field| !field_keys.contains(field)))
769 {
770 return Err(SchemaContractError::InvalidLocalReference);
771 }
772 }
773 for constraint in &constraints {
774 let invalid = match constraint.kind() {
775 ConstraintFragmentKind::Check(expression) => expression
776 .dependencies()
777 .iter()
778 .any(|field| !field_keys.contains(field)),
779 ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
780 };
781 if invalid {
782 return Err(SchemaContractError::InvalidLocalReference);
783 }
784 }
785 Ok(Self {
786 source_key,
787 name,
788 version,
789 fields,
790 primary_key,
791 indexes,
792 relations,
793 constraints,
794 })
795 }
796
797 #[must_use]
799 pub const fn source_key(&self) -> &EntitySourceKey {
800 &self.source_key
801 }
802
803 #[must_use]
805 pub const fn name(&self) -> &SchemaName {
806 &self.name
807 }
808
809 #[must_use]
811 pub const fn version(&self) -> DeclaredEntityVersion {
812 self.version
813 }
814
815 #[must_use]
817 pub fn fields(&self) -> &[FieldFragment] {
818 &self.fields
819 }
820
821 #[must_use]
823 pub fn primary_key(&self) -> &[FieldSourceKey] {
824 &self.primary_key
825 }
826
827 #[must_use]
829 pub fn indexes(&self) -> &[IndexFragment] {
830 &self.indexes
831 }
832
833 #[must_use]
835 pub fn relations(&self) -> &[RelationFragment] {
836 &self.relations
837 }
838
839 #[must_use]
841 pub fn constraints(&self) -> &[ConstraintFragment] {
842 &self.constraints
843 }
844
845 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
846 let rebuilt = Self::try_new(
847 self.name.clone(),
848 self.version,
849 self.fields.clone(),
850 self.primary_key.clone(),
851 self.indexes.clone(),
852 self.relations.clone(),
853 self.constraints.clone(),
854 )?;
855 ensure_canonical_rebuild(self, &rebuilt)
856 }
857}
858
859fn validate_insert_generation(
863 fields: &[FieldFragment],
864 primary_key: &[FieldSourceKey],
865) -> Result<(), SchemaContractError> {
866 for field in fields {
867 if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
868 continue;
869 }
870 if field.nullable() || field.management().is_some() {
871 return Err(SchemaContractError::InvalidFieldPolicy);
872 }
873 match field.field_type() {
874 FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
875 FieldType::Scalar(
876 ScalarType::Nat8
877 | ScalarType::Nat16
878 | ScalarType::Nat32
879 | ScalarType::Nat64
880 | ScalarType::Nat128,
881 ) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
882 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
883 return Err(SchemaContractError::InvalidFieldPolicy);
884 }
885 }
886 }
887 Ok(())
888}
889
890#[derive(Clone, Debug, Eq, PartialEq)]
895pub struct RecordFieldFragment {
896 source_key: FieldSourceKey,
897 name: SchemaName,
898 field_type: FieldType,
899 nullable: bool,
900}
901
902impl RecordFieldFragment {
903 #[must_use]
905 pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
906 Self {
907 source_key: FieldSourceKey::from_name(&name),
908 name,
909 field_type,
910 nullable,
911 }
912 }
913
914 #[must_use]
916 pub const fn source_key(&self) -> &FieldSourceKey {
917 &self.source_key
918 }
919
920 #[must_use]
922 pub const fn name(&self) -> &SchemaName {
923 &self.name
924 }
925
926 #[must_use]
928 pub const fn field_type(&self) -> &FieldType {
929 &self.field_type
930 }
931
932 #[must_use]
934 pub const fn nullable(&self) -> bool {
935 self.nullable
936 }
937
938 fn validate(&self) -> Result<(), SchemaContractError> {
939 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
940 return Err(SchemaContractError::NonCanonical);
941 }
942 self.field_type.validate()
943 }
944}
945
946#[derive(Clone, Debug, Eq, PartialEq)]
949pub struct TupleElementFragment {
950 field_type: FieldType,
951 nullable: bool,
952}
953
954impl TupleElementFragment {
955 #[must_use]
957 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
958 Self {
959 field_type,
960 nullable,
961 }
962 }
963
964 #[must_use]
966 pub const fn field_type(&self) -> &FieldType {
967 &self.field_type
968 }
969
970 #[must_use]
972 pub const fn nullable(&self) -> bool {
973 self.nullable
974 }
975
976 const fn validate(&self) -> Result<(), SchemaContractError> {
977 self.field_type.validate()
978 }
979}
980
981#[derive(Clone, Debug, Eq, PartialEq)]
983pub struct RecordTypeFragment {
984 source_key: TypeSourceKey,
985 name: SchemaName,
986 fields: Vec<RecordFieldFragment>,
987}
988
989impl RecordTypeFragment {
990 pub fn try_new(
997 name: SchemaName,
998 mut fields: Vec<RecordFieldFragment>,
999 ) -> Result<Self, SchemaContractError> {
1000 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
1001 crate::compact_sort_unstable_by(&mut fields, |left, right| {
1002 left.source_key.cmp(&right.source_key)
1003 });
1004 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
1005 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
1006 for field in &fields {
1007 field.validate()?;
1008 }
1009 Ok(Self {
1010 source_key: TypeSourceKey::from_name(&name),
1011 name,
1012 fields,
1013 })
1014 }
1015
1016 #[must_use]
1018 pub const fn source_key(&self) -> &TypeSourceKey {
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 fn fields(&self) -> &[RecordFieldFragment] {
1031 &self.fields
1032 }
1033
1034 fn validate(&self) -> Result<(), SchemaContractError> {
1035 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1036 if rebuilt != *self {
1037 return Err(SchemaContractError::NonCanonical);
1038 }
1039 Ok(())
1040 }
1041}
1042
1043#[derive(Clone, Debug, Eq, PartialEq)]
1045pub struct EnumVariantFragment {
1046 source_key: TypeSourceKey,
1047 name: SchemaName,
1048 payload: Option<FieldType>,
1049}
1050
1051impl EnumVariantFragment {
1052 #[must_use]
1054 pub fn new(name: SchemaName) -> Self {
1055 Self {
1056 source_key: TypeSourceKey::from_name(&name),
1057 name,
1058 payload: None,
1059 }
1060 }
1061
1062 #[must_use]
1064 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1065 Self {
1066 source_key: TypeSourceKey::from_name(&name),
1067 name,
1068 payload: Some(payload),
1069 }
1070 }
1071
1072 #[must_use]
1074 pub const fn source_key(&self) -> &TypeSourceKey {
1075 &self.source_key
1076 }
1077
1078 #[must_use]
1080 pub const fn name(&self) -> &SchemaName {
1081 &self.name
1082 }
1083
1084 #[must_use]
1086 pub const fn payload(&self) -> Option<&FieldType> {
1087 self.payload.as_ref()
1088 }
1089
1090 fn validate(&self) -> Result<(), SchemaContractError> {
1091 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1092 return Err(SchemaContractError::NonCanonical);
1093 }
1094 match &self.payload {
1095 Some(payload) => payload.validate(),
1096 None => Ok(()),
1097 }
1098 }
1099}
1100
1101#[derive(Clone, Debug, Eq, PartialEq)]
1103pub struct EnumTypeFragment {
1104 source_key: TypeSourceKey,
1105 name: SchemaName,
1106 variants: Vec<EnumVariantFragment>,
1107}
1108
1109impl EnumTypeFragment {
1110 pub fn try_new(
1117 name: SchemaName,
1118 mut variants: Vec<EnumVariantFragment>,
1119 ) -> Result<Self, SchemaContractError> {
1120 if variants.is_empty() {
1121 return Err(SchemaContractError::InvalidReferenceList);
1122 }
1123 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1124 crate::compact_sort_unstable_by(&mut variants, |left, right| {
1125 left.source_key.cmp(&right.source_key)
1126 });
1127 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1128 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1129 for variant in &variants {
1130 variant.validate()?;
1131 }
1132 Ok(Self {
1133 source_key: TypeSourceKey::from_name(&name),
1134 name,
1135 variants,
1136 })
1137 }
1138
1139 #[must_use]
1141 pub const fn source_key(&self) -> &TypeSourceKey {
1142 &self.source_key
1143 }
1144
1145 #[must_use]
1147 pub const fn name(&self) -> &SchemaName {
1148 &self.name
1149 }
1150
1151 #[must_use]
1153 pub fn variants(&self) -> &[EnumVariantFragment] {
1154 &self.variants
1155 }
1156
1157 fn validate(&self) -> Result<(), SchemaContractError> {
1158 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1159 if rebuilt != *self {
1160 return Err(SchemaContractError::NonCanonical);
1161 }
1162 Ok(())
1163 }
1164}
1165
1166#[derive(Clone, Debug, Eq, PartialEq)]
1168pub enum NamedTypeFragment {
1169 Record(RecordTypeFragment),
1171 Enum(EnumTypeFragment),
1173 Newtype {
1175 source_key: TypeSourceKey,
1177 name: SchemaName,
1179 inner: FieldType,
1181 },
1182 List {
1184 source_key: TypeSourceKey,
1186 name: SchemaName,
1188 item: FieldType,
1190 },
1191 Set {
1193 source_key: TypeSourceKey,
1195 name: SchemaName,
1197 item: FieldType,
1199 },
1200 Map {
1202 source_key: TypeSourceKey,
1204 name: SchemaName,
1206 key: FieldType,
1208 value: FieldType,
1210 },
1211 Tuple {
1213 source_key: TypeSourceKey,
1215 name: SchemaName,
1217 members: Vec<TupleElementFragment>,
1219 },
1220}
1221
1222impl NamedTypeFragment {
1223 #[must_use]
1225 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1226 Self::Newtype {
1227 source_key: TypeSourceKey::from_name(&name),
1228 name,
1229 inner,
1230 }
1231 }
1232
1233 #[must_use]
1235 pub fn list(name: SchemaName, item: FieldType) -> Self {
1236 Self::List {
1237 source_key: TypeSourceKey::from_name(&name),
1238 name,
1239 item,
1240 }
1241 }
1242
1243 #[must_use]
1245 pub fn set(name: SchemaName, item: FieldType) -> Self {
1246 Self::Set {
1247 source_key: TypeSourceKey::from_name(&name),
1248 name,
1249 item,
1250 }
1251 }
1252
1253 #[must_use]
1255 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1256 Self::Map {
1257 source_key: TypeSourceKey::from_name(&name),
1258 name,
1259 key,
1260 value,
1261 }
1262 }
1263
1264 #[must_use]
1266 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1267 Self::Tuple {
1268 source_key: TypeSourceKey::from_name(&name),
1269 name,
1270 members,
1271 }
1272 }
1273
1274 #[must_use]
1276 pub const fn source_key(&self) -> &TypeSourceKey {
1277 match self {
1278 Self::Record(record) => record.source_key(),
1279 Self::Enum(r#enum) => r#enum.source_key(),
1280 Self::Newtype { source_key, .. }
1281 | Self::List { source_key, .. }
1282 | Self::Set { source_key, .. }
1283 | Self::Map { source_key, .. }
1284 | Self::Tuple { source_key, .. } => source_key,
1285 }
1286 }
1287
1288 #[must_use]
1290 pub const fn name(&self) -> &SchemaName {
1291 match self {
1292 Self::Record(record) => record.name(),
1293 Self::Enum(r#enum) => r#enum.name(),
1294 Self::Newtype { name, .. }
1295 | Self::List { name, .. }
1296 | Self::Set { name, .. }
1297 | Self::Map { name, .. }
1298 | Self::Tuple { name, .. } => name,
1299 }
1300 }
1301
1302 fn validate(&self) -> Result<(), SchemaContractError> {
1303 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1304 match self {
1305 Self::Record(record) => record.validate(),
1306 Self::Enum(r#enum) => r#enum.validate(),
1307 Self::Newtype { inner, .. }
1308 | Self::List { item: inner, .. }
1309 | Self::Set { item: inner, .. } => inner.validate(),
1310 Self::Map { key, value, .. } => {
1311 key.validate()?;
1312 value.validate()
1313 }
1314 Self::Tuple { members, .. } => {
1315 if members.is_empty() {
1316 return Err(SchemaContractError::InvalidReferenceList);
1317 }
1318 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1319 members.iter().try_for_each(TupleElementFragment::validate)
1320 }
1321 }
1322 }
1323}
1324
1325#[derive(Clone, Debug, Eq, PartialEq)]
1327pub struct SchemaFragment {
1328 entities: Vec<EntityFragment>,
1329 types: Vec<NamedTypeFragment>,
1330}
1331
1332impl SchemaFragment {
1333 pub fn try_new(
1340 mut entities: Vec<EntityFragment>,
1341 mut types: Vec<NamedTypeFragment>,
1342 ) -> Result<Self, SchemaContractError> {
1343 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1344 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1345 crate::compact_sort_unstable_by(&mut entities, |left, right| {
1346 left.source_key.cmp(&right.source_key)
1347 });
1348 crate::compact_sort_unstable_by(&mut types, |left, right| {
1349 left.source_key().cmp(right.source_key())
1350 });
1351 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1352 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1353 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1354 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1355 for entity in &entities {
1356 entity.validate()?;
1357 }
1358 for r#type in &types {
1359 r#type.validate()?;
1360 }
1361 Ok(Self { entities, types })
1362 }
1363
1364 #[must_use]
1366 pub fn entities(&self) -> &[EntityFragment] {
1367 &self.entities
1368 }
1369
1370 #[must_use]
1372 pub fn types(&self) -> &[NamedTypeFragment] {
1373 &self.types
1374 }
1375
1376 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1377 for r#type in &self.types {
1378 r#type.validate()?;
1379 }
1380 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1381 if rebuilt != *self {
1382 return Err(SchemaContractError::NonCanonical);
1383 }
1384 Ok(())
1385 }
1386}
1387
1388pub(crate) const fn check_len(
1389 kind: &'static str,
1390 len: usize,
1391 max: usize,
1392) -> Result<(), SchemaContractError> {
1393 if len > max {
1394 return Err(SchemaContractError::TooManyItems { kind, len, max });
1395 }
1396 Ok(())
1397}
1398
1399fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1400 source_key == name.as_str()
1401}
1402
1403fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1404 if !current_name_key_matches(source_key, name) {
1405 return Err(SchemaContractError::NonCanonical);
1406 }
1407 Ok(())
1408}
1409
1410fn ensure_canonical_rebuild<T: PartialEq>(
1411 current: &T,
1412 rebuilt: &T,
1413) -> Result<(), SchemaContractError> {
1414 if current != rebuilt {
1415 return Err(SchemaContractError::NonCanonical);
1416 }
1417 Ok(())
1418}
1419
1420fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1421where
1422 T: Ord,
1423{
1424 let mut seen = BTreeSet::new();
1425 if values.iter().any(|value| !seen.insert(value)) {
1426 return Err(SchemaContractError::InvalidReferenceList);
1427 }
1428 Ok(())
1429}
1430
1431fn ensure_unique_sorted_by<T, K>(
1432 values: &[T],
1433 key: impl Fn(&T) -> &K,
1434) -> Result<(), SchemaContractError>
1435where
1436 K: Eq,
1437{
1438 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1439 return Err(SchemaContractError::DuplicateSourceKey);
1440 }
1441 Ok(())
1442}
1443
1444fn ensure_unique_names<'a>(
1445 names: impl IntoIterator<Item = &'a SchemaName>,
1446) -> Result<(), SchemaContractError> {
1447 let mut seen = BTreeSet::new();
1448 if names.into_iter().any(|name| !seen.insert(name)) {
1449 return Err(SchemaContractError::DuplicateName);
1450 }
1451 Ok(())
1452}
1453
1454fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1455 for policy in [
1456 FieldManagementPolicy::CreatedAt,
1457 FieldManagementPolicy::UpdatedAt,
1458 ] {
1459 if fields
1460 .iter()
1461 .filter(|field| field.management() == Some(policy))
1462 .count()
1463 > 1
1464 {
1465 return Err(SchemaContractError::InvalidFieldPolicy);
1466 }
1467 }
1468 Ok(())
1469}
1470
1471fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1472 match value.scale().cmp(&scale) {
1473 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1474 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1475 }
1476}
1477
1478#[cfg(test)]
1479mod tests {
1480 use super::{
1481 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1482 SchemaContractError, SchemaName,
1483 };
1484
1485 #[test]
1486 fn independently_decoded_field_key_and_name_must_match() {
1487 let field = FieldFragment {
1488 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1489 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1490 field_type: FieldType::Scalar(ScalarType::Nat64),
1491 nullable: false,
1492 insert_policy: FieldInsertPolicy::Required,
1493 management: None,
1494 };
1495
1496 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1497 }
1498}