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
350/// A timezone-aware date-time in the shared canonical lexical form.
351///
352/// Named-zone values retain their provider-resolved effective offset in the
353/// private canonical value so both sides of a daylight-saving overlap survive
354/// generated-model hydration and later CRUD/query lowering.
355#[derive(Clone, Eq, PartialEq, Hash)]
356pub struct DateTimeTz {
357    spelling: String,
358    canonical: type_bridge_contract::temporal::CanonicalDateTimeTz,
359}
360
361impl std::fmt::Debug for DateTimeTz {
362    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363        // Preserve the released tuple-wrapper Debug shape.
364        formatter
365            .debug_tuple("DateTimeTz")
366            .field(&self.spelling)
367            .finish()
368    }
369}
370
371impl DateTimeTz {
372    /// Validate and wrap a canonical timezone-aware scalar spelling.
373    ///
374    /// # Errors
375    ///
376    /// Returns `noncanonical_datetime_tz` when the spelling is not the exact
377    /// canonical rendering under the frozen provider timezone policy.
378    pub fn try_new(value: impl Into<String>) -> Result<Self, ValidationError> {
379        let spelling = value.into();
380        if type_bridge_contract::value::CanonicalString::new(&spelling).is_err() {
381            return Err(ValidationError::new("", "string_limit_exceeded"));
382        }
383        let canonical = type_bridge_schema::parse_provider_datetime_tz(&spelling)
384            .map_err(|_| ValidationError::new("", "noncanonical_datetime_tz"))?;
385        if canonical.to_string() != spelling {
386            return Err(ValidationError::new("", "noncanonical_datetime_tz"));
387        }
388        Ok(Self {
389            spelling,
390            canonical,
391        })
392    }
393
394    #[must_use]
395    pub(crate) fn from_canonical(
396        canonical: type_bridge_contract::temporal::CanonicalDateTimeTz,
397    ) -> Self {
398        Self {
399            spelling: canonical.to_string(),
400            canonical,
401        }
402    }
403
404    /// Return the canonical scalar spelling.
405    #[must_use]
406    pub fn as_str(&self) -> &str {
407        &self.spelling
408    }
409
410    #[must_use]
411    pub(crate) const fn canonical(&self) -> &type_bridge_contract::temporal::CanonicalDateTimeTz {
412        &self.canonical
413    }
414}
415
416canonical_scalar!(
417    Duration,
418    "A duration in the shared canonical lexical form.",
419    "noncanonical_duration",
420    |s| {
421        if let Ok(dur) = s.parse::<type_bridge_contract::temporal::CanonicalDuration>() {
422            dur.to_string() == s
423        } else {
424            false
425        }
426    }
427);
428
429/// An opaque capability token required for model materialization.
430#[doc(hidden)]
431#[derive(Clone, Copy, Debug, Eq, PartialEq)]
432pub struct HydrationCapability {
433    _private: (),
434}
435
436impl HydrationCapability {
437    /// Construct the generated-code-only materialization capability.
438    #[doc(hidden)]
439    pub const fn new() -> Self {
440        Self { _private: () }
441    }
442}
443
444/// Test-support materializer entry point available under test-harness feature.
445#[cfg(feature = "test-harness")]
446#[doc(hidden)]
447pub fn materialize_model_for_test<M: MaterializeModel>(
448    row: &HydratedRow,
449) -> Result<M, ValidationError> {
450    let cap = HydrationCapability::new();
451    M::materialize(row, &cap)
452}
453
454/// Conversion trait into an `EncodedScalar`.
455#[doc(hidden)]
456pub trait IntoEncodedScalar {
457    #[allow(clippy::wrong_self_convention)]
458    fn into_encoded_scalar(&self) -> EncodedScalar;
459}
460
461impl IntoEncodedScalar for String {
462    fn into_encoded_scalar(&self) -> EncodedScalar {
463        EncodedScalar::String(self.clone())
464    }
465}
466
467impl IntoEncodedScalar for i64 {
468    fn into_encoded_scalar(&self) -> EncodedScalar {
469        EncodedScalar::Long(*self)
470    }
471}
472
473impl IntoEncodedScalar for CanonicalDouble {
474    fn into_encoded_scalar(&self) -> EncodedScalar {
475        EncodedScalar::Double(*self)
476    }
477}
478
479impl IntoEncodedScalar for bool {
480    fn into_encoded_scalar(&self) -> EncodedScalar {
481        EncodedScalar::Boolean(*self)
482    }
483}
484
485impl IntoEncodedScalar for Decimal {
486    fn into_encoded_scalar(&self) -> EncodedScalar {
487        EncodedScalar::Decimal(self.clone())
488    }
489}
490
491impl IntoEncodedScalar for Date {
492    fn into_encoded_scalar(&self) -> EncodedScalar {
493        EncodedScalar::Date(self.clone())
494    }
495}
496
497impl IntoEncodedScalar for DateTime {
498    fn into_encoded_scalar(&self) -> EncodedScalar {
499        EncodedScalar::DateTime(self.clone())
500    }
501}
502
503impl IntoEncodedScalar for DateTimeTz {
504    fn into_encoded_scalar(&self) -> EncodedScalar {
505        EncodedScalar::DateTimeTz(self.clone())
506    }
507}
508
509impl IntoEncodedScalar for Duration {
510    fn into_encoded_scalar(&self) -> EncodedScalar {
511        EncodedScalar::Duration(self.clone())
512    }
513}
514
515impl IntoEncodedScalar for EncodedScalar {
516    fn into_encoded_scalar(&self) -> EncodedScalar {
517        self.clone()
518    }
519}
520
521impl<T: IntoEncodedScalar> IntoEncodedScalar for &T {
522    fn into_encoded_scalar(&self) -> EncodedScalar {
523        (*self).into_encoded_scalar()
524    }
525}
526
527/// A generated value wrapper's canonical scalar query domain.
528///
529/// Generated schema crates implement this marker for attribute wrappers so
530/// equality operands can preserve their scalar domain without exposing engine
531/// value DTOs.
532#[doc(hidden)]
533pub trait QueryValued: IntoEncodedScalar {
534    /// The canonical scalar type represented by this value.
535    type Domain;
536}
537
538/// A generated attribute wrapper that can materialize a validated grouped
539/// query key from its canonical scalar evidence.
540#[doc(hidden)]
541pub trait GroupedQueryValue: QueryValued + Sized {
542    fn from_group_scalar(value: EncodedScalar) -> Result<Self, ValidationError>;
543}
544
545impl QueryValued for String {
546    type Domain = String;
547}
548impl QueryValued for i64 {
549    type Domain = i64;
550}
551impl QueryValued for CanonicalDouble {
552    type Domain = CanonicalDouble;
553}
554impl QueryValued for bool {
555    type Domain = bool;
556}
557impl QueryValued for Decimal {
558    type Domain = Decimal;
559}
560impl QueryValued for Date {
561    type Domain = Date;
562}
563impl QueryValued for DateTime {
564    type Domain = DateTime;
565}
566impl QueryValued for DateTimeTz {
567    type Domain = DateTimeTz;
568}
569impl QueryValued for Duration {
570    type Domain = Duration;
571}
572impl<T: QueryValued> QueryValued for &T {
573    type Domain = T::Domain;
574}
575
576/// Target thing category: Entity or Relation.
577#[derive(Clone, Copy, Debug, Eq, PartialEq)]
578pub enum ThingKind {
579    /// A generated entity type.
580    Entity,
581    /// A generated relation type.
582    Relation,
583}
584
585/// A sealed generated schema model associated with its schema marker `S`.
586pub trait Model: sealed::Sealed {
587    /// The generated schema that owns this model.
588    type Schema: Schema;
589    /// Canonical JSON identity for the projected schema type.
590    const TYPE_ID_JSON: &'static str;
591}
592
593/// An entity or relation schema model.
594pub trait ThingModel: Model {
595    /// Return whether this generated model represents an entity or relation.
596    fn thing_kind() -> ThingKind;
597}
598
599/// An entity schema model.
600pub trait EntityModel: ThingModel {}
601
602/// A relation schema model.
603pub trait RelationModel: ThingModel {}
604
605/// A complete materialized generated model with a mandatory canonical IID.
606pub trait CompleteModel: ThingModel + MaterializeModel {
607    /// The generated payload accepted when creating this model.
608    type Create: IntoEncodedCreate + Clone;
609    /// Return the canonical IID of the materialized thing.
610    fn iid(&self) -> &str;
611}
612
613#[doc(hidden)]
614pub trait SubtypeRootModel: ThingModel {
615    type Subtypes;
616    fn __tb_dispatch_subtype(
617        row: &HydratedRow,
618        cap: &HydrationCapability,
619    ) -> Result<Self::Subtypes, ValidationError>;
620}
621
622/// An abstract generated model marker.
623pub trait AbstractModel: ThingModel {}
624
625/// Marker for generated attribute values whose canonical domain is text.
626pub trait TextValued {}
627impl TextValued for String {}
628
629/// Marker for generated attribute values whose canonical domain admits
630/// canonical ordering for range comparisons.
631pub trait OrderedValued {}
632
633/// Marker for generated attribute values whose canonical domain admits
634/// numeric reduction, carrying the canonical reduced scalar domain.
635pub trait NumericValued {
636    /// The canonical scalar domain of domain-preserving reductions.
637    type Reduced;
638}
639impl NumericValued for i64 {
640    type Reduced = i64;
641}
642impl NumericValued for CanonicalDouble {
643    type Reduced = f64;
644}
645impl OrderedValued for i64 {}
646impl OrderedValued for CanonicalDouble {}
647impl OrderedValued for Date {}
648impl OrderedValued for DateTime {}
649impl OrderedValued for DateTimeTz {}
650impl OrderedValued for Decimal {}
651impl OrderedValued for Duration {}
652
653/// A nonrecursive generated reference model. Key-based references may not have an IID initially.
654pub trait ReferenceModel: ThingModel {
655    /// Return the canonical IID when this reference is IID-backed.
656    fn iid(&self) -> Option<&str>;
657}
658
659/// A closed client scalar value over all nine canonical domains.
660#[doc(hidden)]
661#[derive(Clone, Debug, PartialEq)]
662pub enum EncodedScalar {
663    String(String),
664    Long(i64),
665    Double(CanonicalDouble),
666    Decimal(Decimal),
667    Boolean(bool),
668    Date(Date),
669    DateTime(DateTime),
670    DateTimeTz(DateTimeTz),
671    Duration(Duration),
672}
673
674impl EncodedScalar {
675    pub fn as_string(&self) -> Option<&str> {
676        match self {
677            Self::String(s) => Some(s.as_str()),
678            _ => None,
679        }
680    }
681
682    pub fn as_long(&self) -> Option<i64> {
683        match self {
684            Self::Long(n) => Some(*n),
685            _ => None,
686        }
687    }
688
689    pub fn as_double(&self) -> Option<CanonicalDouble> {
690        match self {
691            Self::Double(d) => Some(*d),
692            _ => None,
693        }
694    }
695
696    pub fn as_boolean(&self) -> Option<bool> {
697        match self {
698            Self::Boolean(b) => Some(*b),
699            _ => None,
700        }
701    }
702
703    pub fn as_decimal(&self) -> Option<&Decimal> {
704        match self {
705            Self::Decimal(d) => Some(d),
706            _ => None,
707        }
708    }
709
710    pub fn as_date(&self) -> Option<&Date> {
711        match self {
712            Self::Date(d) => Some(d),
713            _ => None,
714        }
715    }
716
717    pub fn as_datetime(&self) -> Option<&DateTime> {
718        match self {
719            Self::DateTime(d) => Some(d),
720            _ => None,
721        }
722    }
723
724    pub fn as_datetime_tz(&self) -> Option<&DateTimeTz> {
725        match self {
726            Self::DateTimeTz(d) => Some(d),
727            _ => None,
728        }
729    }
730
731    pub fn as_duration(&self) -> Option<&Duration> {
732        match self {
733            Self::Duration(d) => Some(d),
734            _ => None,
735        }
736    }
737
738    pub(crate) fn to_canonical_value(
739        &self,
740        path: &ValidationPath,
741    ) -> Result<type_bridge_contract::value::CanonicalValue, ValidationError> {
742        use type_bridge_contract::temporal::{CanonicalDate, CanonicalDateTime, CanonicalDuration};
743        use type_bridge_contract::value::{CanonicalDouble, CanonicalString, CanonicalValue};
744        match self {
745            Self::String(s) => CanonicalString::new(s)
746                .map(CanonicalValue::String)
747                .map_err(|_| ValidationError::new(path.path(), "string_limit_exceeded")),
748            Self::Long(n) => Ok(CanonicalValue::Long(*n)),
749            Self::Double(d) => CanonicalDouble::new(d.get())
750                .map(CanonicalValue::Double)
751                .map_err(|_| ValidationError::new(path.path(), "noncanonical_double")),
752            Self::Boolean(b) => Ok(CanonicalValue::Boolean(*b)),
753            Self::Decimal(d) => {
754                if type_bridge_contract::decimal::parse_decimal(d.as_str()).is_some() {
755                    type_bridge_contract::value::DecimalValue::new(d.as_str())
756                        .map(CanonicalValue::Decimal)
757                        .map_err(|_| ValidationError::new(path.path(), "noncanonical_decimal"))
758                } else {
759                    Err(ValidationError::new(path.path(), "noncanonical_decimal"))
760                }
761            }
762            Self::Date(d) => d
763                .as_str()
764                .parse::<CanonicalDate>()
765                .map(CanonicalValue::Date)
766                .map_err(|_| ValidationError::new(path.path(), "noncanonical_date")),
767            Self::DateTime(dt) => dt
768                .as_str()
769                .parse::<CanonicalDateTime>()
770                .map(CanonicalValue::DateTime)
771                .map_err(|_| ValidationError::new(path.path(), "noncanonical_datetime")),
772            Self::DateTimeTz(dtz) => Ok(CanonicalValue::DateTimeTz(dtz.canonical().clone())),
773            Self::Duration(dur) => dur
774                .as_str()
775                .parse::<CanonicalDuration>()
776                .map(CanonicalValue::Duration)
777                .map_err(|_| ValidationError::new(path.path(), "noncanonical_duration")),
778        }
779    }
780}
781
782/// Validate a string against the shared canonical string ceiling.
783///
784/// # Errors
785///
786/// Returns `string_limit_exceeded` at `path` when the value is too large.
787pub fn validate_canonical_string(
788    value: &str,
789    path: &ValidationPath,
790) -> Result<(), ValidationError> {
791    if type_bridge_contract::value::CanonicalString::new(value).is_err() {
792        return Err(ValidationError::new(path.path(), "string_limit_exceeded"));
793    }
794    Ok(())
795}
796
797/// Prefix a nested validation failure with its generated parent path.
798pub fn prefix_validation_path(
799    err: ValidationError,
800    parent_path: &ValidationPath,
801) -> ValidationError {
802    let sub_path = err.field();
803    let full_path = if sub_path.is_empty() || sub_path == "value" {
804        parent_path.path()
805    } else {
806        format!("{}.{}", parent_path.path(), sub_path)
807    };
808    ValidationError::new(full_path, err.code())
809}
810
811/// A document-hidden closed constraint descriptor for attribute value and owns-edge validation.
812#[doc(hidden)]
813#[derive(Clone, Debug, PartialEq)]
814pub struct ConstraintDescriptor {
815    range_min: Option<EncodedScalar>,
816    range_max: Option<EncodedScalar>,
817    regex: Option<&'static str>,
818    values: Option<Vec<EncodedScalar>>,
819}
820
821impl ConstraintDescriptor {
822    #[must_use]
823    pub fn new(
824        range_min: Option<EncodedScalar>,
825        range_max: Option<EncodedScalar>,
826        regex: Option<&'static str>,
827        values: Option<Vec<EncodedScalar>>,
828    ) -> Self {
829        Self {
830            range_min,
831            range_max,
832            regex,
833            values,
834        }
835    }
836
837    pub fn validate(
838        &self,
839        value: &EncodedScalar,
840        path: &ValidationPath,
841    ) -> Result<(), ValidationError> {
842        if let Some(pattern) = self.regex {
843            let Some(s) = value.as_string() else {
844                return Err(ValidationError::new(path.path(), "wrong_scalar_domain"));
845            };
846            let re = regex::Regex::new(pattern)
847                .map_err(|_| ValidationError::new(path.path(), "invalid_regex_pattern"))?;
848            if !re.is_match(s) {
849                return Err(ValidationError::new(path.path(), "regex_violation"));
850            }
851        }
852
853        let canonical_val = value.to_canonical_value(path)?;
854
855        if let Some(allowed) = &self.values {
856            let mut found = false;
857            for item in allowed {
858                let allowed_canon = item.to_canonical_value(path)?;
859                if canonical_val.value_type() != allowed_canon.value_type() {
860                    return Err(ValidationError::new(path.path(), "wrong_scalar_domain"));
861                }
862                let equal = match canonical_val.semantic_cmp_same_domain(&allowed_canon) {
863                    Some(std::cmp::Ordering::Equal) => true,
864                    Some(_) => false,
865                    None => canonical_val == allowed_canon,
866                };
867                if equal {
868                    found = true;
869                    break;
870                }
871            }
872            if !found {
873                return Err(ValidationError::new(path.path(), "values_violation"));
874            }
875        }
876        if let Some(min) = &self.range_min {
877            let min_canon = min.to_canonical_value(path)?;
878            let cmp = canonical_val
879                .semantic_cmp_same_domain(&min_canon)
880                .ok_or_else(|| ValidationError::new(path.path(), "wrong_scalar_domain"))?;
881            if cmp == std::cmp::Ordering::Less {
882                return Err(ValidationError::new(path.path(), "range_violation"));
883            }
884        }
885        if let Some(max) = &self.range_max {
886            let max_canon = max.to_canonical_value(path)?;
887            let cmp = canonical_val
888                .semantic_cmp_same_domain(&max_canon)
889                .ok_or_else(|| ValidationError::new(path.path(), "wrong_scalar_domain"))?;
890            if cmp == std::cmp::Ordering::Greater {
891                return Err(ValidationError::new(path.path(), "range_violation"));
892            }
893        }
894        Ok(())
895    }
896}
897
898/// Opaque origin state retained by generated complete models and references.
899///
900/// The state is deliberately uninspectable, redacted in debug output, and
901/// ignored by value equality. User-authored references carry the unbound
902/// default; only runtime hydration can construct a bound origin.
903#[doc(hidden)]
904#[derive(Clone, Default)]
905pub struct ReferenceOrigin {
906    projected: Option<type_bridge_orm::ProjectedReferenceOrigin>,
907    detached_snapshot: bool,
908}
909
910impl ReferenceOrigin {
911    #[must_use]
912    pub(crate) const fn from_projected(
913        origin: Option<type_bridge_orm::ProjectedReferenceOrigin>,
914    ) -> Self {
915        Self {
916            projected: origin,
917            detached_snapshot: false,
918        }
919    }
920
921    #[must_use]
922    pub(crate) fn projected(&self) -> Option<type_bridge_orm::ProjectedReferenceOrigin> {
923        self.projected.clone()
924    }
925
926    #[must_use]
927    pub(crate) const fn detached_snapshot() -> Self {
928        Self {
929            projected: None,
930            detached_snapshot: true,
931        }
932    }
933
934    #[must_use]
935    pub(crate) const fn is_detached_snapshot(&self) -> bool {
936        self.detached_snapshot
937    }
938}
939
940impl fmt::Debug for ReferenceOrigin {
941    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
942        formatter.write_str("ReferenceOrigin([REDACTED])")
943    }
944}
945
946impl PartialEq for ReferenceOrigin {
947    fn eq(&self, _other: &Self) -> bool {
948        true
949    }
950}
951
952impl Eq for ReferenceOrigin {}
953
954/// A transport-neutral encoded IID-or-typed-key reference.
955#[doc(hidden)]
956#[derive(Clone, Debug, PartialEq)]
957pub struct EncodedReference {
958    type_id_json: &'static str,
959    iid: Option<String>,
960    keys: Vec<(&'static str, EncodedScalar)>,
961    origin: ReferenceOrigin,
962}
963
964impl EncodedReference {
965    pub fn try_new(
966        type_id_json: &'static str,
967        iid: Option<String>,
968        keys: Vec<(&'static str, EncodedScalar)>,
969        path: &ValidationPath,
970    ) -> Result<Self, ValidationError> {
971        Self::try_new_with_origin(type_id_json, iid, keys, ReferenceOrigin::default(), path)
972    }
973
974    #[doc(hidden)]
975    pub fn try_new_with_origin(
976        type_id_json: &'static str,
977        iid: Option<String>,
978        keys: Vec<(&'static str, EncodedScalar)>,
979        origin: ReferenceOrigin,
980        path: &ValidationPath,
981    ) -> Result<Self, ValidationError> {
982        if iid.as_deref().is_some_and(|value| value.trim().is_empty()) {
983            return Err(ValidationError::new(path.join("iid").path(), "empty_iid"));
984        }
985        let mut seen = std::collections::BTreeSet::new();
986        for (index, (token, _)) in keys.iter().enumerate() {
987            if !seen.insert(*token) {
988                return Err(ValidationError::new(
989                    path.join("keys").join_index(index).path(),
990                    "duplicate_reference_key",
991                ));
992            }
993        }
994        if iid.is_none() && keys.is_empty() {
995            return Err(ValidationError::new(
996                path.path(),
997                "missing_reference_identity",
998            ));
999        }
1000        if iid.is_none() && keys.len() > 1 {
1001            return Err(ValidationError::new(
1002                path.path(),
1003                "multiple_reference_keys_without_iid",
1004            ));
1005        }
1006        Ok(Self {
1007            type_id_json,
1008            iid,
1009            keys,
1010            origin,
1011        })
1012    }
1013
1014    #[must_use]
1015    pub const fn type_id_json(&self) -> &'static str {
1016        self.type_id_json
1017    }
1018
1019    #[must_use]
1020    pub fn iid(&self) -> Option<&str> {
1021        self.iid.as_deref()
1022    }
1023
1024    #[must_use]
1025    pub fn keys(&self) -> &[(&'static str, EncodedScalar)] {
1026        &self.keys
1027    }
1028
1029    #[doc(hidden)]
1030    #[must_use]
1031    pub const fn origin(&self) -> &ReferenceOrigin {
1032        &self.origin
1033    }
1034}
1035
1036/// Ordered encoded owned fields and active roles for a thing creation payload.
1037#[doc(hidden)]
1038#[derive(Clone, Debug, PartialEq)]
1039pub struct EncodedCreate {
1040    type_id_json: &'static str,
1041    fields: Vec<(&'static str, Vec<EncodedScalar>)>,
1042    roles: Vec<(&'static str, Vec<EncodedReference>)>,
1043}
1044
1045impl EncodedCreate {
1046    #[must_use]
1047    pub const fn new(
1048        type_id_json: &'static str,
1049        fields: Vec<(&'static str, Vec<EncodedScalar>)>,
1050        roles: Vec<(&'static str, Vec<EncodedReference>)>,
1051    ) -> Self {
1052        Self {
1053            type_id_json,
1054            fields,
1055            roles,
1056        }
1057    }
1058
1059    #[must_use]
1060    pub const fn type_id_json(&self) -> &'static str {
1061        self.type_id_json
1062    }
1063
1064    #[must_use]
1065    pub fn fields(&self) -> &[(&'static str, Vec<EncodedScalar>)] {
1066        &self.fields
1067    }
1068
1069    #[must_use]
1070    pub fn roles(&self) -> &[(&'static str, Vec<EncodedReference>)] {
1071        &self.roles
1072    }
1073}
1074
1075/// Nonrecursive player reference evidence carried by a hydrated role.
1076#[doc(hidden)]
1077#[derive(Clone, Debug, PartialEq)]
1078pub struct HydratedPlayer {
1079    type_id_json: String,
1080    iid: Option<String>,
1081    keys: Vec<(String, EncodedScalar)>,
1082    fields: Option<Vec<(String, Vec<EncodedScalar>)>>,
1083    exact_reference: bool,
1084    origin: ReferenceOrigin,
1085}
1086
1087impl HydratedPlayer {
1088    #[must_use]
1089    pub fn new(
1090        type_id_json: &'static str,
1091        iid: Option<String>,
1092        keys: Vec<(&'static str, EncodedScalar)>,
1093    ) -> Self {
1094        Self {
1095            type_id_json: type_id_json.to_owned(),
1096            iid,
1097            keys: keys
1098                .into_iter()
1099                .map(|(identity, value)| (identity.to_owned(), value))
1100                .collect(),
1101            fields: None,
1102            exact_reference: false,
1103            origin: ReferenceOrigin::default(),
1104        }
1105    }
1106
1107    #[must_use]
1108    #[allow(dead_code)]
1109    pub(crate) fn from_owned(
1110        type_id_json: String,
1111        iid: Option<String>,
1112        keys: Vec<(String, EncodedScalar)>,
1113    ) -> Self {
1114        Self {
1115            type_id_json,
1116            iid,
1117            keys,
1118            fields: None,
1119            exact_reference: false,
1120            origin: ReferenceOrigin::default(),
1121        }
1122    }
1123
1124    #[must_use]
1125    pub(crate) fn from_reference_owned(
1126        type_id_json: String,
1127        iid: Option<String>,
1128        keys: Vec<(String, EncodedScalar)>,
1129    ) -> Self {
1130        Self {
1131            type_id_json,
1132            iid,
1133            keys,
1134            fields: None,
1135            exact_reference: true,
1136            origin: ReferenceOrigin::default(),
1137        }
1138    }
1139
1140    /// Convert one complete nonrecursive hydrated entity row into exact role-player evidence.
1141    #[must_use]
1142    pub fn from_complete_row(row: HydratedRow) -> Self {
1143        Self::from_complete_row_with_keys(row, Vec::new())
1144    }
1145
1146    #[must_use]
1147    pub(crate) fn from_complete_row_with_keys(
1148        row: HydratedRow,
1149        keys: Vec<(String, EncodedScalar)>,
1150    ) -> Self {
1151        debug_assert!(row.roles.is_empty());
1152        Self {
1153            type_id_json: row.type_id_json,
1154            iid: Some(row.iid),
1155            keys,
1156            fields: Some(row.fields),
1157            exact_reference: false,
1158            origin: row.origin,
1159        }
1160    }
1161
1162    #[must_use]
1163    pub(crate) fn from_owned_with_origin(
1164        type_id_json: String,
1165        iid: Option<String>,
1166        keys: Vec<(String, EncodedScalar)>,
1167        origin: ReferenceOrigin,
1168    ) -> Self {
1169        Self {
1170            type_id_json,
1171            iid,
1172            keys,
1173            fields: None,
1174            exact_reference: true,
1175            origin,
1176        }
1177    }
1178
1179    #[must_use]
1180    pub fn type_id_json(&self) -> &str {
1181        &self.type_id_json
1182    }
1183
1184    #[must_use]
1185    pub fn iid(&self) -> Option<&str> {
1186        self.iid.as_deref()
1187    }
1188
1189    #[must_use]
1190    pub fn keys(&self) -> &[(String, EncodedScalar)] {
1191        &self.keys
1192    }
1193
1194    /// Return complete nonrecursive field evidence, or `None` for reference-form players.
1195    #[must_use]
1196    pub fn fields(&self) -> Option<&[(String, Vec<EncodedScalar>)]> {
1197        self.fields.as_deref()
1198    }
1199
1200    #[must_use]
1201    pub(crate) const fn is_exact_reference(&self) -> bool {
1202        self.exact_reference
1203    }
1204
1205    /// Reconstruct the complete nonrecursive hydrated row carried by this player.
1206    #[must_use]
1207    pub fn complete_row(&self) -> Option<HydratedRow> {
1208        Some(HydratedRow {
1209            type_id_json: self.type_id_json.clone(),
1210            iid: self.iid.clone()?,
1211            fields: self.fields.clone()?,
1212            roles: Vec::new(),
1213            origin: self.origin.clone(),
1214        })
1215    }
1216
1217    #[doc(hidden)]
1218    #[must_use]
1219    pub const fn origin(&self) -> &ReferenceOrigin {
1220        &self.origin
1221    }
1222}
1223
1224/// A transport-neutral hydrated thing with exact concrete type identity, mandatory IID, owned-value evidence, and role-player evidence.
1225#[doc(hidden)]
1226#[derive(Clone, Debug, PartialEq)]
1227pub struct HydratedRow {
1228    type_id_json: String,
1229    iid: String,
1230    fields: Vec<(String, Vec<EncodedScalar>)>,
1231    roles: Vec<(String, Vec<HydratedPlayer>)>,
1232    origin: ReferenceOrigin,
1233}
1234
1235impl HydratedRow {
1236    #[must_use]
1237    pub fn new(
1238        type_id_json: &'static str,
1239        iid: String,
1240        fields: Vec<(&'static str, Vec<EncodedScalar>)>,
1241        roles: Vec<(&'static str, Vec<HydratedPlayer>)>,
1242    ) -> Self {
1243        Self {
1244            type_id_json: type_id_json.to_owned(),
1245            iid,
1246            fields: fields
1247                .into_iter()
1248                .map(|(identity, values)| (identity.to_owned(), values))
1249                .collect(),
1250            roles: roles
1251                .into_iter()
1252                .map(|(identity, players)| (identity.to_owned(), players))
1253                .collect(),
1254            origin: ReferenceOrigin::default(),
1255        }
1256    }
1257
1258    #[must_use]
1259    #[allow(dead_code)]
1260    pub(crate) fn from_owned(
1261        type_id_json: String,
1262        iid: String,
1263        fields: Vec<(String, Vec<EncodedScalar>)>,
1264        roles: Vec<(String, Vec<HydratedPlayer>)>,
1265    ) -> Self {
1266        Self {
1267            type_id_json,
1268            iid,
1269            fields,
1270            roles,
1271            origin: ReferenceOrigin::default(),
1272        }
1273    }
1274
1275    #[must_use]
1276    pub(crate) fn from_owned_with_origin(
1277        type_id_json: String,
1278        iid: String,
1279        fields: Vec<(String, Vec<EncodedScalar>)>,
1280        roles: Vec<(String, Vec<HydratedPlayer>)>,
1281        origin: ReferenceOrigin,
1282    ) -> Self {
1283        Self {
1284            type_id_json,
1285            iid,
1286            fields,
1287            roles,
1288            origin,
1289        }
1290    }
1291
1292    pub(crate) fn mark_detached_snapshot(&mut self) {
1293        self.origin = ReferenceOrigin::detached_snapshot();
1294    }
1295
1296    #[must_use]
1297    pub fn type_id_json(&self) -> &str {
1298        &self.type_id_json
1299    }
1300
1301    #[must_use]
1302    pub fn iid(&self) -> &str {
1303        &self.iid
1304    }
1305
1306    #[must_use]
1307    pub fn fields(&self) -> &[(String, Vec<EncodedScalar>)] {
1308        &self.fields
1309    }
1310
1311    #[must_use]
1312    pub fn roles(&self) -> &[(String, Vec<HydratedPlayer>)] {
1313        &self.roles
1314    }
1315
1316    #[doc(hidden)]
1317    #[must_use]
1318    pub const fn origin(&self) -> &ReferenceOrigin {
1319        &self.origin
1320    }
1321
1322    pub fn validate_shape(
1323        &self,
1324        expected_type_id: &'static str,
1325        expected_fields: &[&'static str],
1326        expected_roles: &[&'static str],
1327        path: &ValidationPath,
1328    ) -> Result<(), ValidationError> {
1329        if self.type_id_json != expected_type_id {
1330            return Err(ValidationError::new(
1331                path.path(),
1332                "wrong_concrete_model_type",
1333            ));
1334        }
1335        let mut seen_fields = std::collections::BTreeSet::new();
1336        for (k, _) in &self.fields {
1337            if !seen_fields.insert(k.as_str()) {
1338                return Err(ValidationError::new(
1339                    path.path(),
1340                    "duplicate_scalar_evidence",
1341                ));
1342            }
1343            if !expected_fields.contains(&k.as_str()) {
1344                return Err(ValidationError::new(
1345                    path.path(),
1346                    "unexpected_field_evidence",
1347                ));
1348            }
1349        }
1350        let mut seen_roles = std::collections::BTreeSet::new();
1351        for (r, _) in &self.roles {
1352            if !seen_roles.insert(r.as_str()) {
1353                return Err(ValidationError::new(path.path(), "duplicate_role_evidence"));
1354            }
1355            if !expected_roles.contains(&r.as_str()) {
1356                return Err(ValidationError::new(
1357                    path.path(),
1358                    "unexpected_role_evidence",
1359                ));
1360            }
1361        }
1362        Ok(())
1363    }
1364}
1365
1366/// Lowering trait from a generated create payload into an `EncodedCreate`.
1367#[doc(hidden)]
1368pub trait IntoEncodedCreate: sealed::Sealed {
1369    fn into_encoded_create(self) -> Result<EncodedCreate, ValidationError>;
1370}
1371
1372/// Materializing trait for one exact generated create type from canonical evidence.
1373#[doc(hidden)]
1374pub trait MaterializeCreate: IntoEncodedCreate + Sized {
1375    type Schema: crate::schema::Schema;
1376    fn materialize_create(
1377        value: &DecodedCreate,
1378        path: &ValidationPath,
1379    ) -> Result<Self, ValidationError>;
1380}
1381
1382/// Materializing trait for one exact generated reference type from detached evidence.
1383#[doc(hidden)]
1384pub trait MaterializeReference: IntoEncodedReference + Sized {
1385    type Schema: crate::schema::Schema;
1386    fn materialize_reference(
1387        value: &HydratedPlayer,
1388        path: &ValidationPath,
1389    ) -> Result<Self, ValidationError>;
1390}
1391
1392/// Owned canonical create evidence used only at generated nominal decode.
1393#[doc(hidden)]
1394#[derive(Clone, Debug, PartialEq)]
1395pub struct DecodedCreate {
1396    type_id_json: String,
1397    fields: Vec<(String, Vec<EncodedScalar>)>,
1398    roles: Vec<(String, Vec<HydratedPlayer>)>,
1399}
1400
1401impl DecodedCreate {
1402    #[must_use]
1403    pub fn new(
1404        type_id_json: String,
1405        fields: Vec<(String, Vec<EncodedScalar>)>,
1406        roles: Vec<(String, Vec<HydratedPlayer>)>,
1407    ) -> Self {
1408        Self {
1409            type_id_json,
1410            fields,
1411            roles,
1412        }
1413    }
1414
1415    #[must_use]
1416    pub fn type_id_json(&self) -> &str {
1417        &self.type_id_json
1418    }
1419
1420    #[must_use]
1421    pub fn fields(&self) -> &[(String, Vec<EncodedScalar>)] {
1422        &self.fields
1423    }
1424
1425    #[must_use]
1426    pub fn roles(&self) -> &[(String, Vec<HydratedPlayer>)] {
1427        &self.roles
1428    }
1429}
1430
1431/// Uninhabited create payload for a concrete read model whose schema shape
1432/// cannot be instantiated at its own scope.
1433#[doc(hidden)]
1434#[derive(Clone, Debug, PartialEq)]
1435pub enum UnconstructibleCreate {}
1436
1437impl sealed::Sealed for UnconstructibleCreate {}
1438
1439impl IntoEncodedCreate for UnconstructibleCreate {
1440    fn into_encoded_create(self) -> Result<EncodedCreate, ValidationError> {
1441        match self {}
1442    }
1443}
1444
1445/// Lowering trait from a generated reference into an `EncodedReference`.
1446#[doc(hidden)]
1447pub trait IntoEncodedReference: sealed::Sealed {
1448    fn into_encoded_reference(self) -> Result<EncodedReference, ValidationError>;
1449}
1450
1451/// Materializing trait for generated complete read models or families from a `HydratedRow`.
1452#[doc(hidden)]
1453pub trait MaterializeModel: Model + Sized {
1454    fn materialize(row: &HydratedRow, cap: &HydrationCapability) -> Result<Self, ValidationError>;
1455}
1456
1457/// Lowering trait from one exact generated hydrated model into detached snapshot evidence.
1458#[doc(hidden)]
1459pub trait IntoHydratedSnapshot: Model + Sized {
1460    fn into_hydrated_snapshot(self) -> Result<HydratedRow, ValidationError>;
1461}
1462
1463/// Convert generated encoded reference evidence into a detached hydrated role player.
1464#[doc(hidden)]
1465pub fn hydrated_player_from_encoded_reference(value: EncodedReference) -> HydratedPlayer {
1466    HydratedPlayer::from_reference_owned(
1467        value.type_id_json().to_owned(),
1468        value.iid().map(str::to_owned),
1469        value
1470            .keys()
1471            .iter()
1472            .map(|(identity, scalar)| ((*identity).to_owned(), scalar.clone()))
1473            .collect(),
1474    )
1475}
1476
1477/// A closed subtype family enum representing a concrete descendant closure for a root `Root`.
1478pub trait ModelFamily: sealed::Sealed {
1479    /// The generated root model whose concrete descendants form this family.
1480    type Root: ThingModel;
1481    /// The generated schema that owns the family.
1482    type Schema: Schema;
1483    /// Return the canonical IID of the materialized family member.
1484    fn iid(&self) -> &str;
1485}
1486
1487/// A sealed generated struct value associated with its schema marker `S`.
1488pub trait StructValue: sealed::Sealed {
1489    /// The generated schema that owns this struct value.
1490    type Schema: Schema;
1491    /// Canonical JSON identity for the projected schema struct.
1492    const STRUCT_ID_JSON: &'static str;
1493}
1494
1495/// Lowering trait from one exact generated struct into ordered scalar evidence.
1496#[doc(hidden)]
1497pub trait IntoEncodedStruct: StructValue + Sized {
1498    fn into_encoded_struct(self) -> EncodedStruct;
1499}
1500
1501/// Materializing trait for one exact generated struct from canonical evidence.
1502#[doc(hidden)]
1503pub trait MaterializeStruct: IntoEncodedStruct + Sized {
1504    fn materialize_struct(
1505        value: &DecodedStruct,
1506        path: &ValidationPath,
1507    ) -> Result<Self, ValidationError>;
1508}
1509
1510/// Ordered generated struct evidence before installed-projection validation.
1511#[doc(hidden)]
1512#[derive(Clone, Debug, PartialEq)]
1513pub struct EncodedStruct {
1514    type_id_json: &'static str,
1515    members: Vec<Option<EncodedScalar>>,
1516}
1517
1518impl EncodedStruct {
1519    #[must_use]
1520    pub fn new(type_id_json: &'static str, members: Vec<Option<EncodedScalar>>) -> Self {
1521        Self {
1522            type_id_json,
1523            members,
1524        }
1525    }
1526
1527    #[must_use]
1528    pub const fn type_id_json(&self) -> &'static str {
1529        self.type_id_json
1530    }
1531
1532    #[must_use]
1533    pub fn members(&self) -> &[Option<EncodedScalar>] {
1534        &self.members
1535    }
1536}
1537
1538/// Owned canonical struct evidence used only at generated nominal decode.
1539#[doc(hidden)]
1540#[derive(Clone, Debug, PartialEq)]
1541pub struct DecodedStruct {
1542    type_id_json: String,
1543    members: Vec<Option<EncodedScalar>>,
1544}
1545
1546impl DecodedStruct {
1547    #[must_use]
1548    pub fn new(type_id_json: String, members: Vec<Option<EncodedScalar>>) -> Self {
1549        Self {
1550            type_id_json,
1551            members,
1552        }
1553    }
1554
1555    #[must_use]
1556    pub fn type_id_json(&self) -> &str {
1557        &self.type_id_json
1558    }
1559
1560    #[must_use]
1561    pub fn members(&self) -> &[Option<EncodedScalar>] {
1562        &self.members
1563    }
1564}
1565
1566/// A resolver-proven nominal upcast relation.
1567pub trait NominalUpcast<Target: Model>: Model {}
1568
1569/// A resolver-proven specialized-role upcast relation.
1570pub trait RoleUpcast<ActiveRole, AncestorRole>: Model {}
1571
1572/// Positive generated evidence that one role token is active on a relation.
1573///
1574/// Unlike nominal inheritance, this relation is intentionally subtractive:
1575/// generated specializing relations do not implement compatibility for the
1576/// specialized-away ancestor role.
1577pub trait RoleTokenCompatible<Owner: RelationModel, Players>: RelationModel {}
1578
1579/// Positive generated evidence that a role's exact player union admits one
1580/// bound player model.
1581pub trait RolePlayer<Player: ThingModel> {}
1582
1583/// Positive generated evidence that a role admits one binding mode and model.
1584pub trait RolePlayerBinding<Player: ThingModel, Mode> {}
1585
1586impl<Players, Player> RolePlayerBinding<Player, crate::query::Exact> for Players
1587where
1588    Player: ThingModel,
1589    Players: RolePlayer<Player>,
1590{
1591}
1592
1593/// A schema type/query token branded by its exact owner.
1594#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1595pub struct TypeToken<Owner: Model> {
1596    type_id_json: &'static str,
1597    metadata_json: &'static str,
1598    marker: PhantomData<fn() -> Owner>,
1599}
1600
1601impl<Owner: Model> TypeToken<Owner> {
1602    /// Construct a token from canonical type identity and metadata JSON.
1603    #[must_use]
1604    pub const fn new(type_id_json: &'static str, metadata_json: &'static str) -> Self {
1605        Self {
1606            type_id_json,
1607            metadata_json,
1608            marker: PhantomData,
1609        }
1610    }
1611
1612    /// Return the token's canonical type identity JSON.
1613    #[must_use]
1614    pub const fn type_id_json(self) -> &'static str {
1615        self.type_id_json
1616    }
1617
1618    /// Return the token's canonical metadata JSON.
1619    #[must_use]
1620    pub const fn metadata_json(self) -> &'static str {
1621        self.metadata_json
1622    }
1623}
1624
1625/// An owned-field token branded by owner and value model.
1626#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1627pub struct FieldToken<Owner: Model, Value> {
1628    owns_id_json: &'static str,
1629    metadata_json: &'static str,
1630    marker: PhantomData<fn() -> (Owner, Value)>,
1631}
1632
1633impl<Owner: Model, Value> FieldToken<Owner, Value> {
1634    /// Construct a token from canonical owns-edge identity and metadata JSON.
1635    #[must_use]
1636    pub const fn new(owns_id_json: &'static str, metadata_json: &'static str) -> Self {
1637        Self {
1638            owns_id_json,
1639            metadata_json,
1640            marker: PhantomData,
1641        }
1642    }
1643
1644    /// Return the token's canonical owns-edge identity JSON.
1645    #[must_use]
1646    pub const fn owns_id_json(self) -> &'static str {
1647        self.owns_id_json
1648    }
1649
1650    /// Return the token's canonical metadata JSON.
1651    #[must_use]
1652    pub const fn metadata_json(self) -> &'static str {
1653        self.metadata_json
1654    }
1655
1656    /// Borrow both canonical evidence fragments without consuming the token.
1657    pub(crate) const fn evidence_json(&self) -> (&'static str, &'static str) {
1658        (self.owns_id_json, self.metadata_json)
1659    }
1660}
1661
1662/// A related-role token branded by owner and exact accepted-player enum.
1663#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1664pub struct RoleToken<Owner: Model, Players> {
1665    role_id_json: &'static str,
1666    metadata_json: &'static str,
1667    marker: PhantomData<fn() -> (Owner, Players)>,
1668}
1669
1670impl<Owner: Model, Players> RoleToken<Owner, Players> {
1671    /// Construct a token from canonical role identity and metadata JSON.
1672    #[must_use]
1673    pub const fn new(role_id_json: &'static str, metadata_json: &'static str) -> Self {
1674        Self {
1675            role_id_json,
1676            metadata_json,
1677            marker: PhantomData,
1678        }
1679    }
1680
1681    /// Return the token's canonical role identity JSON.
1682    #[must_use]
1683    pub const fn role_id_json(self) -> &'static str {
1684        self.role_id_json
1685    }
1686
1687    /// Return the token's canonical metadata JSON.
1688    #[must_use]
1689    pub const fn metadata_json(self) -> &'static str {
1690        self.metadata_json
1691    }
1692}
1693
1694/// A playing-fact token branded by player, role owner, and accepted-player enum.
1695#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1696pub struct PlaysToken<Player: Model, Owner: Model, Players> {
1697    plays_id_json: &'static str,
1698    metadata_json: &'static str,
1699    #[allow(clippy::type_complexity)]
1700    marker: PhantomData<fn() -> (Player, Owner, Players)>,
1701}
1702
1703impl<Player: Model, Owner: Model, Players> PlaysToken<Player, Owner, Players> {
1704    /// Construct a token from canonical playing identity and metadata JSON.
1705    #[must_use]
1706    pub const fn new(plays_id_json: &'static str, metadata_json: &'static str) -> Self {
1707        Self {
1708            plays_id_json,
1709            metadata_json,
1710            marker: PhantomData,
1711        }
1712    }
1713
1714    /// Return the token's canonical playing identity JSON.
1715    #[must_use]
1716    pub const fn plays_id_json(self) -> &'static str {
1717        self.plays_id_json
1718    }
1719
1720    /// Return the token's canonical metadata JSON.
1721    #[must_use]
1722    pub const fn metadata_json(self) -> &'static str {
1723        self.metadata_json
1724    }
1725}
1726
1727/// A typed schema-function token branded by schema `S`.
1728#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1729pub struct FunctionToken<S: Schema, Arguments, Output> {
1730    function_id: &'static str,
1731    metadata_json: &'static str,
1732    marker: PhantomData<fn(Arguments) -> (S, Output)>,
1733}
1734
1735impl<S: Schema, Arguments, Output> FunctionToken<S, Arguments, Output> {
1736    /// Construct a token from a function identifier and canonical metadata JSON.
1737    #[must_use]
1738    pub const fn new(function_id: &'static str, metadata_json: &'static str) -> Self {
1739        Self {
1740            function_id,
1741            metadata_json,
1742            marker: PhantomData,
1743        }
1744    }
1745
1746    /// Return the schema function identifier to the generated query facade.
1747    ///
1748    /// Application code invokes the nominal generated function wrapper rather
1749    /// than resolving a function by this provider-facing spelling.
1750    #[doc(hidden)]
1751    #[must_use]
1752    pub const fn __function_id(self) -> &'static str {
1753        self.function_id
1754    }
1755
1756    /// Return the token's canonical metadata JSON.
1757    #[must_use]
1758    pub const fn metadata_json(self) -> &'static str {
1759        self.metadata_json
1760    }
1761}
1762
1763/// A typed asynchronous stream result marker.
1764#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1765pub struct Stream<T>(PhantomData<fn() -> T>);
1766
1767#[cfg(test)]
1768mod tests {
1769    use super::*;
1770
1771    #[test]
1772    fn canonical_scalar_wrappers_do_not_expose_lexical_ordering() {
1773        let source = include_str!("__codegen.rs");
1774        let (_, macro_and_invocations) = source
1775            .split_once("macro_rules! canonical_scalar")
1776            .expect("canonical scalar macro remains present");
1777        let (macro_body, _) = macro_and_invocations
1778            .split_once("canonical_scalar!(\n    Decimal")
1779            .expect("canonical scalar invocations remain present");
1780        assert!(macro_body.contains("#[derive(Clone, Debug, Eq, PartialEq, Hash)]"));
1781        assert!(!macro_body.contains("Ord"));
1782        assert!(!macro_body.contains("PartialOrd"));
1783    }
1784
1785    #[test]
1786    fn scalar_domain_wrappers_and_validation() {
1787        let path = ValidationPath::root().join("test");
1788
1789        let double_ok = CanonicalDouble::try_new(3.125).unwrap();
1790        assert_eq!(double_ok.get(), 3.125);
1791        assert_eq!(
1792            CanonicalDouble::try_new(f64::NAN).unwrap_err().code(),
1793            "noncanonical_double"
1794        );
1795        assert_eq!(
1796            CanonicalDouble::try_new(f64::INFINITY).unwrap_err().code(),
1797            "noncanonical_double"
1798        );
1799        let neg_zero = CanonicalDouble::try_new(-0.0).unwrap();
1800        let pos_zero = CanonicalDouble::try_new(0.0).unwrap();
1801        assert_ne!(neg_zero.to_bits(), pos_zero.to_bits());
1802
1803        let dec = Decimal::try_new("123.45").unwrap();
1804        assert_eq!(dec.as_str(), "123.45");
1805        assert_eq!(
1806            Decimal::try_new("123.4500").unwrap_err().code(),
1807            "noncanonical_decimal"
1808        );
1809
1810        let date = Date::try_new("2026-07-28").unwrap();
1811        assert_eq!(date.as_str(), "2026-07-28");
1812        assert_eq!(
1813            Date::try_new("2026-7-28").unwrap_err().code(),
1814            "noncanonical_date"
1815        );
1816
1817        let dt = DateTime::try_new("2026-07-28T03:55:00").unwrap();
1818        assert_eq!(dt.as_str(), "2026-07-28T03:55:00");
1819
1820        let dtz = DateTimeTz::try_new("2026-07-28T03:55:00Z").unwrap();
1821        assert_eq!(dtz.as_str(), "2026-07-28T03:55:00Z");
1822        let named = DateTimeTz::try_new("2026-07-28T03:55:00[Europe/Amsterdam]").unwrap();
1823        assert_eq!(named.as_str(), "2026-07-28T03:55:00[Europe/Amsterdam]");
1824
1825        let overlap_earlier = type_bridge_schema::parse_provider_datetime_tz_evidence(
1826            "2024-10-27T01:30:00+01:00[Europe/London]",
1827        )
1828        .unwrap();
1829        let overlap_later = type_bridge_schema::parse_provider_datetime_tz_evidence(
1830            "2024-10-27T01:30:00Z[Europe/London]",
1831        )
1832        .unwrap();
1833        let earlier = DateTimeTz::from_canonical(overlap_earlier.clone());
1834        let later = DateTimeTz::from_canonical(overlap_later.clone());
1835        assert_eq!(earlier.as_str(), later.as_str());
1836        assert_ne!(earlier, later);
1837        assert_eq!(
1838            EncodedScalar::DateTimeTz(earlier)
1839                .to_canonical_value(&path)
1840                .unwrap(),
1841            type_bridge_contract::value::CanonicalValue::DateTimeTz(overlap_earlier)
1842        );
1843        assert_eq!(
1844            EncodedScalar::DateTimeTz(later)
1845                .to_canonical_value(&path)
1846                .unwrap(),
1847            type_bridge_contract::value::CanonicalValue::DateTimeTz(overlap_later)
1848        );
1849
1850        let dur = Duration::try_new("P1D").unwrap();
1851        assert_eq!(dur.as_str(), "P1D");
1852
1853        let seq = Sequence::try_new(vec![1, 2], Cardinality::new(1, Some(3)), &path).unwrap();
1854        assert_eq!(seq.as_slice(), &[1, 2]);
1855
1856        let seq_err = Sequence::try_new(vec![1, 2, 3, 4], Cardinality::new(1, Some(3)), &path);
1857        assert_eq!(seq_err.unwrap_err().code(), "cardinality_violation");
1858
1859        let constraint = ConstraintDescriptor::new(
1860            Some(EncodedScalar::String("a".to_owned())),
1861            Some(EncodedScalar::String("z".to_owned())),
1862            Some("^a.*z$"),
1863            None,
1864        );
1865        let s_val = EncodedScalar::String("abcz".to_owned());
1866        assert_eq!(constraint.validate(&s_val, &path), Ok(()));
1867    }
1868}