Skip to main content

icydb_schema/
fragment.rs

1//! Store-free reusable schema fragments.
2
3use 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/// Logical type reference in a proposal fragment.
17#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
18pub enum FieldType {
19    /// Exact built-in scalar contract.
20    Scalar(ScalarType),
21    /// Ordered repeated values with one exact element contract.
22    List(Box<Self>),
23    /// Named record, enum, newtype, or collection definition.
24    Named(TypeSourceKey),
25}
26
27/// Exact scalar field contract required by accepted-schema lowering.
28#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
29pub enum ScalarType {
30    /// ICRC account.
31    Account,
32    /// Binary value with an optional maximum byte length.
33    Blob {
34        /// Maximum bytes, or no field-specific maximum.
35        max_len: Option<u32>,
36    },
37    /// Boolean.
38    Bool,
39    /// Day-precision date.
40    Date,
41    /// Fixed-point decimal with exact accepted scale.
42    Decimal {
43        /// Accepted decimal scale.
44        scale: u32,
45    },
46    /// Millisecond duration.
47    Duration,
48    /// Finite 32-bit float.
49    Float32,
50    /// Finite 64-bit float.
51    Float64,
52    /// Signed 8-bit integer.
53    Int8,
54    /// Signed 16-bit integer.
55    Int16,
56    /// Signed 32-bit integer.
57    Int32,
58    /// Signed 64-bit integer.
59    Int64,
60    /// Signed 128-bit integer.
61    Int128,
62    /// Arbitrary-precision signed integer with an encoded-byte bound.
63    IntBig {
64        /// Maximum canonical encoded bytes.
65        max_bytes: u32,
66    },
67    /// Principal.
68    Principal,
69    /// Fixed-width subaccount.
70    Subaccount,
71    /// Text with an optional maximum Unicode-scalar length.
72    Text {
73        /// Maximum Unicode scalar count, or no field-specific maximum.
74        max_len: Option<u32>,
75    },
76    /// Millisecond timestamp.
77    Timestamp,
78    /// Unsigned 8-bit integer.
79    Nat8,
80    /// Unsigned 16-bit integer.
81    Nat16,
82    /// Unsigned 32-bit integer.
83    Nat32,
84    /// Unsigned 64-bit integer.
85    Nat64,
86    /// Unsigned 128-bit integer.
87    Nat128,
88    /// Arbitrary-precision unsigned integer with an encoded-byte bound.
89    NatBig {
90        /// Maximum canonical encoded bytes.
91        max_bytes: u32,
92    },
93    /// ULID.
94    Ulid,
95    /// Unit.
96    Unit,
97}
98
99impl ScalarType {
100    /// Return the intrinsic scalar capability kind.
101    #[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/// Insert policy authored for one field.
203#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
204pub enum FieldInsertPolicy {
205    /// Omission rejects.
206    Required,
207    /// Omission resolves to explicit null.
208    Nullable,
209    /// Omission or explicit database `DEFAULT` resolves to this constant.
210    Default(ScalarLiteral),
211    /// IcyDB generates the value.
212    Generated,
213}
214
215/// Accepted database-owned lifecycle policy.
216#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
217pub enum FieldManagementPolicy {
218    /// Set once on insert and preserve thereafter.
219    CreatedAt,
220    /// Set on insert and on each logical row change.
221    UpdatedAt,
222}
223
224/// One field definition keyed by its current source name.
225#[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    /// Construct one field definition.
237    #[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    /// Borrow the typed proposal key derived from the current field name.
256    #[must_use]
257    pub const fn source_key(&self) -> &FieldSourceKey {
258        &self.source_key
259    }
260
261    /// Borrow the current field name.
262    #[must_use]
263    pub const fn name(&self) -> &SchemaName {
264        &self.name
265    }
266
267    /// Borrow the logical field type.
268    #[must_use]
269    pub const fn field_type(&self) -> &FieldType {
270        &self.field_type
271    }
272
273    /// Return whether the field admits authored null.
274    #[must_use]
275    pub const fn nullable(&self) -> bool {
276        self.nullable
277    }
278
279    /// Borrow the future-write insert policy.
280    #[must_use]
281    pub const fn insert_policy(&self) -> &FieldInsertPolicy {
282        &self.insert_policy
283    }
284
285    /// Return the optional accepted management policy.
286    #[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/// One ordered index key component.
319#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
320pub enum IndexKeyFragment {
321    /// Direct field key.
322    Field(FieldSourceKey),
323    /// Lower-cased text expression.
324    Lower(FieldSourceKey),
325    /// Upper-cased text expression.
326    Upper(FieldSourceKey),
327    /// Trimmed text expression.
328    Trim(FieldSourceKey),
329    /// Lower-cased and trimmed text expression.
330    LowerTrim(FieldSourceKey),
331    /// Date extraction expression.
332    Date(FieldSourceKey),
333    /// Year extraction expression.
334    Year(FieldSourceKey),
335    /// Month extraction expression.
336    Month(FieldSourceKey),
337    /// Day extraction expression.
338    Day(FieldSourceKey),
339}
340
341impl IndexKeyFragment {
342    /// Borrow the field source key consumed by this component.
343    #[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/// One secondary-index proposal definition.
360#[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    /// Construct one bounded index definition.
371    ///
372    /// # Errors
373    ///
374    /// Returns a typed reference-list error when no key component is present.
375    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    /// Borrow the typed proposal key derived from the current index name.
397    #[must_use]
398    pub const fn source_key(&self) -> &IndexSourceKey {
399        &self.source_key
400    }
401
402    /// Borrow the current index name.
403    #[must_use]
404    pub const fn name(&self) -> &SchemaName {
405        &self.name
406    }
407
408    /// Borrow ordered index key components.
409    #[must_use]
410    pub fn key(&self) -> &[IndexKeyFragment] {
411        &self.key
412    }
413
414    /// Return whether the index enforces uniqueness.
415    #[must_use]
416    pub const fn unique(&self) -> bool {
417        self.unique
418    }
419
420    /// Borrow the optional source predicate.
421    #[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/// Maintained referential action in the current proposal contract.
438#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
439pub enum RelationDeleteAction {
440    /// Reject deletion while a source row refers to the target.
441    Restrict,
442}
443
444/// One source-owned relation definition.
445#[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    /// Construct one relation with ordered source/target components.
457    ///
458    /// # Errors
459    ///
460    /// Returns a typed reference-list error for empty or arity-mismatched
461    /// components.
462    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    /// Borrow the typed proposal key derived from the current relation name.
485    #[must_use]
486    pub const fn source_key(&self) -> &RelationSourceKey {
487        &self.source_key
488    }
489
490    /// Borrow the current relation name.
491    #[must_use]
492    pub const fn name(&self) -> &SchemaName {
493        &self.name
494    }
495
496    /// Borrow ordered source fields.
497    #[must_use]
498    pub fn local_fields(&self) -> &[FieldSourceKey] {
499        &self.local_fields
500    }
501
502    /// Borrow the target entity source key.
503    #[must_use]
504    pub const fn target_entity(&self) -> &EntitySourceKey {
505        &self.target_entity
506    }
507
508    /// Borrow ordered target fields.
509    #[must_use]
510    pub fn target_fields(&self) -> &[FieldSourceKey] {
511        &self.target_fields
512    }
513
514    /// Return the maintained delete action.
515    #[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/// One source constraint kind.
533#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
534pub enum ConstraintFragmentKind {
535    /// General row check over top-level source fields.
536    Check(SourceCheckExpr),
537    /// Closed durable operation over one nominal target below a persisted root.
538    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/// One source-bound nominal durable-rule target.
551#[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    /// Construct one targeted rule below a persisted root field.
561    #[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    /// Borrow the persisted root-field identity.
577    #[must_use]
578    pub const fn root(&self) -> &FieldSourceKey {
579        &self.root
580    }
581
582    /// Borrow the ruled nominal type identity.
583    #[must_use]
584    pub const fn target_type(&self) -> &TypeSourceKey {
585        &self.target_type
586    }
587
588    /// Borrow the current durable-rule name as a typed proposal key.
589    #[must_use]
590    pub const fn rule(&self) -> &RuleSourceKey {
591        &self.rule
592    }
593
594    /// Borrow the closed durable operation.
595    #[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/// One source constraint declaration.
606#[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    /// Construct one general source check.
615    #[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    /// Construct one source-bound targeted durable rule.
625    #[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    /// Borrow the typed proposal key derived from the current declaration.
641    #[must_use]
642    pub const fn source_key(&self) -> &ConstraintSourceKey {
643        &self.source_key
644    }
645
646    /// Borrow the current constraint name.
647    #[must_use]
648    pub const fn name(&self) -> &SchemaName {
649        &self.name
650    }
651
652    /// Borrow the exact source constraint kind.
653    #[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/// Store-free logical entity definition.
671#[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    /// Construct and canonicalize one entity definition.
684    ///
685    /// # Errors
686    ///
687    /// Returns a typed contract error for collection overflow, duplicate
688    /// source keys, malformed policy, expression, or primary-key references.
689    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    /// Borrow the typed proposal key derived from the current entity name.
795    #[must_use]
796    pub const fn source_key(&self) -> &EntitySourceKey {
797        &self.source_key
798    }
799
800    /// Borrow the current entity name.
801    #[must_use]
802    pub const fn name(&self) -> &SchemaName {
803        &self.name
804    }
805
806    /// Borrow canonical field definitions.
807    #[must_use]
808    pub fn fields(&self) -> &[FieldFragment] {
809        &self.fields
810    }
811
812    /// Borrow ordered primary-key fields.
813    #[must_use]
814    pub fn primary_key(&self) -> &[FieldSourceKey] {
815        &self.primary_key
816    }
817
818    /// Borrow canonical secondary-index definitions.
819    #[must_use]
820    pub fn indexes(&self) -> &[IndexFragment] {
821        &self.indexes
822    }
823
824    /// Borrow canonical relation definitions.
825    #[must_use]
826    pub fn relations(&self) -> &[RelationFragment] {
827        &self.relations
828    }
829
830    /// Borrow canonical source constraint definitions.
831    #[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/// One structural field in a named record type.
850///
851/// Composite fields carry exact type and nullability facts only. Insert,
852/// generation, and management policies belong to persisted entity fields.
853#[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    /// Construct one exact structural record field.
863    #[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    /// Borrow the typed proposal key derived from the current field name.
874    #[must_use]
875    pub const fn source_key(&self) -> &FieldSourceKey {
876        &self.source_key
877    }
878
879    /// Borrow the current record-field name.
880    #[must_use]
881    pub const fn name(&self) -> &SchemaName {
882        &self.name
883    }
884
885    /// Borrow the exact logical field type.
886    #[must_use]
887    pub const fn field_type(&self) -> &FieldType {
888        &self.field_type
889    }
890
891    /// Return whether the structural field admits null.
892    #[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/// One positional tuple member and its explicit-null policy.
906
907#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
908pub struct TupleElementFragment {
909    field_type: FieldType,
910    nullable: bool,
911}
912
913impl TupleElementFragment {
914    /// Construct one exact tuple-member contract.
915    #[must_use]
916    pub const fn new(field_type: FieldType, nullable: bool) -> Self {
917        Self {
918            field_type,
919            nullable,
920        }
921    }
922
923    /// Borrow the exact logical member type.
924    #[must_use]
925    pub const fn field_type(&self) -> &FieldType {
926        &self.field_type
927    }
928
929    /// Return whether this tuple member admits explicit null.
930    #[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/// Named record-type definition.
941#[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    /// Construct and canonicalize a record definition.
950    ///
951    /// # Errors
952    ///
953    /// Returns a typed contract error for overflow, duplicates, or malformed
954    /// fields.
955    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    /// Borrow the typed proposal key derived from the current record name.
974    #[must_use]
975    pub const fn source_key(&self) -> &TypeSourceKey {
976        &self.source_key
977    }
978
979    /// Borrow the current record name.
980    #[must_use]
981    pub const fn name(&self) -> &SchemaName {
982        &self.name
983    }
984
985    /// Borrow canonical record fields.
986    #[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/// One named enum variant.
1001#[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    /// Construct one unit variant.
1010    #[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    /// Construct one payload-bearing variant.
1020    #[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    /// Borrow the typed proposal key derived from the current variant name.
1030    #[must_use]
1031    pub const fn source_key(&self) -> &TypeSourceKey {
1032        &self.source_key
1033    }
1034
1035    /// Borrow the current variant name.
1036    #[must_use]
1037    pub const fn name(&self) -> &SchemaName {
1038        &self.name
1039    }
1040
1041    /// Borrow the optional exact payload contract.
1042    #[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/// Named enum-type definition.
1059#[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    /// Construct and canonicalize an enum definition.
1068    ///
1069    /// # Errors
1070    ///
1071    /// Returns a typed contract error for empty, oversized, or duplicate
1072    /// variants.
1073    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    /// Borrow the typed proposal key derived from the current enum name.
1095    #[must_use]
1096    pub const fn source_key(&self) -> &TypeSourceKey {
1097        &self.source_key
1098    }
1099
1100    /// Borrow the current enum name.
1101    #[must_use]
1102    pub const fn name(&self) -> &SchemaName {
1103        &self.name
1104    }
1105
1106    /// Borrow canonical enum variants.
1107    #[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/// Named reusable type definition.
1122#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1123pub enum NamedTypeFragment {
1124    /// Record type.
1125    Record(RecordTypeFragment),
1126    /// Enum type.
1127    Enum(EnumTypeFragment),
1128    /// Transparent named wrapper.
1129    Newtype {
1130        /// Typed proposal key derived from the current name.
1131        source_key: TypeSourceKey,
1132        /// Current schema name.
1133        name: SchemaName,
1134        /// Wrapped logical type.
1135        inner: FieldType,
1136    },
1137    /// Homogeneous ordered collection.
1138    List {
1139        /// Typed proposal key derived from the current name.
1140        source_key: TypeSourceKey,
1141        /// Current schema name.
1142        name: SchemaName,
1143        /// Element type.
1144        item: FieldType,
1145    },
1146    /// Homogeneous unique collection.
1147    Set {
1148        /// Typed proposal key derived from the current name.
1149        source_key: TypeSourceKey,
1150        /// Current schema name.
1151        name: SchemaName,
1152        /// Element type.
1153        item: FieldType,
1154    },
1155    /// Homogeneous key/value collection.
1156    Map {
1157        /// Typed proposal key derived from the current name.
1158        source_key: TypeSourceKey,
1159        /// Current schema name.
1160        name: SchemaName,
1161        /// Key type.
1162        key: FieldType,
1163        /// Value type.
1164        value: FieldType,
1165    },
1166    /// Ordered heterogeneous product.
1167    Tuple {
1168        /// Typed proposal key derived from the current name.
1169        source_key: TypeSourceKey,
1170        /// Current schema name.
1171        name: SchemaName,
1172        /// Ordered member types.
1173        members: Vec<TupleElementFragment>,
1174    },
1175}
1176
1177impl NamedTypeFragment {
1178    /// Construct one transparent named wrapper from its current name.
1179    #[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    /// Construct one named ordered collection from its current name.
1189    #[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    /// Construct one named unique collection from its current name.
1199    #[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    /// Construct one named key/value collection from its current name.
1209    #[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    /// Construct one named heterogeneous product from its current name.
1220    #[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    /// Borrow the typed proposal key derived from the current type name.
1230    #[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    /// Borrow the current type name.
1244    #[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/// Reusable store-free collection of entity and type definitions.
1281#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1282pub struct SchemaFragment {
1283    entities: Vec<EntityFragment>,
1284    types: Vec<NamedTypeFragment>,
1285}
1286
1287impl SchemaFragment {
1288    /// Construct and canonicalize one reusable fragment.
1289    ///
1290    /// # Errors
1291    ///
1292    /// Returns a typed contract error for overflow, duplicate definitions, or
1293    /// malformed nested definitions.
1294    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    /// Borrow entity definitions.
1316    #[must_use]
1317    pub fn entities(&self) -> &[EntityFragment] {
1318        &self.entities
1319    }
1320
1321    /// Borrow named type definitions.
1322    #[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}