1use std::collections::BTreeSet;
4
5use candid::CandidType;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 ConstraintSourceKey, Decimal, EntitySourceKey, FieldSourceKey, IndexSourceKey,
10 MAX_FRAGMENT_CONSTRAINTS, MAX_FRAGMENT_ENTITIES, MAX_FRAGMENT_FIELDS, MAX_FRAGMENT_INDEXES,
11 MAX_FRAGMENT_RELATIONS, MAX_FRAGMENT_TYPES, MAX_SCHEMA_FIELD_TYPE_DEPTH, RelationSourceKey,
12 RuleSourceKey, ScalarKind, ScalarLiteral, SchemaContractError, SchemaName, SourceCheckExpr,
13 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 fields: Vec<FieldFragment>,
676 primary_key: Vec<FieldSourceKey>,
677 indexes: Vec<IndexFragment>,
678 relations: Vec<RelationFragment>,
679 constraints: Vec<ConstraintFragment>,
680}
681
682impl EntityFragment {
683 pub fn try_new(
690 name: SchemaName,
691 mut fields: Vec<FieldFragment>,
692 primary_key: Vec<FieldSourceKey>,
693 mut indexes: Vec<IndexFragment>,
694 mut relations: Vec<RelationFragment>,
695 mut constraints: Vec<ConstraintFragment>,
696 ) -> Result<Self, SchemaContractError> {
697 let source_key = EntitySourceKey::from_name(&name);
698 check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
699 check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
700 check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
701 check_len(
702 "entity constraints",
703 constraints.len(),
704 MAX_FRAGMENT_CONSTRAINTS,
705 )?;
706 if primary_key.is_empty() {
707 return Err(SchemaContractError::InvalidReferenceList);
708 }
709 ensure_unique(&primary_key)?;
710 fields.sort_by(|left, right| left.source_key.cmp(&right.source_key));
711 indexes.sort_by(|left, right| left.source_key.cmp(&right.source_key));
712 relations.sort_by(|left, right| left.source_key.cmp(&right.source_key));
713 constraints.sort_by(|left, right| left.source_key.cmp(&right.source_key));
714 ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
715 ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
716 ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
717 ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
718 ensure_unique_names(fields.iter().map(FieldFragment::name))?;
719 ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
720 ensure_unique_names(relations.iter().map(RelationFragment::name))?;
721 ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
722 for field in &fields {
723 field.validate()?;
724 }
725 validate_management_cardinality(&fields)?;
726 for index in &indexes {
727 index.validate()?;
728 }
729 for relation in &relations {
730 relation.validate()?;
731 }
732 for constraint in &constraints {
733 constraint.validate()?;
734 }
735 let field_keys = fields
736 .iter()
737 .map(|field| field.source_key.clone())
738 .collect::<BTreeSet<_>>();
739 if primary_key.iter().any(|field| !field_keys.contains(field)) {
740 return Err(SchemaContractError::InvalidLocalReference);
741 }
742 validate_insert_generation(&fields, &primary_key)?;
743 for index in &indexes {
744 if index
745 .key()
746 .iter()
747 .any(|component| !field_keys.contains(component.field()))
748 || index.predicate().is_some_and(|predicate| {
749 predicate
750 .dependencies()
751 .iter()
752 .any(|field| !field_keys.contains(field))
753 })
754 {
755 return Err(SchemaContractError::InvalidLocalReference);
756 }
757 }
758 for relation in &relations {
759 if relation
760 .local_fields()
761 .iter()
762 .any(|field| !field_keys.contains(field))
763 || (relation.target_entity() == &source_key
764 && relation
765 .target_fields()
766 .iter()
767 .any(|field| !field_keys.contains(field)))
768 {
769 return Err(SchemaContractError::InvalidLocalReference);
770 }
771 }
772 for constraint in &constraints {
773 let invalid = match constraint.kind() {
774 ConstraintFragmentKind::Check(expression) => expression
775 .dependencies()
776 .iter()
777 .any(|field| !field_keys.contains(field)),
778 ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
779 };
780 if invalid {
781 return Err(SchemaContractError::InvalidLocalReference);
782 }
783 }
784 Ok(Self {
785 source_key,
786 name,
787 fields,
788 primary_key,
789 indexes,
790 relations,
791 constraints,
792 })
793 }
794
795 #[must_use]
797 pub const fn source_key(&self) -> &EntitySourceKey {
798 &self.source_key
799 }
800
801 #[must_use]
803 pub const fn name(&self) -> &SchemaName {
804 &self.name
805 }
806
807 #[must_use]
809 pub fn fields(&self) -> &[FieldFragment] {
810 &self.fields
811 }
812
813 #[must_use]
815 pub fn primary_key(&self) -> &[FieldSourceKey] {
816 &self.primary_key
817 }
818
819 #[must_use]
821 pub fn indexes(&self) -> &[IndexFragment] {
822 &self.indexes
823 }
824
825 #[must_use]
827 pub fn relations(&self) -> &[RelationFragment] {
828 &self.relations
829 }
830
831 #[must_use]
833 pub fn constraints(&self) -> &[ConstraintFragment] {
834 &self.constraints
835 }
836
837 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
838 let rebuilt = Self::try_new(
839 self.name.clone(),
840 self.fields.clone(),
841 self.primary_key.clone(),
842 self.indexes.clone(),
843 self.relations.clone(),
844 self.constraints.clone(),
845 )?;
846 ensure_canonical_rebuild(self, &rebuilt)
847 }
848}
849
850fn validate_insert_generation(
854 fields: &[FieldFragment],
855 primary_key: &[FieldSourceKey],
856) -> Result<(), SchemaContractError> {
857 for field in fields {
858 if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
859 continue;
860 }
861 if field.nullable() || field.management().is_some() {
862 return Err(SchemaContractError::InvalidFieldPolicy);
863 }
864 match field.field_type() {
865 FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
866 FieldType::Scalar(
867 ScalarType::Nat8
868 | ScalarType::Nat16
869 | ScalarType::Nat32
870 | ScalarType::Nat64
871 | ScalarType::Nat128,
872 ) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
873 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
874 return Err(SchemaContractError::InvalidFieldPolicy);
875 }
876 }
877 }
878 Ok(())
879}
880
881#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
886pub struct RecordFieldFragment {
887 source_key: FieldSourceKey,
888 name: SchemaName,
889 field_type: FieldType,
890 nullable: bool,
891}
892
893impl RecordFieldFragment {
894 #[must_use]
896 pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
897 Self {
898 source_key: FieldSourceKey::from_name(&name),
899 name,
900 field_type,
901 nullable,
902 }
903 }
904
905 #[must_use]
907 pub const fn source_key(&self) -> &FieldSourceKey {
908 &self.source_key
909 }
910
911 #[must_use]
913 pub const fn name(&self) -> &SchemaName {
914 &self.name
915 }
916
917 #[must_use]
919 pub const fn field_type(&self) -> &FieldType {
920 &self.field_type
921 }
922
923 #[must_use]
925 pub const fn nullable(&self) -> bool {
926 self.nullable
927 }
928
929 fn validate(&self) -> Result<(), SchemaContractError> {
930 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
931 return Err(SchemaContractError::NonCanonical);
932 }
933 self.field_type.validate()
934 }
935}
936
937#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
940pub struct TupleElementFragment {
941 field_type: FieldType,
942 nullable: bool,
943}
944
945impl TupleElementFragment {
946 #[must_use]
948 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
949 Self {
950 field_type,
951 nullable,
952 }
953 }
954
955 #[must_use]
957 pub const fn field_type(&self) -> &FieldType {
958 &self.field_type
959 }
960
961 #[must_use]
963 pub const fn nullable(&self) -> bool {
964 self.nullable
965 }
966
967 const fn validate(&self) -> Result<(), SchemaContractError> {
968 self.field_type.validate()
969 }
970}
971
972#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
974pub struct RecordTypeFragment {
975 source_key: TypeSourceKey,
976 name: SchemaName,
977 fields: Vec<RecordFieldFragment>,
978}
979
980impl RecordTypeFragment {
981 pub fn try_new(
988 name: SchemaName,
989 mut fields: Vec<RecordFieldFragment>,
990 ) -> Result<Self, SchemaContractError> {
991 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
992 fields.sort_by(|left, right| left.source_key.cmp(&right.source_key));
993 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
994 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
995 for field in &fields {
996 field.validate()?;
997 }
998 Ok(Self {
999 source_key: TypeSourceKey::from_name(&name),
1000 name,
1001 fields,
1002 })
1003 }
1004
1005 #[must_use]
1007 pub const fn source_key(&self) -> &TypeSourceKey {
1008 &self.source_key
1009 }
1010
1011 #[must_use]
1013 pub const fn name(&self) -> &SchemaName {
1014 &self.name
1015 }
1016
1017 #[must_use]
1019 pub fn fields(&self) -> &[RecordFieldFragment] {
1020 &self.fields
1021 }
1022
1023 fn validate(&self) -> Result<(), SchemaContractError> {
1024 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1025 if rebuilt != *self {
1026 return Err(SchemaContractError::NonCanonical);
1027 }
1028 Ok(())
1029 }
1030}
1031
1032#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1034pub struct EnumVariantFragment {
1035 source_key: TypeSourceKey,
1036 name: SchemaName,
1037 payload: Option<FieldType>,
1038}
1039
1040impl EnumVariantFragment {
1041 #[must_use]
1043 pub fn new(name: SchemaName) -> Self {
1044 Self {
1045 source_key: TypeSourceKey::from_name(&name),
1046 name,
1047 payload: None,
1048 }
1049 }
1050
1051 #[must_use]
1053 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1054 Self {
1055 source_key: TypeSourceKey::from_name(&name),
1056 name,
1057 payload: Some(payload),
1058 }
1059 }
1060
1061 #[must_use]
1063 pub const fn source_key(&self) -> &TypeSourceKey {
1064 &self.source_key
1065 }
1066
1067 #[must_use]
1069 pub const fn name(&self) -> &SchemaName {
1070 &self.name
1071 }
1072
1073 #[must_use]
1075 pub const fn payload(&self) -> Option<&FieldType> {
1076 self.payload.as_ref()
1077 }
1078
1079 fn validate(&self) -> Result<(), SchemaContractError> {
1080 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1081 return Err(SchemaContractError::NonCanonical);
1082 }
1083 match &self.payload {
1084 Some(payload) => payload.validate(),
1085 None => Ok(()),
1086 }
1087 }
1088}
1089
1090#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1092pub struct EnumTypeFragment {
1093 source_key: TypeSourceKey,
1094 name: SchemaName,
1095 variants: Vec<EnumVariantFragment>,
1096}
1097
1098impl EnumTypeFragment {
1099 pub fn try_new(
1106 name: SchemaName,
1107 mut variants: Vec<EnumVariantFragment>,
1108 ) -> Result<Self, SchemaContractError> {
1109 if variants.is_empty() {
1110 return Err(SchemaContractError::InvalidReferenceList);
1111 }
1112 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1113 variants.sort_by(|left, right| left.source_key.cmp(&right.source_key));
1114 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1115 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1116 for variant in &variants {
1117 variant.validate()?;
1118 }
1119 Ok(Self {
1120 source_key: TypeSourceKey::from_name(&name),
1121 name,
1122 variants,
1123 })
1124 }
1125
1126 #[must_use]
1128 pub const fn source_key(&self) -> &TypeSourceKey {
1129 &self.source_key
1130 }
1131
1132 #[must_use]
1134 pub const fn name(&self) -> &SchemaName {
1135 &self.name
1136 }
1137
1138 #[must_use]
1140 pub fn variants(&self) -> &[EnumVariantFragment] {
1141 &self.variants
1142 }
1143
1144 fn validate(&self) -> Result<(), SchemaContractError> {
1145 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1146 if rebuilt != *self {
1147 return Err(SchemaContractError::NonCanonical);
1148 }
1149 Ok(())
1150 }
1151}
1152
1153#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1155pub enum NamedTypeFragment {
1156 Record(RecordTypeFragment),
1158 Enum(EnumTypeFragment),
1160 Newtype {
1162 source_key: TypeSourceKey,
1164 name: SchemaName,
1166 inner: FieldType,
1168 },
1169 List {
1171 source_key: TypeSourceKey,
1173 name: SchemaName,
1175 item: FieldType,
1177 },
1178 Set {
1180 source_key: TypeSourceKey,
1182 name: SchemaName,
1184 item: FieldType,
1186 },
1187 Map {
1189 source_key: TypeSourceKey,
1191 name: SchemaName,
1193 key: FieldType,
1195 value: FieldType,
1197 },
1198 Tuple {
1200 source_key: TypeSourceKey,
1202 name: SchemaName,
1204 members: Vec<TupleElementFragment>,
1206 },
1207}
1208
1209impl NamedTypeFragment {
1210 #[must_use]
1212 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1213 Self::Newtype {
1214 source_key: TypeSourceKey::from_name(&name),
1215 name,
1216 inner,
1217 }
1218 }
1219
1220 #[must_use]
1222 pub fn list(name: SchemaName, item: FieldType) -> Self {
1223 Self::List {
1224 source_key: TypeSourceKey::from_name(&name),
1225 name,
1226 item,
1227 }
1228 }
1229
1230 #[must_use]
1232 pub fn set(name: SchemaName, item: FieldType) -> Self {
1233 Self::Set {
1234 source_key: TypeSourceKey::from_name(&name),
1235 name,
1236 item,
1237 }
1238 }
1239
1240 #[must_use]
1242 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1243 Self::Map {
1244 source_key: TypeSourceKey::from_name(&name),
1245 name,
1246 key,
1247 value,
1248 }
1249 }
1250
1251 #[must_use]
1253 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1254 Self::Tuple {
1255 source_key: TypeSourceKey::from_name(&name),
1256 name,
1257 members,
1258 }
1259 }
1260
1261 #[must_use]
1263 pub const fn source_key(&self) -> &TypeSourceKey {
1264 match self {
1265 Self::Record(record) => record.source_key(),
1266 Self::Enum(r#enum) => r#enum.source_key(),
1267 Self::Newtype { source_key, .. }
1268 | Self::List { source_key, .. }
1269 | Self::Set { source_key, .. }
1270 | Self::Map { source_key, .. }
1271 | Self::Tuple { source_key, .. } => source_key,
1272 }
1273 }
1274
1275 #[must_use]
1277 pub const fn name(&self) -> &SchemaName {
1278 match self {
1279 Self::Record(record) => record.name(),
1280 Self::Enum(r#enum) => r#enum.name(),
1281 Self::Newtype { name, .. }
1282 | Self::List { name, .. }
1283 | Self::Set { name, .. }
1284 | Self::Map { name, .. }
1285 | Self::Tuple { name, .. } => name,
1286 }
1287 }
1288
1289 fn validate(&self) -> Result<(), SchemaContractError> {
1290 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1291 match self {
1292 Self::Record(record) => record.validate(),
1293 Self::Enum(r#enum) => r#enum.validate(),
1294 Self::Newtype { inner, .. }
1295 | Self::List { item: inner, .. }
1296 | Self::Set { item: inner, .. } => inner.validate(),
1297 Self::Map { key, value, .. } => {
1298 key.validate()?;
1299 value.validate()
1300 }
1301 Self::Tuple { members, .. } => {
1302 if members.is_empty() {
1303 return Err(SchemaContractError::InvalidReferenceList);
1304 }
1305 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1306 members.iter().try_for_each(TupleElementFragment::validate)
1307 }
1308 }
1309 }
1310}
1311
1312#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1314pub struct SchemaFragment {
1315 entities: Vec<EntityFragment>,
1316 types: Vec<NamedTypeFragment>,
1317}
1318
1319impl SchemaFragment {
1320 pub fn try_new(
1327 mut entities: Vec<EntityFragment>,
1328 mut types: Vec<NamedTypeFragment>,
1329 ) -> Result<Self, SchemaContractError> {
1330 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1331 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1332 entities.sort_by(|left, right| left.source_key.cmp(&right.source_key));
1333 types.sort_by(|left, right| left.source_key().cmp(right.source_key()));
1334 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1335 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1336 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1337 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1338 for entity in &entities {
1339 entity.validate()?;
1340 }
1341 for r#type in &types {
1342 r#type.validate()?;
1343 }
1344 Ok(Self { entities, types })
1345 }
1346
1347 #[must_use]
1349 pub fn entities(&self) -> &[EntityFragment] {
1350 &self.entities
1351 }
1352
1353 #[must_use]
1355 pub fn types(&self) -> &[NamedTypeFragment] {
1356 &self.types
1357 }
1358
1359 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1360 for r#type in &self.types {
1361 r#type.validate()?;
1362 }
1363 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1364 if rebuilt != *self {
1365 return Err(SchemaContractError::NonCanonical);
1366 }
1367 Ok(())
1368 }
1369}
1370
1371pub(crate) const fn check_len(
1372 kind: &'static str,
1373 len: usize,
1374 max: usize,
1375) -> Result<(), SchemaContractError> {
1376 if len > max {
1377 return Err(SchemaContractError::TooManyItems { kind, len, max });
1378 }
1379 Ok(())
1380}
1381
1382fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1383 source_key == name.as_str()
1384}
1385
1386fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1387 if !current_name_key_matches(source_key, name) {
1388 return Err(SchemaContractError::NonCanonical);
1389 }
1390 Ok(())
1391}
1392
1393fn ensure_canonical_rebuild<T: PartialEq>(
1394 current: &T,
1395 rebuilt: &T,
1396) -> Result<(), SchemaContractError> {
1397 if current != rebuilt {
1398 return Err(SchemaContractError::NonCanonical);
1399 }
1400 Ok(())
1401}
1402
1403fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1404where
1405 T: Ord,
1406{
1407 let mut seen = BTreeSet::new();
1408 if values.iter().any(|value| !seen.insert(value)) {
1409 return Err(SchemaContractError::InvalidReferenceList);
1410 }
1411 Ok(())
1412}
1413
1414fn ensure_unique_sorted_by<T, K>(
1415 values: &[T],
1416 key: impl Fn(&T) -> &K,
1417) -> Result<(), SchemaContractError>
1418where
1419 K: Eq,
1420{
1421 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1422 return Err(SchemaContractError::DuplicateSourceKey);
1423 }
1424 Ok(())
1425}
1426
1427fn ensure_unique_names<'a>(
1428 names: impl IntoIterator<Item = &'a SchemaName>,
1429) -> Result<(), SchemaContractError> {
1430 let mut seen = BTreeSet::new();
1431 if names.into_iter().any(|name| !seen.insert(name)) {
1432 return Err(SchemaContractError::DuplicateName);
1433 }
1434 Ok(())
1435}
1436
1437fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1438 for policy in [
1439 FieldManagementPolicy::CreatedAt,
1440 FieldManagementPolicy::UpdatedAt,
1441 ] {
1442 if fields
1443 .iter()
1444 .filter(|field| field.management() == Some(policy))
1445 .count()
1446 > 1
1447 {
1448 return Err(SchemaContractError::InvalidFieldPolicy);
1449 }
1450 }
1451 Ok(())
1452}
1453
1454fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1455 match value.scale().cmp(&scale) {
1456 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1457 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1458 }
1459}
1460
1461#[cfg(test)]
1462mod tests {
1463 use super::{
1464 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1465 SchemaContractError, SchemaName,
1466 };
1467
1468 #[test]
1469 fn independently_decoded_field_key_and_name_must_match() {
1470 let field = FieldFragment {
1471 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1472 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1473 field_type: FieldType::Scalar(ScalarType::Nat64),
1474 nullable: false,
1475 insert_policy: FieldInsertPolicy::Required,
1476 management: None,
1477 };
1478
1479 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1480 }
1481}