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    ScalarKind, ScalarLiteral, SchemaContractError, SchemaName, SourceCheckExpr, TypeSourceKey,
13};
14
15/// Logical type reference in a proposal fragment.
16#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
17pub enum FieldType {
18    /// Exact built-in scalar contract.
19    Scalar(ScalarType),
20    /// Ordered repeated values with one exact element contract.
21    List(Box<Self>),
22    /// Named record, enum, newtype, or collection definition.
23    Named(TypeSourceKey),
24}
25
26/// Exact scalar field contract required by accepted-schema lowering.
27#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
28pub enum ScalarType {
29    /// ICRC account.
30    Account,
31    /// Binary value with an optional maximum byte length.
32    Blob {
33        /// Maximum bytes, or no field-specific maximum.
34        max_len: Option<u32>,
35    },
36    /// Boolean.
37    Bool,
38    /// Day-precision date.
39    Date,
40    /// Fixed-point decimal with exact accepted scale.
41    Decimal {
42        /// Accepted decimal scale.
43        scale: u32,
44    },
45    /// Millisecond duration.
46    Duration,
47    /// Finite 32-bit float.
48    Float32,
49    /// Finite 64-bit float.
50    Float64,
51    /// Signed 8-bit integer.
52    Int8,
53    /// Signed 16-bit integer.
54    Int16,
55    /// Signed 32-bit integer.
56    Int32,
57    /// Signed 64-bit integer.
58    Int64,
59    /// Signed 128-bit integer.
60    Int128,
61    /// Arbitrary-precision signed integer with an encoded-byte bound.
62    IntBig {
63        /// Maximum canonical encoded bytes.
64        max_bytes: u32,
65    },
66    /// Principal.
67    Principal,
68    /// Fixed-width subaccount.
69    Subaccount,
70    /// Text with an optional maximum Unicode-scalar length.
71    Text {
72        /// Maximum Unicode scalar count, or no field-specific maximum.
73        max_len: Option<u32>,
74    },
75    /// Millisecond timestamp.
76    Timestamp,
77    /// Unsigned 8-bit integer.
78    Nat8,
79    /// Unsigned 16-bit integer.
80    Nat16,
81    /// Unsigned 32-bit integer.
82    Nat32,
83    /// Unsigned 64-bit integer.
84    Nat64,
85    /// Unsigned 128-bit integer.
86    Nat128,
87    /// Arbitrary-precision unsigned integer with an encoded-byte bound.
88    NatBig {
89        /// Maximum canonical encoded bytes.
90        max_bytes: u32,
91    },
92    /// ULID.
93    Ulid,
94    /// Unit.
95    Unit,
96}
97
98impl ScalarType {
99    /// Return the intrinsic scalar capability kind.
100    #[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::FieldTypeDepthExceeded);
189        };
190        if depth > MAX_SCHEMA_FIELD_TYPE_DEPTH {
191            return Err(SchemaContractError::FieldTypeDepthExceeded);
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/// Insert policy authored for one field.
202#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
203pub enum FieldInsertPolicy {
204    /// Omission rejects.
205    Required,
206    /// Omission resolves to explicit null.
207    Nullable,
208    /// Omission or explicit database `DEFAULT` resolves to this constant.
209    Default(ScalarLiteral),
210    /// IcyDB generates the value.
211    Generated,
212}
213
214/// Accepted database-owned lifecycle policy.
215#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
216pub enum FieldManagementPolicy {
217    /// Set once on insert and preserve thereafter.
218    CreatedAt,
219    /// Set on insert and on each logical row change.
220    UpdatedAt,
221}
222
223/// One field definition keyed by immutable authorship identity.
224#[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    /// Construct one field definition.
236    #[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    /// Borrow the immutable source key.
256    #[must_use]
257    pub const fn source_key(&self) -> &FieldSourceKey {
258        &self.source_key
259    }
260
261    /// Borrow the editable 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        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/// One ordered index key component.
318#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
319pub enum IndexKeyFragment {
320    /// Direct field key.
321    Field(FieldSourceKey),
322    /// Lower-cased text expression.
323    Lower(FieldSourceKey),
324    /// Upper-cased text expression.
325    Upper(FieldSourceKey),
326    /// Trimmed text expression.
327    Trim(FieldSourceKey),
328    /// Lower-cased and trimmed text expression.
329    LowerTrim(FieldSourceKey),
330    /// Date extraction expression.
331    Date(FieldSourceKey),
332    /// Year extraction expression.
333    Year(FieldSourceKey),
334    /// Month extraction expression.
335    Month(FieldSourceKey),
336    /// Day extraction expression.
337    Day(FieldSourceKey),
338}
339
340impl IndexKeyFragment {
341    /// Borrow the field source key consumed by this component.
342    #[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/// One secondary-index proposal definition.
359#[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    /// Construct one bounded index definition.
370    ///
371    /// # Errors
372    ///
373    /// Returns a typed reference-list error when no key component is present.
374    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    /// Borrow the immutable source key.
397    #[must_use]
398    pub const fn source_key(&self) -> &IndexSourceKey {
399        &self.source_key
400    }
401
402    /// Borrow the editable 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        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/// Maintained referential action in proposal version 1.
439#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
440pub enum RelationDeleteAction {
441    /// Reject deletion while a source row refers to the target.
442    Restrict,
443}
444
445/// One source-owned relation definition.
446#[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    /// Construct one relation with ordered source/target components.
458    ///
459    /// # Errors
460    ///
461    /// Returns a typed reference-list error for empty or arity-mismatched
462    /// components.
463    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    /// Borrow the immutable source key.
487    #[must_use]
488    pub const fn source_key(&self) -> &RelationSourceKey {
489        &self.source_key
490    }
491
492    /// Borrow the editable relation name.
493    #[must_use]
494    pub const fn name(&self) -> &SchemaName {
495        &self.name
496    }
497
498    /// Borrow ordered source fields.
499    #[must_use]
500    pub fn local_fields(&self) -> &[FieldSourceKey] {
501        &self.local_fields
502    }
503
504    /// Borrow the target entity source key.
505    #[must_use]
506    pub const fn target_entity(&self) -> &EntitySourceKey {
507        &self.target_entity
508    }
509
510    /// Borrow ordered target fields.
511    #[must_use]
512    pub fn target_fields(&self) -> &[FieldSourceKey] {
513        &self.target_fields
514    }
515
516    /// Return the maintained delete action.
517    #[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/// One accepted-check declaration in source-key form.
536#[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    /// Construct one source constraint.
545    #[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    /// Borrow the immutable source key.
559    #[must_use]
560    pub const fn source_key(&self) -> &ConstraintSourceKey {
561        &self.source_key
562    }
563
564    /// Borrow the editable constraint name.
565    #[must_use]
566    pub const fn name(&self) -> &SchemaName {
567        &self.name
568    }
569
570    /// Borrow the source expression.
571    #[must_use]
572    pub const fn expression(&self) -> &SourceCheckExpr {
573        &self.expression
574    }
575}
576
577/// Store-free logical entity definition.
578#[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    /// Construct and canonicalize one entity definition.
591    ///
592    /// # Errors
593    ///
594    /// Returns a typed contract error for collection overflow, duplicate
595    /// source keys, malformed policy, expression, or primary-key references.
596    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    /// Borrow the immutable source key.
700    #[must_use]
701    pub const fn source_key(&self) -> &EntitySourceKey {
702        &self.source_key
703    }
704
705    /// Borrow the editable entity name.
706    #[must_use]
707    pub const fn name(&self) -> &SchemaName {
708        &self.name
709    }
710
711    /// Borrow canonical field definitions.
712    #[must_use]
713    pub fn fields(&self) -> &[FieldFragment] {
714        &self.fields
715    }
716
717    /// Borrow ordered primary-key fields.
718    #[must_use]
719    pub fn primary_key(&self) -> &[FieldSourceKey] {
720        &self.primary_key
721    }
722
723    /// Borrow canonical secondary-index definitions.
724    #[must_use]
725    pub fn indexes(&self) -> &[IndexFragment] {
726        &self.indexes
727    }
728
729    /// Borrow canonical relation definitions.
730    #[must_use]
731    pub fn relations(&self) -> &[RelationFragment] {
732        &self.relations
733    }
734
735    /// Borrow canonical accepted-check definitions.
736    #[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/// One structural field in a named record type.
756///
757/// Composite fields carry exact type and nullability facts only. Insert,
758/// generation, and management policies belong to persisted entity fields.
759#[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    /// Construct one exact structural record field.
769    #[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    /// Borrow the immutable source key.
785    #[must_use]
786    pub const fn source_key(&self) -> &FieldSourceKey {
787        &self.source_key
788    }
789
790    /// Borrow the editable field name.
791    #[must_use]
792    pub const fn name(&self) -> &SchemaName {
793        &self.name
794    }
795
796    /// Borrow the exact logical field type.
797    #[must_use]
798    pub const fn field_type(&self) -> &FieldType {
799        &self.field_type
800    }
801
802    /// Return whether the structural field admits null.
803    #[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/// One positional tuple member and its explicit-null policy.
814
815#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
816pub struct TupleElementFragment {
817    field_type: FieldType,
818    nullable: bool,
819}
820
821impl TupleElementFragment {
822    /// Construct one exact tuple-member contract.
823    #[must_use]
824    pub const fn new(field_type: FieldType, nullable: bool) -> Self {
825        Self {
826            field_type,
827            nullable,
828        }
829    }
830
831    /// Borrow the exact logical member type.
832    #[must_use]
833    pub const fn field_type(&self) -> &FieldType {
834        &self.field_type
835    }
836
837    /// Return whether this tuple member admits explicit null.
838    #[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/// Named record-type definition.
849#[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    /// Construct and canonicalize a record definition.
858    ///
859    /// # Errors
860    ///
861    /// Returns a typed contract error for overflow, duplicates, or malformed
862    /// fields.
863    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    /// Borrow the immutable source key.
883    #[must_use]
884    pub const fn source_key(&self) -> &TypeSourceKey {
885        &self.source_key
886    }
887
888    /// Borrow the editable record name.
889    #[must_use]
890    pub const fn name(&self) -> &SchemaName {
891        &self.name
892    }
893
894    /// Borrow canonical record fields.
895    #[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/// One named enum variant.
914#[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    /// Construct one unit variant.
923    #[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    /// Construct one payload-bearing variant.
933    #[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    /// Borrow the immutable variant source key.
947    #[must_use]
948    pub const fn source_key(&self) -> &TypeSourceKey {
949        &self.source_key
950    }
951
952    /// Borrow the editable variant name.
953    #[must_use]
954    pub const fn name(&self) -> &SchemaName {
955        &self.name
956    }
957
958    /// Borrow the optional exact payload contract.
959    #[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/// Named enum-type definition.
973#[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    /// Construct and canonicalize an enum definition.
982    ///
983    /// # Errors
984    ///
985    /// Returns a typed contract error for empty, oversized, or duplicate
986    /// variants.
987    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    /// Borrow the immutable source key.
1010    #[must_use]
1011    pub const fn source_key(&self) -> &TypeSourceKey {
1012        &self.source_key
1013    }
1014
1015    /// Borrow the editable enum name.
1016    #[must_use]
1017    pub const fn name(&self) -> &SchemaName {
1018        &self.name
1019    }
1020
1021    /// Borrow canonical enum variants.
1022    #[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/// Named reusable type definition.
1041#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1042pub enum NamedTypeFragment {
1043    /// Record type.
1044    Record(RecordTypeFragment),
1045    /// Enum type.
1046    Enum(EnumTypeFragment),
1047    /// Transparent named wrapper.
1048    Newtype {
1049        /// Immutable type identity.
1050        source_key: TypeSourceKey,
1051        /// Editable display name.
1052        name: SchemaName,
1053        /// Wrapped logical type.
1054        inner: FieldType,
1055    },
1056    /// Homogeneous ordered collection.
1057    List {
1058        /// Immutable type identity.
1059        source_key: TypeSourceKey,
1060        /// Editable display name.
1061        name: SchemaName,
1062        /// Element type.
1063        item: FieldType,
1064    },
1065    /// Homogeneous unique collection.
1066    Set {
1067        /// Immutable type identity.
1068        source_key: TypeSourceKey,
1069        /// Editable display name.
1070        name: SchemaName,
1071        /// Element type.
1072        item: FieldType,
1073    },
1074    /// Homogeneous key/value collection.
1075    Map {
1076        /// Immutable type identity.
1077        source_key: TypeSourceKey,
1078        /// Editable display name.
1079        name: SchemaName,
1080        /// Key type.
1081        key: FieldType,
1082        /// Value type.
1083        value: FieldType,
1084    },
1085    /// Ordered heterogeneous product.
1086    Tuple {
1087        /// Immutable type identity.
1088        source_key: TypeSourceKey,
1089        /// Editable display name.
1090        name: SchemaName,
1091        /// Ordered member types.
1092        members: Vec<TupleElementFragment>,
1093    },
1094}
1095
1096impl NamedTypeFragment {
1097    /// Borrow the immutable type source key.
1098    #[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    /// Borrow the editable type name.
1112    #[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/// Reusable store-free collection of entity and type definitions.
1148#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1149pub struct SchemaFragment {
1150    entities: Vec<EntityFragment>,
1151    types: Vec<NamedTypeFragment>,
1152}
1153
1154impl SchemaFragment {
1155    /// Construct and canonicalize one reusable fragment.
1156    ///
1157    /// # Errors
1158    ///
1159    /// Returns a typed contract error for overflow, duplicate definitions, or
1160    /// malformed nested definitions.
1161    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    /// Borrow entity definitions.
1183    #[must_use]
1184    pub fn entities(&self) -> &[EntityFragment] {
1185        &self.entities
1186    }
1187
1188    /// Borrow named type definitions.
1189    #[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}