1use std::collections::BTreeSet;
4
5use candid::CandidType;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 ConstraintSourceKey, Decimal, DeclaredEntityVersion, EntitySourceKey, FieldSourceKey,
10 IndexSourceKey, MAX_FRAGMENT_CONSTRAINTS, MAX_FRAGMENT_ENTITIES, MAX_FRAGMENT_FIELDS,
11 MAX_FRAGMENT_INDEXES, MAX_FRAGMENT_RELATIONS, MAX_FRAGMENT_TYPES, MAX_SCHEMA_FIELD_TYPE_DEPTH,
12 RelationSourceKey, RuleSourceKey, ScalarKind, ScalarLiteral, SchemaContractError, SchemaName,
13 SourceCheckExpr, SourceRuleOperation, TypeSourceKey,
14};
15
16#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
18pub enum FieldType {
19 Scalar(ScalarType),
21 List(Box<Self>),
23 Named(TypeSourceKey),
25}
26
27#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
29pub enum ScalarType {
30 Account,
32 Blob {
34 max_len: Option<u32>,
36 },
37 Bool,
39 Date,
41 Decimal {
43 scale: u32,
45 },
46 Duration,
48 Float32,
50 Float64,
52 Int8,
54 Int16,
56 Int32,
58 Int64,
60 Int128,
62 IntBig {
64 max_bytes: u32,
66 },
67 Principal,
69 Subaccount,
71 Text {
73 max_len: Option<u32>,
75 },
76 Timestamp,
78 Nat8,
80 Nat16,
82 Nat32,
84 Nat64,
86 Nat128,
88 NatBig {
90 max_bytes: u32,
92 },
93 Ulid,
95 Unit,
97}
98
99impl ScalarType {
100 #[must_use]
102 pub const fn kind(self) -> ScalarKind {
103 match self {
104 Self::Account => ScalarKind::Account,
105 Self::Blob { .. } => ScalarKind::Blob,
106 Self::Bool => ScalarKind::Bool,
107 Self::Date => ScalarKind::Date,
108 Self::Decimal { .. } => ScalarKind::Decimal,
109 Self::Duration => ScalarKind::Duration,
110 Self::Float32 => ScalarKind::Float32,
111 Self::Float64 => ScalarKind::Float64,
112 Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 => ScalarKind::Int,
113 Self::Int128 => ScalarKind::Int128,
114 Self::IntBig { .. } => ScalarKind::IntBig,
115 Self::Principal => ScalarKind::Principal,
116 Self::Subaccount => ScalarKind::Subaccount,
117 Self::Text { .. } => ScalarKind::Text,
118 Self::Timestamp => ScalarKind::Timestamp,
119 Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 => ScalarKind::Nat,
120 Self::Nat128 => ScalarKind::Nat128,
121 Self::NatBig { .. } => ScalarKind::NatBig,
122 Self::Ulid => ScalarKind::Ulid,
123 Self::Unit => ScalarKind::Unit,
124 }
125 }
126
127 pub(crate) const fn validate(self) -> Result<(), SchemaContractError> {
128 match self {
129 Self::Decimal { scale } if scale > Decimal::max_supported_scale() => {
130 Err(SchemaContractError::InvalidFieldType)
131 }
132 Self::IntBig { max_bytes: 0 } | Self::NatBig { max_bytes: 0 } => {
133 Err(SchemaContractError::InvalidFieldType)
134 }
135 _ => Ok(()),
136 }
137 }
138
139 pub(crate) fn accepts_literal(self, literal: &ScalarLiteral) -> bool {
140 match (self, literal) {
141 (Self::Account, ScalarLiteral::Account(_))
142 | (Self::Bool, ScalarLiteral::Bool(_))
143 | (Self::Date, ScalarLiteral::Date(_))
144 | (Self::Duration, ScalarLiteral::Duration(_))
145 | (Self::Float32, ScalarLiteral::Float32(_))
146 | (Self::Float64, ScalarLiteral::Float64(_))
147 | (Self::Int128, ScalarLiteral::Int(_))
148 | (Self::Principal, ScalarLiteral::Principal(_))
149 | (Self::Subaccount, ScalarLiteral::Subaccount(_))
150 | (Self::Timestamp, ScalarLiteral::Timestamp(_))
151 | (Self::Nat128, ScalarLiteral::Nat(_))
152 | (Self::Ulid, ScalarLiteral::Ulid(_))
153 | (Self::Unit, ScalarLiteral::Unit(_)) => true,
154 (Self::Blob { max_len }, ScalarLiteral::Blob(value)) => {
155 max_len.is_none_or(|max| value.len() <= max as usize)
156 }
157 (Self::Text { max_len }, ScalarLiteral::Text(value)) => {
158 max_len.is_none_or(|max| value.chars().count() <= max as usize)
159 }
160 (Self::Int8, ScalarLiteral::Int(value)) => i8::try_from(*value).is_ok(),
161 (Self::Int16, ScalarLiteral::Int(value)) => i16::try_from(*value).is_ok(),
162 (Self::Int32, ScalarLiteral::Int(value)) => i32::try_from(*value).is_ok(),
163 (Self::Int64, ScalarLiteral::Int(value)) => i64::try_from(*value).is_ok(),
164 (Self::IntBig { max_bytes }, ScalarLiteral::IntBig(value)) => {
165 value.to_leb128().len() <= max_bytes as usize
166 }
167 (Self::Nat8, ScalarLiteral::Nat(value)) => u8::try_from(*value).is_ok(),
168 (Self::Nat16, ScalarLiteral::Nat(value)) => u16::try_from(*value).is_ok(),
169 (Self::Nat32, ScalarLiteral::Nat(value)) => u32::try_from(*value).is_ok(),
170 (Self::Nat64, ScalarLiteral::Nat(value)) => u64::try_from(*value).is_ok(),
171 (Self::NatBig { max_bytes }, ScalarLiteral::NatBig(value)) => {
172 value.to_leb128().len() <= max_bytes as usize
173 }
174 (Self::Decimal { scale }, ScalarLiteral::Decimal(value)) => {
175 decimal_fits_scale(*value, scale)
176 }
177 _ => false,
178 }
179 }
180}
181
182impl FieldType {
183 pub(crate) const fn validate(&self) -> Result<(), SchemaContractError> {
184 self.validate_at_depth(0)
185 }
186
187 const fn validate_at_depth(&self, depth: usize) -> Result<(), SchemaContractError> {
188 let Some(depth) = depth.checked_add(1) else {
189 return Err(SchemaContractError::FieldTypeDepthExceeded);
190 };
191 if depth > MAX_SCHEMA_FIELD_TYPE_DEPTH {
192 return Err(SchemaContractError::FieldTypeDepthExceeded);
193 }
194 match self {
195 Self::Scalar(scalar) => scalar.validate(),
196 Self::List(item) => item.validate_at_depth(depth),
197 Self::Named(_) => Ok(()),
198 }
199 }
200}
201
202#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
204pub enum FieldInsertPolicy {
205 Required,
207 Nullable,
209 Default(ScalarLiteral),
211 Generated,
213}
214
215#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
217pub enum FieldManagementPolicy {
218 CreatedAt,
220 UpdatedAt,
222}
223
224#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
226pub struct FieldFragment {
227 source_key: FieldSourceKey,
228 name: SchemaName,
229 field_type: FieldType,
230 nullable: bool,
231 insert_policy: FieldInsertPolicy,
232 management: Option<FieldManagementPolicy>,
233}
234
235impl FieldFragment {
236 #[must_use]
238 pub fn new(
239 name: SchemaName,
240 field_type: FieldType,
241 nullable: bool,
242 insert_policy: FieldInsertPolicy,
243 management: Option<FieldManagementPolicy>,
244 ) -> Self {
245 Self {
246 source_key: FieldSourceKey::from_name(&name),
247 name,
248 field_type,
249 nullable,
250 insert_policy,
251 management,
252 }
253 }
254
255 #[must_use]
257 pub const fn source_key(&self) -> &FieldSourceKey {
258 &self.source_key
259 }
260
261 #[must_use]
263 pub const fn name(&self) -> &SchemaName {
264 &self.name
265 }
266
267 #[must_use]
269 pub const fn field_type(&self) -> &FieldType {
270 &self.field_type
271 }
272
273 #[must_use]
275 pub const fn nullable(&self) -> bool {
276 self.nullable
277 }
278
279 #[must_use]
281 pub const fn insert_policy(&self) -> &FieldInsertPolicy {
282 &self.insert_policy
283 }
284
285 #[must_use]
287 pub const fn management(&self) -> Option<FieldManagementPolicy> {
288 self.management
289 }
290
291 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
292 ensure_current_name_key(self.source_key.as_str(), &self.name)?;
293 self.field_type.validate()?;
294 if let FieldInsertPolicy::Default(literal) = &self.insert_policy {
295 literal.validate()?;
296 match &self.field_type {
297 FieldType::Scalar(scalar) if scalar.accepts_literal(literal) => {}
298 FieldType::Named(_) if matches!(literal, ScalarLiteral::EnumUnit { .. }) => {}
299 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
300 return Err(SchemaContractError::LiteralTypeMismatch);
301 }
302 }
303 }
304 if matches!(self.insert_policy, FieldInsertPolicy::Nullable) && !self.nullable {
305 return Err(SchemaContractError::InvalidFieldPolicy);
306 }
307 if self.management.is_some()
308 && (!matches!(self.field_type, FieldType::Scalar(ScalarType::Timestamp))
309 || self.nullable
310 || !matches!(self.insert_policy, FieldInsertPolicy::Required))
311 {
312 return Err(SchemaContractError::InvalidFieldPolicy);
313 }
314 Ok(())
315 }
316}
317
318#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
320pub enum IndexKeyFragment {
321 Field(FieldSourceKey),
323 Lower(FieldSourceKey),
325 Upper(FieldSourceKey),
327 Trim(FieldSourceKey),
329 LowerTrim(FieldSourceKey),
331 Date(FieldSourceKey),
333 Year(FieldSourceKey),
335 Month(FieldSourceKey),
337 Day(FieldSourceKey),
339}
340
341impl IndexKeyFragment {
342 #[must_use]
344 pub const fn field(&self) -> &FieldSourceKey {
345 match self {
346 Self::Field(field)
347 | Self::Lower(field)
348 | Self::Upper(field)
349 | Self::Trim(field)
350 | Self::LowerTrim(field)
351 | Self::Date(field)
352 | Self::Year(field)
353 | Self::Month(field)
354 | Self::Day(field) => field,
355 }
356 }
357}
358
359#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
361pub struct IndexFragment {
362 source_key: IndexSourceKey,
363 name: SchemaName,
364 key: Vec<IndexKeyFragment>,
365 unique: bool,
366 predicate: Option<SourceCheckExpr>,
367}
368
369impl IndexFragment {
370 pub fn try_new(
376 name: SchemaName,
377 key: Vec<IndexKeyFragment>,
378 unique: bool,
379 predicate: Option<SourceCheckExpr>,
380 ) -> Result<Self, SchemaContractError> {
381 if key.is_empty() {
382 return Err(SchemaContractError::InvalidReferenceList);
383 }
384 if let Some(predicate) = &predicate {
385 predicate.validate()?;
386 }
387 Ok(Self {
388 source_key: IndexSourceKey::from_name(&name),
389 name,
390 key,
391 unique,
392 predicate,
393 })
394 }
395
396 #[must_use]
398 pub const fn source_key(&self) -> &IndexSourceKey {
399 &self.source_key
400 }
401
402 #[must_use]
404 pub const fn name(&self) -> &SchemaName {
405 &self.name
406 }
407
408 #[must_use]
410 pub fn key(&self) -> &[IndexKeyFragment] {
411 &self.key
412 }
413
414 #[must_use]
416 pub const fn unique(&self) -> bool {
417 self.unique
418 }
419
420 #[must_use]
422 pub const fn predicate(&self) -> Option<&SourceCheckExpr> {
423 self.predicate.as_ref()
424 }
425
426 fn validate(&self) -> Result<(), SchemaContractError> {
427 let rebuilt = Self::try_new(
428 self.name.clone(),
429 self.key.clone(),
430 self.unique,
431 self.predicate.clone(),
432 )?;
433 ensure_canonical_rebuild(self, &rebuilt)
434 }
435}
436
437#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
439pub enum RelationDeleteAction {
440 Restrict,
442}
443
444#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
446pub struct RelationFragment {
447 source_key: RelationSourceKey,
448 name: SchemaName,
449 local_fields: Vec<FieldSourceKey>,
450 target_entity: EntitySourceKey,
451 target_fields: Vec<FieldSourceKey>,
452 on_delete: RelationDeleteAction,
453}
454
455impl RelationFragment {
456 pub fn try_new(
463 name: SchemaName,
464 local_fields: Vec<FieldSourceKey>,
465 target_entity: EntitySourceKey,
466 target_fields: Vec<FieldSourceKey>,
467 on_delete: RelationDeleteAction,
468 ) -> Result<Self, SchemaContractError> {
469 if local_fields.is_empty() || local_fields.len() != target_fields.len() {
470 return Err(SchemaContractError::InvalidReferenceList);
471 }
472 ensure_unique(&local_fields)?;
473 ensure_unique(&target_fields)?;
474 Ok(Self {
475 source_key: RelationSourceKey::from_name(&name),
476 name,
477 local_fields,
478 target_entity,
479 target_fields,
480 on_delete,
481 })
482 }
483
484 #[must_use]
486 pub const fn source_key(&self) -> &RelationSourceKey {
487 &self.source_key
488 }
489
490 #[must_use]
492 pub const fn name(&self) -> &SchemaName {
493 &self.name
494 }
495
496 #[must_use]
498 pub fn local_fields(&self) -> &[FieldSourceKey] {
499 &self.local_fields
500 }
501
502 #[must_use]
504 pub const fn target_entity(&self) -> &EntitySourceKey {
505 &self.target_entity
506 }
507
508 #[must_use]
510 pub fn target_fields(&self) -> &[FieldSourceKey] {
511 &self.target_fields
512 }
513
514 #[must_use]
516 pub const fn on_delete(&self) -> RelationDeleteAction {
517 self.on_delete
518 }
519
520 fn validate(&self) -> Result<(), SchemaContractError> {
521 let rebuilt = Self::try_new(
522 self.name.clone(),
523 self.local_fields.clone(),
524 self.target_entity.clone(),
525 self.target_fields.clone(),
526 self.on_delete,
527 )?;
528 ensure_canonical_rebuild(self, &rebuilt)
529 }
530}
531
532#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
534pub enum ConstraintFragmentKind {
535 Check(SourceCheckExpr),
537 TargetedRule(TargetedRuleFragment),
539}
540
541impl ConstraintFragmentKind {
542 fn validate(&self) -> Result<(), SchemaContractError> {
543 match self {
544 Self::Check(expression) => expression.validate(),
545 Self::TargetedRule(rule) => rule.validate(),
546 }
547 }
548}
549
550#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
552pub struct TargetedRuleFragment {
553 root: FieldSourceKey,
554 target_type: TypeSourceKey,
555 rule: RuleSourceKey,
556 operation: SourceRuleOperation,
557}
558
559impl TargetedRuleFragment {
560 #[must_use]
562 pub fn new(
563 root: FieldSourceKey,
564 target_type: TypeSourceKey,
565 rule: SchemaName,
566 operation: SourceRuleOperation,
567 ) -> Self {
568 Self {
569 root,
570 target_type,
571 rule: RuleSourceKey::from_name(&rule),
572 operation,
573 }
574 }
575
576 #[must_use]
578 pub const fn root(&self) -> &FieldSourceKey {
579 &self.root
580 }
581
582 #[must_use]
584 pub const fn target_type(&self) -> &TypeSourceKey {
585 &self.target_type
586 }
587
588 #[must_use]
590 pub const fn rule(&self) -> &RuleSourceKey {
591 &self.rule
592 }
593
594 #[must_use]
596 pub const fn operation(&self) -> &SourceRuleOperation {
597 &self.operation
598 }
599
600 fn validate(&self) -> Result<(), SchemaContractError> {
601 self.operation.validate()
602 }
603}
604
605#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
607pub struct ConstraintFragment {
608 source_key: ConstraintSourceKey,
609 name: SchemaName,
610 kind: ConstraintFragmentKind,
611}
612
613impl ConstraintFragment {
614 #[must_use]
616 pub fn check(name: SchemaName, expression: SourceCheckExpr) -> Self {
617 Self {
618 source_key: ConstraintSourceKey::from_name(&name),
619 name,
620 kind: ConstraintFragmentKind::Check(expression),
621 }
622 }
623
624 #[must_use]
626 pub fn targeted_rule(rule: TargetedRuleFragment) -> Self {
627 let source_key = ConstraintSourceKey::for_targeted_field_rule(
628 rule.root(),
629 rule.target_type(),
630 rule.rule(),
631 );
632 let name = SchemaName::for_targeted_rule(&source_key);
633 Self {
634 source_key,
635 name,
636 kind: ConstraintFragmentKind::TargetedRule(rule),
637 }
638 }
639
640 #[must_use]
642 pub const fn source_key(&self) -> &ConstraintSourceKey {
643 &self.source_key
644 }
645
646 #[must_use]
648 pub const fn name(&self) -> &SchemaName {
649 &self.name
650 }
651
652 #[must_use]
654 pub const fn kind(&self) -> &ConstraintFragmentKind {
655 &self.kind
656 }
657
658 fn validate(&self) -> Result<(), SchemaContractError> {
659 self.kind.validate()?;
660 let rebuilt = match &self.kind {
661 ConstraintFragmentKind::Check(expression) => {
662 Self::check(self.name.clone(), expression.clone())
663 }
664 ConstraintFragmentKind::TargetedRule(rule) => Self::targeted_rule(rule.clone()),
665 };
666 ensure_canonical_rebuild(self, &rebuilt)
667 }
668}
669
670#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
672pub struct EntityFragment {
673 source_key: EntitySourceKey,
674 name: SchemaName,
675 version: DeclaredEntityVersion,
676 fields: Vec<FieldFragment>,
677 primary_key: Vec<FieldSourceKey>,
678 indexes: Vec<IndexFragment>,
679 relations: Vec<RelationFragment>,
680 constraints: Vec<ConstraintFragment>,
681}
682
683impl EntityFragment {
684 pub fn try_new(
691 name: SchemaName,
692 version: DeclaredEntityVersion,
693 mut fields: Vec<FieldFragment>,
694 primary_key: Vec<FieldSourceKey>,
695 mut indexes: Vec<IndexFragment>,
696 mut relations: Vec<RelationFragment>,
697 mut constraints: Vec<ConstraintFragment>,
698 ) -> Result<Self, SchemaContractError> {
699 let source_key = EntitySourceKey::from_name(&name);
700 check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
701 check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
702 check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
703 check_len(
704 "entity constraints",
705 constraints.len(),
706 MAX_FRAGMENT_CONSTRAINTS,
707 )?;
708 if primary_key.is_empty() {
709 return Err(SchemaContractError::InvalidReferenceList);
710 }
711 ensure_unique(&primary_key)?;
712 fields.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
715 indexes.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
716 relations.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
717 constraints.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
718 ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
719 ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
720 ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
721 ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
722 ensure_unique_names(fields.iter().map(FieldFragment::name))?;
723 ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
724 ensure_unique_names(relations.iter().map(RelationFragment::name))?;
725 ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
726 for field in &fields {
727 field.validate()?;
728 }
729 validate_management_cardinality(&fields)?;
730 for index in &indexes {
731 index.validate()?;
732 }
733 for relation in &relations {
734 relation.validate()?;
735 }
736 for constraint in &constraints {
737 constraint.validate()?;
738 }
739 let field_keys = fields
740 .iter()
741 .map(|field| field.source_key.clone())
742 .collect::<BTreeSet<_>>();
743 if primary_key.iter().any(|field| !field_keys.contains(field)) {
744 return Err(SchemaContractError::InvalidLocalReference);
745 }
746 validate_insert_generation(&fields, &primary_key)?;
747 for index in &indexes {
748 if index
749 .key()
750 .iter()
751 .any(|component| !field_keys.contains(component.field()))
752 || index.predicate().is_some_and(|predicate| {
753 predicate
754 .dependencies()
755 .iter()
756 .any(|field| !field_keys.contains(field))
757 })
758 {
759 return Err(SchemaContractError::InvalidLocalReference);
760 }
761 }
762 for relation in &relations {
763 if relation
764 .local_fields()
765 .iter()
766 .any(|field| !field_keys.contains(field))
767 || (relation.target_entity() == &source_key
768 && relation
769 .target_fields()
770 .iter()
771 .any(|field| !field_keys.contains(field)))
772 {
773 return Err(SchemaContractError::InvalidLocalReference);
774 }
775 }
776 for constraint in &constraints {
777 let invalid = match constraint.kind() {
778 ConstraintFragmentKind::Check(expression) => expression
779 .dependencies()
780 .iter()
781 .any(|field| !field_keys.contains(field)),
782 ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
783 };
784 if invalid {
785 return Err(SchemaContractError::InvalidLocalReference);
786 }
787 }
788 Ok(Self {
789 source_key,
790 name,
791 version,
792 fields,
793 primary_key,
794 indexes,
795 relations,
796 constraints,
797 })
798 }
799
800 #[must_use]
802 pub const fn source_key(&self) -> &EntitySourceKey {
803 &self.source_key
804 }
805
806 #[must_use]
808 pub const fn name(&self) -> &SchemaName {
809 &self.name
810 }
811
812 #[must_use]
814 pub const fn version(&self) -> DeclaredEntityVersion {
815 self.version
816 }
817
818 #[must_use]
820 pub fn fields(&self) -> &[FieldFragment] {
821 &self.fields
822 }
823
824 #[must_use]
826 pub fn primary_key(&self) -> &[FieldSourceKey] {
827 &self.primary_key
828 }
829
830 #[must_use]
832 pub fn indexes(&self) -> &[IndexFragment] {
833 &self.indexes
834 }
835
836 #[must_use]
838 pub fn relations(&self) -> &[RelationFragment] {
839 &self.relations
840 }
841
842 #[must_use]
844 pub fn constraints(&self) -> &[ConstraintFragment] {
845 &self.constraints
846 }
847
848 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
849 let rebuilt = Self::try_new(
850 self.name.clone(),
851 self.version,
852 self.fields.clone(),
853 self.primary_key.clone(),
854 self.indexes.clone(),
855 self.relations.clone(),
856 self.constraints.clone(),
857 )?;
858 ensure_canonical_rebuild(self, &rebuilt)
859 }
860}
861
862fn validate_insert_generation(
866 fields: &[FieldFragment],
867 primary_key: &[FieldSourceKey],
868) -> Result<(), SchemaContractError> {
869 for field in fields {
870 if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
871 continue;
872 }
873 if field.nullable() || field.management().is_some() {
874 return Err(SchemaContractError::InvalidFieldPolicy);
875 }
876 match field.field_type() {
877 FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
878 FieldType::Scalar(
879 ScalarType::Nat8
880 | ScalarType::Nat16
881 | ScalarType::Nat32
882 | ScalarType::Nat64
883 | ScalarType::Nat128,
884 ) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
885 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
886 return Err(SchemaContractError::InvalidFieldPolicy);
887 }
888 }
889 }
890 Ok(())
891}
892
893#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
898pub struct RecordFieldFragment {
899 source_key: FieldSourceKey,
900 name: SchemaName,
901 field_type: FieldType,
902 nullable: bool,
903}
904
905impl RecordFieldFragment {
906 #[must_use]
908 pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
909 Self {
910 source_key: FieldSourceKey::from_name(&name),
911 name,
912 field_type,
913 nullable,
914 }
915 }
916
917 #[must_use]
919 pub const fn source_key(&self) -> &FieldSourceKey {
920 &self.source_key
921 }
922
923 #[must_use]
925 pub const fn name(&self) -> &SchemaName {
926 &self.name
927 }
928
929 #[must_use]
931 pub const fn field_type(&self) -> &FieldType {
932 &self.field_type
933 }
934
935 #[must_use]
937 pub const fn nullable(&self) -> bool {
938 self.nullable
939 }
940
941 fn validate(&self) -> Result<(), SchemaContractError> {
942 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
943 return Err(SchemaContractError::NonCanonical);
944 }
945 self.field_type.validate()
946 }
947}
948
949#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
952pub struct TupleElementFragment {
953 field_type: FieldType,
954 nullable: bool,
955}
956
957impl TupleElementFragment {
958 #[must_use]
960 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
961 Self {
962 field_type,
963 nullable,
964 }
965 }
966
967 #[must_use]
969 pub const fn field_type(&self) -> &FieldType {
970 &self.field_type
971 }
972
973 #[must_use]
975 pub const fn nullable(&self) -> bool {
976 self.nullable
977 }
978
979 const fn validate(&self) -> Result<(), SchemaContractError> {
980 self.field_type.validate()
981 }
982}
983
984#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
986pub struct RecordTypeFragment {
987 source_key: TypeSourceKey,
988 name: SchemaName,
989 fields: Vec<RecordFieldFragment>,
990}
991
992impl RecordTypeFragment {
993 pub fn try_new(
1000 name: SchemaName,
1001 mut fields: Vec<RecordFieldFragment>,
1002 ) -> Result<Self, SchemaContractError> {
1003 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
1004 fields.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
1005 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
1006 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
1007 for field in &fields {
1008 field.validate()?;
1009 }
1010 Ok(Self {
1011 source_key: TypeSourceKey::from_name(&name),
1012 name,
1013 fields,
1014 })
1015 }
1016
1017 #[must_use]
1019 pub const fn source_key(&self) -> &TypeSourceKey {
1020 &self.source_key
1021 }
1022
1023 #[must_use]
1025 pub const fn name(&self) -> &SchemaName {
1026 &self.name
1027 }
1028
1029 #[must_use]
1031 pub fn fields(&self) -> &[RecordFieldFragment] {
1032 &self.fields
1033 }
1034
1035 fn validate(&self) -> Result<(), SchemaContractError> {
1036 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1037 if rebuilt != *self {
1038 return Err(SchemaContractError::NonCanonical);
1039 }
1040 Ok(())
1041 }
1042}
1043
1044#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1046pub struct EnumVariantFragment {
1047 source_key: TypeSourceKey,
1048 name: SchemaName,
1049 payload: Option<FieldType>,
1050}
1051
1052impl EnumVariantFragment {
1053 #[must_use]
1055 pub fn new(name: SchemaName) -> Self {
1056 Self {
1057 source_key: TypeSourceKey::from_name(&name),
1058 name,
1059 payload: None,
1060 }
1061 }
1062
1063 #[must_use]
1065 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1066 Self {
1067 source_key: TypeSourceKey::from_name(&name),
1068 name,
1069 payload: Some(payload),
1070 }
1071 }
1072
1073 #[must_use]
1075 pub const fn source_key(&self) -> &TypeSourceKey {
1076 &self.source_key
1077 }
1078
1079 #[must_use]
1081 pub const fn name(&self) -> &SchemaName {
1082 &self.name
1083 }
1084
1085 #[must_use]
1087 pub const fn payload(&self) -> Option<&FieldType> {
1088 self.payload.as_ref()
1089 }
1090
1091 fn validate(&self) -> Result<(), SchemaContractError> {
1092 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1093 return Err(SchemaContractError::NonCanonical);
1094 }
1095 match &self.payload {
1096 Some(payload) => payload.validate(),
1097 None => Ok(()),
1098 }
1099 }
1100}
1101
1102#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1104pub struct EnumTypeFragment {
1105 source_key: TypeSourceKey,
1106 name: SchemaName,
1107 variants: Vec<EnumVariantFragment>,
1108}
1109
1110impl EnumTypeFragment {
1111 pub fn try_new(
1118 name: SchemaName,
1119 mut variants: Vec<EnumVariantFragment>,
1120 ) -> Result<Self, SchemaContractError> {
1121 if variants.is_empty() {
1122 return Err(SchemaContractError::InvalidReferenceList);
1123 }
1124 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1125 variants.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
1126 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1127 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1128 for variant in &variants {
1129 variant.validate()?;
1130 }
1131 Ok(Self {
1132 source_key: TypeSourceKey::from_name(&name),
1133 name,
1134 variants,
1135 })
1136 }
1137
1138 #[must_use]
1140 pub const fn source_key(&self) -> &TypeSourceKey {
1141 &self.source_key
1142 }
1143
1144 #[must_use]
1146 pub const fn name(&self) -> &SchemaName {
1147 &self.name
1148 }
1149
1150 #[must_use]
1152 pub fn variants(&self) -> &[EnumVariantFragment] {
1153 &self.variants
1154 }
1155
1156 fn validate(&self) -> Result<(), SchemaContractError> {
1157 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1158 if rebuilt != *self {
1159 return Err(SchemaContractError::NonCanonical);
1160 }
1161 Ok(())
1162 }
1163}
1164
1165#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1167pub enum NamedTypeFragment {
1168 Record(RecordTypeFragment),
1170 Enum(EnumTypeFragment),
1172 Newtype {
1174 source_key: TypeSourceKey,
1176 name: SchemaName,
1178 inner: FieldType,
1180 },
1181 List {
1183 source_key: TypeSourceKey,
1185 name: SchemaName,
1187 item: FieldType,
1189 },
1190 Set {
1192 source_key: TypeSourceKey,
1194 name: SchemaName,
1196 item: FieldType,
1198 },
1199 Map {
1201 source_key: TypeSourceKey,
1203 name: SchemaName,
1205 key: FieldType,
1207 value: FieldType,
1209 },
1210 Tuple {
1212 source_key: TypeSourceKey,
1214 name: SchemaName,
1216 members: Vec<TupleElementFragment>,
1218 },
1219}
1220
1221impl NamedTypeFragment {
1222 #[must_use]
1224 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1225 Self::Newtype {
1226 source_key: TypeSourceKey::from_name(&name),
1227 name,
1228 inner,
1229 }
1230 }
1231
1232 #[must_use]
1234 pub fn list(name: SchemaName, item: FieldType) -> Self {
1235 Self::List {
1236 source_key: TypeSourceKey::from_name(&name),
1237 name,
1238 item,
1239 }
1240 }
1241
1242 #[must_use]
1244 pub fn set(name: SchemaName, item: FieldType) -> Self {
1245 Self::Set {
1246 source_key: TypeSourceKey::from_name(&name),
1247 name,
1248 item,
1249 }
1250 }
1251
1252 #[must_use]
1254 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1255 Self::Map {
1256 source_key: TypeSourceKey::from_name(&name),
1257 name,
1258 key,
1259 value,
1260 }
1261 }
1262
1263 #[must_use]
1265 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1266 Self::Tuple {
1267 source_key: TypeSourceKey::from_name(&name),
1268 name,
1269 members,
1270 }
1271 }
1272
1273 #[must_use]
1275 pub const fn source_key(&self) -> &TypeSourceKey {
1276 match self {
1277 Self::Record(record) => record.source_key(),
1278 Self::Enum(r#enum) => r#enum.source_key(),
1279 Self::Newtype { source_key, .. }
1280 | Self::List { source_key, .. }
1281 | Self::Set { source_key, .. }
1282 | Self::Map { source_key, .. }
1283 | Self::Tuple { source_key, .. } => source_key,
1284 }
1285 }
1286
1287 #[must_use]
1289 pub const fn name(&self) -> &SchemaName {
1290 match self {
1291 Self::Record(record) => record.name(),
1292 Self::Enum(r#enum) => r#enum.name(),
1293 Self::Newtype { name, .. }
1294 | Self::List { name, .. }
1295 | Self::Set { name, .. }
1296 | Self::Map { name, .. }
1297 | Self::Tuple { name, .. } => name,
1298 }
1299 }
1300
1301 fn validate(&self) -> Result<(), SchemaContractError> {
1302 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1303 match self {
1304 Self::Record(record) => record.validate(),
1305 Self::Enum(r#enum) => r#enum.validate(),
1306 Self::Newtype { inner, .. }
1307 | Self::List { item: inner, .. }
1308 | Self::Set { item: inner, .. } => inner.validate(),
1309 Self::Map { key, value, .. } => {
1310 key.validate()?;
1311 value.validate()
1312 }
1313 Self::Tuple { members, .. } => {
1314 if members.is_empty() {
1315 return Err(SchemaContractError::InvalidReferenceList);
1316 }
1317 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1318 members.iter().try_for_each(TupleElementFragment::validate)
1319 }
1320 }
1321 }
1322}
1323
1324#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1326pub struct SchemaFragment {
1327 entities: Vec<EntityFragment>,
1328 types: Vec<NamedTypeFragment>,
1329}
1330
1331impl SchemaFragment {
1332 pub fn try_new(
1339 mut entities: Vec<EntityFragment>,
1340 mut types: Vec<NamedTypeFragment>,
1341 ) -> Result<Self, SchemaContractError> {
1342 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1343 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1344 entities.sort_unstable_by(|left, right| left.source_key.cmp(&right.source_key));
1345 types.sort_unstable_by(|left, right| left.source_key().cmp(right.source_key()));
1346 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1347 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1348 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1349 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1350 for entity in &entities {
1351 entity.validate()?;
1352 }
1353 for r#type in &types {
1354 r#type.validate()?;
1355 }
1356 Ok(Self { entities, types })
1357 }
1358
1359 #[must_use]
1361 pub fn entities(&self) -> &[EntityFragment] {
1362 &self.entities
1363 }
1364
1365 #[must_use]
1367 pub fn types(&self) -> &[NamedTypeFragment] {
1368 &self.types
1369 }
1370
1371 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1372 for r#type in &self.types {
1373 r#type.validate()?;
1374 }
1375 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1376 if rebuilt != *self {
1377 return Err(SchemaContractError::NonCanonical);
1378 }
1379 Ok(())
1380 }
1381}
1382
1383pub(crate) const fn check_len(
1384 kind: &'static str,
1385 len: usize,
1386 max: usize,
1387) -> Result<(), SchemaContractError> {
1388 if len > max {
1389 return Err(SchemaContractError::TooManyItems { kind, len, max });
1390 }
1391 Ok(())
1392}
1393
1394fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1395 source_key == name.as_str()
1396}
1397
1398fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1399 if !current_name_key_matches(source_key, name) {
1400 return Err(SchemaContractError::NonCanonical);
1401 }
1402 Ok(())
1403}
1404
1405fn ensure_canonical_rebuild<T: PartialEq>(
1406 current: &T,
1407 rebuilt: &T,
1408) -> Result<(), SchemaContractError> {
1409 if current != rebuilt {
1410 return Err(SchemaContractError::NonCanonical);
1411 }
1412 Ok(())
1413}
1414
1415fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1416where
1417 T: Ord,
1418{
1419 let mut seen = BTreeSet::new();
1420 if values.iter().any(|value| !seen.insert(value)) {
1421 return Err(SchemaContractError::InvalidReferenceList);
1422 }
1423 Ok(())
1424}
1425
1426fn ensure_unique_sorted_by<T, K>(
1427 values: &[T],
1428 key: impl Fn(&T) -> &K,
1429) -> Result<(), SchemaContractError>
1430where
1431 K: Eq,
1432{
1433 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1434 return Err(SchemaContractError::DuplicateSourceKey);
1435 }
1436 Ok(())
1437}
1438
1439fn ensure_unique_names<'a>(
1440 names: impl IntoIterator<Item = &'a SchemaName>,
1441) -> Result<(), SchemaContractError> {
1442 let mut seen = BTreeSet::new();
1443 if names.into_iter().any(|name| !seen.insert(name)) {
1444 return Err(SchemaContractError::DuplicateName);
1445 }
1446 Ok(())
1447}
1448
1449fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1450 for policy in [
1451 FieldManagementPolicy::CreatedAt,
1452 FieldManagementPolicy::UpdatedAt,
1453 ] {
1454 if fields
1455 .iter()
1456 .filter(|field| field.management() == Some(policy))
1457 .count()
1458 > 1
1459 {
1460 return Err(SchemaContractError::InvalidFieldPolicy);
1461 }
1462 }
1463 Ok(())
1464}
1465
1466fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1467 match value.scale().cmp(&scale) {
1468 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1469 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1470 }
1471}
1472
1473#[cfg(test)]
1474mod tests {
1475 use super::{
1476 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1477 SchemaContractError, SchemaName,
1478 };
1479
1480 #[test]
1481 fn independently_decoded_field_key_and_name_must_match() {
1482 let field = FieldFragment {
1483 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1484 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1485 field_type: FieldType::Scalar(ScalarType::Nat64),
1486 nullable: false,
1487 insert_policy: FieldInsertPolicy::Required,
1488 management: None,
1489 };
1490
1491 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1492 }
1493}