Skip to main content

icydb_schema/
fragment.rs

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