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(
769 name: SchemaName,
770 version: DeclaredEntityVersion,
771 mut fields: Vec<FieldFragment>,
772 primary_key: Vec<FieldSourceKey>,
773 mut indexes: Vec<IndexFragment>,
774 mut relations: Vec<RelationFragment>,
775 mut constraints: Vec<ConstraintFragment>,
776 ) -> Result<Self, SchemaContractError> {
777 let source_key = EntitySourceKey::from_name(&name);
778 check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
779 check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
780 check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
781 check_len(
782 "entity constraints",
783 constraints.len(),
784 MAX_FRAGMENT_CONSTRAINTS,
785 )?;
786 if primary_key.is_empty() {
787 return Err(SchemaContractError::InvalidReferenceList);
788 }
789 ensure_unique(&primary_key)?;
790 crate::compact_sort_unstable_by(&mut fields, |a, b| a.source_key.cmp(&b.source_key));
793 crate::compact_sort_unstable_by(&mut indexes, |a, b| a.source_key.cmp(&b.source_key));
794 crate::compact_sort_unstable_by(&mut relations, |a, b| a.source_key.cmp(&b.source_key));
795 crate::compact_sort_unstable_by(&mut constraints, |a, b| a.source_key.cmp(&b.source_key));
796 ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
797 ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
798 ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
799 ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
800 ensure_unique_names(fields.iter().map(FieldFragment::name))?;
801 ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
802 ensure_unique_names(relations.iter().map(RelationFragment::name))?;
803 ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
804 for field in &fields {
805 field.validate()?;
806 }
807 validate_management_cardinality(&fields)?;
808 for index in &indexes {
809 index.validate()?;
810 }
811 for relation in &relations {
812 relation.validate()?;
813 }
814 for constraint in &constraints {
815 constraint.validate()?;
816 }
817 let field_keys = fields
818 .iter()
819 .map(|field| field.source_key.clone())
820 .collect::<BTreeSet<_>>();
821 if primary_key.iter().any(|field| !field_keys.contains(field)) {
822 return Err(SchemaContractError::InvalidLocalReference);
823 }
824 validate_insert_generation(&fields, &primary_key)?;
825 for index in &indexes {
826 if index
827 .key()
828 .iter()
829 .any(|component| !field_keys.contains(component.field()))
830 || index.predicate().is_some_and(|predicate| {
831 predicate
832 .dependencies()
833 .iter()
834 .any(|field| !field_keys.contains(field))
835 })
836 {
837 return Err(SchemaContractError::InvalidLocalReference);
838 }
839 }
840 for relation in &relations {
841 if relation
842 .source()
843 .root_fields()
844 .any(|field| !field_keys.contains(field))
845 || (relation.target_entity() == &source_key
846 && relation
847 .target_fields()
848 .iter()
849 .any(|field| !field_keys.contains(field)))
850 {
851 return Err(SchemaContractError::InvalidLocalReference);
852 }
853 }
854 for constraint in &constraints {
855 let invalid = match constraint.kind() {
856 ConstraintFragmentKind::Check(expression) => expression
857 .dependencies()
858 .iter()
859 .any(|field| !field_keys.contains(field)),
860 ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
861 };
862 if invalid {
863 return Err(SchemaContractError::InvalidLocalReference);
864 }
865 }
866 Ok(Self {
867 source_key,
868 name,
869 version,
870 fields,
871 primary_key,
872 indexes,
873 relations,
874 constraints,
875 })
876 }
877
878 #[must_use]
880 pub const fn source_key(&self) -> &EntitySourceKey {
881 &self.source_key
882 }
883
884 #[must_use]
886 pub const fn name(&self) -> &SchemaName {
887 &self.name
888 }
889
890 #[must_use]
892 pub const fn version(&self) -> DeclaredEntityVersion {
893 self.version
894 }
895
896 #[must_use]
898 pub fn fields(&self) -> &[FieldFragment] {
899 &self.fields
900 }
901
902 #[must_use]
904 pub fn primary_key(&self) -> &[FieldSourceKey] {
905 &self.primary_key
906 }
907
908 #[must_use]
910 pub fn indexes(&self) -> &[IndexFragment] {
911 &self.indexes
912 }
913
914 #[must_use]
916 pub fn relations(&self) -> &[RelationFragment] {
917 &self.relations
918 }
919
920 #[must_use]
922 pub fn constraints(&self) -> &[ConstraintFragment] {
923 &self.constraints
924 }
925
926 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
927 let rebuilt = Self::try_new(
928 self.name.clone(),
929 self.version,
930 self.fields.clone(),
931 self.primary_key.clone(),
932 self.indexes.clone(),
933 self.relations.clone(),
934 self.constraints.clone(),
935 )?;
936 ensure_canonical_rebuild(self, &rebuilt)
937 }
938}
939
940fn validate_insert_generation(
944 fields: &[FieldFragment],
945 primary_key: &[FieldSourceKey],
946) -> Result<(), SchemaContractError> {
947 for field in fields {
948 if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
949 continue;
950 }
951 if field.nullable() || field.management().is_some() {
952 return Err(SchemaContractError::InvalidFieldPolicy);
953 }
954 match field.field_type() {
955 FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
956 FieldType::Scalar(
957 ScalarType::Nat8
958 | ScalarType::Nat16
959 | ScalarType::Nat32
960 | ScalarType::Nat64
961 | ScalarType::Nat128,
962 ) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
963 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
964 return Err(SchemaContractError::InvalidFieldPolicy);
965 }
966 }
967 }
968 Ok(())
969}
970
971#[derive(Clone, Debug, Eq, PartialEq)]
976pub struct RecordFieldFragment {
977 source_key: FieldSourceKey,
978 name: SchemaName,
979 field_type: FieldType,
980 nullable: bool,
981}
982
983impl RecordFieldFragment {
984 #[must_use]
986 pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
987 Self {
988 source_key: FieldSourceKey::from_name(&name),
989 name,
990 field_type,
991 nullable,
992 }
993 }
994
995 #[must_use]
997 pub const fn source_key(&self) -> &FieldSourceKey {
998 &self.source_key
999 }
1000
1001 #[must_use]
1003 pub const fn name(&self) -> &SchemaName {
1004 &self.name
1005 }
1006
1007 #[must_use]
1009 pub const fn field_type(&self) -> &FieldType {
1010 &self.field_type
1011 }
1012
1013 #[must_use]
1015 pub const fn nullable(&self) -> bool {
1016 self.nullable
1017 }
1018
1019 fn validate(&self) -> Result<(), SchemaContractError> {
1020 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1021 return Err(SchemaContractError::NonCanonical);
1022 }
1023 self.field_type.validate()
1024 }
1025}
1026
1027#[derive(Clone, Debug, Eq, PartialEq)]
1030pub struct TupleElementFragment {
1031 field_type: FieldType,
1032 nullable: bool,
1033}
1034
1035impl TupleElementFragment {
1036 #[must_use]
1038 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
1039 Self {
1040 field_type,
1041 nullable,
1042 }
1043 }
1044
1045 #[must_use]
1047 pub const fn field_type(&self) -> &FieldType {
1048 &self.field_type
1049 }
1050
1051 #[must_use]
1053 pub const fn nullable(&self) -> bool {
1054 self.nullable
1055 }
1056
1057 const fn validate(&self) -> Result<(), SchemaContractError> {
1058 self.field_type.validate()
1059 }
1060}
1061
1062#[derive(Clone, Debug, Eq, PartialEq)]
1064pub struct RecordTypeFragment {
1065 source_key: TypeSourceKey,
1066 name: SchemaName,
1067 fields: Vec<RecordFieldFragment>,
1068}
1069
1070impl RecordTypeFragment {
1071 pub fn try_new(
1078 name: SchemaName,
1079 mut fields: Vec<RecordFieldFragment>,
1080 ) -> Result<Self, SchemaContractError> {
1081 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
1082 crate::compact_sort_unstable_by(&mut fields, |left, right| {
1083 left.source_key.cmp(&right.source_key)
1084 });
1085 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
1086 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
1087 for field in &fields {
1088 field.validate()?;
1089 }
1090 Ok(Self {
1091 source_key: TypeSourceKey::from_name(&name),
1092 name,
1093 fields,
1094 })
1095 }
1096
1097 #[must_use]
1099 pub const fn source_key(&self) -> &TypeSourceKey {
1100 &self.source_key
1101 }
1102
1103 #[must_use]
1105 pub const fn name(&self) -> &SchemaName {
1106 &self.name
1107 }
1108
1109 #[must_use]
1111 pub fn fields(&self) -> &[RecordFieldFragment] {
1112 &self.fields
1113 }
1114
1115 fn validate(&self) -> Result<(), SchemaContractError> {
1116 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1117 if rebuilt != *self {
1118 return Err(SchemaContractError::NonCanonical);
1119 }
1120 Ok(())
1121 }
1122}
1123
1124#[derive(Clone, Debug, Eq, PartialEq)]
1126pub struct EnumVariantFragment {
1127 source_key: TypeSourceKey,
1128 name: SchemaName,
1129 payload: Option<FieldType>,
1130}
1131
1132impl EnumVariantFragment {
1133 #[must_use]
1135 pub fn new(name: SchemaName) -> Self {
1136 Self {
1137 source_key: TypeSourceKey::from_name(&name),
1138 name,
1139 payload: None,
1140 }
1141 }
1142
1143 #[must_use]
1145 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1146 Self {
1147 source_key: TypeSourceKey::from_name(&name),
1148 name,
1149 payload: Some(payload),
1150 }
1151 }
1152
1153 #[must_use]
1155 pub const fn source_key(&self) -> &TypeSourceKey {
1156 &self.source_key
1157 }
1158
1159 #[must_use]
1161 pub const fn name(&self) -> &SchemaName {
1162 &self.name
1163 }
1164
1165 #[must_use]
1167 pub const fn payload(&self) -> Option<&FieldType> {
1168 self.payload.as_ref()
1169 }
1170
1171 fn validate(&self) -> Result<(), SchemaContractError> {
1172 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1173 return Err(SchemaContractError::NonCanonical);
1174 }
1175 match &self.payload {
1176 Some(payload) => payload.validate(),
1177 None => Ok(()),
1178 }
1179 }
1180}
1181
1182#[derive(Clone, Debug, Eq, PartialEq)]
1184pub struct EnumTypeFragment {
1185 source_key: TypeSourceKey,
1186 name: SchemaName,
1187 variants: Vec<EnumVariantFragment>,
1188}
1189
1190impl EnumTypeFragment {
1191 pub fn try_new(
1198 name: SchemaName,
1199 mut variants: Vec<EnumVariantFragment>,
1200 ) -> Result<Self, SchemaContractError> {
1201 if variants.is_empty() {
1202 return Err(SchemaContractError::InvalidReferenceList);
1203 }
1204 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1205 crate::compact_sort_unstable_by(&mut variants, |left, right| {
1206 left.source_key.cmp(&right.source_key)
1207 });
1208 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1209 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1210 for variant in &variants {
1211 variant.validate()?;
1212 }
1213 Ok(Self {
1214 source_key: TypeSourceKey::from_name(&name),
1215 name,
1216 variants,
1217 })
1218 }
1219
1220 #[must_use]
1222 pub const fn source_key(&self) -> &TypeSourceKey {
1223 &self.source_key
1224 }
1225
1226 #[must_use]
1228 pub const fn name(&self) -> &SchemaName {
1229 &self.name
1230 }
1231
1232 #[must_use]
1234 pub fn variants(&self) -> &[EnumVariantFragment] {
1235 &self.variants
1236 }
1237
1238 fn validate(&self) -> Result<(), SchemaContractError> {
1239 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1240 if rebuilt != *self {
1241 return Err(SchemaContractError::NonCanonical);
1242 }
1243 Ok(())
1244 }
1245}
1246
1247#[derive(Clone, Debug, Eq, PartialEq)]
1249pub enum NamedTypeFragment {
1250 Record(RecordTypeFragment),
1252 Enum(EnumTypeFragment),
1254 Newtype {
1256 source_key: TypeSourceKey,
1258 name: SchemaName,
1260 inner: FieldType,
1262 },
1263 List {
1265 source_key: TypeSourceKey,
1267 name: SchemaName,
1269 item: FieldType,
1271 },
1272 Set {
1274 source_key: TypeSourceKey,
1276 name: SchemaName,
1278 item: FieldType,
1280 },
1281 Map {
1283 source_key: TypeSourceKey,
1285 name: SchemaName,
1287 key: FieldType,
1289 value: FieldType,
1291 },
1292 Tuple {
1294 source_key: TypeSourceKey,
1296 name: SchemaName,
1298 members: Vec<TupleElementFragment>,
1300 },
1301}
1302
1303impl NamedTypeFragment {
1304 pub fn append_named_type_dependencies(&self, pending: &mut Vec<TypeSourceKey>) {
1310 match self {
1311 Self::Record(record) => {
1312 for field in record.fields() {
1313 field.field_type().append_named_type_dependencies(pending);
1314 }
1315 }
1316 Self::Enum(r#enum) => {
1317 for variant in r#enum.variants() {
1318 if let Some(payload) = variant.payload() {
1319 payload.append_named_type_dependencies(pending);
1320 }
1321 }
1322 }
1323 Self::Newtype { inner, .. }
1324 | Self::List { item: inner, .. }
1325 | Self::Set { item: inner, .. } => {
1326 inner.append_named_type_dependencies(pending);
1327 }
1328 Self::Map { key, value, .. } => {
1329 key.append_named_type_dependencies(pending);
1330 value.append_named_type_dependencies(pending);
1331 }
1332 Self::Tuple { members, .. } => {
1333 for member in members {
1334 member.field_type().append_named_type_dependencies(pending);
1335 }
1336 }
1337 }
1338 }
1339
1340 #[must_use]
1342 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1343 Self::Newtype {
1344 source_key: TypeSourceKey::from_name(&name),
1345 name,
1346 inner,
1347 }
1348 }
1349
1350 #[must_use]
1352 pub fn list(name: SchemaName, item: FieldType) -> Self {
1353 Self::List {
1354 source_key: TypeSourceKey::from_name(&name),
1355 name,
1356 item,
1357 }
1358 }
1359
1360 #[must_use]
1362 pub fn set(name: SchemaName, item: FieldType) -> Self {
1363 Self::Set {
1364 source_key: TypeSourceKey::from_name(&name),
1365 name,
1366 item,
1367 }
1368 }
1369
1370 #[must_use]
1372 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1373 Self::Map {
1374 source_key: TypeSourceKey::from_name(&name),
1375 name,
1376 key,
1377 value,
1378 }
1379 }
1380
1381 #[must_use]
1383 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1384 Self::Tuple {
1385 source_key: TypeSourceKey::from_name(&name),
1386 name,
1387 members,
1388 }
1389 }
1390
1391 #[must_use]
1393 pub const fn source_key(&self) -> &TypeSourceKey {
1394 match self {
1395 Self::Record(record) => record.source_key(),
1396 Self::Enum(r#enum) => r#enum.source_key(),
1397 Self::Newtype { source_key, .. }
1398 | Self::List { source_key, .. }
1399 | Self::Set { source_key, .. }
1400 | Self::Map { source_key, .. }
1401 | Self::Tuple { source_key, .. } => source_key,
1402 }
1403 }
1404
1405 #[must_use]
1407 pub const fn name(&self) -> &SchemaName {
1408 match self {
1409 Self::Record(record) => record.name(),
1410 Self::Enum(r#enum) => r#enum.name(),
1411 Self::Newtype { name, .. }
1412 | Self::List { name, .. }
1413 | Self::Set { name, .. }
1414 | Self::Map { name, .. }
1415 | Self::Tuple { name, .. } => name,
1416 }
1417 }
1418
1419 fn validate(&self) -> Result<(), SchemaContractError> {
1420 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1421 match self {
1422 Self::Record(record) => record.validate(),
1423 Self::Enum(r#enum) => r#enum.validate(),
1424 Self::Newtype { inner, .. }
1425 | Self::List { item: inner, .. }
1426 | Self::Set { item: inner, .. } => inner.validate(),
1427 Self::Map { key, value, .. } => {
1428 key.validate()?;
1429 value.validate()
1430 }
1431 Self::Tuple { members, .. } => {
1432 if members.is_empty() {
1433 return Err(SchemaContractError::InvalidReferenceList);
1434 }
1435 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1436 members.iter().try_for_each(TupleElementFragment::validate)
1437 }
1438 }
1439 }
1440}
1441
1442#[derive(Clone, Debug, Eq, PartialEq)]
1444pub struct SchemaFragment {
1445 entities: Vec<EntityFragment>,
1446 types: Vec<NamedTypeFragment>,
1447}
1448
1449impl SchemaFragment {
1450 pub fn try_new(
1457 mut entities: Vec<EntityFragment>,
1458 mut types: Vec<NamedTypeFragment>,
1459 ) -> Result<Self, SchemaContractError> {
1460 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1461 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1462 crate::compact_sort_unstable_by(&mut entities, |left, right| {
1463 left.source_key.cmp(&right.source_key)
1464 });
1465 crate::compact_sort_unstable_by(&mut types, |left, right| {
1466 left.source_key().cmp(right.source_key())
1467 });
1468 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1469 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1470 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1471 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1472 for entity in &entities {
1473 entity.validate()?;
1474 }
1475 for r#type in &types {
1476 r#type.validate()?;
1477 }
1478 Ok(Self { entities, types })
1479 }
1480
1481 #[must_use]
1483 pub fn entities(&self) -> &[EntityFragment] {
1484 &self.entities
1485 }
1486
1487 #[must_use]
1489 pub fn types(&self) -> &[NamedTypeFragment] {
1490 &self.types
1491 }
1492
1493 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1494 for r#type in &self.types {
1495 r#type.validate()?;
1496 }
1497 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1498 if rebuilt != *self {
1499 return Err(SchemaContractError::NonCanonical);
1500 }
1501 Ok(())
1502 }
1503}
1504
1505pub(crate) const fn check_len(
1506 kind: &'static str,
1507 len: usize,
1508 max: usize,
1509) -> Result<(), SchemaContractError> {
1510 if len > max {
1511 return Err(SchemaContractError::TooManyItems { kind, len, max });
1512 }
1513 Ok(())
1514}
1515
1516fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1517 source_key == name.as_str()
1518}
1519
1520fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1521 if !current_name_key_matches(source_key, name) {
1522 return Err(SchemaContractError::NonCanonical);
1523 }
1524 Ok(())
1525}
1526
1527fn ensure_canonical_rebuild<T: PartialEq>(
1528 current: &T,
1529 rebuilt: &T,
1530) -> Result<(), SchemaContractError> {
1531 if current != rebuilt {
1532 return Err(SchemaContractError::NonCanonical);
1533 }
1534 Ok(())
1535}
1536
1537fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1538where
1539 T: Ord,
1540{
1541 let mut seen = BTreeSet::new();
1542 if values.iter().any(|value| !seen.insert(value)) {
1543 return Err(SchemaContractError::InvalidReferenceList);
1544 }
1545 Ok(())
1546}
1547
1548fn ensure_unique_sorted_by<T, K>(
1549 values: &[T],
1550 key: impl Fn(&T) -> &K,
1551) -> Result<(), SchemaContractError>
1552where
1553 K: Eq,
1554{
1555 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1556 return Err(SchemaContractError::DuplicateSourceKey);
1557 }
1558 Ok(())
1559}
1560
1561fn ensure_unique_names<'a>(
1562 names: impl IntoIterator<Item = &'a SchemaName>,
1563) -> Result<(), SchemaContractError> {
1564 let mut seen = BTreeSet::new();
1565 if names.into_iter().any(|name| !seen.insert(name)) {
1566 return Err(SchemaContractError::DuplicateName);
1567 }
1568 Ok(())
1569}
1570
1571fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1572 for policy in [
1573 FieldManagementPolicy::CreatedAt,
1574 FieldManagementPolicy::UpdatedAt,
1575 ] {
1576 if fields
1577 .iter()
1578 .filter(|field| field.management() == Some(policy))
1579 .count()
1580 > 1
1581 {
1582 return Err(SchemaContractError::InvalidFieldPolicy);
1583 }
1584 }
1585 Ok(())
1586}
1587
1588fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1589 match value.scale().cmp(&scale) {
1590 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1591 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1592 }
1593}
1594
1595#[cfg(test)]
1596mod tests {
1597 use super::{
1598 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1599 SchemaContractError, SchemaName,
1600 };
1601
1602 #[test]
1603 fn independently_decoded_field_key_and_name_must_match() {
1604 let field = FieldFragment {
1605 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1606 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1607 field_type: FieldType::Scalar(ScalarType::Nat64),
1608 nullable: false,
1609 insert_policy: FieldInsertPolicy::Required,
1610 management: None,
1611 };
1612
1613 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1614 }
1615}