1use std::error::Error;
8use std::fmt;
9
10use crate::conform::{ConformanceError, Shape, VariantShape, conform_slot};
11use crate::{DecodeRole, SlotValue};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct TypeDescriptor(Repr);
16
17macro_rules! descriptor_model {
18 ($($constructor:ident => $variant:ident : $doc:literal),+ $(,)?) => {
19 #[derive(Debug, Clone, PartialEq, Eq)]
20 enum Repr {
21 $($variant,)+
22 Secret(Box<TypeDescriptor>),
23 Optional(Box<TypeDescriptor>),
24 TriState(Box<TypeDescriptor>),
25 List(Box<TypeDescriptor>),
26 Map(Box<TypeDescriptor>),
27 Struct(Vec<FieldDescriptor>),
28 Enum(Vec<VariantDescriptor>),
29 }
30
31 impl TypeDescriptor {
32 $(
33 #[doc = concat!("Constructs the ", $doc, " descriptor.")]
34 pub fn $constructor() -> Self { Self(Repr::$variant) }
35 )+
36
37 pub fn view(&self) -> DescriptorRef<'_> {
39 match &self.0 {
40 $(Repr::$variant => DescriptorRef::$variant,)+
41 Repr::Secret(inner) => DescriptorRef::Secret(inner),
42 Repr::Optional(inner) => DescriptorRef::Optional(inner),
43 Repr::TriState(inner) => DescriptorRef::TriState(inner),
44 Repr::List(inner) => DescriptorRef::List(inner),
45 Repr::Map(inner) => DescriptorRef::Map(inner),
46 Repr::Struct(fields) => DescriptorRef::Struct(fields),
47 Repr::Enum(variants) => DescriptorRef::Enum(variants),
48 }
49 }
50 }
51
52 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
54 pub enum DescriptorRef<'a> {
55 $(#[doc = concat!("The ", $doc, " descriptor.")] $variant,)+
56 Secret(&'a TypeDescriptor),
58 Optional(&'a TypeDescriptor),
60 TriState(&'a TypeDescriptor),
62 List(&'a TypeDescriptor),
64 Map(&'a TypeDescriptor),
66 Struct(&'a [FieldDescriptor]),
68 Enum(&'a [VariantDescriptor]),
70 }
71 };
72}
73
74descriptor_model!(
75 bool => Bool: "boolean", i8 => I8: "signed 8-bit integer",
76 i16 => I16: "signed 16-bit integer", i32 => I32: "signed 32-bit integer",
77 i64 => I64: "signed 64-bit integer", u8 => U8: "unsigned 8-bit integer",
78 u16 => U16: "unsigned 16-bit integer", u32 => U32: "unsigned 32-bit integer",
79 u64 => U64: "unsigned 64-bit integer", f32 => F32: "32-bit float",
80 f64 => F64: "64-bit float", string => String: "UTF-8 string",
81 blob => Blob: "owned byte string",
82);
83
84macro_rules! unary_descriptors {
85 ($($name:ident => $variant:ident, $check:ident, $error:ident, $doc:literal);+ $(;)?) => {$(
86 #[doc = $doc]
87 pub fn $name(inner: Self) -> Result<Self, DescriptorError> {
88 if $check(&inner.0) { Err(DescriptorError::$error) }
89 else { Ok(Self(Repr::$variant(Box::new(inner)))) }
90 }
91 )+};
92}
93
94impl TypeDescriptor {
95 unary_descriptors!(
96 secret => Secret, is_tri_state, TriStateSecretInner, "Constructs a sensitive value descriptor.";
97 optional => Optional, is_presence, NestedPresence, "Constructs an optional descriptor.";
98 tri_state => TriState, is_presence, NestedPresence, "Constructs a tri-state descriptor.";
99 list => List, is_tri_state, TriStateListElement, "Constructs a list descriptor.";
100 map => Map, is_tri_state, TriStateMapValue, "Constructs a string-keyed map descriptor.";
101 );
102
103 pub fn structure(
105 fields: impl IntoIterator<Item = FieldDescriptor>,
106 ) -> Result<Self, DescriptorError> {
107 unique(
108 fields,
109 FieldDescriptor::name,
110 DescriptorError::DuplicateField,
111 )
112 .map(|fields| Self(Repr::Struct(fields)))
113 }
114
115 pub fn enumeration(
117 variants: impl IntoIterator<Item = VariantDescriptor>,
118 ) -> Result<Self, DescriptorError> {
119 let variants = unique(
120 variants,
121 VariantDescriptor::tag,
122 DescriptorError::DuplicateVariant,
123 )?;
124 if variants.iter().any(|variant| {
125 matches!(variant.payload(), VariantPayload::Value(inner) if matches!(inner.0, Repr::TriState(_)))
126 }) {
127 Err(DescriptorError::TriStateEnumPayload)
128 } else {
129 Ok(Self(Repr::Enum(variants)))
130 }
131 }
132
133 pub fn conform(
135 &self,
136 role: DecodeRole,
137 slot: SlotValue,
138 ) -> Result<SlotValue, ConformanceError> {
139 conform_slot(&self.to_shape(), role, slot)
140 }
141
142 fn to_shape(&self) -> Shape {
143 match &self.0 {
146 Repr::Bool => Shape::bool(),
147 Repr::I8 | Repr::I16 | Repr::I32 | Repr::I64 => Shape::i64(),
148 Repr::U8 | Repr::U16 | Repr::U32 | Repr::U64 => Shape::u64(),
149 Repr::F32 => Shape::f32(),
150 Repr::F64 => Shape::f64(),
151 Repr::String => Shape::string(),
152 Repr::Blob => Shape::bytes(),
153 Repr::Secret(inner) => Shape::sensitive(inner.to_shape())
154 .expect("validated secret descriptor lowers to a sensitive shape"),
155 Repr::Optional(inner) => Shape::optional(inner.to_shape())
156 .expect("validated optional descriptor lowers to an optional shape"),
157 Repr::TriState(inner) => Shape::tri_state(inner.to_shape())
158 .expect("validated tri-state descriptor lowers to a tri-state shape"),
159 Repr::List(inner) => Shape::list(inner.to_shape())
160 .expect("validated list descriptor lowers to a list shape"),
161 Repr::Map(inner) => Shape::map(inner.to_shape())
162 .expect("validated map descriptor lowers to a map shape"),
163 Repr::Struct(fields) => Shape::structure(
164 fields
165 .iter()
166 .map(|field| (field.name().into(), field.descriptor().to_shape())),
167 )
168 .expect("validated struct descriptor lowers to a struct shape"),
169 Repr::Enum(variants) => Shape::enumeration(variants.iter().map(|variant| {
170 let payload = match variant.payload() {
171 VariantPayload::Unit => VariantShape::Unit,
172 VariantPayload::Value(inner) => VariantShape::Value(inner.to_shape()),
173 };
174 (variant.tag().into(), payload)
175 }))
176 .expect("validated enum descriptor lowers to an enum shape"),
177 }
178 }
179}
180
181fn is_presence(repr: &Repr) -> bool {
182 matches!(repr, Repr::Optional(_) | Repr::TriState(_))
183}
184
185fn is_tri_state(repr: &Repr) -> bool {
186 matches!(repr, Repr::TriState(_))
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct Deprecation {
192 note: Option<String>,
193}
194
195impl Deprecation {
196 pub fn new(note: Option<String>) -> Self {
198 Self { note }
199 }
200
201 pub fn note(&self) -> Option<&str> {
203 self.note.as_deref()
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
209pub enum VariantPayload {
210 Unit,
212 Value(TypeDescriptor),
214}
215
216macro_rules! named_descriptor {
217 ($type:ident, $key:ident, $value:ident : $value_type:ty, $noun:literal) => {
218 #[doc = concat!("One named, ordered ", $noun, ".")]
219 #[derive(Debug, Clone, PartialEq, Eq)]
220 pub struct $type {
221 $key: String,
222 $value: $value_type,
223 deprecation: Option<Deprecation>,
224 }
225
226 impl $type {
227 #[doc = concat!("Constructs a ", $noun, " without imposing a name grammar.")]
228 pub fn new(
229 $key: impl Into<String>,
230 $value: $value_type,
231 deprecation: Option<Deprecation>,
232 ) -> Self {
233 Self {
234 $key: $key.into(),
235 $value,
236 deprecation,
237 }
238 }
239
240 #[doc = concat!("Returns the schema-owned ", $noun, " name.")]
241 pub fn $key(&self) -> &str {
242 &self.$key
243 }
244
245 #[doc = concat!("Returns the ", $noun, " value.")]
246 pub fn $value(&self) -> &$value_type {
247 &self.$value
248 }
249
250 #[doc = concat!("Returns the ", $noun, " deprecation metadata.")]
251 pub fn deprecation(&self) -> Option<&Deprecation> {
252 self.deprecation.as_ref()
253 }
254 }
255 };
256}
257
258named_descriptor!(FieldDescriptor, name, descriptor: TypeDescriptor, "struct field");
259named_descriptor!(VariantDescriptor, tag, payload: VariantPayload, "enum variant");
260
261#[derive(Debug, Clone, PartialEq, Eq)]
263#[non_exhaustive]
264pub enum DescriptorError {
265 NestedPresence,
267 TriStateListElement,
269 TriStateMapValue,
271 TriStateEnumPayload,
273 TriStateSecretInner,
275 DuplicateField(String),
277 DuplicateVariant(String),
279}
280
281impl fmt::Display for DescriptorError {
282 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
283 match self {
284 Self::NestedPresence => formatter.write_str("presence wrappers cannot be nested"),
285 Self::TriStateListElement => formatter.write_str("list elements cannot be tri-state"),
286 Self::TriStateMapValue => formatter.write_str("map values cannot be tri-state"),
287 Self::TriStateEnumPayload => formatter.write_str("enum payloads cannot be tri-state"),
288 Self::TriStateSecretInner => formatter.write_str("secret values cannot be tri-state"),
289 Self::DuplicateField(name) => write!(formatter, "duplicate struct field: {name:?}"),
290 Self::DuplicateVariant(tag) => write!(formatter, "duplicate enum variant: {tag:?}"),
291 }
292 }
293}
294
295impl Error for DescriptorError {}
296
297fn unique<T>(
298 items: impl IntoIterator<Item = T>,
299 name: impl Fn(&T) -> &str,
300 duplicate: impl Fn(String) -> DescriptorError,
301) -> Result<Vec<T>, DescriptorError> {
302 let mut result = Vec::new();
303 for item in items {
304 if result.iter().any(|known| name(known) == name(&item)) {
305 return Err(duplicate(name(&item).into()));
306 }
307 result.push(item);
308 }
309 Ok(result)
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use crate::{
316 Blob, ConformanceErrorKind, ContractType, ContractValue, DecodeErrorKind, PathSegment,
317 ValueRef,
318 };
319
320 fn field(name: &str, descriptor: TypeDescriptor) -> FieldDescriptor {
321 FieldDescriptor::new(name, descriptor, None)
322 }
323
324 fn variant(tag: &str, payload: VariantPayload) -> VariantDescriptor {
325 VariantDescriptor::new(tag, payload, None)
326 }
327
328 macro_rules! rejects {
329 ($($value:expr => $error:expr),+ $(,)?) => {$(
330 assert_eq!($value.unwrap_err(), $error);
331 )+};
332 }
333
334 fn view_path(descriptor: &TypeDescriptor) -> String {
335 let (name, inner) = match descriptor.view() {
336 DescriptorRef::List(inner) => ("list", Some(inner)),
337 DescriptorRef::Map(inner) => ("map", Some(inner)),
338 DescriptorRef::Optional(inner) => ("optional", Some(inner)),
339 DescriptorRef::Secret(inner) => ("secret", Some(inner)),
340 DescriptorRef::Blob => ("blob", None),
341 _ => panic!(),
342 };
343 inner.map_or_else(
344 || name.into(),
345 |inner| format!("{name}/{}", view_path(inner)),
346 )
347 }
348
349 #[test]
350 fn recursive_public_view_exposes_owned_children() {
351 let descriptor = TypeDescriptor::list(
352 TypeDescriptor::map(
353 TypeDescriptor::optional(TypeDescriptor::secret(TypeDescriptor::blob()).unwrap())
354 .unwrap(),
355 )
356 .unwrap(),
357 )
358 .unwrap();
359 assert_eq!(view_path(&descriptor), "list/map/optional/secret/blob");
360 }
361
362 #[test]
363 fn every_illegal_position_returns_its_exact_error() {
364 let optional = || TypeDescriptor::optional(TypeDescriptor::bool()).unwrap();
365 let tri_state = || TypeDescriptor::tri_state(TypeDescriptor::bool()).unwrap();
366 rejects!(
367 TypeDescriptor::optional(optional()) => DescriptorError::NestedPresence,
368 TypeDescriptor::optional(tri_state()) => DescriptorError::NestedPresence,
369 TypeDescriptor::tri_state(optional()) => DescriptorError::NestedPresence,
370 TypeDescriptor::tri_state(tri_state()) => DescriptorError::NestedPresence,
371 TypeDescriptor::list(tri_state()) => DescriptorError::TriStateListElement,
372 TypeDescriptor::map(tri_state()) => DescriptorError::TriStateMapValue,
373 TypeDescriptor::secret(tri_state()) => DescriptorError::TriStateSecretInner,
374 TypeDescriptor::enumeration([variant("bad", VariantPayload::Value(tri_state()))])
375 => DescriptorError::TriStateEnumPayload,
376 );
377 }
378
379 #[test]
380 fn duplicate_names_return_exact_errors() {
381 rejects!(
382 TypeDescriptor::structure([
383 field("x", TypeDescriptor::i8()), field("x", TypeDescriptor::i16())
384 ]) => DescriptorError::DuplicateField("x".into()),
385 TypeDescriptor::enumeration([
386 variant("x", VariantPayload::Unit), variant("x", VariantPayload::Unit)
387 ]) => DescriptorError::DuplicateVariant("x".into()),
388 );
389 assert_eq!(
390 DescriptorError::DuplicateField("field".into()).to_string(),
391 "duplicate struct field: \"field\""
392 );
393 assert_eq!(
394 DescriptorError::DuplicateVariant("variant".into()).to_string(),
395 "duplicate enum variant: \"variant\""
396 );
397 }
398
399 #[test]
400 fn legal_edges_metadata_and_public_bounds_are_preserved() {
401 macro_rules! views { ($($name:ident => $variant:ident),+) => {$(
402 assert!(matches!(TypeDescriptor::$name().view(), DescriptorRef::$variant));
403 )+} }
404 views!(
405 bool => Bool, i8 => I8, i16 => I16, i32 => I32, i64 => I64,
406 u8 => U8, u16 => U16, u32 => U32, u64 => U64,
407 f32 => F32, f64 => F64, string => String, blob => Blob
408 );
409 let optional = TypeDescriptor::optional(TypeDescriptor::string()).unwrap();
410 assert!(TypeDescriptor::secret(optional).is_ok());
411 let secret = TypeDescriptor::secret(TypeDescriptor::string()).unwrap();
412 assert!(TypeDescriptor::optional(secret.clone()).is_ok());
413 assert!(TypeDescriptor::secret(secret).is_ok());
414 assert!(TypeDescriptor::structure([]).is_ok());
415 assert!(TypeDescriptor::enumeration([]).is_ok());
416 assert!(
417 TypeDescriptor::structure([field(
418 "any name!",
419 TypeDescriptor::tri_state(TypeDescriptor::bool()).unwrap(),
420 )])
421 .is_ok()
422 );
423 let deprecation = Deprecation::new(Some("use next".into()));
424 let field =
425 FieldDescriptor::new("field", TypeDescriptor::bool(), Some(deprecation.clone()));
426 let variant = VariantDescriptor::new("variant", VariantPayload::Unit, Some(deprecation));
427 assert_eq!(field.name(), "field");
428 assert_eq!(field.deprecation().unwrap().note(), Some("use next"));
429 assert_eq!(variant.tag(), "variant");
430 assert_eq!(variant.deprecation().unwrap().note(), Some("use next"));
431 assert!(matches!(field.descriptor().view(), DescriptorRef::Bool));
432 assert!(matches!(variant.payload(), VariantPayload::Unit));
433
434 fn bounds<T: Send + Sync + 'static>() {}
435 bounds::<TypeDescriptor>();
436 bounds::<Deprecation>();
437 bounds::<FieldDescriptor>();
438 bounds::<VariantDescriptor>();
439 bounds::<VariantPayload>();
440 bounds::<DescriptorError>();
441 assert_eq!(
442 DescriptorError::NestedPresence.to_string(),
443 "presence wrappers cannot be nested"
444 );
445 }
446
447 fn slot(value: ContractValue) -> SlotValue {
448 SlotValue::Value(value)
449 }
450
451 #[test]
452 fn lowering_preserves_carriers_narrow_widths_and_blob_bytes() {
453 let role = DecodeRole::ProviderInput;
454 let signed = slot(ContractValue::i64(128));
455 for descriptor in [
456 TypeDescriptor::i8(),
457 TypeDescriptor::i16(),
458 TypeDescriptor::i32(),
459 TypeDescriptor::i64(),
460 ] {
461 assert_eq!(descriptor.conform(role, signed.clone()), Ok(signed.clone()));
462 }
463 assert_eq!(
464 i8::decode(&signed).unwrap_err().kind(),
465 &DecodeErrorKind::OutOfRange
466 );
467 let unsigned = slot(ContractValue::u64(u64::MAX));
468 for descriptor in [
469 TypeDescriptor::u8(),
470 TypeDescriptor::u16(),
471 TypeDescriptor::u32(),
472 TypeDescriptor::u64(),
473 ] {
474 assert_eq!(
475 descriptor.conform(role, unsigned.clone()),
476 Ok(unsigned.clone())
477 );
478 }
479 for (descriptor, input) in [
480 (TypeDescriptor::bool(), slot(ContractValue::bool(true))),
481 (
482 TypeDescriptor::f32(),
483 slot(ContractValue::f32(1.5).unwrap()),
484 ),
485 (
486 TypeDescriptor::f64(),
487 slot(ContractValue::f64(2.5).unwrap()),
488 ),
489 (
490 TypeDescriptor::string(),
491 slot(ContractValue::string("text")),
492 ),
493 ] {
494 assert_eq!(descriptor.conform(role, input.clone()), Ok(input));
495 }
496 assert_eq!(
497 TypeDescriptor::f32()
498 .conform(role, slot(ContractValue::f64(1.5).unwrap()))
499 .unwrap_err()
500 .kind(),
501 &ConformanceErrorKind::KindMismatch
502 );
503 let bytes = slot(ContractValue::bytes([0, 1, 255]));
504 assert_eq!(
505 TypeDescriptor::blob().conform(role, bytes.clone()),
506 Ok(bytes.clone())
507 );
508 assert_eq!(Blob::decode(&bytes).unwrap().as_bytes(), &[0, 1, 255]);
509 }
510
511 #[test]
512 fn secret_lowering_is_exact_nullable_nested_and_redacted() {
513 const SENTINEL: &str = "descriptor-secret-sentinel";
514 let role = DecodeRole::ProviderInput;
515 let secret = TypeDescriptor::secret(TypeDescriptor::string()).unwrap();
516 let input = slot(ContractValue::sensitive(ContractValue::string(SENTINEL)));
517 let output = secret.conform(role, input.clone()).unwrap();
518 assert_eq!(output, input);
519 let SlotValue::Value(output) = output else {
520 panic!()
521 };
522 let ValueRef::Sensitive(inner) = output.view() else {
523 panic!()
524 };
525 assert!(matches!(inner.view(), ValueRef::String(SENTINEL)));
526
527 let bare = slot(ContractValue::string(SENTINEL));
528 assert_eq!(
529 secret.conform(role, bare).unwrap_err().kind(),
530 &ConformanceErrorKind::KindMismatch
531 );
532 assert_eq!(
533 TypeDescriptor::string()
534 .conform(role, input.clone())
535 .unwrap_err()
536 .kind(),
537 &ConformanceErrorKind::KindMismatch
538 );
539
540 let nullable = TypeDescriptor::optional(TypeDescriptor::string()).unwrap();
541 let transitive =
542 TypeDescriptor::optional(TypeDescriptor::secret(nullable).unwrap()).unwrap();
543 let sensitive_null = slot(ContractValue::sensitive(ContractValue::null()));
544 assert_eq!(
545 transitive.conform(role, sensitive_null.clone()),
546 Ok(sensitive_null)
547 );
548 let nested = TypeDescriptor::secret(secret).unwrap();
549 let nested_input = slot(ContractValue::sensitive(ContractValue::sensitive(
550 ContractValue::string(SENTINEL),
551 )));
552 assert_eq!(nested.conform(role, nested_input.clone()), Ok(nested_input));
553
554 let error = TypeDescriptor::secret(TypeDescriptor::bool())
555 .unwrap()
556 .conform(role, input)
557 .unwrap_err();
558 assert_eq!(error.kind(), &ConformanceErrorKind::KindMismatch);
559 assert!(!format!("{error:?} {error}").contains(SENTINEL));
560 }
561
562 #[test]
563 fn aggregate_lowering_and_public_errors_are_structural() {
564 let payload = TypeDescriptor::map(
565 TypeDescriptor::list(TypeDescriptor::secret(TypeDescriptor::blob()).unwrap()).unwrap(),
566 )
567 .unwrap();
568 let event = TypeDescriptor::enumeration([
569 variant("idle", VariantPayload::Unit),
570 variant("data", VariantPayload::Value(payload)),
571 ])
572 .unwrap();
573 let descriptor = TypeDescriptor::structure([
574 FieldDescriptor::new(
575 "event",
576 event,
577 Some(Deprecation::new(Some("legacy".into()))),
578 ),
579 field(
580 "state",
581 TypeDescriptor::tri_state(TypeDescriptor::bool()).unwrap(),
582 ),
583 ])
584 .unwrap();
585 let map = ContractValue::object([(
586 "key".into(),
587 ContractValue::list([ContractValue::sensitive(ContractValue::bytes([7]))]),
588 )])
589 .unwrap();
590 let input = slot(
591 ContractValue::object([(
592 "event".into(),
593 ContractValue::enum_value("data", SlotValue::Value(map)),
594 )])
595 .unwrap(),
596 );
597 assert_eq!(
598 descriptor.conform(DecodeRole::ProviderInput, input.clone()),
599 Ok(input)
600 );
601
602 let error = TypeDescriptor::list(TypeDescriptor::bool())
603 .unwrap()
604 .conform(
605 DecodeRole::ProviderInput,
606 slot(ContractValue::list([ContractValue::string("wrong")])),
607 )
608 .unwrap_err();
609 assert_eq!(error.kind(), &ConformanceErrorKind::KindMismatch);
610 assert_eq!(error.path(), &[PathSegment::Index(0)]);
611
612 fn error_bounds<T: std::error::Error + Send + Sync + 'static>() {}
613 fn bounds<T: Send + Sync + 'static>() {}
614 error_bounds::<ConformanceError>();
615 bounds::<ConformanceErrorKind>();
616 }
617
618 fn object(entries: impl IntoIterator<Item = (&'static str, ContractValue)>) -> ContractValue {
619 ContractValue::object(
620 entries
621 .into_iter()
622 .map(|(name, value)| (name.into(), value)),
623 )
624 .unwrap()
625 }
626
627 fn agrees_accepts(descriptor: TypeDescriptor, input: SlotValue) {
628 for role in [DecodeRole::ProviderInput, DecodeRole::ConsumerOutput] {
629 let private = conform_slot(&descriptor.to_shape(), role, input.clone());
630 let public = descriptor.conform(role, input.clone());
631 assert_eq!(public, private);
632 assert_eq!(public, Ok(input.clone()));
633 }
634 }
635
636 fn agrees_rejects(
637 descriptor: TypeDescriptor,
638 input: SlotValue,
639 kind: ConformanceErrorKind,
640 path: &[PathSegment],
641 ) {
642 for role in [DecodeRole::ProviderInput, DecodeRole::ConsumerOutput] {
643 let private = conform_slot(&descriptor.to_shape(), role, input.clone());
644 let public = descriptor.conform(role, input.clone());
645 assert_eq!(public, private);
646 let error = public.unwrap_err();
647 assert_eq!(error.kind(), &kind);
648 assert_eq!(error.path(), path);
649 }
650 }
651
652 fn structure_field(descriptor: TypeDescriptor) -> TypeDescriptor {
653 TypeDescriptor::structure([field("x", descriptor)]).unwrap()
654 }
655
656 #[test]
657 fn public_and_private_presence_grids_agree_in_both_roles() {
658 let integer = || ContractValue::i64(7);
659 let value = || slot(integer());
660 let optional = || TypeDescriptor::optional(TypeDescriptor::i64()).unwrap();
661 let tri_state = || TypeDescriptor::tri_state(TypeDescriptor::i64()).unwrap();
662
663 agrees_rejects(
664 TypeDescriptor::i64(),
665 SlotValue::Missing,
666 ConformanceErrorKind::MissingRequired,
667 &[],
668 );
669 agrees_rejects(
670 TypeDescriptor::i64(),
671 SlotValue::Null,
672 ConformanceErrorKind::UnexpectedNull,
673 &[],
674 );
675 agrees_accepts(TypeDescriptor::i64(), value());
676 agrees_rejects(
677 optional(),
678 SlotValue::Missing,
679 ConformanceErrorKind::UnexpectedMissing,
680 &[],
681 );
682 agrees_accepts(optional(), SlotValue::Null);
683 agrees_accepts(optional(), value());
684 agrees_accepts(tri_state(), SlotValue::Missing);
685 agrees_accepts(tri_state(), SlotValue::Null);
686 agrees_accepts(tri_state(), value());
687
688 let field_path = [PathSegment::Field("x".into())];
689 agrees_rejects(
690 structure_field(TypeDescriptor::i64()),
691 slot(object([])),
692 ConformanceErrorKind::MissingRequired,
693 &field_path,
694 );
695 agrees_rejects(
696 structure_field(TypeDescriptor::i64()),
697 slot(object([("x", ContractValue::null())])),
698 ConformanceErrorKind::UnexpectedNull,
699 &field_path,
700 );
701 agrees_accepts(
702 structure_field(TypeDescriptor::i64()),
703 slot(object([("x", integer())])),
704 );
705 agrees_accepts(structure_field(optional()), slot(object([])));
706 agrees_rejects(
707 structure_field(optional()),
708 slot(object([("x", ContractValue::null())])),
709 ConformanceErrorKind::UnexpectedNull,
710 &field_path,
711 );
712 agrees_accepts(
713 structure_field(optional()),
714 slot(object([("x", integer())])),
715 );
716 agrees_accepts(structure_field(tri_state()), slot(object([])));
717 agrees_accepts(
718 structure_field(tri_state()),
719 slot(object([("x", ContractValue::null())])),
720 );
721 agrees_accepts(
722 structure_field(tri_state()),
723 slot(object([("x", integer())])),
724 );
725
726 let index_path = [PathSegment::Index(0)];
727 agrees_accepts(
728 TypeDescriptor::list(TypeDescriptor::i64()).unwrap(),
729 slot(ContractValue::list([integer()])),
730 );
731 agrees_rejects(
732 TypeDescriptor::list(TypeDescriptor::i64()).unwrap(),
733 slot(ContractValue::list([ContractValue::null()])),
734 ConformanceErrorKind::UnexpectedNull,
735 &index_path,
736 );
737 agrees_accepts(
738 TypeDescriptor::list(optional()).unwrap(),
739 slot(ContractValue::list([ContractValue::null(), integer()])),
740 );
741 let key_path = [PathSegment::MapKey("x".into())];
742 agrees_rejects(
743 TypeDescriptor::map(TypeDescriptor::i64()).unwrap(),
744 slot(object([("x", ContractValue::null())])),
745 ConformanceErrorKind::UnexpectedNull,
746 &key_path,
747 );
748 agrees_accepts(
749 TypeDescriptor::map(optional()).unwrap(),
750 slot(object([("x", ContractValue::null())])),
751 );
752 agrees_accepts(
753 structure_field(TypeDescriptor::list(optional()).unwrap()),
754 slot(object([(
755 "x",
756 ContractValue::list([ContractValue::null(), integer()]),
757 )])),
758 );
759
760 let enum_slot = |payload| slot(ContractValue::enum_value("event", payload));
761 let variant_path = [PathSegment::Variant("event".into())];
762 let unit =
763 || TypeDescriptor::enumeration([variant("event", VariantPayload::Unit)]).unwrap();
764 agrees_accepts(unit(), enum_slot(SlotValue::Null));
765 agrees_rejects(
766 unit(),
767 enum_slot(SlotValue::Missing),
768 ConformanceErrorKind::UnexpectedMissing,
769 &variant_path,
770 );
771 agrees_rejects(
772 unit(),
773 enum_slot(value()),
774 ConformanceErrorKind::UnexpectedPayload,
775 &variant_path,
776 );
777 let required = || {
778 TypeDescriptor::enumeration([variant(
779 "event",
780 VariantPayload::Value(TypeDescriptor::i64()),
781 )])
782 .unwrap()
783 };
784 agrees_rejects(
785 required(),
786 enum_slot(SlotValue::Missing),
787 ConformanceErrorKind::MissingRequired,
788 &variant_path,
789 );
790 agrees_rejects(
791 required(),
792 enum_slot(SlotValue::Null),
793 ConformanceErrorKind::UnexpectedNull,
794 &variant_path,
795 );
796 agrees_accepts(required(), enum_slot(value()));
797 let optional_variant = || {
798 TypeDescriptor::enumeration([variant("event", VariantPayload::Value(optional()))])
799 .unwrap()
800 };
801 agrees_rejects(
802 optional_variant(),
803 enum_slot(SlotValue::Missing),
804 ConformanceErrorKind::UnexpectedMissing,
805 &variant_path,
806 );
807 agrees_accepts(optional_variant(), enum_slot(SlotValue::Null));
808 agrees_accepts(optional_variant(), enum_slot(value()));
809 }
810
811 #[test]
812 fn public_struct_conformance_is_strict_or_ordered_and_tolerant() {
813 const SENTINEL: &str = "public-unknown-field-sentinel";
814 let descriptor = TypeDescriptor::structure([
815 field("a", TypeDescriptor::i64()),
816 field("b", TypeDescriptor::i64()),
817 ])
818 .unwrap();
819 let input = slot(object([
820 ("b", ContractValue::i64(2)),
821 ("extra", ContractValue::string(SENTINEL)),
822 ("a", ContractValue::i64(1)),
823 ]));
824 let error = descriptor
825 .conform(DecodeRole::ProviderInput, input.clone())
826 .unwrap_err();
827 assert_eq!(
828 error.kind(),
829 &ConformanceErrorKind::UnknownField("extra".into())
830 );
831 assert_eq!(error.path(), &[PathSegment::Field("extra".into())]);
832 assert!(!format!("{error:?} {error}").contains(SENTINEL));
833 let output = descriptor
834 .conform(DecodeRole::ConsumerOutput, input)
835 .unwrap();
836 assert_eq!(
837 output,
838 slot(object([
839 ("b", ContractValue::i64(2)),
840 ("a", ContractValue::i64(1)),
841 ]))
842 );
843 }
844
845 #[test]
846 fn aggregate_views_expose_ordered_children_and_every_wrapper() {
847 let deprecated = Deprecation::new(Some("legacy".into()));
848 let enumeration = TypeDescriptor::enumeration([
849 VariantDescriptor::new("unit", VariantPayload::Unit, None),
850 VariantDescriptor::new(
851 "value",
852 VariantPayload::Value(TypeDescriptor::list(TypeDescriptor::i64()).unwrap()),
853 Some(deprecated.clone()),
854 ),
855 ])
856 .unwrap();
857 let descriptor = TypeDescriptor::structure([
858 FieldDescriptor::new(
859 "state",
860 TypeDescriptor::tri_state(TypeDescriptor::bool()).unwrap(),
861 Some(deprecated),
862 ),
863 field("event", enumeration),
864 ])
865 .unwrap();
866 let DescriptorRef::Struct(fields) = descriptor.view() else {
867 panic!()
868 };
869 assert_eq!(
870 fields.iter().map(FieldDescriptor::name).collect::<Vec<_>>(),
871 ["state", "event"]
872 );
873 assert!(matches!(
874 fields[0].descriptor().view(),
875 DescriptorRef::TriState(_)
876 ));
877 assert_eq!(fields[0].deprecation().unwrap().note(), Some("legacy"));
878 let DescriptorRef::Enum(variants) = fields[1].descriptor().view() else {
879 panic!()
880 };
881 assert_eq!(
882 variants
883 .iter()
884 .map(VariantDescriptor::tag)
885 .collect::<Vec<_>>(),
886 ["unit", "value"]
887 );
888 assert!(matches!(variants[0].payload(), VariantPayload::Unit));
889 let VariantPayload::Value(value) = variants[1].payload() else {
890 panic!()
891 };
892 assert!(matches!(value.view(), DescriptorRef::List(_)));
893 assert_eq!(variants[1].deprecation().unwrap().note(), Some("legacy"));
894 }
895
896 #[test]
897 fn descriptor_structural_equality_covers_every_semantic_dimension() {
898 let a = || field("a", TypeDescriptor::i64());
899 let b = || field("b", TypeDescriptor::string());
900 let c = || field("c", TypeDescriptor::string());
901 let ordered = || TypeDescriptor::structure([a(), b()]).unwrap();
902 assert_ne!(TypeDescriptor::bool(), TypeDescriptor::string());
903 assert_ne!(TypeDescriptor::i8(), TypeDescriptor::i16());
904 assert_ne!(
905 TypeDescriptor::list(TypeDescriptor::i8()).unwrap(),
906 TypeDescriptor::list(TypeDescriptor::i16()).unwrap()
907 );
908 assert_ne!(ordered(), TypeDescriptor::structure([b(), a()]).unwrap());
909 assert_ne!(
910 TypeDescriptor::structure([field("a", TypeDescriptor::i64())]).unwrap(),
911 TypeDescriptor::structure([field("renamed", TypeDescriptor::i64())]).unwrap()
912 );
913 let deprecated = |note: &str| Some(Deprecation::new(Some(note.into())));
914 assert_ne!(
915 TypeDescriptor::structure([FieldDescriptor::new(
916 "a",
917 TypeDescriptor::i64(),
918 deprecated("one"),
919 )])
920 .unwrap(),
921 TypeDescriptor::structure([FieldDescriptor::new(
922 "a",
923 TypeDescriptor::i64(),
924 deprecated("two"),
925 )])
926 .unwrap()
927 );
928 let enumeration = |tag: &str, payload, deprecation| {
929 TypeDescriptor::enumeration([VariantDescriptor::new(tag, payload, deprecation)])
930 .unwrap()
931 };
932 assert_ne!(
933 enumeration("a", VariantPayload::Unit, None),
934 enumeration("b", VariantPayload::Unit, None)
935 );
936 assert_ne!(
937 enumeration("a", VariantPayload::Unit, None),
938 enumeration("a", VariantPayload::Value(TypeDescriptor::bool()), None)
939 );
940 assert_ne!(
941 enumeration("a", VariantPayload::Unit, deprecated("one")),
942 enumeration("a", VariantPayload::Unit, deprecated("two"))
943 );
944 let enum_pair = |first, second| {
945 TypeDescriptor::enumeration([
946 variant(first, VariantPayload::Unit),
947 variant(second, VariantPayload::Unit),
948 ])
949 .unwrap()
950 };
951 assert_ne!(enum_pair("a", "b"), enum_pair("b", "a"));
952 let nested = TypeDescriptor::optional(
953 TypeDescriptor::secret(TypeDescriptor::optional(ordered()).unwrap()).unwrap(),
954 )
955 .unwrap();
956 assert_eq!(nested, nested.clone());
957 assert_ne!(
958 nested,
959 TypeDescriptor::optional(
960 TypeDescriptor::secret(
961 TypeDescriptor::optional(TypeDescriptor::structure([a(), c()]).unwrap())
962 .unwrap()
963 )
964 .unwrap()
965 )
966 .unwrap()
967 );
968 }
969}