1use std::collections::BTreeSet;
4
5use crate::{
6 ConstraintSourceKey, Decimal, DeclaredEntityVersion, EntitySourceKey, FieldSourceKey,
7 IndexSourceKey, MAX_FRAGMENT_CONSTRAINTS, MAX_FRAGMENT_ENTITIES, MAX_FRAGMENT_FIELDS,
8 MAX_FRAGMENT_INDEXES, MAX_FRAGMENT_RELATIONS, MAX_FRAGMENT_TYPES, MAX_RELATION_PATH_STEPS,
9 MAX_SCHEMA_FIELD_TYPE_DEPTH, RelationSourceKey, RuleSourceKey, ScalarKind, ScalarLiteral,
10 SchemaContractError, SchemaName, SourceCheckExpr, SourceRuleOperation, TypeSourceKey,
11};
12
13#[derive(Clone, Debug, Eq, PartialEq)]
15pub enum FieldType {
16 Scalar(ScalarType),
18 List(Box<Self>),
20 Named(TypeSourceKey),
22}
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum ScalarType {
27 Account,
29 Blob {
31 max_len: Option<u32>,
33 },
34 Bool,
36 Date,
38 Decimal {
40 scale: u32,
42 },
43 Duration,
45 Float32,
47 Float64,
49 Int8,
51 Int16,
53 Int32,
55 Int64,
57 Int128,
59 Principal,
61 Subaccount,
63 Text {
65 max_len: Option<u32>,
67 },
68 Timestamp,
70 Nat8,
72 Nat16,
74 Nat32,
76 Nat64,
78 Nat128,
80 U256,
82 Ulid,
84 Unit,
86}
87
88impl ScalarType {
89 #[must_use]
91 pub const fn kind(self) -> ScalarKind {
92 match self {
93 Self::Account => ScalarKind::Account,
94 Self::Blob { .. } => ScalarKind::Blob,
95 Self::Bool => ScalarKind::Bool,
96 Self::Date => ScalarKind::Date,
97 Self::Decimal { .. } => ScalarKind::Decimal,
98 Self::Duration => ScalarKind::Duration,
99 Self::Float32 => ScalarKind::Float32,
100 Self::Float64 => ScalarKind::Float64,
101 Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 => ScalarKind::Int,
102 Self::Int128 => ScalarKind::Int128,
103 Self::Principal => ScalarKind::Principal,
104 Self::Subaccount => ScalarKind::Subaccount,
105 Self::Text { .. } => ScalarKind::Text,
106 Self::Timestamp => ScalarKind::Timestamp,
107 Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 => ScalarKind::Nat,
108 Self::Nat128 => ScalarKind::Nat128,
109 Self::U256 => ScalarKind::U256,
110 Self::Ulid => ScalarKind::Ulid,
111 Self::Unit => ScalarKind::Unit,
112 }
113 }
114
115 pub(crate) const fn validate(self) -> Result<(), SchemaContractError> {
116 match self {
117 Self::Decimal { scale } if scale > Decimal::max_supported_scale() => {
118 Err(SchemaContractError::InvalidFieldType)
119 }
120 _ => Ok(()),
121 }
122 }
123
124 pub(crate) fn accepts_literal(self, literal: &ScalarLiteral) -> bool {
125 match (self, literal) {
126 (Self::Account, ScalarLiteral::Account(_))
127 | (Self::Bool, ScalarLiteral::Bool(_))
128 | (Self::Date, ScalarLiteral::Date(_))
129 | (Self::Duration, ScalarLiteral::Duration(_))
130 | (Self::Float32, ScalarLiteral::Float32(_))
131 | (Self::Float64, ScalarLiteral::Float64(_))
132 | (Self::Int128, ScalarLiteral::Int(_))
133 | (Self::Principal, ScalarLiteral::Principal(_))
134 | (Self::Subaccount, ScalarLiteral::Subaccount(_))
135 | (Self::Timestamp, ScalarLiteral::Timestamp(_))
136 | (Self::Nat128, ScalarLiteral::Nat(_))
137 | (Self::U256, ScalarLiteral::U256(_))
138 | (Self::Ulid, ScalarLiteral::Ulid(_))
139 | (Self::Unit, ScalarLiteral::Unit(_)) => true,
140 (Self::Blob { max_len }, ScalarLiteral::Blob(value)) => {
141 max_len.is_none_or(|max| value.len() <= max as usize)
142 }
143 (Self::Text { max_len }, ScalarLiteral::Text(value)) => {
144 max_len.is_none_or(|max| value.chars().count() <= max as usize)
145 }
146 (Self::Int8, ScalarLiteral::Int(value)) => i8::try_from(*value).is_ok(),
147 (Self::Int16, ScalarLiteral::Int(value)) => i16::try_from(*value).is_ok(),
148 (Self::Int32, ScalarLiteral::Int(value)) => i32::try_from(*value).is_ok(),
149 (Self::Int64, ScalarLiteral::Int(value)) => i64::try_from(*value).is_ok(),
150 (Self::Nat8, ScalarLiteral::Nat(value)) => u8::try_from(*value).is_ok(),
151 (Self::Nat16, ScalarLiteral::Nat(value)) => u16::try_from(*value).is_ok(),
152 (Self::Nat32, ScalarLiteral::Nat(value)) => u32::try_from(*value).is_ok(),
153 (Self::Nat64, ScalarLiteral::Nat(value)) => u64::try_from(*value).is_ok(),
154 (Self::Decimal { scale }, ScalarLiteral::Decimal(value)) => {
155 decimal_fits_scale(*value, scale)
156 }
157 _ => false,
158 }
159 }
160}
161
162impl FieldType {
163 pub fn append_named_type_dependencies(&self, pending: &mut Vec<TypeSourceKey>) {
168 let mut field_type = self;
169 while let Self::List(item) = field_type {
170 field_type = item;
171 }
172 if let Self::Named(source) = field_type {
173 pending.push(source.clone());
174 }
175 }
176
177 pub(crate) const fn validate(&self) -> Result<(), SchemaContractError> {
178 self.validate_at_depth(0)
179 }
180
181 const fn validate_at_depth(&self, depth: usize) -> Result<(), SchemaContractError> {
182 let Some(depth) = depth.checked_add(1) else {
183 return Err(SchemaContractError::FieldTypeDepthExceeded);
184 };
185 if depth > MAX_SCHEMA_FIELD_TYPE_DEPTH {
186 return Err(SchemaContractError::FieldTypeDepthExceeded);
187 }
188 match self {
189 Self::Scalar(scalar) => scalar.validate(),
190 Self::List(item) => item.validate_at_depth(depth),
191 Self::Named(_) => Ok(()),
192 }
193 }
194}
195
196#[derive(Clone, Debug, Eq, PartialEq)]
198pub enum FieldInsertPolicy {
199 Required,
201 Nullable,
203 Default(ScalarLiteral),
205 Generated,
207}
208
209#[derive(Clone, Copy, Debug, Eq, PartialEq)]
211pub enum FieldManagementPolicy {
212 CreatedAt,
214 UpdatedAt,
216}
217
218#[derive(Clone, Debug, Eq, PartialEq)]
220pub struct FieldFragment {
221 source_key: FieldSourceKey,
222 name: SchemaName,
223 field_type: FieldType,
224 nullable: bool,
225 insert_policy: FieldInsertPolicy,
226 management: Option<FieldManagementPolicy>,
227}
228
229impl FieldFragment {
230 #[must_use]
232 pub fn new(
233 name: SchemaName,
234 field_type: FieldType,
235 nullable: bool,
236 insert_policy: FieldInsertPolicy,
237 management: Option<FieldManagementPolicy>,
238 ) -> Self {
239 Self {
240 source_key: FieldSourceKey::from_name(&name),
241 name,
242 field_type,
243 nullable,
244 insert_policy,
245 management,
246 }
247 }
248
249 #[must_use]
251 pub const fn source_key(&self) -> &FieldSourceKey {
252 &self.source_key
253 }
254
255 #[must_use]
257 pub const fn name(&self) -> &SchemaName {
258 &self.name
259 }
260
261 #[must_use]
263 pub const fn field_type(&self) -> &FieldType {
264 &self.field_type
265 }
266
267 #[must_use]
269 pub const fn nullable(&self) -> bool {
270 self.nullable
271 }
272
273 #[must_use]
275 pub const fn insert_policy(&self) -> &FieldInsertPolicy {
276 &self.insert_policy
277 }
278
279 #[must_use]
281 pub const fn management(&self) -> Option<FieldManagementPolicy> {
282 self.management
283 }
284
285 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
286 ensure_current_name_key(self.source_key.as_str(), &self.name)?;
287 self.field_type.validate()?;
288 if let FieldInsertPolicy::Default(literal) = &self.insert_policy {
289 literal.validate()?;
290 match &self.field_type {
291 FieldType::Scalar(scalar) if scalar.accepts_literal(literal) => {}
292 FieldType::Named(_) if matches!(literal, ScalarLiteral::EnumUnit { .. }) => {}
293 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
294 return Err(SchemaContractError::LiteralTypeMismatch);
295 }
296 }
297 }
298 if matches!(self.insert_policy, FieldInsertPolicy::Nullable) && !self.nullable {
299 return Err(SchemaContractError::InvalidFieldPolicy);
300 }
301 if self.management.is_some()
302 && (!matches!(self.field_type, FieldType::Scalar(ScalarType::Timestamp))
303 || self.nullable
304 || !matches!(self.insert_policy, FieldInsertPolicy::Required))
305 {
306 return Err(SchemaContractError::InvalidFieldPolicy);
307 }
308 Ok(())
309 }
310}
311
312#[derive(Clone, Debug, Eq, PartialEq)]
314pub enum IndexKeyFragment {
315 Field(FieldSourceKey),
317 Lower(FieldSourceKey),
319 Upper(FieldSourceKey),
321 Trim(FieldSourceKey),
323 LowerTrim(FieldSourceKey),
325 Date(FieldSourceKey),
327 Year(FieldSourceKey),
329 Month(FieldSourceKey),
331 Day(FieldSourceKey),
333}
334
335impl IndexKeyFragment {
336 #[must_use]
338 pub const fn field(&self) -> &FieldSourceKey {
339 match self {
340 Self::Field(field)
341 | Self::Lower(field)
342 | Self::Upper(field)
343 | Self::Trim(field)
344 | Self::LowerTrim(field)
345 | Self::Date(field)
346 | Self::Year(field)
347 | Self::Month(field)
348 | Self::Day(field) => field,
349 }
350 }
351}
352
353#[derive(Clone, Debug, Eq, PartialEq)]
355pub struct IndexFragment {
356 source_key: IndexSourceKey,
357 name: SchemaName,
358 key: Vec<IndexKeyFragment>,
359 unique: bool,
360 predicate: Option<SourceCheckExpr>,
361}
362
363impl IndexFragment {
364 pub fn try_new(
370 name: SchemaName,
371 key: Vec<IndexKeyFragment>,
372 unique: bool,
373 predicate: Option<SourceCheckExpr>,
374 ) -> Result<Self, SchemaContractError> {
375 if key.is_empty() {
376 return Err(SchemaContractError::InvalidReferenceList);
377 }
378 if let Some(predicate) = &predicate {
379 predicate.validate()?;
380 }
381 Ok(Self {
382 source_key: IndexSourceKey::from_name(&name),
383 name,
384 key,
385 unique,
386 predicate,
387 })
388 }
389
390 #[must_use]
392 pub const fn source_key(&self) -> &IndexSourceKey {
393 &self.source_key
394 }
395
396 #[must_use]
398 pub const fn name(&self) -> &SchemaName {
399 &self.name
400 }
401
402 #[must_use]
404 pub fn key(&self) -> &[IndexKeyFragment] {
405 &self.key
406 }
407
408 #[must_use]
410 pub const fn unique(&self) -> bool {
411 self.unique
412 }
413
414 #[must_use]
416 pub const fn predicate(&self) -> Option<&SourceCheckExpr> {
417 self.predicate.as_ref()
418 }
419
420 fn validate(&self) -> Result<(), SchemaContractError> {
421 let rebuilt = Self::try_new(
422 self.name.clone(),
423 self.key.clone(),
424 self.unique,
425 self.predicate.clone(),
426 )?;
427 ensure_canonical_rebuild(self, &rebuilt)
428 }
429}
430
431#[derive(Clone, Copy, Debug, Eq, PartialEq)]
433pub enum RelationDeleteAction {
434 Restrict,
436}
437
438#[derive(Clone, Debug, Eq, PartialEq)]
440pub enum RelationPathStepFragment {
441 EnterNamed { r#type: TypeSourceKey },
443 OptionalSome,
445 RecordMember {
447 record: TypeSourceKey,
449 field: FieldSourceKey,
451 },
452 EnumVariantPayload {
454 r#enum: TypeSourceKey,
456 variant: TypeSourceKey,
458 },
459 ListItems,
461 SetItems,
463 MapValues,
465}
466
467#[derive(Clone, Debug, Eq, PartialEq)]
469pub enum RelationSourceFragment {
470 Direct { fields: Vec<FieldSourceKey> },
472 Nested {
474 root: FieldSourceKey,
476 steps: Vec<RelationPathStepFragment>,
478 },
479}
480
481impl RelationSourceFragment {
482 #[must_use]
484 pub const fn direct(fields: Vec<FieldSourceKey>) -> Self {
485 Self::Direct { fields }
486 }
487
488 pub fn root_fields(&self) -> impl Iterator<Item = &FieldSourceKey> {
490 let direct = match self {
491 Self::Direct { fields } => fields.as_slice(),
492 Self::Nested { .. } => &[],
493 };
494 let nested = match self {
495 Self::Nested { root, .. } => Some(root),
496 Self::Direct { .. } => None,
497 };
498 direct.iter().chain(nested)
499 }
500
501 fn validate(&self, target_field_count: usize) -> Result<(), SchemaContractError> {
502 match self {
503 Self::Direct { fields } => {
504 if fields.is_empty() || fields.len() != target_field_count {
505 return Err(SchemaContractError::InvalidReferenceList);
506 }
507 ensure_unique(fields)
508 }
509 Self::Nested { steps, .. } => {
510 if steps.is_empty()
511 || steps.len() > MAX_RELATION_PATH_STEPS
512 || target_field_count != 1
513 {
514 return Err(SchemaContractError::InvalidReferenceList);
515 }
516 Ok(())
517 }
518 }
519 }
520}
521
522#[derive(Clone, Debug, Eq, PartialEq)]
524pub struct RelationFragment {
525 source_key: RelationSourceKey,
526 name: SchemaName,
527 source: RelationSourceFragment,
528 target_entity: EntitySourceKey,
529 target_fields: Vec<FieldSourceKey>,
530 on_delete: RelationDeleteAction,
531}
532
533impl RelationFragment {
534 pub fn try_new(
541 name: SchemaName,
542 source: RelationSourceFragment,
543 target_entity: EntitySourceKey,
544 target_fields: Vec<FieldSourceKey>,
545 on_delete: RelationDeleteAction,
546 ) -> Result<Self, SchemaContractError> {
547 if target_fields.is_empty() {
548 return Err(SchemaContractError::InvalidReferenceList);
549 }
550 source.validate(target_fields.len())?;
551 ensure_unique(&target_fields)?;
552 Ok(Self {
553 source_key: RelationSourceKey::from_name(&name),
554 name,
555 source,
556 target_entity,
557 target_fields,
558 on_delete,
559 })
560 }
561
562 #[must_use]
564 pub const fn source_key(&self) -> &RelationSourceKey {
565 &self.source_key
566 }
567
568 #[must_use]
570 pub const fn name(&self) -> &SchemaName {
571 &self.name
572 }
573
574 #[must_use]
576 pub const fn source(&self) -> &RelationSourceFragment {
577 &self.source
578 }
579
580 #[must_use]
582 pub const fn target_entity(&self) -> &EntitySourceKey {
583 &self.target_entity
584 }
585
586 #[must_use]
588 pub fn target_fields(&self) -> &[FieldSourceKey] {
589 &self.target_fields
590 }
591
592 #[must_use]
594 pub const fn on_delete(&self) -> RelationDeleteAction {
595 self.on_delete
596 }
597
598 fn validate(&self) -> Result<(), SchemaContractError> {
599 let rebuilt = Self::try_new(
600 self.name.clone(),
601 self.source.clone(),
602 self.target_entity.clone(),
603 self.target_fields.clone(),
604 self.on_delete,
605 )?;
606 ensure_canonical_rebuild(self, &rebuilt)
607 }
608}
609
610#[derive(Clone, Debug, Eq, PartialEq)]
612pub enum ConstraintFragmentKind {
613 Check(SourceCheckExpr),
615 TargetedRule(TargetedRuleFragment),
617}
618
619impl ConstraintFragmentKind {
620 fn validate(&self) -> Result<(), SchemaContractError> {
621 match self {
622 Self::Check(expression) => expression.validate(),
623 Self::TargetedRule(rule) => rule.validate(),
624 }
625 }
626}
627
628#[derive(Clone, Debug, Eq, PartialEq)]
630pub struct TargetedRuleFragment {
631 root: FieldSourceKey,
632 target_type: TypeSourceKey,
633 rule: RuleSourceKey,
634 operation: SourceRuleOperation,
635}
636
637impl TargetedRuleFragment {
638 #[must_use]
640 pub fn new(
641 root: FieldSourceKey,
642 target_type: TypeSourceKey,
643 rule: SchemaName,
644 operation: SourceRuleOperation,
645 ) -> Self {
646 Self {
647 root,
648 target_type,
649 rule: RuleSourceKey::from_name(&rule),
650 operation,
651 }
652 }
653
654 #[must_use]
656 pub const fn root(&self) -> &FieldSourceKey {
657 &self.root
658 }
659
660 #[must_use]
662 pub const fn target_type(&self) -> &TypeSourceKey {
663 &self.target_type
664 }
665
666 #[must_use]
668 pub const fn rule(&self) -> &RuleSourceKey {
669 &self.rule
670 }
671
672 #[must_use]
674 pub const fn operation(&self) -> &SourceRuleOperation {
675 &self.operation
676 }
677
678 fn validate(&self) -> Result<(), SchemaContractError> {
679 self.operation.validate()
680 }
681}
682
683#[derive(Clone, Debug, Eq, PartialEq)]
685pub struct ConstraintFragment {
686 source_key: ConstraintSourceKey,
687 name: SchemaName,
688 kind: ConstraintFragmentKind,
689}
690
691impl ConstraintFragment {
692 #[must_use]
694 pub fn check(name: SchemaName, expression: SourceCheckExpr) -> Self {
695 Self {
696 source_key: ConstraintSourceKey::from_name(&name),
697 name,
698 kind: ConstraintFragmentKind::Check(expression),
699 }
700 }
701
702 #[must_use]
704 pub fn targeted_rule(rule: TargetedRuleFragment) -> Self {
705 let source_key = ConstraintSourceKey::for_targeted_field_rule(
706 rule.root(),
707 rule.target_type(),
708 rule.rule(),
709 );
710 let name = SchemaName::for_targeted_rule(&source_key);
711 Self {
712 source_key,
713 name,
714 kind: ConstraintFragmentKind::TargetedRule(rule),
715 }
716 }
717
718 #[must_use]
720 pub const fn source_key(&self) -> &ConstraintSourceKey {
721 &self.source_key
722 }
723
724 #[must_use]
726 pub const fn name(&self) -> &SchemaName {
727 &self.name
728 }
729
730 #[must_use]
732 pub const fn kind(&self) -> &ConstraintFragmentKind {
733 &self.kind
734 }
735
736 fn validate(&self) -> Result<(), SchemaContractError> {
737 self.kind.validate()?;
738 let rebuilt = match &self.kind {
739 ConstraintFragmentKind::Check(expression) => {
740 Self::check(self.name.clone(), expression.clone())
741 }
742 ConstraintFragmentKind::TargetedRule(rule) => Self::targeted_rule(rule.clone()),
743 };
744 ensure_canonical_rebuild(self, &rebuilt)
745 }
746}
747
748#[derive(Clone, Debug, Eq, PartialEq)]
750pub struct EntityFragment {
751 source_key: EntitySourceKey,
752 name: SchemaName,
753 version: DeclaredEntityVersion,
754 fields: Vec<FieldFragment>,
755 primary_key: Vec<FieldSourceKey>,
756 indexes: Vec<IndexFragment>,
757 relations: Vec<RelationFragment>,
758 constraints: Vec<ConstraintFragment>,
759}
760
761impl EntityFragment {
762 pub fn try_new(
771 name: SchemaName,
772 version: DeclaredEntityVersion,
773 mut fields: Vec<FieldFragment>,
774 primary_key: Vec<FieldSourceKey>,
775 mut indexes: Vec<IndexFragment>,
776 mut relations: Vec<RelationFragment>,
777 mut constraints: Vec<ConstraintFragment>,
778 ) -> Result<Self, SchemaContractError> {
779 let source_key = EntitySourceKey::from_name(&name);
780 check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
781 check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
782 check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
783 check_len(
784 "entity constraints",
785 constraints.len(),
786 MAX_FRAGMENT_CONSTRAINTS,
787 )?;
788 if primary_key.is_empty() {
789 return Err(SchemaContractError::InvalidReferenceList);
790 }
791 ensure_unique(&primary_key)?;
792 crate::compact_sort_unstable_by(&mut fields, |a, b| a.source_key.cmp(&b.source_key));
795 crate::compact_sort_unstable_by(&mut indexes, |a, b| a.source_key.cmp(&b.source_key));
796 crate::compact_sort_unstable_by(&mut relations, |a, b| a.source_key.cmp(&b.source_key));
797 crate::compact_sort_unstable_by(&mut constraints, |a, b| a.source_key.cmp(&b.source_key));
798 ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
799 ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
800 ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
801 ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
802 ensure_unique_names(fields.iter().map(FieldFragment::name))?;
803 ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
804 ensure_unique_names(relations.iter().map(RelationFragment::name))?;
805 ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
806 for field in &fields {
807 field.validate()?;
808 }
809 validate_management_cardinality(&fields)?;
810 for index in &indexes {
811 index.validate()?;
812 }
813 for relation in &relations {
814 relation.validate()?;
815 }
816 for constraint in &constraints {
817 constraint.validate()?;
818 }
819 let field_keys = fields
820 .iter()
821 .map(|field| field.source_key.clone())
822 .collect::<BTreeSet<_>>();
823 if primary_key.iter().any(|field| !field_keys.contains(field)) {
824 return Err(SchemaContractError::InvalidLocalReference);
825 }
826 validate_insert_generation(&fields, &primary_key)?;
827 validate_primary_key_field_shapes(&fields, &primary_key)?;
828 for index in &indexes {
829 if index
830 .key()
831 .iter()
832 .any(|component| !field_keys.contains(component.field()))
833 || index.predicate().is_some_and(|predicate| {
834 predicate
835 .dependencies()
836 .iter()
837 .any(|field| !field_keys.contains(field))
838 })
839 {
840 return Err(SchemaContractError::InvalidLocalReference);
841 }
842 }
843 for relation in &relations {
844 if relation
845 .source()
846 .root_fields()
847 .any(|field| !field_keys.contains(field))
848 || (relation.target_entity() == &source_key
849 && relation
850 .target_fields()
851 .iter()
852 .any(|field| !field_keys.contains(field)))
853 {
854 return Err(SchemaContractError::InvalidLocalReference);
855 }
856 }
857 for constraint in &constraints {
858 let invalid = match constraint.kind() {
859 ConstraintFragmentKind::Check(expression) => expression
860 .dependencies()
861 .iter()
862 .any(|field| !field_keys.contains(field)),
863 ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
864 };
865 if invalid {
866 return Err(SchemaContractError::InvalidLocalReference);
867 }
868 }
869 Ok(Self {
870 source_key,
871 name,
872 version,
873 fields,
874 primary_key,
875 indexes,
876 relations,
877 constraints,
878 })
879 }
880
881 #[must_use]
883 pub const fn source_key(&self) -> &EntitySourceKey {
884 &self.source_key
885 }
886
887 #[must_use]
889 pub const fn name(&self) -> &SchemaName {
890 &self.name
891 }
892
893 #[must_use]
895 pub const fn version(&self) -> DeclaredEntityVersion {
896 self.version
897 }
898
899 #[must_use]
901 pub fn fields(&self) -> &[FieldFragment] {
902 &self.fields
903 }
904
905 #[must_use]
907 pub fn primary_key(&self) -> &[FieldSourceKey] {
908 &self.primary_key
909 }
910
911 #[must_use]
913 pub fn indexes(&self) -> &[IndexFragment] {
914 &self.indexes
915 }
916
917 #[must_use]
919 pub fn relations(&self) -> &[RelationFragment] {
920 &self.relations
921 }
922
923 #[must_use]
925 pub fn constraints(&self) -> &[ConstraintFragment] {
926 &self.constraints
927 }
928
929 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
930 let rebuilt = Self::try_new(
931 self.name.clone(),
932 self.version,
933 self.fields.clone(),
934 self.primary_key.clone(),
935 self.indexes.clone(),
936 self.relations.clone(),
937 self.constraints.clone(),
938 )?;
939 ensure_canonical_rebuild(self, &rebuilt)
940 }
941}
942
943fn validate_primary_key_field_shapes(
946 fields: &[FieldFragment],
947 primary_key: &[FieldSourceKey],
948) -> Result<(), SchemaContractError> {
949 if fields.iter().any(|field| {
950 primary_key.contains(&field.source_key)
951 && (field.nullable
952 || !matches!(field.field_type, FieldType::Scalar(scalar)
953 if scalar.kind().is_primary_key_component_encodable()
954 && (primary_key.len() == 1 || scalar != ScalarType::Unit)))
955 }) {
956 return Err(SchemaContractError::InvalidFieldPolicy);
957 }
958 Ok(())
959}
960
961fn validate_insert_generation(
965 fields: &[FieldFragment],
966 primary_key: &[FieldSourceKey],
967) -> Result<(), SchemaContractError> {
968 for field in fields {
969 if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
970 continue;
971 }
972 if field.nullable() || field.management().is_some() {
973 return Err(SchemaContractError::InvalidFieldPolicy);
974 }
975 match field.field_type() {
976 FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
977 FieldType::Scalar(
978 ScalarType::Nat8
979 | ScalarType::Nat16
980 | ScalarType::Nat32
981 | ScalarType::Nat64
982 | ScalarType::Nat128,
983 ) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
984 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
985 return Err(SchemaContractError::InvalidFieldPolicy);
986 }
987 }
988 }
989 Ok(())
990}
991
992#[derive(Clone, Debug, Eq, PartialEq)]
997pub struct RecordFieldFragment {
998 source_key: FieldSourceKey,
999 name: SchemaName,
1000 field_type: FieldType,
1001 nullable: bool,
1002}
1003
1004impl RecordFieldFragment {
1005 #[must_use]
1007 pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
1008 Self {
1009 source_key: FieldSourceKey::from_name(&name),
1010 name,
1011 field_type,
1012 nullable,
1013 }
1014 }
1015
1016 #[must_use]
1018 pub const fn source_key(&self) -> &FieldSourceKey {
1019 &self.source_key
1020 }
1021
1022 #[must_use]
1024 pub const fn name(&self) -> &SchemaName {
1025 &self.name
1026 }
1027
1028 #[must_use]
1030 pub const fn field_type(&self) -> &FieldType {
1031 &self.field_type
1032 }
1033
1034 #[must_use]
1036 pub const fn nullable(&self) -> bool {
1037 self.nullable
1038 }
1039
1040 fn validate(&self) -> Result<(), SchemaContractError> {
1041 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1042 return Err(SchemaContractError::NonCanonical);
1043 }
1044 self.field_type.validate()
1045 }
1046}
1047
1048#[derive(Clone, Debug, Eq, PartialEq)]
1051pub struct TupleElementFragment {
1052 field_type: FieldType,
1053 nullable: bool,
1054}
1055
1056impl TupleElementFragment {
1057 #[must_use]
1059 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
1060 Self {
1061 field_type,
1062 nullable,
1063 }
1064 }
1065
1066 #[must_use]
1068 pub const fn field_type(&self) -> &FieldType {
1069 &self.field_type
1070 }
1071
1072 #[must_use]
1074 pub const fn nullable(&self) -> bool {
1075 self.nullable
1076 }
1077
1078 const fn validate(&self) -> Result<(), SchemaContractError> {
1079 self.field_type.validate()
1080 }
1081}
1082
1083#[derive(Clone, Debug, Eq, PartialEq)]
1085pub struct RecordTypeFragment {
1086 source_key: TypeSourceKey,
1087 name: SchemaName,
1088 fields: Vec<RecordFieldFragment>,
1089}
1090
1091impl RecordTypeFragment {
1092 pub fn try_new(
1099 name: SchemaName,
1100 mut fields: Vec<RecordFieldFragment>,
1101 ) -> Result<Self, SchemaContractError> {
1102 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
1103 crate::compact_sort_unstable_by(&mut fields, |left, right| {
1104 left.source_key.cmp(&right.source_key)
1105 });
1106 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
1107 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
1108 for field in &fields {
1109 field.validate()?;
1110 }
1111 Ok(Self {
1112 source_key: TypeSourceKey::from_name(&name),
1113 name,
1114 fields,
1115 })
1116 }
1117
1118 #[must_use]
1120 pub const fn source_key(&self) -> &TypeSourceKey {
1121 &self.source_key
1122 }
1123
1124 #[must_use]
1126 pub const fn name(&self) -> &SchemaName {
1127 &self.name
1128 }
1129
1130 #[must_use]
1132 pub fn fields(&self) -> &[RecordFieldFragment] {
1133 &self.fields
1134 }
1135
1136 fn validate(&self) -> Result<(), SchemaContractError> {
1137 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1138 if rebuilt != *self {
1139 return Err(SchemaContractError::NonCanonical);
1140 }
1141 Ok(())
1142 }
1143}
1144
1145#[derive(Clone, Debug, Eq, PartialEq)]
1147pub struct EnumVariantFragment {
1148 source_key: TypeSourceKey,
1149 name: SchemaName,
1150 payload: Option<FieldType>,
1151}
1152
1153impl EnumVariantFragment {
1154 #[must_use]
1156 pub fn new(name: SchemaName) -> Self {
1157 Self {
1158 source_key: TypeSourceKey::from_name(&name),
1159 name,
1160 payload: None,
1161 }
1162 }
1163
1164 #[must_use]
1166 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1167 Self {
1168 source_key: TypeSourceKey::from_name(&name),
1169 name,
1170 payload: Some(payload),
1171 }
1172 }
1173
1174 #[must_use]
1176 pub const fn source_key(&self) -> &TypeSourceKey {
1177 &self.source_key
1178 }
1179
1180 #[must_use]
1182 pub const fn name(&self) -> &SchemaName {
1183 &self.name
1184 }
1185
1186 #[must_use]
1188 pub const fn payload(&self) -> Option<&FieldType> {
1189 self.payload.as_ref()
1190 }
1191
1192 fn validate(&self) -> Result<(), SchemaContractError> {
1193 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1194 return Err(SchemaContractError::NonCanonical);
1195 }
1196 match &self.payload {
1197 Some(payload) => payload.validate(),
1198 None => Ok(()),
1199 }
1200 }
1201}
1202
1203#[derive(Clone, Debug, Eq, PartialEq)]
1205pub struct EnumTypeFragment {
1206 source_key: TypeSourceKey,
1207 name: SchemaName,
1208 variants: Vec<EnumVariantFragment>,
1209}
1210
1211impl EnumTypeFragment {
1212 pub fn try_new(
1219 name: SchemaName,
1220 mut variants: Vec<EnumVariantFragment>,
1221 ) -> Result<Self, SchemaContractError> {
1222 if variants.is_empty() {
1223 return Err(SchemaContractError::InvalidReferenceList);
1224 }
1225 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1226 crate::compact_sort_unstable_by(&mut variants, |left, right| {
1227 left.source_key.cmp(&right.source_key)
1228 });
1229 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1230 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1231 for variant in &variants {
1232 variant.validate()?;
1233 }
1234 Ok(Self {
1235 source_key: TypeSourceKey::from_name(&name),
1236 name,
1237 variants,
1238 })
1239 }
1240
1241 #[must_use]
1243 pub const fn source_key(&self) -> &TypeSourceKey {
1244 &self.source_key
1245 }
1246
1247 #[must_use]
1249 pub const fn name(&self) -> &SchemaName {
1250 &self.name
1251 }
1252
1253 #[must_use]
1255 pub fn variants(&self) -> &[EnumVariantFragment] {
1256 &self.variants
1257 }
1258
1259 fn validate(&self) -> Result<(), SchemaContractError> {
1260 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1261 if rebuilt != *self {
1262 return Err(SchemaContractError::NonCanonical);
1263 }
1264 Ok(())
1265 }
1266}
1267
1268#[derive(Clone, Debug, Eq, PartialEq)]
1270pub enum NamedTypeFragment {
1271 Record(RecordTypeFragment),
1273 Enum(EnumTypeFragment),
1275 Newtype {
1277 source_key: TypeSourceKey,
1279 name: SchemaName,
1281 inner: FieldType,
1283 },
1284 List {
1286 source_key: TypeSourceKey,
1288 name: SchemaName,
1290 item: FieldType,
1292 },
1293 Set {
1295 source_key: TypeSourceKey,
1297 name: SchemaName,
1299 item: FieldType,
1301 },
1302 Map {
1304 source_key: TypeSourceKey,
1306 name: SchemaName,
1308 key: FieldType,
1310 value: FieldType,
1312 },
1313 Tuple {
1315 source_key: TypeSourceKey,
1317 name: SchemaName,
1319 members: Vec<TupleElementFragment>,
1321 },
1322}
1323
1324impl NamedTypeFragment {
1325 pub fn append_named_type_dependencies(&self, pending: &mut Vec<TypeSourceKey>) {
1331 match self {
1332 Self::Record(record) => {
1333 for field in record.fields() {
1334 field.field_type().append_named_type_dependencies(pending);
1335 }
1336 }
1337 Self::Enum(r#enum) => {
1338 for variant in r#enum.variants() {
1339 if let Some(payload) = variant.payload() {
1340 payload.append_named_type_dependencies(pending);
1341 }
1342 }
1343 }
1344 Self::Newtype { inner, .. }
1345 | Self::List { item: inner, .. }
1346 | Self::Set { item: inner, .. } => {
1347 inner.append_named_type_dependencies(pending);
1348 }
1349 Self::Map { key, value, .. } => {
1350 key.append_named_type_dependencies(pending);
1351 value.append_named_type_dependencies(pending);
1352 }
1353 Self::Tuple { members, .. } => {
1354 for member in members {
1355 member.field_type().append_named_type_dependencies(pending);
1356 }
1357 }
1358 }
1359 }
1360
1361 #[must_use]
1363 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1364 Self::Newtype {
1365 source_key: TypeSourceKey::from_name(&name),
1366 name,
1367 inner,
1368 }
1369 }
1370
1371 #[must_use]
1373 pub fn list(name: SchemaName, item: FieldType) -> Self {
1374 Self::List {
1375 source_key: TypeSourceKey::from_name(&name),
1376 name,
1377 item,
1378 }
1379 }
1380
1381 #[must_use]
1383 pub fn set(name: SchemaName, item: FieldType) -> Self {
1384 Self::Set {
1385 source_key: TypeSourceKey::from_name(&name),
1386 name,
1387 item,
1388 }
1389 }
1390
1391 #[must_use]
1393 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1394 Self::Map {
1395 source_key: TypeSourceKey::from_name(&name),
1396 name,
1397 key,
1398 value,
1399 }
1400 }
1401
1402 #[must_use]
1404 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1405 Self::Tuple {
1406 source_key: TypeSourceKey::from_name(&name),
1407 name,
1408 members,
1409 }
1410 }
1411
1412 #[must_use]
1414 pub const fn source_key(&self) -> &TypeSourceKey {
1415 match self {
1416 Self::Record(record) => record.source_key(),
1417 Self::Enum(r#enum) => r#enum.source_key(),
1418 Self::Newtype { source_key, .. }
1419 | Self::List { source_key, .. }
1420 | Self::Set { source_key, .. }
1421 | Self::Map { source_key, .. }
1422 | Self::Tuple { source_key, .. } => source_key,
1423 }
1424 }
1425
1426 #[must_use]
1428 pub const fn name(&self) -> &SchemaName {
1429 match self {
1430 Self::Record(record) => record.name(),
1431 Self::Enum(r#enum) => r#enum.name(),
1432 Self::Newtype { name, .. }
1433 | Self::List { name, .. }
1434 | Self::Set { name, .. }
1435 | Self::Map { name, .. }
1436 | Self::Tuple { name, .. } => name,
1437 }
1438 }
1439
1440 fn validate(&self) -> Result<(), SchemaContractError> {
1441 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1442 match self {
1443 Self::Record(record) => record.validate(),
1444 Self::Enum(r#enum) => r#enum.validate(),
1445 Self::Newtype { inner, .. }
1446 | Self::List { item: inner, .. }
1447 | Self::Set { item: inner, .. } => inner.validate(),
1448 Self::Map { key, value, .. } => {
1449 key.validate()?;
1450 value.validate()
1451 }
1452 Self::Tuple { members, .. } => {
1453 if members.is_empty() {
1454 return Err(SchemaContractError::InvalidReferenceList);
1455 }
1456 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1457 members.iter().try_for_each(TupleElementFragment::validate)
1458 }
1459 }
1460 }
1461}
1462
1463#[derive(Clone, Debug, Eq, PartialEq)]
1465pub struct SchemaFragment {
1466 entities: Vec<EntityFragment>,
1467 types: Vec<NamedTypeFragment>,
1468}
1469
1470impl SchemaFragment {
1471 pub fn try_new(
1478 mut entities: Vec<EntityFragment>,
1479 mut types: Vec<NamedTypeFragment>,
1480 ) -> Result<Self, SchemaContractError> {
1481 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1482 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1483 crate::compact_sort_unstable_by(&mut entities, |left, right| {
1484 left.source_key.cmp(&right.source_key)
1485 });
1486 crate::compact_sort_unstable_by(&mut types, |left, right| {
1487 left.source_key().cmp(right.source_key())
1488 });
1489 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1490 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1491 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1492 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1493 for entity in &entities {
1494 entity.validate()?;
1495 }
1496 for r#type in &types {
1497 r#type.validate()?;
1498 }
1499 Ok(Self { entities, types })
1500 }
1501
1502 #[must_use]
1504 pub fn entities(&self) -> &[EntityFragment] {
1505 &self.entities
1506 }
1507
1508 #[must_use]
1510 pub fn types(&self) -> &[NamedTypeFragment] {
1511 &self.types
1512 }
1513
1514 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1515 for r#type in &self.types {
1516 r#type.validate()?;
1517 }
1518 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1519 if rebuilt != *self {
1520 return Err(SchemaContractError::NonCanonical);
1521 }
1522 Ok(())
1523 }
1524}
1525
1526pub(crate) const fn check_len(
1527 kind: &'static str,
1528 len: usize,
1529 max: usize,
1530) -> Result<(), SchemaContractError> {
1531 if len > max {
1532 return Err(SchemaContractError::TooManyItems { kind, len, max });
1533 }
1534 Ok(())
1535}
1536
1537fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1538 source_key == name.as_str()
1539}
1540
1541fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1542 if !current_name_key_matches(source_key, name) {
1543 return Err(SchemaContractError::NonCanonical);
1544 }
1545 Ok(())
1546}
1547
1548fn ensure_canonical_rebuild<T: PartialEq>(
1549 current: &T,
1550 rebuilt: &T,
1551) -> Result<(), SchemaContractError> {
1552 if current != rebuilt {
1553 return Err(SchemaContractError::NonCanonical);
1554 }
1555 Ok(())
1556}
1557
1558fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1559where
1560 T: Ord,
1561{
1562 let mut seen = BTreeSet::new();
1563 if values.iter().any(|value| !seen.insert(value)) {
1564 return Err(SchemaContractError::InvalidReferenceList);
1565 }
1566 Ok(())
1567}
1568
1569fn ensure_unique_sorted_by<T, K>(
1570 values: &[T],
1571 key: impl Fn(&T) -> &K,
1572) -> Result<(), SchemaContractError>
1573where
1574 K: Eq,
1575{
1576 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1577 return Err(SchemaContractError::DuplicateSourceKey);
1578 }
1579 Ok(())
1580}
1581
1582fn ensure_unique_names<'a>(
1583 names: impl IntoIterator<Item = &'a SchemaName>,
1584) -> Result<(), SchemaContractError> {
1585 let mut seen = BTreeSet::new();
1586 if names.into_iter().any(|name| !seen.insert(name)) {
1587 return Err(SchemaContractError::DuplicateName);
1588 }
1589 Ok(())
1590}
1591
1592fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1593 for policy in [
1594 FieldManagementPolicy::CreatedAt,
1595 FieldManagementPolicy::UpdatedAt,
1596 ] {
1597 if fields
1598 .iter()
1599 .filter(|field| field.management() == Some(policy))
1600 .count()
1601 > 1
1602 {
1603 return Err(SchemaContractError::InvalidFieldPolicy);
1604 }
1605 }
1606 Ok(())
1607}
1608
1609fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1610 match value.scale().cmp(&scale) {
1611 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1612 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1613 }
1614}
1615
1616#[cfg(test)]
1617mod tests {
1618 use super::{
1619 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1620 SchemaContractError, SchemaName,
1621 };
1622
1623 #[test]
1624 fn independently_decoded_field_key_and_name_must_match() {
1625 let field = FieldFragment {
1626 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1627 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1628 field_type: FieldType::Scalar(ScalarType::Nat64),
1629 nullable: false,
1630 insert_policy: FieldInsertPolicy::Required,
1631 management: None,
1632 };
1633
1634 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1635 }
1636}