1use std::collections::BTreeMap;
10use std::error::Error;
11use std::fmt;
12
13use crate::{ContractValue, Field, SlotValue, ValueRef};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum PathSegment {
19 Field(String),
20 Index(usize),
21 MapKey(String),
22 Variant(String),
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum EncodeErrorKind {
29 NonFiniteF32,
30 NonFiniteF64,
31 UnsupportedPosition,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36#[non_exhaustive]
37pub enum DecodeErrorKind {
38 MissingRequired,
39 UnexpectedNull,
40 UnexpectedMissing,
41 KindMismatch,
42 OutOfRange,
43 UnexpectedPayload,
44 UnknownField(String),
45 UnknownVariant(String),
46 UnsupportedPosition,
47}
48
49macro_rules! error_type {
50 ($name:ident, $kind:ident) => {
51 #[derive(Debug, Clone, PartialEq, Eq)]
53 pub struct $name {
54 path: Vec<PathSegment>,
55 kind: $kind,
56 }
57
58 impl $name {
59 pub fn new(kind: $kind) -> Self {
60 Self {
61 path: Vec::new(),
62 kind,
63 }
64 }
65
66 pub fn under(mut self, segment: PathSegment) -> Self {
67 self.path.insert(0, segment);
68 self
69 }
70
71 pub fn kind(&self) -> &$kind {
72 &self.kind
73 }
74
75 pub fn path(&self) -> &[PathSegment] {
76 &self.path
77 }
78 }
79
80 impl fmt::Display for $name {
81 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(formatter, "{:?} at {:?}", self.kind, self.path)
83 }
84 }
85
86 impl Error for $name {}
87 };
88}
89
90error_type!(EncodeError, EncodeErrorKind);
91error_type!(DecodeError, DecodeErrorKind);
92
93pub trait ContractType: Sized {
95 fn encode_value(&self) -> Result<ContractValue, EncodeError>;
96 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError>;
97
98 fn encode(&self) -> Result<SlotValue, EncodeError> {
99 Ok(SlotValue::Value(self.encode_value()?))
100 }
101
102 fn decode(slot: &SlotValue) -> Result<Self, DecodeError> {
103 match slot {
104 SlotValue::Missing => Err(DecodeError::new(DecodeErrorKind::MissingRequired)),
105 SlotValue::Null => Err(DecodeError::new(DecodeErrorKind::UnexpectedNull)),
106 SlotValue::Value(value) => Self::decode_value(value),
107 }
108 }
109
110 fn encode_field(&self) -> Result<Option<ContractValue>, EncodeError> {
111 Ok(Some(self.encode_value()?))
112 }
113
114 fn decode_field(field: Option<&ContractValue>) -> Result<Self, DecodeError> {
115 match field {
116 None => Err(DecodeError::new(DecodeErrorKind::MissingRequired)),
117 Some(value) => Self::decode_value(value),
118 }
119 }
120}
121
122fn mismatch<T>() -> Result<T, DecodeError> {
123 Err(DecodeError::new(DecodeErrorKind::KindMismatch))
124}
125
126macro_rules! exact_scalar {
127 ($type:ty, $constructor:ident, $variant:ident) => {
128 impl ContractType for $type {
129 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
130 Ok(ContractValue::$constructor(*self))
131 }
132
133 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError> {
134 match value.view() {
135 ValueRef::$variant(value) => Ok(value),
136 _ => mismatch(),
137 }
138 }
139 }
140 };
141}
142
143exact_scalar!(bool, bool, Bool);
144exact_scalar!(i64, i64, I64);
145exact_scalar!(u64, u64, U64);
146
147macro_rules! narrow_integers {
148 ($constructor:ident, $variant:ident; $($type:ty),+ $(,)?) => {$(
149 impl ContractType for $type {
150 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
151 Ok(ContractValue::$constructor((*self).into()))
152 }
153
154 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError> {
155 match value.view() {
156 ValueRef::$variant(value) => value
157 .try_into()
158 .map_err(|_| DecodeError::new(DecodeErrorKind::OutOfRange)),
159 _ => mismatch(),
160 }
161 }
162 }
163 )+};
164}
165
166narrow_integers!(i64, I64; i8, i16, i32);
167narrow_integers!(u64, U64; u8, u16, u32);
168
169macro_rules! floats {
170 ($type:ty, $constructor:ident, $variant:ident, $kind:ident) => {
171 impl ContractType for $type {
172 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
173 ContractValue::$constructor(*self)
174 .map_err(|_| EncodeError::new(EncodeErrorKind::$kind))
175 }
176
177 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError> {
178 match value.view() {
179 ValueRef::$variant(value) => Ok(value),
180 _ => mismatch(),
181 }
182 }
183 }
184 };
185}
186
187floats!(f32, f32, F32, NonFiniteF32);
188floats!(f64, f64, F64, NonFiniteF64);
189
190impl ContractType for String {
191 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
192 Ok(ContractValue::string(self))
193 }
194
195 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError> {
196 match value.view() {
197 ValueRef::String(value) => Ok(value.into()),
198 _ => mismatch(),
199 }
200 }
201}
202
203impl<T: ContractType> ContractType for Option<T> {
204 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
205 match self {
206 None => Ok(ContractValue::null()),
207 Some(value) => value.encode_value(),
208 }
209 }
210
211 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError> {
212 match value.view() {
213 ValueRef::Null => Ok(None),
214 _ => T::decode_value(value).map(Some),
215 }
216 }
217
218 fn encode(&self) -> Result<SlotValue, EncodeError> {
219 match self {
220 None => Ok(SlotValue::Null),
221 Some(value) => Ok(SlotValue::Value(value.encode_value()?)),
222 }
223 }
224
225 fn decode(slot: &SlotValue) -> Result<Self, DecodeError> {
226 match slot {
227 SlotValue::Missing => Err(DecodeError::new(DecodeErrorKind::UnexpectedMissing)),
228 SlotValue::Null => Ok(None),
229 SlotValue::Value(value) => T::decode_value(value).map(Some),
230 }
231 }
232
233 fn encode_field(&self) -> Result<Option<ContractValue>, EncodeError> {
234 match self {
235 None => Ok(None),
236 Some(value) => value.encode_value().map(Some),
237 }
238 }
239
240 fn decode_field(field: Option<&ContractValue>) -> Result<Self, DecodeError> {
241 match field {
242 None => Ok(None),
243 Some(value) => T::decode_value(value).map(Some),
244 }
245 }
246}
247
248impl<T: ContractType> ContractType for Field<T> {
249 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
250 Err(EncodeError::new(EncodeErrorKind::UnsupportedPosition))
251 }
252
253 fn decode_value(_value: &ContractValue) -> Result<Self, DecodeError> {
254 Err(DecodeError::new(DecodeErrorKind::UnsupportedPosition))
255 }
256
257 fn encode(&self) -> Result<SlotValue, EncodeError> {
258 match self {
259 Field::Missing => Ok(SlotValue::Missing),
260 Field::Null => Ok(SlotValue::Null),
261 Field::Value(value) => Ok(SlotValue::Value(value.encode_value()?)),
262 }
263 }
264
265 fn decode(slot: &SlotValue) -> Result<Self, DecodeError> {
266 match slot {
267 SlotValue::Missing => Ok(Field::Missing),
268 SlotValue::Null => Ok(Field::Null),
269 SlotValue::Value(value) => T::decode_value(value).map(Field::Value),
270 }
271 }
272
273 fn encode_field(&self) -> Result<Option<ContractValue>, EncodeError> {
274 match self {
275 Field::Missing => Ok(None),
276 Field::Null => Ok(Some(ContractValue::null())),
277 Field::Value(value) => value.encode_value().map(Some),
278 }
279 }
280
281 fn decode_field(field: Option<&ContractValue>) -> Result<Self, DecodeError> {
282 match field {
283 None => Ok(Field::Missing),
284 Some(value) if matches!(value.view(), ValueRef::Null) => Ok(Field::Null),
285 Some(value) => T::decode_value(value).map(Field::Value),
286 }
287 }
288}
289
290impl<T: ContractType> ContractType for Vec<T> {
291 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
292 let values = self
293 .iter()
294 .enumerate()
295 .map(|(index, value)| {
296 value
297 .encode_value()
298 .map_err(|error| error.under(PathSegment::Index(index)))
299 })
300 .collect::<Result<Vec<_>, _>>()?;
301 Ok(ContractValue::list(values))
302 }
303
304 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError> {
305 match value.view() {
306 ValueRef::Null => Err(DecodeError::new(DecodeErrorKind::UnexpectedNull)),
307 ValueRef::List(values) => values
308 .iter()
309 .enumerate()
310 .map(|(index, value)| {
311 T::decode_value(value).map_err(|error| error.under(PathSegment::Index(index)))
312 })
313 .collect(),
314 _ => mismatch(),
315 }
316 }
317}
318
319impl<T: ContractType> ContractType for BTreeMap<String, T> {
320 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
321 let entries = self
322 .iter()
323 .map(|(key, value)| {
324 value
325 .encode_value()
326 .map(|value| (key.clone(), value))
327 .map_err(|error| error.under(PathSegment::MapKey(key.clone())))
328 })
329 .collect::<Result<Vec<_>, _>>()?;
330 ContractValue::object(entries).map_err(|_| unreachable!())
331 }
332
333 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError> {
334 match value.view() {
335 ValueRef::Null => Err(DecodeError::new(DecodeErrorKind::UnexpectedNull)),
336 ValueRef::Object(object) => object
337 .entries()
338 .map(|(key, value)| {
339 T::decode_value(value)
340 .map(|value| (key.into(), value))
341 .map_err(|error| error.under(PathSegment::MapKey(key.into())))
342 })
343 .collect(),
344 _ => mismatch(),
345 }
346 }
347}
348
349#[derive(Clone, PartialEq, Eq)]
351pub struct Secret<T>(T);
352
353impl<T> Secret<T> {
354 pub fn new(value: T) -> Self {
355 Self(value)
356 }
357
358 pub fn reveal(&self) -> &T {
362 &self.0
363 }
364
365 pub fn into_revealed(self) -> T {
369 self.0
370 }
371}
372
373impl<T> fmt::Debug for Secret<T> {
374 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
375 formatter.write_str("Secret(<redacted>)")
376 }
377}
378
379impl<T> fmt::Display for Secret<T> {
380 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
381 formatter.write_str("Secret(<redacted>)")
382 }
383}
384
385impl<T: ContractType> ContractType for Secret<T> {
386 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
387 self.0.encode_value().map(ContractValue::sensitive)
388 }
389
390 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError> {
391 match value.view() {
392 ValueRef::Null => Err(DecodeError::new(DecodeErrorKind::UnexpectedNull)),
393 ValueRef::Sensitive(inner) => T::decode_value(inner).map(Self),
394 _ => mismatch(),
395 }
396 }
397}
398
399#[derive(Debug, Clone, PartialEq, Eq)]
401pub struct Blob(Vec<u8>);
402
403impl Blob {
404 pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
405 Self(bytes.into())
406 }
407
408 pub fn as_bytes(&self) -> &[u8] {
409 &self.0
410 }
411
412 pub fn into_bytes(self) -> Vec<u8> {
413 self.0
414 }
415}
416
417impl ContractType for Blob {
418 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
419 Ok(ContractValue::bytes(self.0.clone()))
420 }
421
422 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError> {
423 match value.view() {
424 ValueRef::Bytes(bytes) => Ok(Self(bytes.to_vec())),
425 _ => mismatch(),
426 }
427 }
428}
429
430pub trait ContractError: ContractType {
432 fn error_tag(&self) -> &str;
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438 use crate::conform::{Shape, conform_slot};
439 use crate::{OpaquePayload, OpaqueTree};
440
441 fn round_trip<T: ContractType + fmt::Debug + PartialEq>(value: T) {
442 assert_eq!(T::decode(&value.encode().unwrap()).unwrap(), value);
443 }
444
445 #[test]
446 fn every_supported_scalar_and_string_round_trips() {
447 round_trip(false);
448 round_trip(true);
449 round_trip(i8::MIN);
450 round_trip(i16::MAX);
451 round_trip(i32::MIN);
452 round_trip(i64::MAX);
453 round_trip(u8::MAX);
454 round_trip(u16::MIN);
455 round_trip(u32::MAX);
456 round_trip(u64::MAX);
457 round_trip(-0.0_f32);
458 round_trip(f64::MIN);
459 round_trip(String::from("owned UTF-8 α"));
460 }
461
462 fn out_of_range<T: ContractType + fmt::Debug>(value: ContractValue) {
463 assert_eq!(
464 T::decode_value(&value).unwrap_err().kind(),
465 &DecodeErrorKind::OutOfRange
466 );
467 }
468
469 #[test]
470 fn narrow_integer_boundaries_are_exact() {
471 macro_rules! signed {
472 ($($type:ty),+) => {$(
473 assert_eq!(<$type>::decode_value(&ContractValue::i64(<$type>::MIN.into())), Ok(<$type>::MIN));
474 assert_eq!(<$type>::decode_value(&ContractValue::i64(<$type>::MAX.into())), Ok(<$type>::MAX));
475 out_of_range::<$type>(ContractValue::i64(i64::from(<$type>::MIN) - 1));
476 out_of_range::<$type>(ContractValue::i64(i64::from(<$type>::MAX) + 1));
477 )+}; }
478 macro_rules! unsigned {
479 ($($type:ty),+) => {$(
480 assert_eq!(<$type>::decode_value(&ContractValue::u64(0)), Ok(0));
481 assert_eq!(<$type>::decode_value(&ContractValue::u64(<$type>::MAX.into())), Ok(<$type>::MAX));
482 out_of_range::<$type>(ContractValue::u64(u64::from(<$type>::MAX) + 1));
483 )+}; }
484 signed!(i8, i16, i32);
485 unsigned!(u8, u16, u32);
486 }
487
488 #[test]
489 fn non_finite_float_encoding_is_fallible() {
490 for value in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
491 assert_eq!(
492 value.encode_value().unwrap_err().kind(),
493 &EncodeErrorKind::NonFiniteF32
494 );
495 }
496 for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
497 assert_eq!(
498 value.encode_value().unwrap_err().kind(),
499 &EncodeErrorKind::NonFiniteF64
500 );
501 }
502 }
503
504 fn rejects_other_kinds<T: ContractType + fmt::Debug>(values: &[ContractValue], own: usize) {
505 for (index, value) in values.iter().enumerate() {
506 if index != own {
507 assert_eq!(
508 T::decode_value(value).unwrap_err().kind(),
509 &DecodeErrorKind::KindMismatch
510 );
511 }
512 }
513 }
514
515 #[test]
516 fn scalars_reject_every_other_value_kind() {
517 let values = vec![
518 ContractValue::null(),
519 ContractValue::bool(true),
520 ContractValue::i64(0),
521 ContractValue::u64(0),
522 ContractValue::f32(0.0).unwrap(),
523 ContractValue::f64(0.0).unwrap(),
524 ContractValue::string("text"),
525 ContractValue::bytes([]),
526 ContractValue::list([]),
527 ContractValue::object([("x".into(), ContractValue::null())]).unwrap(),
528 ContractValue::enum_value("tag", SlotValue::Null),
529 ContractValue::opaque(OpaquePayload::new(OpaqueTree::String("raw".into()))),
530 ContractValue::sensitive(ContractValue::string("secret")),
531 ];
532 rejects_other_kinds::<bool>(&values, 1);
533 rejects_other_kinds::<i8>(&values, 2);
534 rejects_other_kinds::<i16>(&values, 2);
535 rejects_other_kinds::<i32>(&values, 2);
536 rejects_other_kinds::<i64>(&values, 2);
537 rejects_other_kinds::<u8>(&values, 3);
538 rejects_other_kinds::<u16>(&values, 3);
539 rejects_other_kinds::<u32>(&values, 3);
540 rejects_other_kinds::<u64>(&values, 3);
541 rejects_other_kinds::<f32>(&values, 4);
542 rejects_other_kinds::<f64>(&values, 5);
543 rejects_other_kinds::<String>(&values, 6);
544 }
545
546 #[test]
547 fn required_defaults_distinguish_missing_null_and_wrong_kind() {
548 assert_eq!(
549 bool::decode(&SlotValue::Missing).unwrap_err().kind(),
550 &DecodeErrorKind::MissingRequired
551 );
552 assert_eq!(
553 bool::decode(&SlotValue::Null).unwrap_err().kind(),
554 &DecodeErrorKind::UnexpectedNull
555 );
556 assert_eq!(
557 bool::decode(&SlotValue::Value(ContractValue::null()))
558 .unwrap_err()
559 .kind(),
560 &DecodeErrorKind::KindMismatch
561 );
562 assert_eq!(
563 bool::decode_field(None).unwrap_err().kind(),
564 &DecodeErrorKind::MissingRequired
565 );
566 let field = 7_u8.encode_field().unwrap();
567 assert_eq!(u8::decode_field(field.as_ref()), Ok(7));
568 }
569
570 #[test]
571 fn error_paths_prepend_and_diagnostics_expose_no_payloads() {
572 let decode = DecodeError::new(DecodeErrorKind::UnknownField("leaf".into()))
573 .under(PathSegment::Index(3))
574 .under(PathSegment::Field("root".into()));
575 assert_eq!(decode.kind(), &DecodeErrorKind::UnknownField("leaf".into()));
576 assert_eq!(
577 decode.path(),
578 &[PathSegment::Field("root".into()), PathSegment::Index(3)]
579 );
580 assert_eq!(
581 decode.to_string(),
582 "UnknownField(\"leaf\") at [Field(\"root\"), Index(3)]"
583 );
584
585 let encode = EncodeError::new(EncodeErrorKind::NonFiniteF64)
586 .under(PathSegment::MapKey("item".into()));
587 assert_eq!(encode.kind(), &EncodeErrorKind::NonFiniteF64);
588 assert_eq!(encode.path(), &[PathSegment::MapKey("item".into())]);
589
590 const SENTINEL: &str = "runtime-payload-must-not-escape";
591 for payload in [
592 ContractValue::sensitive(ContractValue::string(SENTINEL)),
593 ContractValue::opaque(OpaquePayload::new(OpaqueTree::String(SENTINEL.into()))),
594 ] {
595 let error = bool::decode_value(&payload).unwrap_err();
596 for diagnostic in [format!("{error:?}"), error.to_string()] {
597 assert!(!diagnostic.contains(SENTINEL));
598 }
599 }
600 }
601
602 #[test]
603 fn public_diagnostic_types_are_send_sync_and_static() {
604 fn assert_bounds<T: Send + Sync + 'static>() {}
605 assert_bounds::<PathSegment>();
606 assert_bounds::<EncodeErrorKind>();
607 assert_bounds::<EncodeError>();
608 assert_bounds::<DecodeErrorKind>();
609 assert_bounds::<DecodeError>();
610 }
611
612 fn decode_error<T: fmt::Debug>(result: Result<T, DecodeError>, kind: DecodeErrorKind) {
613 assert_eq!(result.unwrap_err().kind(), &kind);
614 }
615
616 #[test]
617 fn presence_grid_is_exact_at_slots_fields_and_value_positions() {
618 let null = ContractValue::null();
619 assert_eq!(Option::<u8>::decode(&SlotValue::Null), Ok(None));
620 assert_eq!(None::<u8>.encode().unwrap(), SlotValue::Null);
621 decode_error(
622 Option::<u8>::decode(&SlotValue::Missing),
623 DecodeErrorKind::UnexpectedMissing,
624 );
625 decode_error(
626 Option::<u8>::decode(&SlotValue::Value(null.clone())),
627 DecodeErrorKind::KindMismatch,
628 );
629 assert_eq!(Option::<u8>::decode_value(&null), Ok(None));
630 assert_eq!(None::<u8>.encode_value().unwrap(), null);
631 assert_eq!(Option::<u8>::decode_field(None), Ok(None));
632 assert_eq!(None::<u8>.encode_field().unwrap(), None);
633 let some = Some(7_u8);
634 assert_eq!(
635 some.encode().unwrap(),
636 SlotValue::Value(ContractValue::u64(7))
637 );
638 assert_eq!(Option::<u8>::decode(&some.encode().unwrap()), Ok(some));
639 let some_field = some.encode_field().unwrap();
640 assert_eq!(some_field, Some(ContractValue::u64(7)));
641 assert_eq!(Option::<u8>::decode_field(some_field.as_ref()), Ok(some));
642 decode_error(
643 Option::<u8>::decode_field(Some(&ContractValue::null())),
644 DecodeErrorKind::KindMismatch,
645 );
646
647 for (field, slot) in [
648 (Field::Missing, SlotValue::Missing),
649 (Field::Null, SlotValue::Null),
650 (Field::Value(7_u8), SlotValue::Value(ContractValue::u64(7))),
651 ] {
652 assert_eq!(field.encode().unwrap(), slot);
653 assert_eq!(Field::<u8>::decode(&slot), Ok(field));
654 }
655 assert_eq!(Field::<u8>::decode_field(None), Ok(Field::Missing));
656 assert_eq!(Field::<u8>::decode_field(Some(&null)), Ok(Field::Null));
657 assert_eq!(
658 Field::<u8>::Null.encode_field().unwrap(),
659 Some(null.clone())
660 );
661 assert_eq!(Field::<u8>::Missing.encode_field().unwrap(), None);
662 let value_field = Field::Value(7_u8).encode_field().unwrap();
663 assert_eq!(value_field, Some(ContractValue::u64(7)));
664 assert_eq!(
665 Field::<u8>::decode_field(value_field.as_ref()),
666 Ok(Field::Value(7))
667 );
668 decode_error(
669 Field::<u8>::decode(&SlotValue::Value(null.clone())),
670 DecodeErrorKind::KindMismatch,
671 );
672 decode_error(
673 Field::<u8>::decode_field(Some(&ContractValue::bool(true))),
674 DecodeErrorKind::KindMismatch,
675 );
676 for field in [Field::Missing, Field::Null, Field::Value(1_u8)] {
677 assert_eq!(
678 field.encode_value().unwrap_err().kind(),
679 &EncodeErrorKind::UnsupportedPosition
680 );
681 }
682 for value in [null, ContractValue::u64(1)] {
683 decode_error(
684 Field::<u8>::decode_value(&value),
685 DecodeErrorKind::UnsupportedPosition,
686 );
687 }
688 }
689
690 #[test]
691 fn nested_optional_lists_round_trip_and_paths_include_indices() {
692 let values = vec![Some(1_u8), None, Some(3)];
693 round_trip(values.clone());
694 let encoded = values.encode_value().unwrap();
695 let ValueRef::List(items) = encoded.view() else {
696 panic!()
697 };
698 assert!(matches!(items[1].view(), ValueRef::Null));
699
700 let encode = vec![0.0_f32, f32::NAN].encode_value().unwrap_err();
701 assert_eq!(encode.kind(), &EncodeErrorKind::NonFiniteF32);
702 assert_eq!(encode.path(), &[PathSegment::Index(1)]);
703 let bad = ContractValue::list([ContractValue::u64(1), ContractValue::u64(256)]);
704 let decode = Vec::<u8>::decode_value(&bad).unwrap_err();
705 assert_eq!(decode.kind(), &DecodeErrorKind::OutOfRange);
706 assert_eq!(decode.path(), &[PathSegment::Index(1)]);
707 decode_error(
708 Vec::<u8>::decode_value(&ContractValue::null()),
709 DecodeErrorKind::UnexpectedNull,
710 );
711 decode_error(
712 Vec::<u8>::decode_value(&ContractValue::bool(false)),
713 DecodeErrorKind::KindMismatch,
714 );
715 }
716
717 #[test]
718 fn maps_round_trip_sort_input_and_report_key_paths() {
719 let map = BTreeMap::from([("z".into(), 2_u8), ("a".into(), 1)]);
720 round_trip(map);
721 let input = ContractValue::object([
722 ("z".into(), ContractValue::u64(2)),
723 ("a".into(), ContractValue::u64(1)),
724 ])
725 .unwrap();
726 let decoded = BTreeMap::<String, u8>::decode_value(&input).unwrap();
727 let encoded = decoded.encode_value().unwrap();
728 let ValueRef::Object(object) = encoded.view() else {
729 panic!()
730 };
731 assert_eq!(
732 object.entries().map(|(key, _)| key).collect::<Vec<_>>(),
733 ["a", "z"]
734 );
735
736 let encode = BTreeMap::from([("secret".into(), f64::NAN)])
737 .encode_value()
738 .unwrap_err();
739 assert_eq!(encode.kind(), &EncodeErrorKind::NonFiniteF64);
740 assert_eq!(encode.path(), &[PathSegment::MapKey("secret".into())]);
741 let bad = ContractValue::object([("bad".into(), ContractValue::u64(256))]).unwrap();
742 let decode = BTreeMap::<String, u8>::decode_value(&bad).unwrap_err();
743 assert_eq!(decode.kind(), &DecodeErrorKind::OutOfRange);
744 assert_eq!(decode.path(), &[PathSegment::MapKey("bad".into())]);
745 decode_error(
746 BTreeMap::<String, u8>::decode_value(&ContractValue::null()),
747 DecodeErrorKind::UnexpectedNull,
748 );
749 decode_error(
750 BTreeMap::<String, u8>::decode_value(&ContractValue::bool(false)),
751 DecodeErrorKind::KindMismatch,
752 );
753 }
754
755 fn walker_agrees<T: ContractType + fmt::Debug>(shape: &Shape, slot: SlotValue) {
756 let typed = T::decode(&slot).unwrap().encode().unwrap();
757 for role in [
758 crate::DecodeRole::ProviderInput,
759 crate::DecodeRole::ConsumerOutput,
760 ] {
761 assert_eq!(conform_slot(shape, role, slot.clone()).unwrap(), typed);
762 }
763 }
764
765 #[test]
766 fn typed_presence_acceptance_agrees_with_the_walker() {
767 let optional = Shape::optional(Shape::i64()).unwrap();
768 walker_agrees::<Option<i64>>(&optional, SlotValue::Null);
769 walker_agrees::<Option<i64>>(&optional, SlotValue::Value(ContractValue::i64(4)));
770 let tri_state = Shape::tri_state(Shape::i64()).unwrap();
771 walker_agrees::<Field<i64>>(&tri_state, SlotValue::Missing);
772 walker_agrees::<Field<i64>>(&tri_state, SlotValue::Null);
773 walker_agrees::<Field<i64>>(&tri_state, SlotValue::Value(ContractValue::i64(4)));
774 }
775
776 #[test]
777 fn secrets_round_trip_in_every_supported_position() {
778 let secret = Secret::new(String::from("classified"));
779 let encoded = secret.encode_value().unwrap();
780 assert_eq!(Secret::<String>::decode_value(&encoded), Ok(secret.clone()));
781 round_trip(secret.clone());
782 round_trip(Some(secret.clone()));
783 round_trip(Field::Value(secret.clone()));
784 round_trip(vec![secret.clone(), Secret::new("second".into())]);
785 round_trip(BTreeMap::from([("key".into(), secret)]));
786 }
787
788 #[test]
789 fn secret_decode_is_strict_and_unwraps_exactly_one_layer() {
790 const SENTINEL: &str = "secret-visitor-sentinel";
791 let encoded = Secret::new(String::from(SENTINEL)).encode_value().unwrap();
792 let ValueRef::Sensitive(inner) = encoded.view() else {
793 panic!()
794 };
795 assert!(matches!(inner.view(), ValueRef::String(SENTINEL)));
796
797 decode_error(
798 Secret::<String>::decode_value(&ContractValue::null()),
799 DecodeErrorKind::UnexpectedNull,
800 );
801 for value in [
802 ContractValue::string(SENTINEL),
803 ContractValue::opaque(OpaquePayload::new(OpaqueTree::String(SENTINEL.into()))),
804 ] {
805 decode_error(
806 Secret::<String>::decode_value(&value),
807 DecodeErrorKind::KindMismatch,
808 );
809 }
810
811 let nested = Secret::new(Secret::new(String::from(SENTINEL)));
812 let nested_value = nested.encode_value().unwrap();
813 assert_eq!(
814 Secret::<Secret<String>>::decode_value(&nested_value),
815 Ok(nested)
816 );
817 decode_error(
818 Secret::<Secret<String>>::decode_value(&encoded),
819 DecodeErrorKind::KindMismatch,
820 );
821 }
822
823 #[test]
824 fn secret_diagnostics_never_reveal_payloads() {
825 const SENTINEL: &str = "never-print-secret-payload";
826 let secret = Secret::new(String::from(SENTINEL));
827 assert_eq!(format!("{secret:?}"), "Secret(<redacted>)");
828 assert_eq!(secret.to_string(), "Secret(<redacted>)");
829
830 let sensitive = secret.encode_value().unwrap();
831 let values = [
832 sensitive.clone(),
833 ContractValue::list([sensitive.clone()]),
834 ContractValue::object([("secret".into(), sensitive.clone())]).unwrap(),
835 ContractValue::enum_value("secret", SlotValue::Value(sensitive)),
836 ];
837 for value in values {
838 for diagnostic in [
839 format!("{value:?}"),
840 format!("{:?}", SlotValue::Value(value)),
841 ] {
842 assert!(!diagnostic.contains(SENTINEL));
843 assert!(diagnostic.contains("<redacted>"));
844 }
845 }
846
847 let sensitive = ContractValue::sensitive(ContractValue::string(SENTINEL));
848 let decode = Secret::<u8>::decode_value(&sensitive).unwrap_err();
849 for diagnostic in [format!("{decode:?}"), decode.to_string()] {
850 assert!(!diagnostic.contains(SENTINEL));
851 }
852 let encode = Secret::new(f32::NAN).encode_value().unwrap_err();
853 assert_eq!(encode.kind(), &EncodeErrorKind::NonFiniteF32);
854 for diagnostic in [format!("{encode:?}"), encode.to_string()] {
855 assert!(!diagnostic.contains("NaN"));
856 }
857 }
858
859 #[test]
860 fn blob_is_an_exact_owned_byte_value() {
861 let blob = Blob::new([0, 1, 255]);
862 assert_eq!(blob.as_bytes(), &[0, 1, 255]);
863 assert_eq!(blob.clone().into_bytes(), vec![0, 1, 255]);
864 round_trip(blob.clone());
865 let encoded = blob.encode_value().unwrap();
866 assert!(matches!(encoded.view(), ValueRef::Bytes([0, 1, 255])));
867 decode_error(
868 Blob::decode_value(&ContractValue::null()),
869 DecodeErrorKind::KindMismatch,
870 );
871 decode_error(
872 Blob::decode_value(&ContractValue::string("bytes")),
873 DecodeErrorKind::KindMismatch,
874 );
875 decode_error(
876 Blob::decode(&SlotValue::Null),
877 DecodeErrorKind::UnexpectedNull,
878 );
879 }
880
881 #[derive(Debug)]
882 struct TestDomainError;
883
884 impl ContractType for TestDomainError {
885 fn encode_value(&self) -> Result<ContractValue, EncodeError> {
886 Ok(ContractValue::string("test-domain"))
887 }
888
889 fn decode_value(value: &ContractValue) -> Result<Self, DecodeError> {
890 String::decode_value(value).map(|_| Self)
891 }
892 }
893
894 impl ContractError for TestDomainError {
895 fn error_tag(&self) -> &str {
896 "test-domain"
897 }
898 }
899
900 #[test]
901 fn new_public_types_have_expected_bounds_and_error_tag() {
902 fn assert_bounds<T: Send + Sync + 'static>() {}
903 fn assert_error<T: ContractError + Send + Sync + 'static>(error: &T) -> &str {
904 error.error_tag()
905 }
906 assert_bounds::<Blob>();
907 assert_bounds::<Secret<String>>();
908 assert_eq!(assert_error(&TestDomainError), "test-domain");
909 }
910}