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 for index in &indexes {
743 if index
744 .key()
745 .iter()
746 .any(|component| !field_keys.contains(component.field()))
747 || index.predicate().is_some_and(|predicate| {
748 predicate
749 .dependencies()
750 .iter()
751 .any(|field| !field_keys.contains(field))
752 })
753 {
754 return Err(SchemaContractError::InvalidLocalReference);
755 }
756 }
757 for relation in &relations {
758 if relation
759 .local_fields()
760 .iter()
761 .any(|field| !field_keys.contains(field))
762 || (relation.target_entity() == &source_key
763 && relation
764 .target_fields()
765 .iter()
766 .any(|field| !field_keys.contains(field)))
767 {
768 return Err(SchemaContractError::InvalidLocalReference);
769 }
770 }
771 for constraint in &constraints {
772 let invalid = match constraint.kind() {
773 ConstraintFragmentKind::Check(expression) => expression
774 .dependencies()
775 .iter()
776 .any(|field| !field_keys.contains(field)),
777 ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
778 };
779 if invalid {
780 return Err(SchemaContractError::InvalidLocalReference);
781 }
782 }
783 Ok(Self {
784 source_key,
785 name,
786 fields,
787 primary_key,
788 indexes,
789 relations,
790 constraints,
791 })
792 }
793
794 #[must_use]
796 pub const fn source_key(&self) -> &EntitySourceKey {
797 &self.source_key
798 }
799
800 #[must_use]
802 pub const fn name(&self) -> &SchemaName {
803 &self.name
804 }
805
806 #[must_use]
808 pub fn fields(&self) -> &[FieldFragment] {
809 &self.fields
810 }
811
812 #[must_use]
814 pub fn primary_key(&self) -> &[FieldSourceKey] {
815 &self.primary_key
816 }
817
818 #[must_use]
820 pub fn indexes(&self) -> &[IndexFragment] {
821 &self.indexes
822 }
823
824 #[must_use]
826 pub fn relations(&self) -> &[RelationFragment] {
827 &self.relations
828 }
829
830 #[must_use]
832 pub fn constraints(&self) -> &[ConstraintFragment] {
833 &self.constraints
834 }
835
836 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
837 let rebuilt = Self::try_new(
838 self.name.clone(),
839 self.fields.clone(),
840 self.primary_key.clone(),
841 self.indexes.clone(),
842 self.relations.clone(),
843 self.constraints.clone(),
844 )?;
845 ensure_canonical_rebuild(self, &rebuilt)
846 }
847}
848
849#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
854pub struct RecordFieldFragment {
855 source_key: FieldSourceKey,
856 name: SchemaName,
857 field_type: FieldType,
858 nullable: bool,
859}
860
861impl RecordFieldFragment {
862 #[must_use]
864 pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
865 Self {
866 source_key: FieldSourceKey::from_name(&name),
867 name,
868 field_type,
869 nullable,
870 }
871 }
872
873 #[must_use]
875 pub const fn source_key(&self) -> &FieldSourceKey {
876 &self.source_key
877 }
878
879 #[must_use]
881 pub const fn name(&self) -> &SchemaName {
882 &self.name
883 }
884
885 #[must_use]
887 pub const fn field_type(&self) -> &FieldType {
888 &self.field_type
889 }
890
891 #[must_use]
893 pub const fn nullable(&self) -> bool {
894 self.nullable
895 }
896
897 fn validate(&self) -> Result<(), SchemaContractError> {
898 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
899 return Err(SchemaContractError::NonCanonical);
900 }
901 self.field_type.validate()
902 }
903}
904
905#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
908pub struct TupleElementFragment {
909 field_type: FieldType,
910 nullable: bool,
911}
912
913impl TupleElementFragment {
914 #[must_use]
916 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
917 Self {
918 field_type,
919 nullable,
920 }
921 }
922
923 #[must_use]
925 pub const fn field_type(&self) -> &FieldType {
926 &self.field_type
927 }
928
929 #[must_use]
931 pub const fn nullable(&self) -> bool {
932 self.nullable
933 }
934
935 const fn validate(&self) -> Result<(), SchemaContractError> {
936 self.field_type.validate()
937 }
938}
939
940#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
942pub struct RecordTypeFragment {
943 source_key: TypeSourceKey,
944 name: SchemaName,
945 fields: Vec<RecordFieldFragment>,
946}
947
948impl RecordTypeFragment {
949 pub fn try_new(
956 name: SchemaName,
957 mut fields: Vec<RecordFieldFragment>,
958 ) -> Result<Self, SchemaContractError> {
959 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
960 fields.sort_by(|left, right| left.source_key.cmp(&right.source_key));
961 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
962 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
963 for field in &fields {
964 field.validate()?;
965 }
966 Ok(Self {
967 source_key: TypeSourceKey::from_name(&name),
968 name,
969 fields,
970 })
971 }
972
973 #[must_use]
975 pub const fn source_key(&self) -> &TypeSourceKey {
976 &self.source_key
977 }
978
979 #[must_use]
981 pub const fn name(&self) -> &SchemaName {
982 &self.name
983 }
984
985 #[must_use]
987 pub fn fields(&self) -> &[RecordFieldFragment] {
988 &self.fields
989 }
990
991 fn validate(&self) -> Result<(), SchemaContractError> {
992 let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
993 if rebuilt != *self {
994 return Err(SchemaContractError::NonCanonical);
995 }
996 Ok(())
997 }
998}
999
1000#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1002pub struct EnumVariantFragment {
1003 source_key: TypeSourceKey,
1004 name: SchemaName,
1005 payload: Option<FieldType>,
1006}
1007
1008impl EnumVariantFragment {
1009 #[must_use]
1011 pub fn new(name: SchemaName) -> Self {
1012 Self {
1013 source_key: TypeSourceKey::from_name(&name),
1014 name,
1015 payload: None,
1016 }
1017 }
1018
1019 #[must_use]
1021 pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1022 Self {
1023 source_key: TypeSourceKey::from_name(&name),
1024 name,
1025 payload: Some(payload),
1026 }
1027 }
1028
1029 #[must_use]
1031 pub const fn source_key(&self) -> &TypeSourceKey {
1032 &self.source_key
1033 }
1034
1035 #[must_use]
1037 pub const fn name(&self) -> &SchemaName {
1038 &self.name
1039 }
1040
1041 #[must_use]
1043 pub const fn payload(&self) -> Option<&FieldType> {
1044 self.payload.as_ref()
1045 }
1046
1047 fn validate(&self) -> Result<(), SchemaContractError> {
1048 if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1049 return Err(SchemaContractError::NonCanonical);
1050 }
1051 match &self.payload {
1052 Some(payload) => payload.validate(),
1053 None => Ok(()),
1054 }
1055 }
1056}
1057
1058#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1060pub struct EnumTypeFragment {
1061 source_key: TypeSourceKey,
1062 name: SchemaName,
1063 variants: Vec<EnumVariantFragment>,
1064}
1065
1066impl EnumTypeFragment {
1067 pub fn try_new(
1074 name: SchemaName,
1075 mut variants: Vec<EnumVariantFragment>,
1076 ) -> Result<Self, SchemaContractError> {
1077 if variants.is_empty() {
1078 return Err(SchemaContractError::InvalidReferenceList);
1079 }
1080 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1081 variants.sort_by(|left, right| left.source_key.cmp(&right.source_key));
1082 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1083 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1084 for variant in &variants {
1085 variant.validate()?;
1086 }
1087 Ok(Self {
1088 source_key: TypeSourceKey::from_name(&name),
1089 name,
1090 variants,
1091 })
1092 }
1093
1094 #[must_use]
1096 pub const fn source_key(&self) -> &TypeSourceKey {
1097 &self.source_key
1098 }
1099
1100 #[must_use]
1102 pub const fn name(&self) -> &SchemaName {
1103 &self.name
1104 }
1105
1106 #[must_use]
1108 pub fn variants(&self) -> &[EnumVariantFragment] {
1109 &self.variants
1110 }
1111
1112 fn validate(&self) -> Result<(), SchemaContractError> {
1113 let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1114 if rebuilt != *self {
1115 return Err(SchemaContractError::NonCanonical);
1116 }
1117 Ok(())
1118 }
1119}
1120
1121#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1123pub enum NamedTypeFragment {
1124 Record(RecordTypeFragment),
1126 Enum(EnumTypeFragment),
1128 Newtype {
1130 source_key: TypeSourceKey,
1132 name: SchemaName,
1134 inner: FieldType,
1136 },
1137 List {
1139 source_key: TypeSourceKey,
1141 name: SchemaName,
1143 item: FieldType,
1145 },
1146 Set {
1148 source_key: TypeSourceKey,
1150 name: SchemaName,
1152 item: FieldType,
1154 },
1155 Map {
1157 source_key: TypeSourceKey,
1159 name: SchemaName,
1161 key: FieldType,
1163 value: FieldType,
1165 },
1166 Tuple {
1168 source_key: TypeSourceKey,
1170 name: SchemaName,
1172 members: Vec<TupleElementFragment>,
1174 },
1175}
1176
1177impl NamedTypeFragment {
1178 #[must_use]
1180 pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1181 Self::Newtype {
1182 source_key: TypeSourceKey::from_name(&name),
1183 name,
1184 inner,
1185 }
1186 }
1187
1188 #[must_use]
1190 pub fn list(name: SchemaName, item: FieldType) -> Self {
1191 Self::List {
1192 source_key: TypeSourceKey::from_name(&name),
1193 name,
1194 item,
1195 }
1196 }
1197
1198 #[must_use]
1200 pub fn set(name: SchemaName, item: FieldType) -> Self {
1201 Self::Set {
1202 source_key: TypeSourceKey::from_name(&name),
1203 name,
1204 item,
1205 }
1206 }
1207
1208 #[must_use]
1210 pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1211 Self::Map {
1212 source_key: TypeSourceKey::from_name(&name),
1213 name,
1214 key,
1215 value,
1216 }
1217 }
1218
1219 #[must_use]
1221 pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1222 Self::Tuple {
1223 source_key: TypeSourceKey::from_name(&name),
1224 name,
1225 members,
1226 }
1227 }
1228
1229 #[must_use]
1231 pub const fn source_key(&self) -> &TypeSourceKey {
1232 match self {
1233 Self::Record(record) => record.source_key(),
1234 Self::Enum(r#enum) => r#enum.source_key(),
1235 Self::Newtype { source_key, .. }
1236 | Self::List { source_key, .. }
1237 | Self::Set { source_key, .. }
1238 | Self::Map { source_key, .. }
1239 | Self::Tuple { source_key, .. } => source_key,
1240 }
1241 }
1242
1243 #[must_use]
1245 pub const fn name(&self) -> &SchemaName {
1246 match self {
1247 Self::Record(record) => record.name(),
1248 Self::Enum(r#enum) => r#enum.name(),
1249 Self::Newtype { name, .. }
1250 | Self::List { name, .. }
1251 | Self::Set { name, .. }
1252 | Self::Map { name, .. }
1253 | Self::Tuple { name, .. } => name,
1254 }
1255 }
1256
1257 fn validate(&self) -> Result<(), SchemaContractError> {
1258 ensure_current_name_key(self.source_key().as_str(), self.name())?;
1259 match self {
1260 Self::Record(record) => record.validate(),
1261 Self::Enum(r#enum) => r#enum.validate(),
1262 Self::Newtype { inner, .. }
1263 | Self::List { item: inner, .. }
1264 | Self::Set { item: inner, .. } => inner.validate(),
1265 Self::Map { key, value, .. } => {
1266 key.validate()?;
1267 value.validate()
1268 }
1269 Self::Tuple { members, .. } => {
1270 if members.is_empty() {
1271 return Err(SchemaContractError::InvalidReferenceList);
1272 }
1273 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1274 members.iter().try_for_each(TupleElementFragment::validate)
1275 }
1276 }
1277 }
1278}
1279
1280#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1282pub struct SchemaFragment {
1283 entities: Vec<EntityFragment>,
1284 types: Vec<NamedTypeFragment>,
1285}
1286
1287impl SchemaFragment {
1288 pub fn try_new(
1295 mut entities: Vec<EntityFragment>,
1296 mut types: Vec<NamedTypeFragment>,
1297 ) -> Result<Self, SchemaContractError> {
1298 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1299 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1300 entities.sort_by(|left, right| left.source_key.cmp(&right.source_key));
1301 types.sort_by(|left, right| left.source_key().cmp(right.source_key()));
1302 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1303 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1304 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1305 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1306 for entity in &entities {
1307 entity.validate()?;
1308 }
1309 for r#type in &types {
1310 r#type.validate()?;
1311 }
1312 Ok(Self { entities, types })
1313 }
1314
1315 #[must_use]
1317 pub fn entities(&self) -> &[EntityFragment] {
1318 &self.entities
1319 }
1320
1321 #[must_use]
1323 pub fn types(&self) -> &[NamedTypeFragment] {
1324 &self.types
1325 }
1326
1327 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1328 for r#type in &self.types {
1329 r#type.validate()?;
1330 }
1331 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1332 if rebuilt != *self {
1333 return Err(SchemaContractError::NonCanonical);
1334 }
1335 Ok(())
1336 }
1337}
1338
1339pub(crate) const fn check_len(
1340 kind: &'static str,
1341 len: usize,
1342 max: usize,
1343) -> Result<(), SchemaContractError> {
1344 if len > max {
1345 return Err(SchemaContractError::TooManyItems { kind, len, max });
1346 }
1347 Ok(())
1348}
1349
1350fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1351 source_key == name.as_str()
1352}
1353
1354fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1355 if !current_name_key_matches(source_key, name) {
1356 return Err(SchemaContractError::NonCanonical);
1357 }
1358 Ok(())
1359}
1360
1361fn ensure_canonical_rebuild<T: PartialEq>(
1362 current: &T,
1363 rebuilt: &T,
1364) -> Result<(), SchemaContractError> {
1365 if current != rebuilt {
1366 return Err(SchemaContractError::NonCanonical);
1367 }
1368 Ok(())
1369}
1370
1371fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1372where
1373 T: Ord,
1374{
1375 let mut seen = BTreeSet::new();
1376 if values.iter().any(|value| !seen.insert(value)) {
1377 return Err(SchemaContractError::InvalidReferenceList);
1378 }
1379 Ok(())
1380}
1381
1382fn ensure_unique_sorted_by<T, K>(
1383 values: &[T],
1384 key: impl Fn(&T) -> &K,
1385) -> Result<(), SchemaContractError>
1386where
1387 K: Eq,
1388{
1389 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1390 return Err(SchemaContractError::DuplicateSourceKey);
1391 }
1392 Ok(())
1393}
1394
1395fn ensure_unique_names<'a>(
1396 names: impl IntoIterator<Item = &'a SchemaName>,
1397) -> Result<(), SchemaContractError> {
1398 let mut seen = BTreeSet::new();
1399 if names.into_iter().any(|name| !seen.insert(name)) {
1400 return Err(SchemaContractError::DuplicateName);
1401 }
1402 Ok(())
1403}
1404
1405fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1406 for policy in [
1407 FieldManagementPolicy::CreatedAt,
1408 FieldManagementPolicy::UpdatedAt,
1409 ] {
1410 if fields
1411 .iter()
1412 .filter(|field| field.management() == Some(policy))
1413 .count()
1414 > 1
1415 {
1416 return Err(SchemaContractError::InvalidFieldPolicy);
1417 }
1418 }
1419 Ok(())
1420}
1421
1422fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1423 match value.scale().cmp(&scale) {
1424 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1425 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1426 }
1427}
1428
1429#[cfg(test)]
1430mod tests {
1431 use super::{
1432 FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1433 SchemaContractError, SchemaName,
1434 };
1435
1436 #[test]
1437 fn independently_decoded_field_key_and_name_must_match() {
1438 let field = FieldFragment {
1439 source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1440 name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1441 field_type: FieldType::Scalar(ScalarType::Nat64),
1442 nullable: false,
1443 insert_policy: FieldInsertPolicy::Required,
1444 management: None,
1445 };
1446
1447 assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1448 }
1449}