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_TYPE_DEPTH, RelationSourceKey,
12 ScalarKind, ScalarLiteral, SchemaContractError, SchemaName, SourceCheckExpr, TypeSourceKey,
13};
14
15#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
17pub enum FieldType {
18 Scalar(ScalarType),
20 List(Box<Self>),
22 Named(TypeSourceKey),
24}
25
26#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
28pub enum ScalarType {
29 Account,
31 Blob {
33 max_len: Option<u32>,
35 },
36 Bool,
38 Date,
40 Decimal {
42 scale: u32,
44 },
45 Duration,
47 Float32,
49 Float64,
51 Int8,
53 Int16,
55 Int32,
57 Int64,
59 Int128,
61 IntBig {
63 max_bytes: u32,
65 },
66 Principal,
68 Subaccount,
70 Text {
72 max_len: Option<u32>,
74 },
75 Timestamp,
77 Nat8,
79 Nat16,
81 Nat32,
83 Nat64,
85 Nat128,
87 NatBig {
89 max_bytes: u32,
91 },
92 Ulid,
94 Unit,
96}
97
98impl ScalarType {
99 #[must_use]
101 pub const fn kind(self) -> ScalarKind {
102 match self {
103 Self::Account => ScalarKind::Account,
104 Self::Blob { .. } => ScalarKind::Blob,
105 Self::Bool => ScalarKind::Bool,
106 Self::Date => ScalarKind::Date,
107 Self::Decimal { .. } => ScalarKind::Decimal,
108 Self::Duration => ScalarKind::Duration,
109 Self::Float32 => ScalarKind::Float32,
110 Self::Float64 => ScalarKind::Float64,
111 Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 => ScalarKind::Int,
112 Self::Int128 => ScalarKind::Int128,
113 Self::IntBig { .. } => ScalarKind::IntBig,
114 Self::Principal => ScalarKind::Principal,
115 Self::Subaccount => ScalarKind::Subaccount,
116 Self::Text { .. } => ScalarKind::Text,
117 Self::Timestamp => ScalarKind::Timestamp,
118 Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 => ScalarKind::Nat,
119 Self::Nat128 => ScalarKind::Nat128,
120 Self::NatBig { .. } => ScalarKind::NatBig,
121 Self::Ulid => ScalarKind::Ulid,
122 Self::Unit => ScalarKind::Unit,
123 }
124 }
125
126 pub(crate) const fn validate(self) -> Result<(), SchemaContractError> {
127 match self {
128 Self::Decimal { scale } if scale > Decimal::max_supported_scale() => {
129 Err(SchemaContractError::InvalidFieldType)
130 }
131 Self::IntBig { max_bytes: 0 } | Self::NatBig { max_bytes: 0 } => {
132 Err(SchemaContractError::InvalidFieldType)
133 }
134 _ => Ok(()),
135 }
136 }
137
138 pub(crate) fn accepts_literal(self, literal: &ScalarLiteral) -> bool {
139 match (self, literal) {
140 (Self::Account, ScalarLiteral::Account(_))
141 | (Self::Bool, ScalarLiteral::Bool(_))
142 | (Self::Date, ScalarLiteral::Date(_))
143 | (Self::Duration, ScalarLiteral::Duration(_))
144 | (Self::Float32, ScalarLiteral::Float32(_))
145 | (Self::Float64, ScalarLiteral::Float64(_))
146 | (Self::Int128, ScalarLiteral::Int(_))
147 | (Self::Principal, ScalarLiteral::Principal(_))
148 | (Self::Subaccount, ScalarLiteral::Subaccount(_))
149 | (Self::Timestamp, ScalarLiteral::Timestamp(_))
150 | (Self::Nat128, ScalarLiteral::Nat(_))
151 | (Self::Ulid, ScalarLiteral::Ulid(_))
152 | (Self::Unit, ScalarLiteral::Unit(_)) => true,
153 (Self::Blob { max_len }, ScalarLiteral::Blob(value)) => {
154 max_len.is_none_or(|max| value.len() <= max as usize)
155 }
156 (Self::Text { max_len }, ScalarLiteral::Text(value)) => {
157 max_len.is_none_or(|max| value.chars().count() <= max as usize)
158 }
159 (Self::Int8, ScalarLiteral::Int(value)) => i8::try_from(*value).is_ok(),
160 (Self::Int16, ScalarLiteral::Int(value)) => i16::try_from(*value).is_ok(),
161 (Self::Int32, ScalarLiteral::Int(value)) => i32::try_from(*value).is_ok(),
162 (Self::Int64, ScalarLiteral::Int(value)) => i64::try_from(*value).is_ok(),
163 (Self::IntBig { max_bytes }, ScalarLiteral::IntBig(value)) => {
164 value.to_leb128().len() <= max_bytes as usize
165 }
166 (Self::Nat8, ScalarLiteral::Nat(value)) => u8::try_from(*value).is_ok(),
167 (Self::Nat16, ScalarLiteral::Nat(value)) => u16::try_from(*value).is_ok(),
168 (Self::Nat32, ScalarLiteral::Nat(value)) => u32::try_from(*value).is_ok(),
169 (Self::Nat64, ScalarLiteral::Nat(value)) => u64::try_from(*value).is_ok(),
170 (Self::NatBig { max_bytes }, ScalarLiteral::NatBig(value)) => {
171 value.to_leb128().len() <= max_bytes as usize
172 }
173 (Self::Decimal { scale }, ScalarLiteral::Decimal(value)) => {
174 decimal_fits_scale(*value, scale)
175 }
176 _ => false,
177 }
178 }
179}
180
181impl FieldType {
182 pub(crate) const fn validate(&self) -> Result<(), SchemaContractError> {
183 self.validate_at_depth(0)
184 }
185
186 const fn validate_at_depth(&self, depth: usize) -> Result<(), SchemaContractError> {
187 let Some(depth) = depth.checked_add(1) else {
188 return Err(SchemaContractError::InvalidNamedTypeGraph);
189 };
190 if depth > MAX_SCHEMA_TYPE_DEPTH {
191 return Err(SchemaContractError::InvalidNamedTypeGraph);
192 }
193 match self {
194 Self::Scalar(scalar) => scalar.validate(),
195 Self::List(item) => item.validate_at_depth(depth),
196 Self::Named(_) => Ok(()),
197 }
198 }
199}
200
201#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
203pub enum FieldInsertPolicy {
204 Required,
206 Nullable,
208 Default(ScalarLiteral),
210 Generated,
212}
213
214#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
216pub enum FieldManagementPolicy {
217 CreatedAt,
219 UpdatedAt,
221}
222
223#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
225pub struct FieldFragment {
226 source_key: FieldSourceKey,
227 name: SchemaName,
228 field_type: FieldType,
229 nullable: bool,
230 insert_policy: FieldInsertPolicy,
231 management: Option<FieldManagementPolicy>,
232}
233
234impl FieldFragment {
235 #[must_use]
237 pub const fn new(
238 source_key: FieldSourceKey,
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,
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 self.field_type.validate()?;
293 if let FieldInsertPolicy::Default(literal) = &self.insert_policy {
294 literal.validate()?;
295 match &self.field_type {
296 FieldType::Scalar(scalar) if scalar.accepts_literal(literal) => {}
297 FieldType::Named(_) if matches!(literal, ScalarLiteral::EnumUnit { .. }) => {}
298 FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
299 return Err(SchemaContractError::LiteralTypeMismatch);
300 }
301 }
302 }
303 if matches!(self.insert_policy, FieldInsertPolicy::Nullable) && !self.nullable {
304 return Err(SchemaContractError::InvalidFieldPolicy);
305 }
306 if self.management.is_some()
307 && (!matches!(self.field_type, FieldType::Scalar(ScalarType::Timestamp))
308 || self.nullable
309 || !matches!(self.insert_policy, FieldInsertPolicy::Required))
310 {
311 return Err(SchemaContractError::InvalidFieldPolicy);
312 }
313 Ok(())
314 }
315}
316
317#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
319pub enum IndexKeyFragment {
320 Field(FieldSourceKey),
322 Lower(FieldSourceKey),
324 Upper(FieldSourceKey),
326 Trim(FieldSourceKey),
328 LowerTrim(FieldSourceKey),
330 Date(FieldSourceKey),
332 Year(FieldSourceKey),
334 Month(FieldSourceKey),
336 Day(FieldSourceKey),
338}
339
340impl IndexKeyFragment {
341 #[must_use]
343 pub const fn field(&self) -> &FieldSourceKey {
344 match self {
345 Self::Field(field)
346 | Self::Lower(field)
347 | Self::Upper(field)
348 | Self::Trim(field)
349 | Self::LowerTrim(field)
350 | Self::Date(field)
351 | Self::Year(field)
352 | Self::Month(field)
353 | Self::Day(field) => field,
354 }
355 }
356}
357
358#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
360pub struct IndexFragment {
361 source_key: IndexSourceKey,
362 name: SchemaName,
363 key: Vec<IndexKeyFragment>,
364 unique: bool,
365 predicate: Option<SourceCheckExpr>,
366}
367
368impl IndexFragment {
369 pub fn try_new(
375 source_key: IndexSourceKey,
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,
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 Self::try_new(
428 self.source_key.clone(),
429 self.name.clone(),
430 self.key.clone(),
431 self.unique,
432 self.predicate.clone(),
433 )
434 .map(|_| ())
435 }
436}
437
438#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
440pub enum RelationDeleteAction {
441 Restrict,
443}
444
445#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
447pub struct RelationFragment {
448 source_key: RelationSourceKey,
449 name: SchemaName,
450 local_fields: Vec<FieldSourceKey>,
451 target_entity: EntitySourceKey,
452 target_fields: Vec<FieldSourceKey>,
453 on_delete: RelationDeleteAction,
454}
455
456impl RelationFragment {
457 pub fn try_new(
464 source_key: RelationSourceKey,
465 name: SchemaName,
466 local_fields: Vec<FieldSourceKey>,
467 target_entity: EntitySourceKey,
468 target_fields: Vec<FieldSourceKey>,
469 on_delete: RelationDeleteAction,
470 ) -> Result<Self, SchemaContractError> {
471 if local_fields.is_empty() || local_fields.len() != target_fields.len() {
472 return Err(SchemaContractError::InvalidReferenceList);
473 }
474 ensure_unique(&local_fields)?;
475 ensure_unique(&target_fields)?;
476 Ok(Self {
477 source_key,
478 name,
479 local_fields,
480 target_entity,
481 target_fields,
482 on_delete,
483 })
484 }
485
486 #[must_use]
488 pub const fn source_key(&self) -> &RelationSourceKey {
489 &self.source_key
490 }
491
492 #[must_use]
494 pub const fn name(&self) -> &SchemaName {
495 &self.name
496 }
497
498 #[must_use]
500 pub fn local_fields(&self) -> &[FieldSourceKey] {
501 &self.local_fields
502 }
503
504 #[must_use]
506 pub const fn target_entity(&self) -> &EntitySourceKey {
507 &self.target_entity
508 }
509
510 #[must_use]
512 pub fn target_fields(&self) -> &[FieldSourceKey] {
513 &self.target_fields
514 }
515
516 #[must_use]
518 pub const fn on_delete(&self) -> RelationDeleteAction {
519 self.on_delete
520 }
521
522 fn validate(&self) -> Result<(), SchemaContractError> {
523 Self::try_new(
524 self.source_key.clone(),
525 self.name.clone(),
526 self.local_fields.clone(),
527 self.target_entity.clone(),
528 self.target_fields.clone(),
529 self.on_delete,
530 )
531 .map(|_| ())
532 }
533}
534
535#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
537pub struct ConstraintFragment {
538 source_key: ConstraintSourceKey,
539 name: SchemaName,
540 expression: SourceCheckExpr,
541}
542
543impl ConstraintFragment {
544 #[must_use]
546 pub const fn new(
547 source_key: ConstraintSourceKey,
548 name: SchemaName,
549 expression: SourceCheckExpr,
550 ) -> Self {
551 Self {
552 source_key,
553 name,
554 expression,
555 }
556 }
557
558 #[must_use]
560 pub const fn source_key(&self) -> &ConstraintSourceKey {
561 &self.source_key
562 }
563
564 #[must_use]
566 pub const fn name(&self) -> &SchemaName {
567 &self.name
568 }
569
570 #[must_use]
572 pub const fn expression(&self) -> &SourceCheckExpr {
573 &self.expression
574 }
575}
576
577#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
579pub struct EntityFragment {
580 source_key: EntitySourceKey,
581 name: SchemaName,
582 fields: Vec<FieldFragment>,
583 primary_key: Vec<FieldSourceKey>,
584 indexes: Vec<IndexFragment>,
585 relations: Vec<RelationFragment>,
586 constraints: Vec<ConstraintFragment>,
587}
588
589impl EntityFragment {
590 pub fn try_new(
597 source_key: EntitySourceKey,
598 name: SchemaName,
599 mut fields: Vec<FieldFragment>,
600 primary_key: Vec<FieldSourceKey>,
601 mut indexes: Vec<IndexFragment>,
602 mut relations: Vec<RelationFragment>,
603 mut constraints: Vec<ConstraintFragment>,
604 ) -> Result<Self, SchemaContractError> {
605 check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
606 check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
607 check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
608 check_len(
609 "entity constraints",
610 constraints.len(),
611 MAX_FRAGMENT_CONSTRAINTS,
612 )?;
613 if primary_key.is_empty() {
614 return Err(SchemaContractError::InvalidReferenceList);
615 }
616 ensure_unique(&primary_key)?;
617 fields.sort_by(|left, right| left.source_key.cmp(&right.source_key));
618 indexes.sort_by(|left, right| left.source_key.cmp(&right.source_key));
619 relations.sort_by(|left, right| left.source_key.cmp(&right.source_key));
620 constraints.sort_by(|left, right| left.source_key.cmp(&right.source_key));
621 ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
622 ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
623 ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
624 ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
625 ensure_unique_names(fields.iter().map(FieldFragment::name))?;
626 ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
627 ensure_unique_names(relations.iter().map(RelationFragment::name))?;
628 ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
629 for field in &fields {
630 field.validate()?;
631 }
632 validate_management_cardinality(&fields)?;
633 for index in &indexes {
634 index.validate()?;
635 }
636 for relation in &relations {
637 relation.validate()?;
638 }
639 for constraint in &constraints {
640 constraint.expression.validate()?;
641 }
642 let field_keys = fields
643 .iter()
644 .map(|field| field.source_key.clone())
645 .collect::<BTreeSet<_>>();
646 if primary_key.iter().any(|field| !field_keys.contains(field)) {
647 return Err(SchemaContractError::InvalidLocalReference);
648 }
649 for index in &indexes {
650 if index
651 .key()
652 .iter()
653 .any(|component| !field_keys.contains(component.field()))
654 || index.predicate().is_some_and(|predicate| {
655 predicate
656 .dependencies()
657 .iter()
658 .any(|field| !field_keys.contains(field))
659 })
660 {
661 return Err(SchemaContractError::InvalidLocalReference);
662 }
663 }
664 for relation in &relations {
665 if relation
666 .local_fields()
667 .iter()
668 .any(|field| !field_keys.contains(field))
669 || (relation.target_entity() == &source_key
670 && relation
671 .target_fields()
672 .iter()
673 .any(|field| !field_keys.contains(field)))
674 {
675 return Err(SchemaContractError::InvalidLocalReference);
676 }
677 }
678 for constraint in &constraints {
679 if constraint
680 .expression()
681 .dependencies()
682 .iter()
683 .any(|field| !field_keys.contains(field))
684 {
685 return Err(SchemaContractError::InvalidLocalReference);
686 }
687 }
688 Ok(Self {
689 source_key,
690 name,
691 fields,
692 primary_key,
693 indexes,
694 relations,
695 constraints,
696 })
697 }
698
699 #[must_use]
701 pub const fn source_key(&self) -> &EntitySourceKey {
702 &self.source_key
703 }
704
705 #[must_use]
707 pub const fn name(&self) -> &SchemaName {
708 &self.name
709 }
710
711 #[must_use]
713 pub fn fields(&self) -> &[FieldFragment] {
714 &self.fields
715 }
716
717 #[must_use]
719 pub fn primary_key(&self) -> &[FieldSourceKey] {
720 &self.primary_key
721 }
722
723 #[must_use]
725 pub fn indexes(&self) -> &[IndexFragment] {
726 &self.indexes
727 }
728
729 #[must_use]
731 pub fn relations(&self) -> &[RelationFragment] {
732 &self.relations
733 }
734
735 #[must_use]
737 pub fn constraints(&self) -> &[ConstraintFragment] {
738 &self.constraints
739 }
740
741 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
742 Self::try_new(
743 self.source_key.clone(),
744 self.name.clone(),
745 self.fields.clone(),
746 self.primary_key.clone(),
747 self.indexes.clone(),
748 self.relations.clone(),
749 self.constraints.clone(),
750 )
751 .map(|_| ())
752 }
753}
754
755#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
760pub struct RecordFieldFragment {
761 source_key: FieldSourceKey,
762 name: SchemaName,
763 field_type: FieldType,
764 nullable: bool,
765}
766
767impl RecordFieldFragment {
768 #[must_use]
770 pub const fn new(
771 source_key: FieldSourceKey,
772 name: SchemaName,
773 field_type: FieldType,
774 nullable: bool,
775 ) -> Self {
776 Self {
777 source_key,
778 name,
779 field_type,
780 nullable,
781 }
782 }
783
784 #[must_use]
786 pub const fn source_key(&self) -> &FieldSourceKey {
787 &self.source_key
788 }
789
790 #[must_use]
792 pub const fn name(&self) -> &SchemaName {
793 &self.name
794 }
795
796 #[must_use]
798 pub const fn field_type(&self) -> &FieldType {
799 &self.field_type
800 }
801
802 #[must_use]
804 pub const fn nullable(&self) -> bool {
805 self.nullable
806 }
807
808 const fn validate(&self) -> Result<(), SchemaContractError> {
809 self.field_type.validate()
810 }
811}
812
813#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
816pub struct TupleElementFragment {
817 field_type: FieldType,
818 nullable: bool,
819}
820
821impl TupleElementFragment {
822 #[must_use]
824 pub const fn new(field_type: FieldType, nullable: bool) -> Self {
825 Self {
826 field_type,
827 nullable,
828 }
829 }
830
831 #[must_use]
833 pub const fn field_type(&self) -> &FieldType {
834 &self.field_type
835 }
836
837 #[must_use]
839 pub const fn nullable(&self) -> bool {
840 self.nullable
841 }
842
843 const fn validate(&self) -> Result<(), SchemaContractError> {
844 self.field_type.validate()
845 }
846}
847
848#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
850pub struct RecordTypeFragment {
851 source_key: TypeSourceKey,
852 name: SchemaName,
853 fields: Vec<RecordFieldFragment>,
854}
855
856impl RecordTypeFragment {
857 pub fn try_new(
864 source_key: TypeSourceKey,
865 name: SchemaName,
866 mut fields: Vec<RecordFieldFragment>,
867 ) -> Result<Self, SchemaContractError> {
868 check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
869 fields.sort_by(|left, right| left.source_key.cmp(&right.source_key));
870 ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
871 ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
872 for field in &fields {
873 field.validate()?;
874 }
875 Ok(Self {
876 source_key,
877 name,
878 fields,
879 })
880 }
881
882 #[must_use]
884 pub const fn source_key(&self) -> &TypeSourceKey {
885 &self.source_key
886 }
887
888 #[must_use]
890 pub const fn name(&self) -> &SchemaName {
891 &self.name
892 }
893
894 #[must_use]
896 pub fn fields(&self) -> &[RecordFieldFragment] {
897 &self.fields
898 }
899
900 fn validate(&self) -> Result<(), SchemaContractError> {
901 let rebuilt = Self::try_new(
902 self.source_key.clone(),
903 self.name.clone(),
904 self.fields.clone(),
905 )?;
906 if rebuilt != *self {
907 return Err(SchemaContractError::NonCanonical);
908 }
909 Ok(())
910 }
911}
912
913#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
915pub struct EnumVariantFragment {
916 source_key: TypeSourceKey,
917 name: SchemaName,
918 payload: Option<FieldType>,
919}
920
921impl EnumVariantFragment {
922 #[must_use]
924 pub const fn new(source_key: TypeSourceKey, name: SchemaName) -> Self {
925 Self {
926 source_key,
927 name,
928 payload: None,
929 }
930 }
931
932 #[must_use]
934 pub const fn with_payload(
935 source_key: TypeSourceKey,
936 name: SchemaName,
937 payload: FieldType,
938 ) -> Self {
939 Self {
940 source_key,
941 name,
942 payload: Some(payload),
943 }
944 }
945
946 #[must_use]
948 pub const fn source_key(&self) -> &TypeSourceKey {
949 &self.source_key
950 }
951
952 #[must_use]
954 pub const fn name(&self) -> &SchemaName {
955 &self.name
956 }
957
958 #[must_use]
960 pub const fn payload(&self) -> Option<&FieldType> {
961 self.payload.as_ref()
962 }
963
964 const fn validate(&self) -> Result<(), SchemaContractError> {
965 match &self.payload {
966 Some(payload) => payload.validate(),
967 None => Ok(()),
968 }
969 }
970}
971
972#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
974pub struct EnumTypeFragment {
975 source_key: TypeSourceKey,
976 name: SchemaName,
977 variants: Vec<EnumVariantFragment>,
978}
979
980impl EnumTypeFragment {
981 pub fn try_new(
988 source_key: TypeSourceKey,
989 name: SchemaName,
990 mut variants: Vec<EnumVariantFragment>,
991 ) -> Result<Self, SchemaContractError> {
992 if variants.is_empty() {
993 return Err(SchemaContractError::InvalidReferenceList);
994 }
995 check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
996 variants.sort_by(|left, right| left.source_key.cmp(&right.source_key));
997 ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
998 ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
999 for variant in &variants {
1000 variant.validate()?;
1001 }
1002 Ok(Self {
1003 source_key,
1004 name,
1005 variants,
1006 })
1007 }
1008
1009 #[must_use]
1011 pub const fn source_key(&self) -> &TypeSourceKey {
1012 &self.source_key
1013 }
1014
1015 #[must_use]
1017 pub const fn name(&self) -> &SchemaName {
1018 &self.name
1019 }
1020
1021 #[must_use]
1023 pub fn variants(&self) -> &[EnumVariantFragment] {
1024 &self.variants
1025 }
1026
1027 fn validate(&self) -> Result<(), SchemaContractError> {
1028 let rebuilt = Self::try_new(
1029 self.source_key.clone(),
1030 self.name.clone(),
1031 self.variants.clone(),
1032 )?;
1033 if rebuilt != *self {
1034 return Err(SchemaContractError::NonCanonical);
1035 }
1036 Ok(())
1037 }
1038}
1039
1040#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1042pub enum NamedTypeFragment {
1043 Record(RecordTypeFragment),
1045 Enum(EnumTypeFragment),
1047 Newtype {
1049 source_key: TypeSourceKey,
1051 name: SchemaName,
1053 inner: FieldType,
1055 },
1056 List {
1058 source_key: TypeSourceKey,
1060 name: SchemaName,
1062 item: FieldType,
1064 },
1065 Set {
1067 source_key: TypeSourceKey,
1069 name: SchemaName,
1071 item: FieldType,
1073 },
1074 Map {
1076 source_key: TypeSourceKey,
1078 name: SchemaName,
1080 key: FieldType,
1082 value: FieldType,
1084 },
1085 Tuple {
1087 source_key: TypeSourceKey,
1089 name: SchemaName,
1091 members: Vec<TupleElementFragment>,
1093 },
1094}
1095
1096impl NamedTypeFragment {
1097 #[must_use]
1099 pub const fn source_key(&self) -> &TypeSourceKey {
1100 match self {
1101 Self::Record(record) => record.source_key(),
1102 Self::Enum(r#enum) => r#enum.source_key(),
1103 Self::Newtype { source_key, .. }
1104 | Self::List { source_key, .. }
1105 | Self::Set { source_key, .. }
1106 | Self::Map { source_key, .. }
1107 | Self::Tuple { source_key, .. } => source_key,
1108 }
1109 }
1110
1111 #[must_use]
1113 pub const fn name(&self) -> &SchemaName {
1114 match self {
1115 Self::Record(record) => record.name(),
1116 Self::Enum(r#enum) => r#enum.name(),
1117 Self::Newtype { name, .. }
1118 | Self::List { name, .. }
1119 | Self::Set { name, .. }
1120 | Self::Map { name, .. }
1121 | Self::Tuple { name, .. } => name,
1122 }
1123 }
1124
1125 fn validate(&self) -> Result<(), SchemaContractError> {
1126 match self {
1127 Self::Record(record) => record.validate(),
1128 Self::Enum(r#enum) => r#enum.validate(),
1129 Self::Newtype { inner, .. }
1130 | Self::List { item: inner, .. }
1131 | Self::Set { item: inner, .. } => inner.validate(),
1132 Self::Map { key, value, .. } => {
1133 key.validate()?;
1134 value.validate()
1135 }
1136 Self::Tuple { members, .. } => {
1137 if members.is_empty() {
1138 return Err(SchemaContractError::InvalidReferenceList);
1139 }
1140 check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1141 members.iter().try_for_each(TupleElementFragment::validate)
1142 }
1143 }
1144 }
1145}
1146
1147#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1149pub struct SchemaFragment {
1150 entities: Vec<EntityFragment>,
1151 types: Vec<NamedTypeFragment>,
1152}
1153
1154impl SchemaFragment {
1155 pub fn try_new(
1162 mut entities: Vec<EntityFragment>,
1163 mut types: Vec<NamedTypeFragment>,
1164 ) -> Result<Self, SchemaContractError> {
1165 check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1166 check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1167 entities.sort_by(|left, right| left.source_key.cmp(&right.source_key));
1168 types.sort_by(|left, right| left.source_key().cmp(right.source_key()));
1169 ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1170 ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1171 ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1172 ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1173 for entity in &entities {
1174 entity.validate()?;
1175 }
1176 for r#type in &types {
1177 r#type.validate()?;
1178 }
1179 Ok(Self { entities, types })
1180 }
1181
1182 #[must_use]
1184 pub fn entities(&self) -> &[EntityFragment] {
1185 &self.entities
1186 }
1187
1188 #[must_use]
1190 pub fn types(&self) -> &[NamedTypeFragment] {
1191 &self.types
1192 }
1193
1194 pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1195 for r#type in &self.types {
1196 r#type.validate()?;
1197 }
1198 let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1199 if rebuilt != *self {
1200 return Err(SchemaContractError::NonCanonical);
1201 }
1202 Ok(())
1203 }
1204}
1205
1206pub(crate) const fn check_len(
1207 kind: &'static str,
1208 len: usize,
1209 max: usize,
1210) -> Result<(), SchemaContractError> {
1211 if len > max {
1212 return Err(SchemaContractError::TooManyItems { kind, len, max });
1213 }
1214 Ok(())
1215}
1216
1217fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1218where
1219 T: Ord,
1220{
1221 let mut seen = BTreeSet::new();
1222 if values.iter().any(|value| !seen.insert(value)) {
1223 return Err(SchemaContractError::InvalidReferenceList);
1224 }
1225 Ok(())
1226}
1227
1228fn ensure_unique_sorted_by<T, K>(
1229 values: &[T],
1230 key: impl Fn(&T) -> &K,
1231) -> Result<(), SchemaContractError>
1232where
1233 K: Eq,
1234{
1235 if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1236 return Err(SchemaContractError::DuplicateSourceKey);
1237 }
1238 Ok(())
1239}
1240
1241fn ensure_unique_names<'a>(
1242 names: impl IntoIterator<Item = &'a SchemaName>,
1243) -> Result<(), SchemaContractError> {
1244 let mut seen = BTreeSet::new();
1245 if names.into_iter().any(|name| !seen.insert(name)) {
1246 return Err(SchemaContractError::DuplicateEditableName);
1247 }
1248 Ok(())
1249}
1250
1251fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1252 for policy in [
1253 FieldManagementPolicy::CreatedAt,
1254 FieldManagementPolicy::UpdatedAt,
1255 ] {
1256 if fields
1257 .iter()
1258 .filter(|field| field.management() == Some(policy))
1259 .count()
1260 > 1
1261 {
1262 return Err(SchemaContractError::InvalidFieldPolicy);
1263 }
1264 }
1265 Ok(())
1266}
1267
1268fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1269 match value.scale().cmp(&scale) {
1270 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1271 std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1272 }
1273}