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 fields.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
712 indexes.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
713 relations.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
714 constraints.sort_unstable_by(|left, right| left.source_key.cmp(&right.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 fields.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
1002 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
1003 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
1004 for field in &fields {
1005 field.validate()?;
1006 }
1007 Ok(Self {
1008 source_key: TypeSourceKey::from_name(&name),
1009 name,
1010 fields,
1011 })
1012 }
1013
1014 #[must_use]
1016 pub const fn source_key(&self) -> &TypeSourceKey {
1017 &self.source_key
1018 }
1019
1020 #[must_use]
1022 pub const fn name(&self) -> &SchemaName {
1023 &self.name
1024 }
1025
1026 #[must_use]
1028 pub fn fields(&self) -> &[RecordFieldFragment] {
1029 &self.fields
1030 }
1031
1032 fn validate(&self) -> Result<(), SchemaContractError> {
1033 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1034 if rebuilt != *self {
1035 return Err(SchemaContractError::NonCanonical);
1036 }
1037 Ok(())
1038 }
1039}
1040
1041#[derive(Clone, Debug, Eq, PartialEq)]
1043pub struct EnumVariantFragment {
1044 source_key: TypeSourceKey,
1045 name: SchemaName,
1046 payload: Option<FieldType>,
1047}
1048
1049impl EnumVariantFragment {
1050 #[must_use]
1052 pub fn new(name: SchemaName) -> Self {
1053 Self {
1054 source_key: TypeSourceKey::from_name(&name),
1055 name,
1056 payload: None,
1057 }
1058 }
1059
1060 #[must_use]
1062 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1063 Self {
1064 source_key: TypeSourceKey::from_name(&name),
1065 name,
1066 payload: Some(payload),
1067 }
1068 }
1069
1070 #[must_use]
1072 pub const fn source_key(&self) -> &TypeSourceKey {
1073 &self.source_key
1074 }
1075
1076 #[must_use]
1078 pub const fn name(&self) -> &SchemaName {
1079 &self.name
1080 }
1081
1082 #[must_use]
1084 pub const fn payload(&self) -> Option<&FieldType> {
1085 self.payload.as_ref()
1086 }
1087
1088 fn validate(&self) -> Result<(), SchemaContractError> {
1089 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1090 return Err(SchemaContractError::NonCanonical);
1091 }
1092 match &self.payload {
1093 Some(payload) => payload.validate(),
1094 None => Ok(()),
1095 }
1096 }
1097}
1098
1099#[derive(Clone, Debug, Eq, PartialEq)]
1101pub struct EnumTypeFragment {
1102 source_key: TypeSourceKey,
1103 name: SchemaName,
1104 variants: Vec<EnumVariantFragment>,
1105}
1106
1107impl EnumTypeFragment {
1108 pub fn try_new(
1115 name: SchemaName,
1116 mut variants: Vec<EnumVariantFragment>,
1117 ) -> Result<Self, SchemaContractError> {
1118 if variants.is_empty() {
1119 return Err(SchemaContractError::InvalidReferenceList);
1120 }
1121 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1122 variants.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
1123 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1124 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1125 for variant in &variants {
1126 variant.validate()?;
1127 }
1128 Ok(Self {
1129 source_key: TypeSourceKey::from_name(&name),
1130 name,
1131 variants,
1132 })
1133 }
1134
1135 #[must_use]
1137 pub const fn source_key(&self) -> &TypeSourceKey {
1138 &self.source_key
1139 }
1140
1141 #[must_use]
1143 pub const fn name(&self) -> &SchemaName {
1144 &self.name
1145 }
1146
1147 #[must_use]
1149 pub fn variants(&self) -> &[EnumVariantFragment] {
1150 &self.variants
1151 }
1152
1153 fn validate(&self) -> Result<(), SchemaContractError> {
1154 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1155 if rebuilt != *self {
1156 return Err(SchemaContractError::NonCanonical);
1157 }
1158 Ok(())
1159 }
1160}
1161
1162#[derive(Clone, Debug, Eq, PartialEq)]
1164pub enum NamedTypeFragment {
1165 Record(RecordTypeFragment),
1167 Enum(EnumTypeFragment),
1169 Newtype {
1171 source_key: TypeSourceKey,
1173 name: SchemaName,
1175 inner: FieldType,
1177 },
1178 List {
1180 source_key: TypeSourceKey,
1182 name: SchemaName,
1184 item: FieldType,
1186 },
1187 Set {
1189 source_key: TypeSourceKey,
1191 name: SchemaName,
1193 item: FieldType,
1195 },
1196 Map {
1198 source_key: TypeSourceKey,
1200 name: SchemaName,
1202 key: FieldType,
1204 value: FieldType,
1206 },
1207 Tuple {
1209 source_key: TypeSourceKey,
1211 name: SchemaName,
1213 members: Vec<TupleElementFragment>,
1215 },
1216}
1217
1218impl NamedTypeFragment {
1219 #[must_use]
1221 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1222 Self::Newtype {
1223 source_key: TypeSourceKey::from_name(&name),
1224 name,
1225 inner,
1226 }
1227 }
1228
1229 #[must_use]
1231 pub fn list(name: SchemaName, item: FieldType) -> Self {
1232 Self::List {
1233 source_key: TypeSourceKey::from_name(&name),
1234 name,
1235 item,
1236 }
1237 }
1238
1239 #[must_use]
1241 pub fn set(name: SchemaName, item: FieldType) -> Self {
1242 Self::Set {
1243 source_key: TypeSourceKey::from_name(&name),
1244 name,
1245 item,
1246 }
1247 }
1248
1249 #[must_use]
1251 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1252 Self::Map {
1253 source_key: TypeSourceKey::from_name(&name),
1254 name,
1255 key,
1256 value,
1257 }
1258 }
1259
1260 #[must_use]
1262 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1263 Self::Tuple {
1264 source_key: TypeSourceKey::from_name(&name),
1265 name,
1266 members,
1267 }
1268 }
1269
1270 #[must_use]
1272 pub const fn source_key(&self) -> &TypeSourceKey {
1273 match self {
1274 Self::Record(record) => record.source_key(),
1275 Self::Enum(r#enum) => r#enum.source_key(),
1276 Self::Newtype { source_key, .. }
1277 | Self::List { source_key, .. }
1278 | Self::Set { source_key, .. }
1279 | Self::Map { source_key, .. }
1280 | Self::Tuple { source_key, .. } => source_key,
1281 }
1282 }
1283
1284 #[must_use]
1286 pub const fn name(&self) -> &SchemaName {
1287 match self {
1288 Self::Record(record) => record.name(),
1289 Self::Enum(r#enum) => r#enum.name(),
1290 Self::Newtype { name, .. }
1291 | Self::List { name, .. }
1292 | Self::Set { name, .. }
1293 | Self::Map { name, .. }
1294 | Self::Tuple { name, .. } => name,
1295 }
1296 }
1297
1298 fn validate(&self) -> Result<(), SchemaContractError> {
1299 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1300 match self {
1301 Self::Record(record) => record.validate(),
1302 Self::Enum(r#enum) => r#enum.validate(),
1303 Self::Newtype { inner, .. }
1304 | Self::List { item: inner, .. }
1305 | Self::Set { item: inner, .. } => inner.validate(),
1306 Self::Map { key, value, .. } => {
1307 key.validate()?;
1308 value.validate()
1309 }
1310 Self::Tuple { members, .. } => {
1311 if members.is_empty() {
1312 return Err(SchemaContractError::InvalidReferenceList);
1313 }
1314 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1315 members.iter().try_for_each(TupleElementFragment::validate)
1316 }
1317 }
1318 }
1319}
1320
1321#[derive(Clone, Debug, Eq, PartialEq)]
1323pub struct SchemaFragment {
1324 entities: Vec<EntityFragment>,
1325 types: Vec<NamedTypeFragment>,
1326}
1327
1328impl SchemaFragment {
1329 pub fn try_new(
1336 mut entities: Vec<EntityFragment>,
1337 mut types: Vec<NamedTypeFragment>,
1338 ) -> Result<Self, SchemaContractError> {
1339 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1340 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1341 entities.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
1342 types.sort_unstable_by(|left, right| left.source_key().cmp(right.source_key()));
1343 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1344 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1345 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1346 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1347 for entity in &entities {
1348 entity.validate()?;
1349 }
1350 for r#type in &types {
1351 r#type.validate()?;
1352 }
1353 Ok(Self { entities, types })
1354 }
1355
1356 #[must_use]
1358 pub fn entities(&self) -> &[EntityFragment] {
1359 &self.entities
1360 }
1361
1362 #[must_use]
1364 pub fn types(&self) -> &[NamedTypeFragment] {
1365 &self.types
1366 }
1367
1368 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1369 for r#type in &self.types {
1370 r#type.validate()?;
1371 }
1372 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1373 if rebuilt != *self {
1374 return Err(SchemaContractError::NonCanonical);
1375 }
1376 Ok(())
1377 }
1378}
1379
1380pub(crate) const fn check_len(
1381 kind: &'static str,
1382 len: usize,
1383 max: usize,
1384) -> Result<(), SchemaContractError> {
1385 if len > max {
1386 return Err(SchemaContractError::TooManyItems { kind, len, max });
1387 }
1388 Ok(())
1389}
1390
1391fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1392 source_key == name.as_str()
1393}
1394
1395fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1396 if !current_name_key_matches(source_key, name) {
1397 return Err(SchemaContractError::NonCanonical);
1398 }
1399 Ok(())
1400}
1401
1402fn ensure_canonical_rebuild<T: PartialEq>(
1403 current: &T,
1404 rebuilt: &T,
1405) -> Result<(), SchemaContractError> {
1406 if current != rebuilt {
1407 return Err(SchemaContractError::NonCanonical);
1408 }
1409 Ok(())
1410}
1411
1412fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1413where
1414 T: Ord,
1415{
1416 let mut seen = BTreeSet::new();
1417 if values.iter().any(|value| !seen.insert(value)) {
1418 return Err(SchemaContractError::InvalidReferenceList);
1419 }
1420 Ok(())
1421}
1422
1423fn ensure_unique_sorted_by<T, K>(
1424 values: &[T],
1425 key: impl Fn(&T) -> &K,
1426) -> Result<(), SchemaContractError>
1427where
1428 K: Eq,
1429{
1430 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1431 return Err(SchemaContractError::DuplicateSourceKey);
1432 }
1433 Ok(())
1434}
1435
1436fn ensure_unique_names<'a>(
1437 names: impl IntoIterator<Item = &'a SchemaName>,
1438) -> Result<(), SchemaContractError> {
1439 let mut seen = BTreeSet::new();
1440 if names.into_iter().any(|name| !seen.insert(name)) {
1441 return Err(SchemaContractError::DuplicateName);
1442 }
1443 Ok(())
1444}
1445
1446fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1447 for policy in [
1448 FieldManagementPolicy::CreatedAt,
1449 FieldManagementPolicy::UpdatedAt,
1450 ] {
1451 if fields
1452 .iter()
1453 .filter(|field| field.management() == Some(policy))
1454 .count()
1455 > 1
1456 {
1457 return Err(SchemaContractError::InvalidFieldPolicy);
1458 }
1459 }
1460 Ok(())
1461}
1462
1463fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1464 match value.scale().cmp(&scale) {
1465 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1466 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1467 }
1468}
1469
1470#[cfg(test)]
1471mod tests {
1472 use super::{
1473 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1474 SchemaContractError, SchemaName,
1475 };
1476
1477 #[test]
1478 fn independently_decoded_field_key_and_name_must_match() {
1479 let field = FieldFragment {
1480 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1481 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1482 field_type: FieldType::Scalar(ScalarType::Nat64),
1483 nullable: false,
1484 insert_policy: FieldInsertPolicy::Required,
1485 management: None,
1486 };
1487
1488 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1489 }
1490}