1use core::fmt;
7use core::marker::PhantomData;
8
9pub use crate::schema::{Schema, SchemaPackage, Unbound, sealed};
10
11#[derive(Clone, Debug, Eq, PartialEq, Default)]
13pub struct ValidationPath {
14 segments: Vec<String>,
15}
16
17impl ValidationPath {
18 #[must_use]
20 pub fn root() -> Self {
21 Self {
22 segments: Vec::new(),
23 }
24 }
25
26 #[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 #[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 #[must_use]
48 pub fn path(&self) -> String {
49 self.segments.join(".")
50 }
51}
52
53#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct ValidationError {
56 path: String,
57 code: String,
58}
59
60impl ValidationError {
61 #[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 #[must_use]
72 pub fn field(&self) -> &str {
73 &self.path
74 }
75
76 #[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97pub struct Cardinality {
98 min: u64,
99 max: Option<u64>,
100}
101
102impl Cardinality {
103 #[must_use]
105 pub const fn new(min: u64, max: Option<u64>) -> Self {
106 Self { min, max }
107 }
108
109 #[must_use]
111 pub const fn min(self) -> u64 {
112 self.min
113 }
114
115 #[must_use]
117 pub const fn max(self) -> Option<u64> {
118 self.max
119 }
120}
121
122#[derive(Clone, Debug, PartialEq)]
124pub struct Required<T>(T);
125
126impl<T> Required<T> {
127 #[must_use]
129 pub const fn new(value: T) -> Self {
130 Self(value)
131 }
132
133 #[must_use]
135 pub const fn get(&self) -> &T {
136 &self.0
137 }
138
139 #[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#[derive(Clone, Debug, PartialEq)]
155pub struct Optional<T>(Option<T>);
156
157impl<T> Optional<T> {
158 #[must_use]
160 pub const fn new(value: Option<T>) -> Self {
161 Self(value)
162 }
163
164 #[must_use]
166 pub const fn as_ref(&self) -> Option<&T> {
167 self.0.as_ref()
168 }
169}
170
171#[derive(Clone, Debug, PartialEq)]
173pub struct Sequence<T> {
174 values: Vec<T>,
175 cardinality: Cardinality,
176}
177
178impl<T> Sequence<T> {
179 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 #[must_use]
203 pub fn as_slice(&self) -> &[T] {
204 &self.values
205 }
206
207 #[must_use]
209 pub const fn cardinality(&self) -> Cardinality {
210 self.cardinality
211 }
212}
213
214#[derive(Clone, Debug, PartialEq)]
216pub enum Either<L, R> {
217 Left(L),
219 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#[derive(Clone, Debug, PartialEq)]
232pub enum Never {}
233
234#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
236pub struct CanonicalDouble(u64);
237
238impl CanonicalDouble {
239 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 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 #[must_use]
266 pub fn get(self) -> f64 {
267 f64::from_bits(self.0)
268 }
269
270 #[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 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 #[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#[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#[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#[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#[doc(hidden)]
478pub trait QueryValued: IntoEncodedScalar {
479 type Domain;
481}
482
483#[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
523pub enum ThingKind {
524 Entity,
526 Relation,
528}
529
530pub trait Model: sealed::Sealed {
532 type Schema: Schema;
534 const TYPE_ID_JSON: &'static str;
536}
537
538pub trait ThingModel: Model {
540 fn thing_kind() -> ThingKind;
542}
543
544pub trait EntityModel: ThingModel {}
546
547pub trait RelationModel: ThingModel {}
549
550pub trait CompleteModel: ThingModel + MaterializeModel {
552 type Create: IntoEncodedCreate + Clone;
554 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
567pub trait AbstractModel: ThingModel {}
569
570pub trait TextValued {}
572impl TextValued for String {}
573
574pub trait OrderedValued {}
577
578pub trait NumericValued {
581 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
598pub trait ReferenceModel: ThingModel {
600 fn iid(&self) -> Option<&str>;
602}
603
604#[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
733pub 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
748pub 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#[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#[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#[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#[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#[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#[doc(hidden)]
1121pub trait IntoEncodedCreate: sealed::Sealed {
1122 fn into_encoded_create(self) -> Result<EncodedCreate, ValidationError>;
1123}
1124
1125#[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#[doc(hidden)]
1141pub trait IntoEncodedReference: sealed::Sealed {
1142 fn into_encoded_reference(self) -> Result<EncodedReference, ValidationError>;
1143}
1144
1145#[doc(hidden)]
1147pub trait MaterializeModel: Model + Sized {
1148 fn materialize(row: &HydratedRow, cap: &HydrationCapability) -> Result<Self, ValidationError>;
1149}
1150
1151pub trait ModelFamily: sealed::Sealed {
1153 type Root: ThingModel;
1155 type Schema: Schema;
1157 fn iid(&self) -> &str;
1159}
1160
1161pub trait StructValue: sealed::Sealed {
1163 type Schema: Schema;
1165 const STRUCT_ID_JSON: &'static str;
1167}
1168
1169pub trait NominalUpcast<Target: Model>: Model {}
1171
1172pub trait RoleUpcast<ActiveRole, AncestorRole>: Model {}
1174
1175pub trait RoleTokenCompatible<Owner: RelationModel, Players>: RelationModel {}
1181
1182pub trait RolePlayer<Player: ThingModel> {}
1185
1186pub 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#[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 #[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 #[must_use]
1217 pub const fn type_id_json(self) -> &'static str {
1218 self.type_id_json
1219 }
1220
1221 #[must_use]
1223 pub const fn metadata_json(self) -> &'static str {
1224 self.metadata_json
1225 }
1226}
1227
1228#[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 #[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 #[must_use]
1249 pub const fn owns_id_json(self) -> &'static str {
1250 self.owns_id_json
1251 }
1252
1253 #[must_use]
1255 pub const fn metadata_json(self) -> &'static str {
1256 self.metadata_json
1257 }
1258}
1259
1260#[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 #[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 #[must_use]
1281 pub const fn role_id_json(self) -> &'static str {
1282 self.role_id_json
1283 }
1284
1285 #[must_use]
1287 pub const fn metadata_json(self) -> &'static str {
1288 self.metadata_json
1289 }
1290}
1291
1292#[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 #[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 #[must_use]
1314 pub const fn plays_id_json(self) -> &'static str {
1315 self.plays_id_json
1316 }
1317
1318 #[must_use]
1320 pub const fn metadata_json(self) -> &'static str {
1321 self.metadata_json
1322 }
1323}
1324
1325#[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 #[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 #[must_use]
1346 pub const fn function_id(self) -> &'static str {
1347 self.function_id
1348 }
1349
1350 #[must_use]
1352 pub const fn metadata_json(self) -> &'static str {
1353 self.metadata_json
1354 }
1355}
1356
1357#[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}