Skip to main content

icydb_schema/
fragment.rs

1//! Store-free reusable schema fragments.
2
3use std::collections::BTreeSet;
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    ConstraintSourceKey, Decimal, DeclaredEntityVersion, EntitySourceKey, FieldSourceKey,
9    IndexSourceKey, MAX_FRAGMENT_CONSTRAINTS, MAX_FRAGMENT_ENTITIES, MAX_FRAGMENT_FIELDS,
10    MAX_FRAGMENT_INDEXES, MAX_FRAGMENT_RELATIONS, MAX_FRAGMENT_TYPES, MAX_RELATION_PATH_STEPS,
11    MAX_SCHEMA_FIELD_TYPE_DEPTH, RelationSourceKey, RuleSourceKey, ScalarKind, ScalarLiteral,
12    SchemaContractError, SchemaName, SourceCheckExpr, SourceRuleOperation, TypeSourceKey,
13};
14
15/// Logical type reference in a proposal fragment.
16#[derive(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(
24        #[serde(
25            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
26        )]
27        TypeSourceKey,
28    ),
29}
30
31/// Exact scalar field contract required by accepted-schema lowering.
32#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
33pub enum ScalarType {
34    /// ICRC account.
35    Account,
36    /// Binary value with an optional maximum byte length.
37    Blob {
38        /// Maximum bytes, or no field-specific maximum.
39        max_len: Option<u32>,
40    },
41    /// Boolean.
42    Bool,
43    /// Day-precision date.
44    Date,
45    /// Fixed-point decimal with exact accepted scale.
46    Decimal {
47        /// Accepted decimal scale.
48        scale: u32,
49    },
50    /// Millisecond duration.
51    Duration,
52    /// Finite 32-bit float.
53    Float32,
54    /// Finite 64-bit float.
55    Float64,
56    /// Signed 8-bit integer.
57    Int8,
58    /// Signed 16-bit integer.
59    Int16,
60    /// Signed 32-bit integer.
61    Int32,
62    /// Signed 64-bit integer.
63    Int64,
64    /// Signed 128-bit integer.
65    Int128,
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    /// Fixed-width unsigned 256-bit integer.
88    U256,
89    /// ULID.
90    Ulid,
91    /// Unit.
92    Unit,
93}
94
95impl ScalarType {
96    /// Return the intrinsic scalar capability kind.
97    #[must_use]
98    pub const fn kind(self) -> ScalarKind {
99        match self {
100            Self::Account => ScalarKind::Account,
101            Self::Blob { .. } => ScalarKind::Blob,
102            Self::Bool => ScalarKind::Bool,
103            Self::Date => ScalarKind::Date,
104            Self::Decimal { .. } => ScalarKind::Decimal,
105            Self::Duration => ScalarKind::Duration,
106            Self::Float32 => ScalarKind::Float32,
107            Self::Float64 => ScalarKind::Float64,
108            Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64 => ScalarKind::Int,
109            Self::Int128 => ScalarKind::Int128,
110            Self::Principal => ScalarKind::Principal,
111            Self::Subaccount => ScalarKind::Subaccount,
112            Self::Text { .. } => ScalarKind::Text,
113            Self::Timestamp => ScalarKind::Timestamp,
114            Self::Nat8 | Self::Nat16 | Self::Nat32 | Self::Nat64 => ScalarKind::Nat,
115            Self::Nat128 => ScalarKind::Nat128,
116            Self::U256 => ScalarKind::U256,
117            Self::Ulid => ScalarKind::Ulid,
118            Self::Unit => ScalarKind::Unit,
119        }
120    }
121
122    pub(crate) const fn validate(self) -> Result<(), SchemaContractError> {
123        match self {
124            Self::Decimal { scale } if scale > Decimal::max_supported_scale() => {
125                Err(SchemaContractError::InvalidFieldType)
126            }
127            _ => Ok(()),
128        }
129    }
130
131    pub(crate) fn accepts_literal(self, literal: &ScalarLiteral) -> bool {
132        match (self, literal) {
133            (Self::Account, ScalarLiteral::Account(_))
134            | (Self::Bool, ScalarLiteral::Bool(_))
135            | (Self::Date, ScalarLiteral::Date(_))
136            | (Self::Duration, ScalarLiteral::Duration(_))
137            | (Self::Float32, ScalarLiteral::Float32(_))
138            | (Self::Float64, ScalarLiteral::Float64(_))
139            | (Self::Int128, ScalarLiteral::Int(_))
140            | (Self::Principal, ScalarLiteral::Principal(_))
141            | (Self::Subaccount, ScalarLiteral::Subaccount(_))
142            | (Self::Timestamp, ScalarLiteral::Timestamp(_))
143            | (Self::Nat128, ScalarLiteral::Nat(_))
144            | (Self::U256, ScalarLiteral::U256(_))
145            | (Self::Ulid, ScalarLiteral::Ulid(_))
146            | (Self::Unit, ScalarLiteral::Unit(_)) => true,
147            (Self::Blob { max_len }, ScalarLiteral::Blob(value)) => {
148                max_len.is_none_or(|max| value.len() <= max as usize)
149            }
150            (Self::Text { max_len }, ScalarLiteral::Text(value)) => {
151                max_len.is_none_or(|max| value.chars().count() <= max as usize)
152            }
153            (Self::Int8, ScalarLiteral::Int(value)) => i8::try_from(*value).is_ok(),
154            (Self::Int16, ScalarLiteral::Int(value)) => i16::try_from(*value).is_ok(),
155            (Self::Int32, ScalarLiteral::Int(value)) => i32::try_from(*value).is_ok(),
156            (Self::Int64, ScalarLiteral::Int(value)) => i64::try_from(*value).is_ok(),
157            (Self::Nat8, ScalarLiteral::Nat(value)) => u8::try_from(*value).is_ok(),
158            (Self::Nat16, ScalarLiteral::Nat(value)) => u16::try_from(*value).is_ok(),
159            (Self::Nat32, ScalarLiteral::Nat(value)) => u32::try_from(*value).is_ok(),
160            (Self::Nat64, ScalarLiteral::Nat(value)) => u64::try_from(*value).is_ok(),
161            (Self::Decimal { scale }, ScalarLiteral::Decimal(value)) => {
162                decimal_fits_scale(*value, scale)
163            }
164            _ => false,
165        }
166    }
167}
168
169impl FieldType {
170    /// Append named-type references through inline list layers.
171    ///
172    /// This preserves the caller's existing entries and does not resolve the
173    /// referenced definition or compute its transitive dependency closure.
174    pub fn append_named_type_dependencies(&self, pending: &mut Vec<TypeSourceKey>) {
175        let mut field_type = self;
176        while let Self::List(item) = field_type {
177            field_type = item;
178        }
179        if let Self::Named(source) = field_type {
180            pending.push(source.clone());
181        }
182    }
183
184    pub(crate) const fn validate(&self) -> Result<(), SchemaContractError> {
185        self.validate_at_depth(0)
186    }
187
188    const fn validate_at_depth(&self, depth: usize) -> Result<(), SchemaContractError> {
189        let Some(depth) = depth.checked_add(1) else {
190            return Err(SchemaContractError::FieldTypeDepthExceeded);
191        };
192        if depth > MAX_SCHEMA_FIELD_TYPE_DEPTH {
193            return Err(SchemaContractError::FieldTypeDepthExceeded);
194        }
195        match self {
196            Self::Scalar(scalar) => scalar.validate(),
197            Self::List(item) => item.validate_at_depth(depth),
198            Self::Named(_) => Ok(()),
199        }
200    }
201}
202
203/// Insert policy authored for one field.
204#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
205pub enum FieldInsertPolicy {
206    /// Omission rejects.
207    Required,
208    /// Omission resolves to explicit null.
209    Nullable,
210    /// Omission or explicit database `DEFAULT` resolves to this constant.
211    Default(ScalarLiteral),
212    /// IcyDB generates the value.
213    Generated,
214}
215
216/// Accepted database-owned lifecycle policy.
217#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
218pub enum FieldManagementPolicy {
219    /// Set once on insert and preserve thereafter.
220    CreatedAt,
221    /// Set on insert and on each logical row change.
222    UpdatedAt,
223}
224
225/// One field definition keyed by its current source name.
226#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
227pub struct FieldFragment {
228    #[serde(
229        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
230    )]
231    source_key: FieldSourceKey,
232    #[serde(
233        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
234    )]
235    name: SchemaName,
236    field_type: FieldType,
237    nullable: bool,
238    insert_policy: FieldInsertPolicy,
239    management: Option<FieldManagementPolicy>,
240}
241
242impl FieldFragment {
243    /// Construct one field definition.
244    #[must_use]
245    pub fn new(
246        name: SchemaName,
247        field_type: FieldType,
248        nullable: bool,
249        insert_policy: FieldInsertPolicy,
250        management: Option<FieldManagementPolicy>,
251    ) -> Self {
252        Self {
253            source_key: FieldSourceKey::from_name(&name),
254            name,
255            field_type,
256            nullable,
257            insert_policy,
258            management,
259        }
260    }
261
262    /// Borrow the typed proposal key derived from the current field name.
263    #[must_use]
264    pub const fn source_key(&self) -> &FieldSourceKey {
265        &self.source_key
266    }
267
268    /// Borrow the current field name.
269    #[must_use]
270    pub const fn name(&self) -> &SchemaName {
271        &self.name
272    }
273
274    /// Borrow the logical field type.
275    #[must_use]
276    pub const fn field_type(&self) -> &FieldType {
277        &self.field_type
278    }
279
280    /// Return whether the field admits authored null.
281    #[must_use]
282    pub const fn nullable(&self) -> bool {
283        self.nullable
284    }
285
286    /// Borrow the future-write insert policy.
287    #[must_use]
288    pub const fn insert_policy(&self) -> &FieldInsertPolicy {
289        &self.insert_policy
290    }
291
292    /// Return the optional accepted management policy.
293    #[must_use]
294    pub const fn management(&self) -> Option<FieldManagementPolicy> {
295        self.management
296    }
297
298    pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
299        ensure_current_name_key(self.source_key.as_str(), &self.name)?;
300        self.field_type.validate()?;
301        if let FieldInsertPolicy::Default(literal) = &self.insert_policy {
302            literal.validate()?;
303            match &self.field_type {
304                FieldType::Scalar(scalar) if scalar.accepts_literal(literal) => {}
305                FieldType::Named(_) if matches!(literal, ScalarLiteral::EnumUnit { .. }) => {}
306                FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
307                    return Err(SchemaContractError::LiteralTypeMismatch);
308                }
309            }
310        }
311        if matches!(self.insert_policy, FieldInsertPolicy::Nullable) && !self.nullable {
312            return Err(SchemaContractError::InvalidFieldPolicy);
313        }
314        if self.management.is_some()
315            && (!matches!(self.field_type, FieldType::Scalar(ScalarType::Timestamp))
316                || self.nullable
317                || !matches!(self.insert_policy, FieldInsertPolicy::Required))
318        {
319            return Err(SchemaContractError::InvalidFieldPolicy);
320        }
321        Ok(())
322    }
323}
324
325/// One ordered index key component.
326#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
327pub enum IndexKeyFragment {
328    /// Direct field key.
329    Field(
330        #[serde(
331            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
332        )]
333        FieldSourceKey,
334    ),
335    /// Lower-cased text expression.
336    Lower(
337        #[serde(
338            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
339        )]
340        FieldSourceKey,
341    ),
342    /// Upper-cased text expression.
343    Upper(
344        #[serde(
345            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
346        )]
347        FieldSourceKey,
348    ),
349    /// Trimmed text expression.
350    Trim(
351        #[serde(
352            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
353        )]
354        FieldSourceKey,
355    ),
356    /// Lower-cased and trimmed text expression.
357    LowerTrim(
358        #[serde(
359            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
360        )]
361        FieldSourceKey,
362    ),
363    /// Date extraction expression.
364    Date(
365        #[serde(
366            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
367        )]
368        FieldSourceKey,
369    ),
370    /// Year extraction expression.
371    Year(
372        #[serde(
373            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
374        )]
375        FieldSourceKey,
376    ),
377    /// Month extraction expression.
378    Month(
379        #[serde(
380            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
381        )]
382        FieldSourceKey,
383    ),
384    /// Day extraction expression.
385    Day(
386        #[serde(
387            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
388        )]
389        FieldSourceKey,
390    ),
391}
392
393impl IndexKeyFragment {
394    /// Borrow the field source key consumed by this component.
395    #[must_use]
396    pub const fn field(&self) -> &FieldSourceKey {
397        match self {
398            Self::Field(field)
399            | Self::Lower(field)
400            | Self::Upper(field)
401            | Self::Trim(field)
402            | Self::LowerTrim(field)
403            | Self::Date(field)
404            | Self::Year(field)
405            | Self::Month(field)
406            | Self::Day(field) => field,
407        }
408    }
409}
410
411/// One secondary-index proposal definition.
412#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
413pub struct IndexFragment {
414    #[serde(
415        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
416    )]
417    source_key: IndexSourceKey,
418    #[serde(
419        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
420    )]
421    name: SchemaName,
422    #[serde(
423        deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_INDEX_FIELDS }>"
424    )]
425    key: Vec<IndexKeyFragment>,
426    unique: bool,
427    predicate: Option<SourceCheckExpr>,
428}
429
430impl IndexFragment {
431    /// Construct one bounded index definition.
432    ///
433    /// # Errors
434    ///
435    /// Returns a typed contract error for an engine-reserved name or an empty key.
436    pub fn try_new(
437        name: SchemaName,
438        key: Vec<IndexKeyFragment>,
439        unique: bool,
440        predicate: Option<SourceCheckExpr>,
441    ) -> Result<Self, SchemaContractError> {
442        if crate::is_engine_reserved_schema_name(name.as_str()) {
443            return Err(SchemaContractError::InvalidSourceKey);
444        }
445        if key.is_empty() {
446            return Err(SchemaContractError::InvalidReferenceList);
447        }
448        if let Some(predicate) = &predicate {
449            predicate.validate()?;
450        }
451        Ok(Self {
452            source_key: IndexSourceKey::from_name(&name),
453            name,
454            key,
455            unique,
456            predicate,
457        })
458    }
459
460    /// Borrow the typed proposal key derived from the current index name.
461    #[must_use]
462    pub const fn source_key(&self) -> &IndexSourceKey {
463        &self.source_key
464    }
465
466    /// Borrow the current index name.
467    #[must_use]
468    pub const fn name(&self) -> &SchemaName {
469        &self.name
470    }
471
472    /// Borrow ordered index key components.
473    #[must_use]
474    pub fn key(&self) -> &[IndexKeyFragment] {
475        &self.key
476    }
477
478    /// Return whether the index enforces uniqueness.
479    #[must_use]
480    pub const fn unique(&self) -> bool {
481        self.unique
482    }
483
484    /// Borrow the optional source predicate.
485    #[must_use]
486    pub const fn predicate(&self) -> Option<&SourceCheckExpr> {
487        self.predicate.as_ref()
488    }
489
490    fn validate(&self) -> Result<(), SchemaContractError> {
491        let rebuilt = Self::try_new(
492            self.name.clone(),
493            self.key.clone(),
494            self.unique,
495            self.predicate.clone(),
496        )?;
497        ensure_canonical_rebuild(self, &rebuilt)
498    }
499}
500
501/// Maintained referential action in the current proposal contract.
502#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
503pub enum RelationDeleteAction {
504    /// Reject deletion while a source row refers to the target.
505    Restrict,
506}
507
508/// One deterministic step below an entity-root relation field.
509#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
510pub enum RelationPathStepFragment {
511    /// Enter one accepted named record, enum, or transparent newtype.
512    EnterNamed { r#type: TypeSourceKey },
513    /// Continue through a present optional value; `NULL` means no target.
514    OptionalSome,
515    /// Select one accepted member of an accepted record.
516    RecordMember {
517        /// Record declaration containing the member.
518        #[serde(
519            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
520        )]
521        record: TypeSourceKey,
522        /// Stable proposal key for the member.
523        #[serde(
524            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
525        )]
526        field: FieldSourceKey,
527    },
528    /// Select one payload-bearing accepted enum variant.
529    EnumVariantPayload {
530        /// Enum declaration containing the variant.
531        r#enum: TypeSourceKey,
532        /// Stable proposal key for the variant.
533        #[serde(
534            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
535        )]
536        variant: TypeSourceKey,
537    },
538    /// Traverse every item of an accepted ordered collection.
539    ListItems,
540    /// Traverse every item of an accepted unique collection.
541    SetItems,
542    /// Traverse every value of an accepted map in canonical key order.
543    MapValues,
544}
545
546/// Current source program for one relation edge.
547#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
548pub enum RelationSourceFragment {
549    /// Ordered entity-root fields for the direct relation fast path.
550    Direct {
551        #[serde(
552            deserialize_with = "crate::codec::deserialize_keys::<_, _, { crate::MAX_FRAGMENT_FIELDS }>"
553        )]
554        fields: Vec<FieldSourceKey>,
555    },
556    /// One deterministic path from an entity-root field to scalar targets.
557    Nested {
558        /// Entity-root field that owns the finite nested value.
559        #[serde(
560            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
561        )]
562        root: FieldSourceKey,
563        /// Complete ordered traversal program below `root`.
564        #[serde(
565            deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_RELATION_PATH_STEPS }>"
566        )]
567        steps: Vec<RelationPathStepFragment>,
568    },
569}
570
571impl RelationSourceFragment {
572    /// Construct a direct relation source from ordered root fields.
573    #[must_use]
574    pub const fn direct(fields: Vec<FieldSourceKey>) -> Self {
575        Self::Direct { fields }
576    }
577
578    /// Borrow every entity-root field owned by this relation source.
579    pub fn root_fields(&self) -> impl Iterator<Item = &FieldSourceKey> {
580        let direct = match self {
581            Self::Direct { fields } => fields.as_slice(),
582            Self::Nested { .. } => &[],
583        };
584        let nested = match self {
585            Self::Nested { root, .. } => Some(root),
586            Self::Direct { .. } => None,
587        };
588        direct.iter().chain(nested)
589    }
590
591    fn validate(&self, target_field_count: usize) -> Result<(), SchemaContractError> {
592        match self {
593            Self::Direct { fields } => {
594                if fields.is_empty() || fields.len() != target_field_count {
595                    return Err(SchemaContractError::InvalidReferenceList);
596                }
597                ensure_unique(fields)
598            }
599            Self::Nested { steps, .. } => {
600                if steps.is_empty()
601                    || steps.len() > MAX_RELATION_PATH_STEPS
602                    || target_field_count != 1
603                {
604                    return Err(SchemaContractError::InvalidReferenceList);
605                }
606                Ok(())
607            }
608        }
609    }
610}
611
612/// One source-owned relation definition.
613#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
614pub struct RelationFragment {
615    #[serde(
616        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
617    )]
618    source_key: RelationSourceKey,
619    #[serde(
620        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
621    )]
622    name: SchemaName,
623    source: RelationSourceFragment,
624    #[serde(
625        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
626    )]
627    target_entity: EntitySourceKey,
628    #[serde(
629        deserialize_with = "crate::codec::deserialize_keys::<_, _, { crate::MAX_FRAGMENT_FIELDS }>"
630    )]
631    target_fields: Vec<FieldSourceKey>,
632    on_delete: RelationDeleteAction,
633}
634
635impl RelationFragment {
636    /// Construct one relation with one bounded source program and ordered target components.
637    ///
638    /// # Errors
639    ///
640    /// Returns a typed reference-list error for empty or arity-mismatched
641    /// components.
642    pub fn try_new(
643        name: SchemaName,
644        source: RelationSourceFragment,
645        target_entity: EntitySourceKey,
646        target_fields: Vec<FieldSourceKey>,
647        on_delete: RelationDeleteAction,
648    ) -> Result<Self, SchemaContractError> {
649        if target_fields.is_empty() {
650            return Err(SchemaContractError::InvalidReferenceList);
651        }
652        source.validate(target_fields.len())?;
653        ensure_unique(&target_fields)?;
654        Ok(Self {
655            source_key: RelationSourceKey::from_name(&name),
656            name,
657            source,
658            target_entity,
659            target_fields,
660            on_delete,
661        })
662    }
663
664    /// Borrow the typed proposal key derived from the current relation name.
665    #[must_use]
666    pub const fn source_key(&self) -> &RelationSourceKey {
667        &self.source_key
668    }
669
670    /// Borrow the current relation name.
671    #[must_use]
672    pub const fn name(&self) -> &SchemaName {
673        &self.name
674    }
675
676    /// Borrow the complete current relation source program.
677    #[must_use]
678    pub const fn source(&self) -> &RelationSourceFragment {
679        &self.source
680    }
681
682    /// Borrow the target entity source key.
683    #[must_use]
684    pub const fn target_entity(&self) -> &EntitySourceKey {
685        &self.target_entity
686    }
687
688    /// Borrow ordered target fields.
689    #[must_use]
690    pub fn target_fields(&self) -> &[FieldSourceKey] {
691        &self.target_fields
692    }
693
694    /// Return the maintained delete action.
695    #[must_use]
696    pub const fn on_delete(&self) -> RelationDeleteAction {
697        self.on_delete
698    }
699
700    fn validate(&self) -> Result<(), SchemaContractError> {
701        let rebuilt = Self::try_new(
702            self.name.clone(),
703            self.source.clone(),
704            self.target_entity.clone(),
705            self.target_fields.clone(),
706            self.on_delete,
707        )?;
708        ensure_canonical_rebuild(self, &rebuilt)
709    }
710}
711
712/// One source constraint kind.
713#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
714pub enum ConstraintFragmentKind {
715    /// General row check over top-level source fields.
716    Check(SourceCheckExpr),
717    /// Closed durable operation over one nominal target below a persisted root.
718    TargetedRule(TargetedRuleFragment),
719}
720
721impl ConstraintFragmentKind {
722    fn validate(&self) -> Result<(), SchemaContractError> {
723        match self {
724            Self::Check(expression) => expression.validate(),
725            Self::TargetedRule(rule) => rule.validate(),
726        }
727    }
728}
729
730/// One source-bound nominal durable-rule target.
731#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
732pub struct TargetedRuleFragment {
733    #[serde(
734        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
735    )]
736    root: FieldSourceKey,
737    #[serde(
738        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
739    )]
740    target_type: TypeSourceKey,
741    #[serde(
742        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
743    )]
744    rule: RuleSourceKey,
745    operation: SourceRuleOperation,
746}
747
748impl TargetedRuleFragment {
749    /// Construct one targeted rule below a persisted root field.
750    #[must_use]
751    pub fn new(
752        root: FieldSourceKey,
753        target_type: TypeSourceKey,
754        rule: SchemaName,
755        operation: SourceRuleOperation,
756    ) -> Self {
757        Self {
758            root,
759            target_type,
760            rule: RuleSourceKey::from_name(&rule),
761            operation,
762        }
763    }
764
765    /// Borrow the persisted root-field identity.
766    #[must_use]
767    pub const fn root(&self) -> &FieldSourceKey {
768        &self.root
769    }
770
771    /// Borrow the ruled nominal type identity.
772    #[must_use]
773    pub const fn target_type(&self) -> &TypeSourceKey {
774        &self.target_type
775    }
776
777    /// Borrow the current durable-rule name as a typed proposal key.
778    #[must_use]
779    pub const fn rule(&self) -> &RuleSourceKey {
780        &self.rule
781    }
782
783    /// Borrow the closed durable operation.
784    #[must_use]
785    pub const fn operation(&self) -> &SourceRuleOperation {
786        &self.operation
787    }
788
789    fn validate(&self) -> Result<(), SchemaContractError> {
790        self.operation.validate()
791    }
792}
793
794/// One source constraint declaration.
795#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
796pub struct ConstraintFragment {
797    #[serde(
798        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
799    )]
800    source_key: ConstraintSourceKey,
801    #[serde(
802        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
803    )]
804    name: SchemaName,
805    kind: ConstraintFragmentKind,
806}
807
808impl ConstraintFragment {
809    /// Construct one general source check.
810    #[must_use]
811    pub fn check(name: SchemaName, expression: SourceCheckExpr) -> Self {
812        Self {
813            source_key: ConstraintSourceKey::from_name(&name),
814            name,
815            kind: ConstraintFragmentKind::Check(expression),
816        }
817    }
818
819    /// Construct one source-bound targeted durable rule.
820    #[must_use]
821    pub fn targeted_rule(rule: TargetedRuleFragment) -> Self {
822        let source_key = ConstraintSourceKey::for_targeted_field_rule(
823            rule.root(),
824            rule.target_type(),
825            rule.rule(),
826        );
827        let name = SchemaName::for_targeted_rule(&source_key);
828        Self {
829            source_key,
830            name,
831            kind: ConstraintFragmentKind::TargetedRule(rule),
832        }
833    }
834
835    /// Borrow the typed proposal key derived from the current declaration.
836    #[must_use]
837    pub const fn source_key(&self) -> &ConstraintSourceKey {
838        &self.source_key
839    }
840
841    /// Borrow the current constraint name.
842    #[must_use]
843    pub const fn name(&self) -> &SchemaName {
844        &self.name
845    }
846
847    /// Borrow the exact source constraint kind.
848    #[must_use]
849    pub const fn kind(&self) -> &ConstraintFragmentKind {
850        &self.kind
851    }
852
853    fn validate(&self) -> Result<(), SchemaContractError> {
854        self.kind.validate()?;
855        let rebuilt = match &self.kind {
856            ConstraintFragmentKind::Check(expression) => {
857                if crate::is_engine_reserved_schema_name(self.name.as_str()) {
858                    return Err(SchemaContractError::InvalidSourceKey);
859                }
860                Self::check(self.name.clone(), expression.clone())
861            }
862            ConstraintFragmentKind::TargetedRule(rule) => Self::targeted_rule(rule.clone()),
863        };
864        ensure_canonical_rebuild(self, &rebuilt)
865    }
866}
867
868/// Store-free logical entity definition.
869#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
870pub struct EntityFragment {
871    #[serde(
872        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
873    )]
874    source_key: EntitySourceKey,
875    #[serde(
876        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
877    )]
878    name: SchemaName,
879    version: DeclaredEntityVersion,
880    #[serde(
881        deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_FRAGMENT_FIELDS }>"
882    )]
883    fields: Vec<FieldFragment>,
884    #[serde(
885        deserialize_with = "crate::codec::deserialize_keys::<_, _, { crate::MAX_FRAGMENT_FIELDS }>"
886    )]
887    primary_key: Vec<FieldSourceKey>,
888    #[serde(
889        deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_FRAGMENT_INDEXES }>"
890    )]
891    indexes: Vec<IndexFragment>,
892    #[serde(
893        deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_FRAGMENT_RELATIONS }>"
894    )]
895    relations: Vec<RelationFragment>,
896    #[serde(
897        deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_FRAGMENT_CONSTRAINTS }>"
898    )]
899    constraints: Vec<ConstraintFragment>,
900}
901
902impl EntityFragment {
903    /// Construct and canonicalize one entity definition.
904    ///
905    /// # Errors
906    ///
907    /// Returns a typed contract error for collection overflow, duplicate
908    /// source keys, malformed policy, expression, or primary-key references.
909    /// Primary keys require non-null scalar key fields; Unit is valid only as
910    /// the sole primary-key field.
911    pub fn try_new(
912        name: SchemaName,
913        version: DeclaredEntityVersion,
914        mut fields: Vec<FieldFragment>,
915        primary_key: Vec<FieldSourceKey>,
916        mut indexes: Vec<IndexFragment>,
917        mut relations: Vec<RelationFragment>,
918        mut constraints: Vec<ConstraintFragment>,
919    ) -> Result<Self, SchemaContractError> {
920        let source_key = EntitySourceKey::from_name(&name);
921        check_len("entity fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
922        check_len("entity indexes", indexes.len(), MAX_FRAGMENT_INDEXES)?;
923        check_len("entity relations", relations.len(), MAX_FRAGMENT_RELATIONS)?;
924        check_len(
925            "entity constraints",
926            constraints.len(),
927            MAX_FRAGMENT_CONSTRAINTS,
928        )?;
929        if primary_key.is_empty() {
930            return Err(SchemaContractError::InvalidReferenceList);
931        }
932        ensure_unique(&primary_key)?;
933        // Equal source keys are rejected below, so stable tie ordering cannot
934        // be observed and need not retain stable-sort machinery in Wasm.
935        crate::compact_sort_unstable_by(&mut fields, |a, b| a.source_key.cmp(&b.source_key));
936        crate::compact_sort_unstable_by(&mut indexes, |a, b| a.source_key.cmp(&b.source_key));
937        crate::compact_sort_unstable_by(&mut relations, |a, b| a.source_key.cmp(&b.source_key));
938        crate::compact_sort_unstable_by(&mut constraints, |a, b| a.source_key.cmp(&b.source_key));
939        ensure_unique_sorted_by(&fields, FieldFragment::source_key)?;
940        ensure_unique_sorted_by(&indexes, IndexFragment::source_key)?;
941        ensure_unique_sorted_by(&relations, RelationFragment::source_key)?;
942        ensure_unique_sorted_by(&constraints, ConstraintFragment::source_key)?;
943        ensure_unique_names(fields.iter().map(FieldFragment::name))?;
944        ensure_unique_names(indexes.iter().map(IndexFragment::name))?;
945        ensure_unique_names(relations.iter().map(RelationFragment::name))?;
946        ensure_unique_names(constraints.iter().map(ConstraintFragment::name))?;
947        for field in &fields {
948            field.validate()?;
949        }
950        validate_management_cardinality(&fields)?;
951        for index in &indexes {
952            index.validate()?;
953        }
954        for relation in &relations {
955            relation.validate()?;
956        }
957        for constraint in &constraints {
958            constraint.validate()?;
959        }
960        let field_keys = fields
961            .iter()
962            .map(|field| field.source_key.clone())
963            .collect::<BTreeSet<_>>();
964        if primary_key.iter().any(|field| !field_keys.contains(field)) {
965            return Err(SchemaContractError::InvalidLocalReference);
966        }
967        validate_insert_generation(&fields, &primary_key)?;
968        validate_primary_key_field_shapes(&fields, &primary_key)?;
969        for index in &indexes {
970            if index
971                .key()
972                .iter()
973                .any(|component| !field_keys.contains(component.field()))
974                || index.predicate().is_some_and(|predicate| {
975                    predicate
976                        .dependencies()
977                        .iter()
978                        .any(|field| !field_keys.contains(field))
979                })
980            {
981                return Err(SchemaContractError::InvalidLocalReference);
982            }
983        }
984        for relation in &relations {
985            if relation
986                .source()
987                .root_fields()
988                .any(|field| !field_keys.contains(field))
989                || (relation.target_entity() == &source_key
990                    && relation
991                        .target_fields()
992                        .iter()
993                        .any(|field| !field_keys.contains(field)))
994            {
995                return Err(SchemaContractError::InvalidLocalReference);
996            }
997        }
998        for constraint in &constraints {
999            let invalid = match constraint.kind() {
1000                ConstraintFragmentKind::Check(expression) => expression
1001                    .dependencies()
1002                    .iter()
1003                    .any(|field| !field_keys.contains(field)),
1004                ConstraintFragmentKind::TargetedRule(rule) => !field_keys.contains(rule.root()),
1005            };
1006            if invalid {
1007                return Err(SchemaContractError::InvalidLocalReference);
1008            }
1009        }
1010        Ok(Self {
1011            source_key,
1012            name,
1013            version,
1014            fields,
1015            primary_key,
1016            indexes,
1017            relations,
1018            constraints,
1019        })
1020    }
1021
1022    /// Borrow the typed proposal key derived from the current entity name.
1023    #[must_use]
1024    pub const fn source_key(&self) -> &EntitySourceKey {
1025        &self.source_key
1026    }
1027
1028    /// Borrow the current entity name.
1029    #[must_use]
1030    pub const fn name(&self) -> &SchemaName {
1031        &self.name
1032    }
1033
1034    /// Return the application-declared current entity version.
1035    #[must_use]
1036    pub const fn version(&self) -> DeclaredEntityVersion {
1037        self.version
1038    }
1039
1040    /// Borrow canonical field definitions.
1041    #[must_use]
1042    pub fn fields(&self) -> &[FieldFragment] {
1043        &self.fields
1044    }
1045
1046    /// Borrow ordered primary-key fields.
1047    #[must_use]
1048    pub fn primary_key(&self) -> &[FieldSourceKey] {
1049        &self.primary_key
1050    }
1051
1052    /// Borrow canonical secondary-index definitions.
1053    #[must_use]
1054    pub fn indexes(&self) -> &[IndexFragment] {
1055        &self.indexes
1056    }
1057
1058    /// Borrow canonical relation definitions.
1059    #[must_use]
1060    pub fn relations(&self) -> &[RelationFragment] {
1061        &self.relations
1062    }
1063
1064    /// Borrow canonical source constraint definitions.
1065    #[must_use]
1066    pub fn constraints(&self) -> &[ConstraintFragment] {
1067        &self.constraints
1068    }
1069
1070    pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1071        let rebuilt = Self::try_new(
1072            self.name.clone(),
1073            self.version,
1074            self.fields.clone(),
1075            self.primary_key.clone(),
1076            self.indexes.clone(),
1077            self.relations.clone(),
1078            self.constraints.clone(),
1079        )?;
1080        ensure_canonical_rebuild(self, &rebuilt)
1081    }
1082}
1083
1084// Share intrinsic key capabilities with generated model admission. A named
1085// type or collection cannot substitute for an exact scalar key.
1086fn validate_primary_key_field_shapes(
1087    fields: &[FieldFragment],
1088    primary_key: &[FieldSourceKey],
1089) -> Result<(), SchemaContractError> {
1090    if fields.iter().any(|field| {
1091        primary_key.contains(&field.source_key)
1092            && (field.nullable
1093                || !matches!(field.field_type, FieldType::Scalar(scalar)
1094                    if scalar.kind().is_primary_key_component_encodable()
1095                        && (primary_key.len() == 1 || scalar != ScalarType::Unit)))
1096    }) {
1097        return Err(SchemaContractError::InvalidFieldPolicy);
1098    }
1099    Ok(())
1100}
1101
1102// Keep the public proposal contract exact even when fragments are constructed
1103// without the source macro. Numeric generation is unsigned identity
1104// generation and therefore belongs only to one non-null scalar primary key.
1105fn validate_insert_generation(
1106    fields: &[FieldFragment],
1107    primary_key: &[FieldSourceKey],
1108) -> Result<(), SchemaContractError> {
1109    for field in fields {
1110        if !matches!(field.insert_policy(), FieldInsertPolicy::Generated) {
1111            continue;
1112        }
1113        if field.nullable() || field.management().is_some() {
1114            return Err(SchemaContractError::InvalidFieldPolicy);
1115        }
1116        match field.field_type() {
1117            FieldType::Scalar(ScalarType::Ulid | ScalarType::Timestamp) => {}
1118            FieldType::Scalar(
1119                ScalarType::Nat8
1120                | ScalarType::Nat16
1121                | ScalarType::Nat32
1122                | ScalarType::Nat64
1123                | ScalarType::Nat128,
1124            ) if primary_key.len() == 1 && primary_key.first() == Some(field.source_key()) => {}
1125            FieldType::Scalar(_) | FieldType::List(_) | FieldType::Named(_) => {
1126                return Err(SchemaContractError::InvalidFieldPolicy);
1127            }
1128        }
1129    }
1130    Ok(())
1131}
1132
1133/// One structural field in a named record type.
1134///
1135/// Composite fields carry exact type and nullability facts only. Insert,
1136/// generation, and management policies belong to persisted entity fields.
1137#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1138pub struct RecordFieldFragment {
1139    #[serde(
1140        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
1141    )]
1142    source_key: FieldSourceKey,
1143    #[serde(
1144        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
1145    )]
1146    name: SchemaName,
1147    field_type: FieldType,
1148    nullable: bool,
1149}
1150
1151impl RecordFieldFragment {
1152    /// Construct one exact structural record field.
1153    #[must_use]
1154    pub fn new(name: SchemaName, field_type: FieldType, nullable: bool) -> Self {
1155        Self {
1156            source_key: FieldSourceKey::from_name(&name),
1157            name,
1158            field_type,
1159            nullable,
1160        }
1161    }
1162
1163    /// Borrow the typed proposal key derived from the current field name.
1164    #[must_use]
1165    pub const fn source_key(&self) -> &FieldSourceKey {
1166        &self.source_key
1167    }
1168
1169    /// Borrow the current record-field name.
1170    #[must_use]
1171    pub const fn name(&self) -> &SchemaName {
1172        &self.name
1173    }
1174
1175    /// Borrow the exact logical field type.
1176    #[must_use]
1177    pub const fn field_type(&self) -> &FieldType {
1178        &self.field_type
1179    }
1180
1181    /// Return whether the structural field admits null.
1182    #[must_use]
1183    pub const fn nullable(&self) -> bool {
1184        self.nullable
1185    }
1186
1187    fn validate(&self) -> Result<(), SchemaContractError> {
1188        if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1189            return Err(SchemaContractError::NonCanonical);
1190        }
1191        self.field_type.validate()
1192    }
1193}
1194
1195/// One positional tuple member and its explicit-null policy.
1196
1197#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1198pub struct TupleElementFragment {
1199    field_type: FieldType,
1200    nullable: bool,
1201}
1202
1203impl TupleElementFragment {
1204    /// Construct one exact tuple-member contract.
1205    #[must_use]
1206    pub const fn new(field_type: FieldType, nullable: bool) -> Self {
1207        Self {
1208            field_type,
1209            nullable,
1210        }
1211    }
1212
1213    /// Borrow the exact logical member type.
1214    #[must_use]
1215    pub const fn field_type(&self) -> &FieldType {
1216        &self.field_type
1217    }
1218
1219    /// Return whether this tuple member admits explicit null.
1220    #[must_use]
1221    pub const fn nullable(&self) -> bool {
1222        self.nullable
1223    }
1224
1225    const fn validate(&self) -> Result<(), SchemaContractError> {
1226        self.field_type.validate()
1227    }
1228}
1229
1230/// Named record-type definition.
1231#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1232pub struct RecordTypeFragment {
1233    #[serde(
1234        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
1235    )]
1236    source_key: TypeSourceKey,
1237    #[serde(
1238        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
1239    )]
1240    name: SchemaName,
1241    #[serde(
1242        deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_FRAGMENT_FIELDS }>"
1243    )]
1244    fields: Vec<RecordFieldFragment>,
1245}
1246
1247impl RecordTypeFragment {
1248    /// Construct and canonicalize a record definition.
1249    ///
1250    /// # Errors
1251    ///
1252    /// Returns a typed contract error for overflow, duplicates, or malformed
1253    /// fields.
1254    pub fn try_new(
1255        name: SchemaName,
1256        mut fields: Vec<RecordFieldFragment>,
1257    ) -> Result<Self, SchemaContractError> {
1258        check_len("record fields", fields.len(), MAX_FRAGMENT_FIELDS)?;
1259        crate::compact_sort_unstable_by(&mut fields, |left, right| {
1260            left.source_key.cmp(&right.source_key)
1261        });
1262        ensure_unique_sorted_by(&fields, RecordFieldFragment::source_key)?;
1263        ensure_unique_names(fields.iter().map(RecordFieldFragment::name))?;
1264        for field in &fields {
1265            field.validate()?;
1266        }
1267        Ok(Self {
1268            source_key: TypeSourceKey::from_name(&name),
1269            name,
1270            fields,
1271        })
1272    }
1273
1274    /// Borrow the typed proposal key derived from the current record name.
1275    #[must_use]
1276    pub const fn source_key(&self) -> &TypeSourceKey {
1277        &self.source_key
1278    }
1279
1280    /// Borrow the current record name.
1281    #[must_use]
1282    pub const fn name(&self) -> &SchemaName {
1283        &self.name
1284    }
1285
1286    /// Borrow canonical record fields.
1287    #[must_use]
1288    pub fn fields(&self) -> &[RecordFieldFragment] {
1289        &self.fields
1290    }
1291
1292    fn validate(&self) -> Result<(), SchemaContractError> {
1293        let rebuilt = Self::try_new(self.name.clone(), self.fields.clone())?;
1294        if rebuilt != *self {
1295            return Err(SchemaContractError::NonCanonical);
1296        }
1297        Ok(())
1298    }
1299}
1300
1301/// One named enum variant.
1302#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1303pub struct EnumVariantFragment {
1304    #[serde(
1305        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
1306    )]
1307    source_key: TypeSourceKey,
1308    #[serde(
1309        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
1310    )]
1311    name: SchemaName,
1312    payload: Option<FieldType>,
1313}
1314
1315impl EnumVariantFragment {
1316    /// Construct one unit variant.
1317    #[must_use]
1318    pub fn new(name: SchemaName) -> Self {
1319        Self {
1320            source_key: TypeSourceKey::from_name(&name),
1321            name,
1322            payload: None,
1323        }
1324    }
1325
1326    /// Construct one payload-bearing variant.
1327    #[must_use]
1328    pub fn with_payload(name: SchemaName, payload: FieldType) -> Self {
1329        Self {
1330            source_key: TypeSourceKey::from_name(&name),
1331            name,
1332            payload: Some(payload),
1333        }
1334    }
1335
1336    /// Borrow the typed proposal key derived from the current variant name.
1337    #[must_use]
1338    pub const fn source_key(&self) -> &TypeSourceKey {
1339        &self.source_key
1340    }
1341
1342    /// Borrow the current variant name.
1343    #[must_use]
1344    pub const fn name(&self) -> &SchemaName {
1345        &self.name
1346    }
1347
1348    /// Borrow the optional exact payload contract.
1349    #[must_use]
1350    pub const fn payload(&self) -> Option<&FieldType> {
1351        self.payload.as_ref()
1352    }
1353
1354    fn validate(&self) -> Result<(), SchemaContractError> {
1355        if !current_name_key_matches(self.source_key.as_str(), &self.name) {
1356            return Err(SchemaContractError::NonCanonical);
1357        }
1358        match &self.payload {
1359            Some(payload) => payload.validate(),
1360            None => Ok(()),
1361        }
1362    }
1363}
1364
1365/// Named enum-type definition.
1366#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1367pub struct EnumTypeFragment {
1368    #[serde(
1369        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
1370    )]
1371    source_key: TypeSourceKey,
1372    #[serde(
1373        deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
1374    )]
1375    name: SchemaName,
1376    #[serde(
1377        deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_FRAGMENT_FIELDS }>"
1378    )]
1379    variants: Vec<EnumVariantFragment>,
1380}
1381
1382impl EnumTypeFragment {
1383    /// Construct and canonicalize an enum definition.
1384    ///
1385    /// # Errors
1386    ///
1387    /// Returns a typed contract error for empty, oversized, or duplicate
1388    /// variants.
1389    pub fn try_new(
1390        name: SchemaName,
1391        mut variants: Vec<EnumVariantFragment>,
1392    ) -> Result<Self, SchemaContractError> {
1393        if variants.is_empty() {
1394            return Err(SchemaContractError::InvalidReferenceList);
1395        }
1396        check_len("enum variants", variants.len(), MAX_FRAGMENT_FIELDS)?;
1397        crate::compact_sort_unstable_by(&mut variants, |left, right| {
1398            left.source_key.cmp(&right.source_key)
1399        });
1400        ensure_unique_sorted_by(&variants, |variant| &variant.source_key)?;
1401        ensure_unique_names(variants.iter().map(EnumVariantFragment::name))?;
1402        for variant in &variants {
1403            variant.validate()?;
1404        }
1405        Ok(Self {
1406            source_key: TypeSourceKey::from_name(&name),
1407            name,
1408            variants,
1409        })
1410    }
1411
1412    /// Borrow the typed proposal key derived from the current enum name.
1413    #[must_use]
1414    pub const fn source_key(&self) -> &TypeSourceKey {
1415        &self.source_key
1416    }
1417
1418    /// Borrow the current enum name.
1419    #[must_use]
1420    pub const fn name(&self) -> &SchemaName {
1421        &self.name
1422    }
1423
1424    /// Borrow canonical enum variants.
1425    #[must_use]
1426    pub fn variants(&self) -> &[EnumVariantFragment] {
1427        &self.variants
1428    }
1429
1430    fn validate(&self) -> Result<(), SchemaContractError> {
1431        let rebuilt = Self::try_new(self.name.clone(), self.variants.clone())?;
1432        if rebuilt != *self {
1433            return Err(SchemaContractError::NonCanonical);
1434        }
1435        Ok(())
1436    }
1437}
1438
1439/// Named reusable type definition.
1440#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1441pub enum NamedTypeFragment {
1442    /// Record type.
1443    Record(RecordTypeFragment),
1444    /// Enum type.
1445    Enum(EnumTypeFragment),
1446    /// Transparent named wrapper.
1447    Newtype {
1448        /// Typed proposal key derived from the current name.
1449        #[serde(
1450            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
1451        )]
1452        source_key: TypeSourceKey,
1453        /// Current schema name.
1454        #[serde(
1455            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
1456        )]
1457        name: SchemaName,
1458        /// Wrapped logical type.
1459        inner: FieldType,
1460    },
1461    /// Homogeneous ordered collection.
1462    List {
1463        /// Typed proposal key derived from the current name.
1464        #[serde(
1465            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
1466        )]
1467        source_key: TypeSourceKey,
1468        /// Current schema name.
1469        #[serde(
1470            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
1471        )]
1472        name: SchemaName,
1473        /// Element type.
1474        item: FieldType,
1475    },
1476    /// Homogeneous unique collection.
1477    Set {
1478        /// Typed proposal key derived from the current name.
1479        #[serde(
1480            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
1481        )]
1482        source_key: TypeSourceKey,
1483        /// Current schema name.
1484        #[serde(
1485            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
1486        )]
1487        name: SchemaName,
1488        /// Element type.
1489        item: FieldType,
1490    },
1491    /// Homogeneous key/value collection.
1492    Map {
1493        /// Typed proposal key derived from the current name.
1494        #[serde(
1495            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
1496        )]
1497        source_key: TypeSourceKey,
1498        /// Current schema name.
1499        #[serde(
1500            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
1501        )]
1502        name: SchemaName,
1503        /// Key type.
1504        key: FieldType,
1505        /// Value type.
1506        value: FieldType,
1507    },
1508    /// Ordered heterogeneous product.
1509    Tuple {
1510        /// Typed proposal key derived from the current name.
1511        #[serde(
1512            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
1513        )]
1514        source_key: TypeSourceKey,
1515        /// Current schema name.
1516        #[serde(
1517            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SCHEMA_NAME_BYTES }>"
1518        )]
1519        name: SchemaName,
1520        /// Ordered member types.
1521        #[serde(
1522            deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_FRAGMENT_FIELDS }>"
1523        )]
1524        members: Vec<TupleElementFragment>,
1525    },
1526}
1527
1528impl NamedTypeFragment {
1529    /// Append direct named-type dependencies in fragment order.
1530    ///
1531    /// Inline list layers are traversed, but referenced definitions are not.
1532    /// Existing entries and duplicate references are preserved so callers own
1533    /// reachability, canonicalization and accepted-catalog validation.
1534    pub fn append_named_type_dependencies(&self, pending: &mut Vec<TypeSourceKey>) {
1535        match self {
1536            Self::Record(record) => {
1537                for field in record.fields() {
1538                    field.field_type().append_named_type_dependencies(pending);
1539                }
1540            }
1541            Self::Enum(r#enum) => {
1542                for variant in r#enum.variants() {
1543                    if let Some(payload) = variant.payload() {
1544                        payload.append_named_type_dependencies(pending);
1545                    }
1546                }
1547            }
1548            Self::Newtype { inner, .. }
1549            | Self::List { item: inner, .. }
1550            | Self::Set { item: inner, .. } => {
1551                inner.append_named_type_dependencies(pending);
1552            }
1553            Self::Map { key, value, .. } => {
1554                key.append_named_type_dependencies(pending);
1555                value.append_named_type_dependencies(pending);
1556            }
1557            Self::Tuple { members, .. } => {
1558                for member in members {
1559                    member.field_type().append_named_type_dependencies(pending);
1560                }
1561            }
1562        }
1563    }
1564
1565    /// Construct one transparent named wrapper from its current name.
1566    #[must_use]
1567    pub fn newtype(name: SchemaName, inner: FieldType) -> Self {
1568        Self::Newtype {
1569            source_key: TypeSourceKey::from_name(&name),
1570            name,
1571            inner,
1572        }
1573    }
1574
1575    /// Construct one named ordered collection from its current name.
1576    #[must_use]
1577    pub fn list(name: SchemaName, item: FieldType) -> Self {
1578        Self::List {
1579            source_key: TypeSourceKey::from_name(&name),
1580            name,
1581            item,
1582        }
1583    }
1584
1585    /// Construct one named unique collection from its current name.
1586    #[must_use]
1587    pub fn set(name: SchemaName, item: FieldType) -> Self {
1588        Self::Set {
1589            source_key: TypeSourceKey::from_name(&name),
1590            name,
1591            item,
1592        }
1593    }
1594
1595    /// Construct one named key/value collection from its current name.
1596    #[must_use]
1597    pub fn map(name: SchemaName, key: FieldType, value: FieldType) -> Self {
1598        Self::Map {
1599            source_key: TypeSourceKey::from_name(&name),
1600            name,
1601            key,
1602            value,
1603        }
1604    }
1605
1606    /// Construct one named heterogeneous product from its current name.
1607    #[must_use]
1608    pub fn tuple(name: SchemaName, members: Vec<TupleElementFragment>) -> Self {
1609        Self::Tuple {
1610            source_key: TypeSourceKey::from_name(&name),
1611            name,
1612            members,
1613        }
1614    }
1615
1616    /// Borrow the typed proposal key derived from the current type name.
1617    #[must_use]
1618    pub const fn source_key(&self) -> &TypeSourceKey {
1619        match self {
1620            Self::Record(record) => record.source_key(),
1621            Self::Enum(r#enum) => r#enum.source_key(),
1622            Self::Newtype { source_key, .. }
1623            | Self::List { source_key, .. }
1624            | Self::Set { source_key, .. }
1625            | Self::Map { source_key, .. }
1626            | Self::Tuple { source_key, .. } => source_key,
1627        }
1628    }
1629
1630    /// Borrow the current type name.
1631    #[must_use]
1632    pub const fn name(&self) -> &SchemaName {
1633        match self {
1634            Self::Record(record) => record.name(),
1635            Self::Enum(r#enum) => r#enum.name(),
1636            Self::Newtype { name, .. }
1637            | Self::List { name, .. }
1638            | Self::Set { name, .. }
1639            | Self::Map { name, .. }
1640            | Self::Tuple { name, .. } => name,
1641        }
1642    }
1643
1644    fn validate(&self) -> Result<(), SchemaContractError> {
1645        ensure_current_name_key(self.source_key().as_str(), self.name())?;
1646        match self {
1647            Self::Record(record) => record.validate(),
1648            Self::Enum(r#enum) => r#enum.validate(),
1649            Self::Newtype { inner, .. }
1650            | Self::List { item: inner, .. }
1651            | Self::Set { item: inner, .. } => inner.validate(),
1652            Self::Map { key, value, .. } => {
1653                key.validate()?;
1654                value.validate()
1655            }
1656            Self::Tuple { members, .. } => {
1657                if members.is_empty() {
1658                    return Err(SchemaContractError::InvalidReferenceList);
1659                }
1660                check_len("tuple members", members.len(), MAX_FRAGMENT_FIELDS)?;
1661                members.iter().try_for_each(TupleElementFragment::validate)
1662            }
1663        }
1664    }
1665}
1666
1667/// Reusable store-free collection of entity and type definitions.
1668#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1669pub struct SchemaFragment {
1670    #[serde(
1671        deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_FRAGMENT_ENTITIES }>"
1672    )]
1673    entities: Vec<EntityFragment>,
1674    #[serde(
1675        deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_FRAGMENT_TYPES }>"
1676    )]
1677    types: Vec<NamedTypeFragment>,
1678}
1679
1680impl SchemaFragment {
1681    /// Construct and canonicalize one reusable fragment.
1682    ///
1683    /// # Errors
1684    ///
1685    /// Returns a typed contract error for overflow, duplicate definitions, or
1686    /// malformed nested definitions.
1687    pub fn try_new(
1688        mut entities: Vec<EntityFragment>,
1689        mut types: Vec<NamedTypeFragment>,
1690    ) -> Result<Self, SchemaContractError> {
1691        check_len("fragment entities", entities.len(), MAX_FRAGMENT_ENTITIES)?;
1692        check_len("fragment types", types.len(), MAX_FRAGMENT_TYPES)?;
1693        crate::compact_sort_unstable_by(&mut entities, |left, right| {
1694            left.source_key.cmp(&right.source_key)
1695        });
1696        crate::compact_sort_unstable_by(&mut types, |left, right| {
1697            left.source_key().cmp(right.source_key())
1698        });
1699        ensure_unique_sorted_by(&entities, EntityFragment::source_key)?;
1700        ensure_unique_sorted_by(&types, NamedTypeFragment::source_key)?;
1701        ensure_unique_names(entities.iter().map(EntityFragment::name))?;
1702        ensure_unique_names(types.iter().map(NamedTypeFragment::name))?;
1703        for entity in &entities {
1704            entity.validate()?;
1705        }
1706        for r#type in &types {
1707            r#type.validate()?;
1708        }
1709        Ok(Self { entities, types })
1710    }
1711
1712    /// Borrow entity definitions.
1713    #[must_use]
1714    pub fn entities(&self) -> &[EntityFragment] {
1715        &self.entities
1716    }
1717
1718    /// Borrow named type definitions.
1719    #[must_use]
1720    pub fn types(&self) -> &[NamedTypeFragment] {
1721        &self.types
1722    }
1723
1724    pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
1725        for r#type in &self.types {
1726            r#type.validate()?;
1727        }
1728        let rebuilt = Self::try_new(self.entities.clone(), self.types.clone())?;
1729        if rebuilt != *self {
1730            return Err(SchemaContractError::NonCanonical);
1731        }
1732        Ok(())
1733    }
1734}
1735
1736pub(crate) const fn check_len(
1737    kind: &'static str,
1738    len: usize,
1739    max: usize,
1740) -> Result<(), SchemaContractError> {
1741    if len > max {
1742        return Err(SchemaContractError::TooManyItems { kind, len, max });
1743    }
1744    Ok(())
1745}
1746
1747fn current_name_key_matches(source_key: &str, name: &SchemaName) -> bool {
1748    source_key == name.as_str()
1749}
1750
1751fn ensure_current_name_key(source_key: &str, name: &SchemaName) -> Result<(), SchemaContractError> {
1752    if !current_name_key_matches(source_key, name) {
1753        return Err(SchemaContractError::NonCanonical);
1754    }
1755    Ok(())
1756}
1757
1758fn ensure_canonical_rebuild<T: PartialEq>(
1759    current: &T,
1760    rebuilt: &T,
1761) -> Result<(), SchemaContractError> {
1762    if current != rebuilt {
1763        return Err(SchemaContractError::NonCanonical);
1764    }
1765    Ok(())
1766}
1767
1768fn ensure_unique<T>(values: &[T]) -> Result<(), SchemaContractError>
1769where
1770    T: Ord,
1771{
1772    let mut seen = BTreeSet::new();
1773    if values.iter().any(|value| !seen.insert(value)) {
1774        return Err(SchemaContractError::InvalidReferenceList);
1775    }
1776    Ok(())
1777}
1778
1779fn ensure_unique_sorted_by<T, K>(
1780    values: &[T],
1781    key: impl Fn(&T) -> &K,
1782) -> Result<(), SchemaContractError>
1783where
1784    K: Eq,
1785{
1786    if values.windows(2).any(|pair| key(&pair[0]) == key(&pair[1])) {
1787        return Err(SchemaContractError::DuplicateSourceKey);
1788    }
1789    Ok(())
1790}
1791
1792fn ensure_unique_names<'a>(
1793    names: impl IntoIterator<Item = &'a SchemaName>,
1794) -> Result<(), SchemaContractError> {
1795    let mut seen = BTreeSet::new();
1796    if names.into_iter().any(|name| !seen.insert(name)) {
1797        return Err(SchemaContractError::DuplicateName);
1798    }
1799    Ok(())
1800}
1801
1802fn validate_management_cardinality(fields: &[FieldFragment]) -> Result<(), SchemaContractError> {
1803    for policy in [
1804        FieldManagementPolicy::CreatedAt,
1805        FieldManagementPolicy::UpdatedAt,
1806    ] {
1807        if fields
1808            .iter()
1809            .filter(|field| field.management() == Some(policy))
1810            .count()
1811            > 1
1812        {
1813            return Err(SchemaContractError::InvalidFieldPolicy);
1814        }
1815    }
1816    Ok(())
1817}
1818
1819fn decimal_fits_scale(value: Decimal, scale: u32) -> bool {
1820    match value.scale().cmp(&scale) {
1821        std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
1822        std::cmp::Ordering::Less => value.scale_to_integer(scale).is_some(),
1823    }
1824}
1825
1826#[cfg(test)]
1827mod tests {
1828    use super::{
1829        FieldFragment, FieldInsertPolicy, FieldSourceKey, FieldType, ScalarType,
1830        SchemaContractError, SchemaName,
1831    };
1832
1833    #[test]
1834    fn independently_decoded_field_key_and_name_must_match() {
1835        let field = FieldFragment {
1836            source_key: FieldSourceKey::try_new("legacy_name").expect("fixture key should admit"),
1837            name: SchemaName::try_new("current_name").expect("fixture name should admit"),
1838            field_type: FieldType::Scalar(ScalarType::Nat64),
1839            nullable: false,
1840            insert_policy: FieldInsertPolicy::Required,
1841            management: None,
1842        };
1843
1844        assert_eq!(field.validate(), Err(SchemaContractError::NonCanonical));
1845    }
1846}