Skip to main content

type_bridge/
__codegen.rs

1//! Generated-code SPI for TypeBridge.
2//!
3//! Internal primitives and traits required by generated schema crates.
4//! Hand-written trait implementations are not schema evidence.
5
6use core::fmt;
7use core::marker::PhantomData;
8
9pub use crate::schema::{Schema, SchemaPackage, Unbound, sealed};
10
11/// An owned, nestable validation path.
12#[derive(Clone, Debug, Eq, PartialEq, Default)]
13pub struct ValidationPath {
14    segments: Vec<String>,
15}
16
17impl ValidationPath {
18    /// Construct an empty validation path at the generated value root.
19    #[must_use]
20    pub fn root() -> Self {
21        Self {
22            segments: Vec::new(),
23        }
24    }
25
26    /// Return a path with one generated field segment appended.
27    #[must_use]
28    pub fn join(&self, segment: impl Into<String>) -> Self {
29        let mut s = self.segments.clone();
30        s.push(segment.into());
31        Self { segments: s }
32    }
33
34    /// Return a path with an index appended to its final segment.
35    #[must_use]
36    pub fn join_index(&self, index: usize) -> Self {
37        let mut s = self.segments.clone();
38        if let Some(last) = s.last_mut() {
39            last.push_str(&format!("[{index}]"));
40        } else {
41            s.push(format!("[{index}]"));
42        }
43        Self { segments: s }
44    }
45
46    /// Render the path using the canonical dotted-field notation.
47    #[must_use]
48    pub fn path(&self) -> String {
49        self.segments.join(".")
50    }
51}
52
53/// A stable generated-input validation failure.
54#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct ValidationError {
56    path: String,
57    code: String,
58}
59
60impl ValidationError {
61    /// Construct a failure for a canonical validation path and stable code.
62    #[must_use]
63    pub fn new(path: impl Into<String>, code: impl Into<String>) -> Self {
64        Self {
65            path: path.into(),
66            code: code.into(),
67        }
68    }
69
70    /// Return the canonical field path that failed validation.
71    #[must_use]
72    pub fn field(&self) -> &str {
73        &self.path
74    }
75
76    /// Return the stable language-neutral validation code.
77    #[must_use]
78    pub fn code(&self) -> &str {
79        &self.code
80    }
81}
82
83impl fmt::Display for ValidationError {
84    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85        if self.path.is_empty() {
86            write!(formatter, "{}", self.code)
87        } else {
88            write!(formatter, "{}: {}", self.path, self.code)
89        }
90    }
91}
92
93impl std::error::Error for ValidationError {}
94
95/// An exact resolved cardinality.
96#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97pub struct Cardinality {
98    min: u64,
99    max: Option<u64>,
100}
101
102impl Cardinality {
103    /// Construct an inclusive cardinality interval.
104    #[must_use]
105    pub const fn new(min: u64, max: Option<u64>) -> Self {
106        Self { min, max }
107    }
108
109    /// Return the inclusive minimum number of values.
110    #[must_use]
111    pub const fn min(self) -> u64 {
112        self.min
113    }
114
115    /// Return the inclusive maximum, or `None` when unbounded.
116    #[must_use]
117    pub const fn max(self) -> Option<u64> {
118        self.max
119    }
120}
121
122/// A statically required scalar value.
123#[derive(Clone, Debug, PartialEq)]
124pub struct Required<T>(T);
125
126impl<T> Required<T> {
127    /// Wrap a value required by the resolved schema.
128    #[must_use]
129    pub const fn new(value: T) -> Self {
130        Self(value)
131    }
132
133    /// Borrow the required value.
134    #[must_use]
135    pub const fn get(&self) -> &T {
136        &self.0
137    }
138
139    /// Borrow the required value.
140    #[must_use]
141    pub const fn value(&self) -> &T {
142        &self.0
143    }
144}
145
146impl<T> core::ops::Deref for Required<T> {
147    type Target = T;
148    fn deref(&self) -> &Self::Target {
149        &self.0
150    }
151}
152
153/// An optional scalar value.
154#[derive(Clone, Debug, PartialEq)]
155pub struct Optional<T>(Option<T>);
156
157impl<T> Optional<T> {
158    /// Wrap an optional value from the resolved schema.
159    #[must_use]
160    pub const fn new(value: Option<T>) -> Self {
161        Self(value)
162    }
163
164    /// Borrow the optional value without consuming the wrapper.
165    #[must_use]
166    pub const fn as_ref(&self) -> Option<&T> {
167        self.0.as_ref()
168    }
169}
170
171/// A sequence checked against its exact resolved cardinality.
172#[derive(Clone, Debug, PartialEq)]
173pub struct Sequence<T> {
174    values: Vec<T>,
175    cardinality: Cardinality,
176}
177
178impl<T> Sequence<T> {
179    /// Validate and wrap a sequence against its resolved cardinality.
180    ///
181    /// # Errors
182    ///
183    /// Returns a stable validation error when the length cannot be represented
184    /// or falls outside `cardinality`.
185    pub fn try_new(
186        values: Vec<T>,
187        cardinality: Cardinality,
188        path: &ValidationPath,
189    ) -> Result<Self, ValidationError> {
190        let length = u64::try_from(values.len())
191            .map_err(|_| ValidationError::new(path.path(), "cardinality_overflow"))?;
192        if length < cardinality.min() || cardinality.max().is_some_and(|maximum| length > maximum) {
193            return Err(ValidationError::new(path.path(), "cardinality_violation"));
194        }
195        Ok(Self {
196            values,
197            cardinality,
198        })
199    }
200
201    /// Borrow the validated values in schema order.
202    #[must_use]
203    pub fn as_slice(&self) -> &[T] {
204        &self.values
205    }
206
207    /// Return the cardinality against which this sequence was validated.
208    #[must_use]
209    pub const fn cardinality(&self) -> Cardinality {
210        self.cardinality
211    }
212}
213
214/// A binary sum used to preserve exact heterogeneous model forms.
215#[derive(Clone, Debug, PartialEq)]
216pub enum Either<L, R> {
217    /// The left accepted model form.
218    Left(L),
219    /// The right accepted model form.
220    Right(R),
221}
222
223impl<L: sealed::Sealed, R: sealed::Sealed> sealed::Sealed for Either<L, R> {}
224
225impl<L: Model<Schema = S>, R: Model<Schema = S>, S: Schema> Model for Either<L, R> {
226    type Schema = S;
227    const TYPE_ID_JSON: &'static str = "either";
228}
229
230/// The uninhabited projection of an empty accepted-player set.
231#[derive(Clone, Debug, PartialEq)]
232pub enum Never {}
233
234/// A finite, normalized floating-point value stored as IEEE 754 bits.
235#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
236pub struct CanonicalDouble(u64);
237
238impl CanonicalDouble {
239    /// Validate and wrap a finite IEEE 754 double.
240    ///
241    /// # Errors
242    ///
243    /// Returns `noncanonical_double` for NaN or either infinity.
244    pub fn try_new(value: f64) -> Result<Self, ValidationError> {
245        if value.is_nan() || value.is_infinite() {
246            return Err(ValidationError::new("", "noncanonical_double"));
247        }
248        Ok(Self(value.to_bits()))
249    }
250
251    /// Validate and wrap the bits of a finite IEEE 754 double.
252    ///
253    /// # Errors
254    ///
255    /// Returns `noncanonical_double` when `bits` encodes NaN or infinity.
256    pub fn try_from_bits(bits: u64) -> Result<Self, ValidationError> {
257        let val = f64::from_bits(bits);
258        if val.is_nan() || val.is_infinite() {
259            return Err(ValidationError::new("", "noncanonical_double"));
260        }
261        Ok(Self(bits))
262    }
263
264    /// Return the wrapped floating-point value.
265    #[must_use]
266    pub fn get(self) -> f64 {
267        f64::from_bits(self.0)
268    }
269
270    /// Return the canonical IEEE 754 bit representation.
271    #[must_use]
272    pub const fn to_bits(self) -> u64 {
273        self.0
274    }
275}
276
277macro_rules! canonical_scalar {
278    ($name:ident, $doc:literal, $code:expr, $parse_expr:expr) => {
279        #[doc = $doc]
280        #[derive(Clone, Debug, Eq, PartialEq, Hash)]
281        pub struct $name(String);
282
283        impl $name {
284            /// Validate and wrap a canonical scalar spelling.
285            ///
286            /// # Errors
287            ///
288            /// Returns a stable validation error when the value exceeds the
289            /// shared string ceiling or is not canonical for this domain.
290            pub fn try_new(value: impl Into<String>) -> Result<Self, ValidationError> {
291                let s = value.into();
292                if type_bridge_contract::value::CanonicalString::new(&s).is_err() {
293                    return Err(ValidationError::new("", "string_limit_exceeded"));
294                }
295                let check_fn: fn(&str) -> bool = $parse_expr;
296                if !check_fn(&s) {
297                    return Err(ValidationError::new("", $code));
298                }
299                Ok(Self(s))
300            }
301
302            /// Return the canonical scalar spelling.
303            #[must_use]
304            pub fn as_str(&self) -> &str {
305                &self.0
306            }
307        }
308    };
309}
310
311canonical_scalar!(
312    Decimal,
313    "A decimal value in the shared canonical lexical form.",
314    "noncanonical_decimal",
315    |s| {
316        if let Some(dec) = type_bridge_contract::decimal::parse_decimal(s) {
317            dec.canonical_string() == s
318        } else {
319            false
320        }
321    }
322);
323
324canonical_scalar!(
325    Date,
326    "A calendar date in the shared canonical lexical form.",
327    "noncanonical_date",
328    |s| {
329        if let Ok(d) = s.parse::<type_bridge_contract::temporal::CanonicalDate>() {
330            d.to_string() == s
331        } else {
332            false
333        }
334    }
335);
336
337canonical_scalar!(
338    DateTime,
339    "A timezone-free date-time in the shared canonical lexical form.",
340    "noncanonical_datetime",
341    |s| {
342        if let Ok(dt) = s.parse::<type_bridge_contract::temporal::CanonicalDateTime>() {
343            dt.to_string() == s
344        } else {
345            false
346        }
347    }
348);
349
350canonical_scalar!(
351    DateTimeTz,
352    "A timezone-aware date-time in the shared canonical lexical form.",
353    "noncanonical_datetime_tz",
354    |s| {
355        if let Ok(dtz) = s.parse::<type_bridge_contract::temporal::CanonicalDateTimeTz>() {
356            dtz.to_string() == s
357        } else {
358            false
359        }
360    }
361);
362
363canonical_scalar!(
364    Duration,
365    "A duration in the shared canonical lexical form.",
366    "noncanonical_duration",
367    |s| {
368        if let Ok(dur) = s.parse::<type_bridge_contract::temporal::CanonicalDuration>() {
369            dur.to_string() == s
370        } else {
371            false
372        }
373    }
374);
375
376/// An opaque capability token required for model materialization.
377#[doc(hidden)]
378#[derive(Clone, Copy, Debug, Eq, PartialEq)]
379pub struct HydrationCapability {
380    _private: (),
381}
382
383impl HydrationCapability {
384    pub(crate) const fn new() -> Self {
385        Self { _private: () }
386    }
387}
388
389/// Test-support materializer entry point available under test-harness feature.
390#[cfg(feature = "test-harness")]
391#[doc(hidden)]
392pub fn materialize_model_for_test<M: MaterializeModel>(
393    row: &HydratedRow,
394) -> Result<M, ValidationError> {
395    let cap = HydrationCapability::new();
396    M::materialize(row, &cap)
397}
398
399/// Conversion trait into an `EncodedScalar`.
400#[doc(hidden)]
401pub trait IntoEncodedScalar {
402    #[allow(clippy::wrong_self_convention)]
403    fn into_encoded_scalar(&self) -> EncodedScalar;
404}
405
406impl IntoEncodedScalar for String {
407    fn into_encoded_scalar(&self) -> EncodedScalar {
408        EncodedScalar::String(self.clone())
409    }
410}
411
412impl IntoEncodedScalar for i64 {
413    fn into_encoded_scalar(&self) -> EncodedScalar {
414        EncodedScalar::Long(*self)
415    }
416}
417
418impl IntoEncodedScalar for CanonicalDouble {
419    fn into_encoded_scalar(&self) -> EncodedScalar {
420        EncodedScalar::Double(*self)
421    }
422}
423
424impl IntoEncodedScalar for bool {
425    fn into_encoded_scalar(&self) -> EncodedScalar {
426        EncodedScalar::Boolean(*self)
427    }
428}
429
430impl IntoEncodedScalar for Decimal {
431    fn into_encoded_scalar(&self) -> EncodedScalar {
432        EncodedScalar::Decimal(self.clone())
433    }
434}
435
436impl IntoEncodedScalar for Date {
437    fn into_encoded_scalar(&self) -> EncodedScalar {
438        EncodedScalar::Date(self.clone())
439    }
440}
441
442impl IntoEncodedScalar for DateTime {
443    fn into_encoded_scalar(&self) -> EncodedScalar {
444        EncodedScalar::DateTime(self.clone())
445    }
446}
447
448impl IntoEncodedScalar for DateTimeTz {
449    fn into_encoded_scalar(&self) -> EncodedScalar {
450        EncodedScalar::DateTimeTz(self.clone())
451    }
452}
453
454impl IntoEncodedScalar for Duration {
455    fn into_encoded_scalar(&self) -> EncodedScalar {
456        EncodedScalar::Duration(self.clone())
457    }
458}
459
460impl IntoEncodedScalar for EncodedScalar {
461    fn into_encoded_scalar(&self) -> EncodedScalar {
462        self.clone()
463    }
464}
465
466impl<T: IntoEncodedScalar> IntoEncodedScalar for &T {
467    fn into_encoded_scalar(&self) -> EncodedScalar {
468        (*self).into_encoded_scalar()
469    }
470}
471
472/// A generated value wrapper's canonical scalar query domain.
473///
474/// Generated schema crates implement this marker for attribute wrappers so
475/// equality operands can preserve their scalar domain without exposing engine
476/// value DTOs.
477#[doc(hidden)]
478pub trait QueryValued: IntoEncodedScalar {
479    /// The canonical scalar type represented by this value.
480    type Domain;
481}
482
483/// A generated attribute wrapper that can materialize a validated grouped
484/// query key from its canonical scalar evidence.
485#[doc(hidden)]
486pub trait GroupedQueryValue: QueryValued + Sized {
487    fn from_group_scalar(value: EncodedScalar) -> Result<Self, ValidationError>;
488}
489
490impl QueryValued for String {
491    type Domain = String;
492}
493impl QueryValued for i64 {
494    type Domain = i64;
495}
496impl QueryValued for CanonicalDouble {
497    type Domain = CanonicalDouble;
498}
499impl QueryValued for bool {
500    type Domain = bool;
501}
502impl QueryValued for Decimal {
503    type Domain = Decimal;
504}
505impl QueryValued for Date {
506    type Domain = Date;
507}
508impl QueryValued for DateTime {
509    type Domain = DateTime;
510}
511impl QueryValued for DateTimeTz {
512    type Domain = DateTimeTz;
513}
514impl QueryValued for Duration {
515    type Domain = Duration;
516}
517impl<T: QueryValued> QueryValued for &T {
518    type Domain = T::Domain;
519}
520
521/// Target thing category: Entity or Relation.
522#[derive(Clone, Copy, Debug, Eq, PartialEq)]
523pub enum ThingKind {
524    /// A generated entity type.
525    Entity,
526    /// A generated relation type.
527    Relation,
528}
529
530/// A sealed generated schema model associated with its schema marker `S`.
531pub trait Model: sealed::Sealed {
532    /// The generated schema that owns this model.
533    type Schema: Schema;
534    /// Canonical JSON identity for the projected schema type.
535    const TYPE_ID_JSON: &'static str;
536}
537
538/// An entity or relation schema model.
539pub trait ThingModel: Model {
540    /// Return whether this generated model represents an entity or relation.
541    fn thing_kind() -> ThingKind;
542}
543
544/// An entity schema model.
545pub trait EntityModel: ThingModel {}
546
547/// A relation schema model.
548pub trait RelationModel: ThingModel {}
549
550/// A complete materialized generated model with a mandatory canonical IID.
551pub trait CompleteModel: ThingModel + MaterializeModel {
552    /// The generated payload accepted when creating this model.
553    type Create: IntoEncodedCreate + Clone;
554    /// Return the canonical IID of the materialized thing.
555    fn iid(&self) -> &str;
556}
557
558#[doc(hidden)]
559pub trait SubtypeRootModel: ThingModel {
560    type Subtypes;
561    fn __tb_dispatch_subtype(
562        row: &HydratedRow,
563        cap: &HydrationCapability,
564    ) -> Result<Self::Subtypes, ValidationError>;
565}
566
567/// An abstract generated model marker.
568pub trait AbstractModel: ThingModel {}
569
570/// Marker for generated attribute values whose canonical domain is text.
571pub trait TextValued {}
572impl TextValued for String {}
573
574/// Marker for generated attribute values whose canonical domain admits
575/// canonical ordering for range comparisons.
576pub trait OrderedValued {}
577
578/// Marker for generated attribute values whose canonical domain admits
579/// numeric reduction, carrying the canonical reduced scalar domain.
580pub trait NumericValued {
581    /// The canonical scalar domain of domain-preserving reductions.
582    type Reduced;
583}
584impl NumericValued for i64 {
585    type Reduced = i64;
586}
587impl NumericValued for CanonicalDouble {
588    type Reduced = f64;
589}
590impl OrderedValued for i64 {}
591impl OrderedValued for CanonicalDouble {}
592impl OrderedValued for Date {}
593impl OrderedValued for DateTime {}
594impl OrderedValued for DateTimeTz {}
595impl OrderedValued for Decimal {}
596impl OrderedValued for Duration {}
597
598/// A nonrecursive generated reference model. Key-based references may not have an IID initially.
599pub trait ReferenceModel: ThingModel {
600    /// Return the canonical IID when this reference is IID-backed.
601    fn iid(&self) -> Option<&str>;
602}
603
604/// A closed client scalar value over all nine canonical domains.
605#[doc(hidden)]
606#[derive(Clone, Debug, PartialEq)]
607pub enum EncodedScalar {
608    String(String),
609    Long(i64),
610    Double(CanonicalDouble),
611    Decimal(Decimal),
612    Boolean(bool),
613    Date(Date),
614    DateTime(DateTime),
615    DateTimeTz(DateTimeTz),
616    Duration(Duration),
617}
618
619impl EncodedScalar {
620    pub fn as_string(&self) -> Option<&str> {
621        match self {
622            Self::String(s) => Some(s.as_str()),
623            _ => None,
624        }
625    }
626
627    pub fn as_long(&self) -> Option<i64> {
628        match self {
629            Self::Long(n) => Some(*n),
630            _ => None,
631        }
632    }
633
634    pub fn as_double(&self) -> Option<CanonicalDouble> {
635        match self {
636            Self::Double(d) => Some(*d),
637            _ => None,
638        }
639    }
640
641    pub fn as_boolean(&self) -> Option<bool> {
642        match self {
643            Self::Boolean(b) => Some(*b),
644            _ => None,
645        }
646    }
647
648    pub fn as_decimal(&self) -> Option<&Decimal> {
649        match self {
650            Self::Decimal(d) => Some(d),
651            _ => None,
652        }
653    }
654
655    pub fn as_date(&self) -> Option<&Date> {
656        match self {
657            Self::Date(d) => Some(d),
658            _ => None,
659        }
660    }
661
662    pub fn as_datetime(&self) -> Option<&DateTime> {
663        match self {
664            Self::DateTime(d) => Some(d),
665            _ => None,
666        }
667    }
668
669    pub fn as_datetime_tz(&self) -> Option<&DateTimeTz> {
670        match self {
671            Self::DateTimeTz(d) => Some(d),
672            _ => None,
673        }
674    }
675
676    pub fn as_duration(&self) -> Option<&Duration> {
677        match self {
678            Self::Duration(d) => Some(d),
679            _ => None,
680        }
681    }
682
683    fn to_canonical_value(
684        &self,
685        path: &ValidationPath,
686    ) -> Result<type_bridge_contract::value::CanonicalValue, ValidationError> {
687        use type_bridge_contract::temporal::{
688            CanonicalDate, CanonicalDateTime, CanonicalDateTimeTz, CanonicalDuration,
689        };
690        use type_bridge_contract::value::{CanonicalDouble, CanonicalString, CanonicalValue};
691        match self {
692            Self::String(s) => CanonicalString::new(s)
693                .map(CanonicalValue::String)
694                .map_err(|_| ValidationError::new(path.path(), "string_limit_exceeded")),
695            Self::Long(n) => Ok(CanonicalValue::Long(*n)),
696            Self::Double(d) => CanonicalDouble::new(d.get())
697                .map(CanonicalValue::Double)
698                .map_err(|_| ValidationError::new(path.path(), "noncanonical_double")),
699            Self::Boolean(b) => Ok(CanonicalValue::Boolean(*b)),
700            Self::Decimal(d) => {
701                if type_bridge_contract::decimal::parse_decimal(d.as_str()).is_some() {
702                    type_bridge_contract::value::DecimalValue::new(d.as_str())
703                        .map(CanonicalValue::Decimal)
704                        .map_err(|_| ValidationError::new(path.path(), "noncanonical_decimal"))
705                } else {
706                    Err(ValidationError::new(path.path(), "noncanonical_decimal"))
707                }
708            }
709            Self::Date(d) => d
710                .as_str()
711                .parse::<CanonicalDate>()
712                .map(CanonicalValue::Date)
713                .map_err(|_| ValidationError::new(path.path(), "noncanonical_date")),
714            Self::DateTime(dt) => dt
715                .as_str()
716                .parse::<CanonicalDateTime>()
717                .map(CanonicalValue::DateTime)
718                .map_err(|_| ValidationError::new(path.path(), "noncanonical_datetime")),
719            Self::DateTimeTz(dtz) => dtz
720                .as_str()
721                .parse::<CanonicalDateTimeTz>()
722                .map(CanonicalValue::DateTimeTz)
723                .map_err(|_| ValidationError::new(path.path(), "noncanonical_datetime_tz")),
724            Self::Duration(dur) => dur
725                .as_str()
726                .parse::<CanonicalDuration>()
727                .map(CanonicalValue::Duration)
728                .map_err(|_| ValidationError::new(path.path(), "noncanonical_duration")),
729        }
730    }
731}
732
733/// Validate a string against the shared canonical string ceiling.
734///
735/// # Errors
736///
737/// Returns `string_limit_exceeded` at `path` when the value is too large.
738pub fn validate_canonical_string(
739    value: &str,
740    path: &ValidationPath,
741) -> Result<(), ValidationError> {
742    if type_bridge_contract::value::CanonicalString::new(value).is_err() {
743        return Err(ValidationError::new(path.path(), "string_limit_exceeded"));
744    }
745    Ok(())
746}
747
748/// Prefix a nested validation failure with its generated parent path.
749pub fn prefix_validation_path(
750    err: ValidationError,
751    parent_path: &ValidationPath,
752) -> ValidationError {
753    let sub_path = err.field();
754    let full_path = if sub_path.is_empty() || sub_path == "value" {
755        parent_path.path()
756    } else {
757        format!("{}.{}", parent_path.path(), sub_path)
758    };
759    ValidationError::new(full_path, err.code())
760}
761
762/// A document-hidden closed constraint descriptor for attribute value and owns-edge validation.
763#[doc(hidden)]
764#[derive(Clone, Debug, PartialEq)]
765pub struct ConstraintDescriptor {
766    range_min: Option<EncodedScalar>,
767    range_max: Option<EncodedScalar>,
768    regex: Option<&'static str>,
769    values: Option<Vec<EncodedScalar>>,
770}
771
772impl ConstraintDescriptor {
773    #[must_use]
774    pub fn new(
775        range_min: Option<EncodedScalar>,
776        range_max: Option<EncodedScalar>,
777        regex: Option<&'static str>,
778        values: Option<Vec<EncodedScalar>>,
779    ) -> Self {
780        Self {
781            range_min,
782            range_max,
783            regex,
784            values,
785        }
786    }
787
788    pub fn validate(
789        &self,
790        value: &EncodedScalar,
791        path: &ValidationPath,
792    ) -> Result<(), ValidationError> {
793        if let Some(pattern) = self.regex {
794            let Some(s) = value.as_string() else {
795                return Err(ValidationError::new(path.path(), "wrong_scalar_domain"));
796            };
797            let re = regex::Regex::new(pattern)
798                .map_err(|_| ValidationError::new(path.path(), "invalid_regex_pattern"))?;
799            if !re.is_match(s) {
800                return Err(ValidationError::new(path.path(), "regex_violation"));
801            }
802        }
803
804        let canonical_val = value.to_canonical_value(path)?;
805
806        if let Some(allowed) = &self.values {
807            let mut found = false;
808            for item in allowed {
809                let allowed_canon = item.to_canonical_value(path)?;
810                if canonical_val.value_type() != allowed_canon.value_type() {
811                    return Err(ValidationError::new(path.path(), "wrong_scalar_domain"));
812                }
813                let equal = match canonical_val.semantic_cmp_same_domain(&allowed_canon) {
814                    Some(std::cmp::Ordering::Equal) => true,
815                    Some(_) => false,
816                    None => canonical_val == allowed_canon,
817                };
818                if equal {
819                    found = true;
820                    break;
821                }
822            }
823            if !found {
824                return Err(ValidationError::new(path.path(), "values_violation"));
825            }
826        }
827        if let Some(min) = &self.range_min {
828            let min_canon = min.to_canonical_value(path)?;
829            let cmp = canonical_val
830                .semantic_cmp_same_domain(&min_canon)
831                .ok_or_else(|| ValidationError::new(path.path(), "wrong_scalar_domain"))?;
832            if cmp == std::cmp::Ordering::Less {
833                return Err(ValidationError::new(path.path(), "range_violation"));
834            }
835        }
836        if let Some(max) = &self.range_max {
837            let max_canon = max.to_canonical_value(path)?;
838            let cmp = canonical_val
839                .semantic_cmp_same_domain(&max_canon)
840                .ok_or_else(|| ValidationError::new(path.path(), "wrong_scalar_domain"))?;
841            if cmp == std::cmp::Ordering::Greater {
842                return Err(ValidationError::new(path.path(), "range_violation"));
843            }
844        }
845        Ok(())
846    }
847}
848
849/// A transport-neutral encoded IID-or-typed-key reference.
850#[doc(hidden)]
851#[derive(Clone, Debug, PartialEq)]
852pub struct EncodedReference {
853    type_id_json: &'static str,
854    iid: Option<String>,
855    keys: Vec<(&'static str, EncodedScalar)>,
856}
857
858impl EncodedReference {
859    pub fn try_new(
860        type_id_json: &'static str,
861        iid: Option<String>,
862        keys: Vec<(&'static str, EncodedScalar)>,
863        path: &ValidationPath,
864    ) -> Result<Self, ValidationError> {
865        if iid.as_deref().is_some_and(|value| value.trim().is_empty()) {
866            return Err(ValidationError::new(path.join("iid").path(), "empty_iid"));
867        }
868        let mut seen = std::collections::BTreeSet::new();
869        for (index, (token, _)) in keys.iter().enumerate() {
870            if !seen.insert(*token) {
871                return Err(ValidationError::new(
872                    path.join("keys").join_index(index).path(),
873                    "duplicate_reference_key",
874                ));
875            }
876        }
877        if iid.is_none() && keys.is_empty() {
878            return Err(ValidationError::new(
879                path.path(),
880                "missing_reference_identity",
881            ));
882        }
883        if iid.is_none() && keys.len() > 1 {
884            return Err(ValidationError::new(
885                path.path(),
886                "multiple_reference_keys_without_iid",
887            ));
888        }
889        Ok(Self {
890            type_id_json,
891            iid,
892            keys,
893        })
894    }
895
896    #[must_use]
897    pub const fn type_id_json(&self) -> &'static str {
898        self.type_id_json
899    }
900
901    #[must_use]
902    pub fn iid(&self) -> Option<&str> {
903        self.iid.as_deref()
904    }
905
906    #[must_use]
907    pub fn keys(&self) -> &[(&'static str, EncodedScalar)] {
908        &self.keys
909    }
910}
911
912/// Ordered encoded owned fields and active roles for a thing creation payload.
913#[doc(hidden)]
914#[derive(Clone, Debug, PartialEq)]
915pub struct EncodedCreate {
916    type_id_json: &'static str,
917    fields: Vec<(&'static str, Vec<EncodedScalar>)>,
918    roles: Vec<(&'static str, Vec<EncodedReference>)>,
919}
920
921impl EncodedCreate {
922    #[must_use]
923    pub const fn new(
924        type_id_json: &'static str,
925        fields: Vec<(&'static str, Vec<EncodedScalar>)>,
926        roles: Vec<(&'static str, Vec<EncodedReference>)>,
927    ) -> Self {
928        Self {
929            type_id_json,
930            fields,
931            roles,
932        }
933    }
934
935    #[must_use]
936    pub const fn type_id_json(&self) -> &'static str {
937        self.type_id_json
938    }
939
940    #[must_use]
941    pub fn fields(&self) -> &[(&'static str, Vec<EncodedScalar>)] {
942        &self.fields
943    }
944
945    #[must_use]
946    pub fn roles(&self) -> &[(&'static str, Vec<EncodedReference>)] {
947        &self.roles
948    }
949}
950
951/// Nonrecursive player reference evidence carried by a hydrated role.
952#[doc(hidden)]
953#[derive(Clone, Debug, PartialEq)]
954pub struct HydratedPlayer {
955    type_id_json: String,
956    iid: Option<String>,
957    keys: Vec<(String, EncodedScalar)>,
958}
959
960impl HydratedPlayer {
961    #[must_use]
962    pub fn new(
963        type_id_json: &'static str,
964        iid: Option<String>,
965        keys: Vec<(&'static str, EncodedScalar)>,
966    ) -> Self {
967        Self {
968            type_id_json: type_id_json.to_owned(),
969            iid,
970            keys: keys
971                .into_iter()
972                .map(|(identity, value)| (identity.to_owned(), value))
973                .collect(),
974        }
975    }
976
977    #[must_use]
978    #[allow(dead_code)]
979    pub(crate) fn from_owned(
980        type_id_json: String,
981        iid: Option<String>,
982        keys: Vec<(String, EncodedScalar)>,
983    ) -> Self {
984        Self {
985            type_id_json,
986            iid,
987            keys,
988        }
989    }
990
991    #[must_use]
992    pub fn type_id_json(&self) -> &str {
993        &self.type_id_json
994    }
995
996    #[must_use]
997    pub fn iid(&self) -> Option<&str> {
998        self.iid.as_deref()
999    }
1000
1001    #[must_use]
1002    pub fn keys(&self) -> &[(String, EncodedScalar)] {
1003        &self.keys
1004    }
1005}
1006
1007/// A transport-neutral hydrated thing with exact concrete type identity, mandatory IID, owned-value evidence, and role-player evidence.
1008#[doc(hidden)]
1009#[derive(Clone, Debug, PartialEq)]
1010pub struct HydratedRow {
1011    type_id_json: String,
1012    iid: String,
1013    fields: Vec<(String, Vec<EncodedScalar>)>,
1014    roles: Vec<(String, Vec<HydratedPlayer>)>,
1015}
1016
1017impl HydratedRow {
1018    #[must_use]
1019    pub fn new(
1020        type_id_json: &'static str,
1021        iid: String,
1022        fields: Vec<(&'static str, Vec<EncodedScalar>)>,
1023        roles: Vec<(&'static str, Vec<HydratedPlayer>)>,
1024    ) -> Self {
1025        Self {
1026            type_id_json: type_id_json.to_owned(),
1027            iid,
1028            fields: fields
1029                .into_iter()
1030                .map(|(identity, values)| (identity.to_owned(), values))
1031                .collect(),
1032            roles: roles
1033                .into_iter()
1034                .map(|(identity, players)| (identity.to_owned(), players))
1035                .collect(),
1036        }
1037    }
1038
1039    #[must_use]
1040    #[allow(dead_code)]
1041    pub(crate) fn from_owned(
1042        type_id_json: String,
1043        iid: String,
1044        fields: Vec<(String, Vec<EncodedScalar>)>,
1045        roles: Vec<(String, Vec<HydratedPlayer>)>,
1046    ) -> Self {
1047        Self {
1048            type_id_json,
1049            iid,
1050            fields,
1051            roles,
1052        }
1053    }
1054
1055    #[must_use]
1056    pub fn type_id_json(&self) -> &str {
1057        &self.type_id_json
1058    }
1059
1060    #[must_use]
1061    pub fn iid(&self) -> &str {
1062        &self.iid
1063    }
1064
1065    #[must_use]
1066    pub fn fields(&self) -> &[(String, Vec<EncodedScalar>)] {
1067        &self.fields
1068    }
1069
1070    #[must_use]
1071    pub fn roles(&self) -> &[(String, Vec<HydratedPlayer>)] {
1072        &self.roles
1073    }
1074
1075    pub fn validate_shape(
1076        &self,
1077        expected_type_id: &'static str,
1078        expected_fields: &[&'static str],
1079        expected_roles: &[&'static str],
1080        path: &ValidationPath,
1081    ) -> Result<(), ValidationError> {
1082        if self.type_id_json != expected_type_id {
1083            return Err(ValidationError::new(
1084                path.path(),
1085                "wrong_concrete_model_type",
1086            ));
1087        }
1088        let mut seen_fields = std::collections::BTreeSet::new();
1089        for (k, _) in &self.fields {
1090            if !seen_fields.insert(k.as_str()) {
1091                return Err(ValidationError::new(
1092                    path.path(),
1093                    "duplicate_scalar_evidence",
1094                ));
1095            }
1096            if !expected_fields.contains(&k.as_str()) {
1097                return Err(ValidationError::new(
1098                    path.path(),
1099                    "unexpected_field_evidence",
1100                ));
1101            }
1102        }
1103        let mut seen_roles = std::collections::BTreeSet::new();
1104        for (r, _) in &self.roles {
1105            if !seen_roles.insert(r.as_str()) {
1106                return Err(ValidationError::new(path.path(), "duplicate_role_evidence"));
1107            }
1108            if !expected_roles.contains(&r.as_str()) {
1109                return Err(ValidationError::new(
1110                    path.path(),
1111                    "unexpected_role_evidence",
1112                ));
1113            }
1114        }
1115        Ok(())
1116    }
1117}
1118
1119/// Lowering trait from a generated create payload into an `EncodedCreate`.
1120#[doc(hidden)]
1121pub trait IntoEncodedCreate: sealed::Sealed {
1122    fn into_encoded_create(self) -> Result<EncodedCreate, ValidationError>;
1123}
1124
1125/// Uninhabited create payload for a concrete read model whose schema shape
1126/// cannot be instantiated at its own scope.
1127#[doc(hidden)]
1128#[derive(Clone, Debug, PartialEq)]
1129pub enum UnconstructibleCreate {}
1130
1131impl sealed::Sealed for UnconstructibleCreate {}
1132
1133impl IntoEncodedCreate for UnconstructibleCreate {
1134    fn into_encoded_create(self) -> Result<EncodedCreate, ValidationError> {
1135        match self {}
1136    }
1137}
1138
1139/// Lowering trait from a generated reference into an `EncodedReference`.
1140#[doc(hidden)]
1141pub trait IntoEncodedReference: sealed::Sealed {
1142    fn into_encoded_reference(self) -> Result<EncodedReference, ValidationError>;
1143}
1144
1145/// Materializing trait for generated complete read models or families from a `HydratedRow`.
1146#[doc(hidden)]
1147pub trait MaterializeModel: Model + Sized {
1148    fn materialize(row: &HydratedRow, cap: &HydrationCapability) -> Result<Self, ValidationError>;
1149}
1150
1151/// A closed subtype family enum representing a concrete descendant closure for a root `Root`.
1152pub trait ModelFamily: sealed::Sealed {
1153    /// The generated root model whose concrete descendants form this family.
1154    type Root: ThingModel;
1155    /// The generated schema that owns the family.
1156    type Schema: Schema;
1157    /// Return the canonical IID of the materialized family member.
1158    fn iid(&self) -> &str;
1159}
1160
1161/// A sealed generated struct value associated with its schema marker `S`.
1162pub trait StructValue: sealed::Sealed {
1163    /// The generated schema that owns this struct value.
1164    type Schema: Schema;
1165    /// Canonical JSON identity for the projected schema struct.
1166    const STRUCT_ID_JSON: &'static str;
1167}
1168
1169/// A resolver-proven nominal upcast relation.
1170pub trait NominalUpcast<Target: Model>: Model {}
1171
1172/// A resolver-proven specialized-role upcast relation.
1173pub trait RoleUpcast<ActiveRole, AncestorRole>: Model {}
1174
1175/// Positive generated evidence that one role token is active on a relation.
1176///
1177/// Unlike nominal inheritance, this relation is intentionally subtractive:
1178/// generated specializing relations do not implement compatibility for the
1179/// specialized-away ancestor role.
1180pub trait RoleTokenCompatible<Owner: RelationModel, Players>: RelationModel {}
1181
1182/// Positive generated evidence that a role's exact player union admits one
1183/// bound player model.
1184pub trait RolePlayer<Player: ThingModel> {}
1185
1186/// Positive generated evidence that a role admits one binding mode and model.
1187pub trait RolePlayerBinding<Player: ThingModel, Mode> {}
1188
1189impl<Players, Player> RolePlayerBinding<Player, crate::query::Exact> for Players
1190where
1191    Player: ThingModel,
1192    Players: RolePlayer<Player>,
1193{
1194}
1195
1196/// A schema type/query token branded by its exact owner.
1197#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1198pub struct TypeToken<Owner: Model> {
1199    type_id_json: &'static str,
1200    metadata_json: &'static str,
1201    marker: PhantomData<fn() -> Owner>,
1202}
1203
1204impl<Owner: Model> TypeToken<Owner> {
1205    /// Construct a token from canonical type identity and metadata JSON.
1206    #[must_use]
1207    pub const fn new(type_id_json: &'static str, metadata_json: &'static str) -> Self {
1208        Self {
1209            type_id_json,
1210            metadata_json,
1211            marker: PhantomData,
1212        }
1213    }
1214
1215    /// Return the token's canonical type identity JSON.
1216    #[must_use]
1217    pub const fn type_id_json(self) -> &'static str {
1218        self.type_id_json
1219    }
1220
1221    /// Return the token's canonical metadata JSON.
1222    #[must_use]
1223    pub const fn metadata_json(self) -> &'static str {
1224        self.metadata_json
1225    }
1226}
1227
1228/// An owned-field token branded by owner and value model.
1229#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1230pub struct FieldToken<Owner: Model, Value> {
1231    owns_id_json: &'static str,
1232    metadata_json: &'static str,
1233    marker: PhantomData<fn() -> (Owner, Value)>,
1234}
1235
1236impl<Owner: Model, Value> FieldToken<Owner, Value> {
1237    /// Construct a token from canonical owns-edge identity and metadata JSON.
1238    #[must_use]
1239    pub const fn new(owns_id_json: &'static str, metadata_json: &'static str) -> Self {
1240        Self {
1241            owns_id_json,
1242            metadata_json,
1243            marker: PhantomData,
1244        }
1245    }
1246
1247    /// Return the token's canonical owns-edge identity JSON.
1248    #[must_use]
1249    pub const fn owns_id_json(self) -> &'static str {
1250        self.owns_id_json
1251    }
1252
1253    /// Return the token's canonical metadata JSON.
1254    #[must_use]
1255    pub const fn metadata_json(self) -> &'static str {
1256        self.metadata_json
1257    }
1258}
1259
1260/// A related-role token branded by owner and exact accepted-player enum.
1261#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1262pub struct RoleToken<Owner: Model, Players> {
1263    role_id_json: &'static str,
1264    metadata_json: &'static str,
1265    marker: PhantomData<fn() -> (Owner, Players)>,
1266}
1267
1268impl<Owner: Model, Players> RoleToken<Owner, Players> {
1269    /// Construct a token from canonical role identity and metadata JSON.
1270    #[must_use]
1271    pub const fn new(role_id_json: &'static str, metadata_json: &'static str) -> Self {
1272        Self {
1273            role_id_json,
1274            metadata_json,
1275            marker: PhantomData,
1276        }
1277    }
1278
1279    /// Return the token's canonical role identity JSON.
1280    #[must_use]
1281    pub const fn role_id_json(self) -> &'static str {
1282        self.role_id_json
1283    }
1284
1285    /// Return the token's canonical metadata JSON.
1286    #[must_use]
1287    pub const fn metadata_json(self) -> &'static str {
1288        self.metadata_json
1289    }
1290}
1291
1292/// A playing-fact token branded by player, role owner, and accepted-player enum.
1293#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1294pub struct PlaysToken<Player: Model, Owner: Model, Players> {
1295    plays_id_json: &'static str,
1296    metadata_json: &'static str,
1297    #[allow(clippy::type_complexity)]
1298    marker: PhantomData<fn() -> (Player, Owner, Players)>,
1299}
1300
1301impl<Player: Model, Owner: Model, Players> PlaysToken<Player, Owner, Players> {
1302    /// Construct a token from canonical playing identity and metadata JSON.
1303    #[must_use]
1304    pub const fn new(plays_id_json: &'static str, metadata_json: &'static str) -> Self {
1305        Self {
1306            plays_id_json,
1307            metadata_json,
1308            marker: PhantomData,
1309        }
1310    }
1311
1312    /// Return the token's canonical playing identity JSON.
1313    #[must_use]
1314    pub const fn plays_id_json(self) -> &'static str {
1315        self.plays_id_json
1316    }
1317
1318    /// Return the token's canonical metadata JSON.
1319    #[must_use]
1320    pub const fn metadata_json(self) -> &'static str {
1321        self.metadata_json
1322    }
1323}
1324
1325/// A typed schema-function token branded by schema `S`.
1326#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1327pub struct FunctionToken<S: Schema, Arguments, Output> {
1328    function_id: &'static str,
1329    metadata_json: &'static str,
1330    marker: PhantomData<fn(Arguments) -> (S, Output)>,
1331}
1332
1333impl<S: Schema, Arguments, Output> FunctionToken<S, Arguments, Output> {
1334    /// Construct a token from a function identifier and canonical metadata JSON.
1335    #[must_use]
1336    pub const fn new(function_id: &'static str, metadata_json: &'static str) -> Self {
1337        Self {
1338            function_id,
1339            metadata_json,
1340            marker: PhantomData,
1341        }
1342    }
1343
1344    /// Return the schema function identifier.
1345    #[must_use]
1346    pub const fn function_id(self) -> &'static str {
1347        self.function_id
1348    }
1349
1350    /// Return the token's canonical metadata JSON.
1351    #[must_use]
1352    pub const fn metadata_json(self) -> &'static str {
1353        self.metadata_json
1354    }
1355}
1356
1357/// A typed asynchronous stream result marker.
1358#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1359pub struct Stream<T>(PhantomData<fn() -> T>);
1360
1361#[cfg(test)]
1362mod tests {
1363    use super::*;
1364
1365    #[test]
1366    fn canonical_scalar_wrappers_do_not_expose_lexical_ordering() {
1367        let source = include_str!("__codegen.rs");
1368        let (_, macro_and_invocations) = source
1369            .split_once("macro_rules! canonical_scalar")
1370            .expect("canonical scalar macro remains present");
1371        let (macro_body, _) = macro_and_invocations
1372            .split_once("canonical_scalar!(\n    Decimal")
1373            .expect("canonical scalar invocations remain present");
1374        assert!(macro_body.contains("#[derive(Clone, Debug, Eq, PartialEq, Hash)]"));
1375        assert!(!macro_body.contains("Ord"));
1376        assert!(!macro_body.contains("PartialOrd"));
1377    }
1378
1379    #[test]
1380    fn scalar_domain_wrappers_and_validation() {
1381        let path = ValidationPath::root().join("test");
1382
1383        let double_ok = CanonicalDouble::try_new(3.125).unwrap();
1384        assert_eq!(double_ok.get(), 3.125);
1385        assert_eq!(
1386            CanonicalDouble::try_new(f64::NAN).unwrap_err().code(),
1387            "noncanonical_double"
1388        );
1389        assert_eq!(
1390            CanonicalDouble::try_new(f64::INFINITY).unwrap_err().code(),
1391            "noncanonical_double"
1392        );
1393        let neg_zero = CanonicalDouble::try_new(-0.0).unwrap();
1394        let pos_zero = CanonicalDouble::try_new(0.0).unwrap();
1395        assert_ne!(neg_zero.to_bits(), pos_zero.to_bits());
1396
1397        let dec = Decimal::try_new("123.45").unwrap();
1398        assert_eq!(dec.as_str(), "123.45");
1399        assert_eq!(
1400            Decimal::try_new("123.4500").unwrap_err().code(),
1401            "noncanonical_decimal"
1402        );
1403
1404        let date = Date::try_new("2026-07-28").unwrap();
1405        assert_eq!(date.as_str(), "2026-07-28");
1406        assert_eq!(
1407            Date::try_new("2026-7-28").unwrap_err().code(),
1408            "noncanonical_date"
1409        );
1410
1411        let dt = DateTime::try_new("2026-07-28T03:55:00").unwrap();
1412        assert_eq!(dt.as_str(), "2026-07-28T03:55:00");
1413
1414        let dtz = DateTimeTz::try_new("2026-07-28T03:55:00Z").unwrap();
1415        assert_eq!(dtz.as_str(), "2026-07-28T03:55:00Z");
1416
1417        let dur = Duration::try_new("P1D").unwrap();
1418        assert_eq!(dur.as_str(), "P1D");
1419
1420        let seq = Sequence::try_new(vec![1, 2], Cardinality::new(1, Some(3)), &path).unwrap();
1421        assert_eq!(seq.as_slice(), &[1, 2]);
1422
1423        let seq_err = Sequence::try_new(vec![1, 2, 3, 4], Cardinality::new(1, Some(3)), &path);
1424        assert_eq!(seq_err.unwrap_err().code(), "cardinality_violation");
1425
1426        let constraint = ConstraintDescriptor::new(
1427            Some(EncodedScalar::String("a".to_owned())),
1428            Some(EncodedScalar::String("z".to_owned())),
1429            Some("^a.*z$"),
1430            None,
1431        );
1432        let s_val = EncodedScalar::String("abcz".to_owned());
1433        assert_eq!(constraint.validate(&s_val, &path), Ok(()));
1434    }
1435}