1#![forbid(unsafe_code)]
2
3use arborium::Highlighter;
4use core::fmt::{Debug, Display};
5use std::any::Any;
6use std::panic::{self, AssertUnwindSafe};
7
8use facet::Facet;
9use facet_assert::assert_same;
10use facet_pretty::{FacetPretty, PrettyPrinter};
11use indoc::formatdoc;
12
13pub trait FormatSuite {
23 type Error: Debug + Display;
25
26 fn format_name() -> &'static str;
28
29 fn highlight_language() -> Option<&'static str> {
31 None
32 }
33
34 fn deserialize<T>(input: &[u8]) -> Result<T, Self::Error>
36 where
37 for<'facet> T: Facet<'facet>,
38 T: Debug;
39
40 fn serialize<T>(value: &T) -> Option<Result<Vec<u8>, String>>
50 where
51 for<'facet> T: Facet<'facet>,
52 T: Debug,
53 {
54 let _ = value;
55 None
56 }
57
58 #[cfg(feature = "tokio")]
65 fn deserialize_async<T>(
66 input: &[u8],
67 ) -> impl std::future::Future<Output = Option<Result<T, Self::Error>>>
68 where
69 for<'facet> T: Facet<'facet>,
70 T: Debug,
71 {
72 let _ = input;
73 async { None }
74 }
75
76 fn struct_single_field() -> CaseSpec;
78 fn sequence_numbers() -> CaseSpec;
80 fn sequence_mixed_scalars() -> CaseSpec;
82 fn struct_nested() -> CaseSpec;
84 fn enum_complex() -> CaseSpec;
86
87 fn attr_rename_field() -> CaseSpec;
91 fn attr_rename_all_camel() -> CaseSpec;
93 fn attr_default_field() -> CaseSpec;
95 fn attr_default_struct() -> CaseSpec;
97 fn attr_default_function() -> CaseSpec;
99 fn option_none() -> CaseSpec;
101 fn option_some() -> CaseSpec;
103 fn option_null() -> CaseSpec;
105 fn attr_skip_serializing() -> CaseSpec;
107 fn attr_skip_serializing_if() -> CaseSpec;
109 fn attr_skip() -> CaseSpec;
111
112 fn enum_internally_tagged() -> CaseSpec;
116 fn enum_adjacently_tagged() -> CaseSpec;
118
119 fn struct_flatten() -> CaseSpec;
123 fn transparent_newtype() -> CaseSpec;
125
126 fn flatten_optional_some() -> CaseSpec;
130 fn flatten_optional_none() -> CaseSpec;
132 fn flatten_overlapping_fields_error() -> CaseSpec;
134 fn flatten_multilevel() -> CaseSpec;
136 fn flatten_multiple_enums() -> CaseSpec;
138
139 fn deny_unknown_fields() -> CaseSpec;
143 fn error_type_mismatch_string_to_int() -> CaseSpec;
145 fn error_type_mismatch_object_to_array() -> CaseSpec;
147 fn error_missing_required_field() -> CaseSpec;
149
150 fn attr_alias() -> CaseSpec;
154
155 fn attr_rename_vs_alias_precedence() -> CaseSpec;
159 fn attr_rename_all_kebab() -> CaseSpec;
161 fn attr_rename_all_screaming() -> CaseSpec;
163 fn attr_rename_unicode() -> CaseSpec;
165 fn attr_rename_special_chars() -> CaseSpec;
167
168 fn proxy_container() -> CaseSpec;
172 fn proxy_field_level() -> CaseSpec;
174 fn proxy_validation_error() -> CaseSpec;
176 fn proxy_with_option() -> CaseSpec;
178 fn proxy_with_enum() -> CaseSpec;
180 fn proxy_with_transparent() -> CaseSpec;
182
183 fn opaque_proxy() -> CaseSpec;
185
186 fn opaque_proxy_option() -> CaseSpec;
188
189 fn transparent_multilevel() -> CaseSpec;
193 fn transparent_option() -> CaseSpec;
195 fn transparent_nonzero() -> CaseSpec;
197
198 fn scalar_bool() -> CaseSpec;
202 fn scalar_integers() -> CaseSpec;
204 fn scalar_floats() -> CaseSpec;
206 fn scalar_floats_scientific() -> CaseSpec;
208
209 fn map_string_keys() -> CaseSpec;
213 fn tuple_simple() -> CaseSpec;
215 fn tuple_nested() -> CaseSpec;
217 fn tuple_empty() -> CaseSpec;
219 fn tuple_single_element() -> CaseSpec;
221 fn tuple_struct_variant() -> CaseSpec;
223 fn tuple_newtype_variant() -> CaseSpec;
225
226 fn enum_unit_variant() -> CaseSpec;
230 fn numeric_enum() -> CaseSpec;
232 fn signed_numeric_enum() -> CaseSpec;
234 fn inferred_numeric_enum() -> CaseSpec;
236 fn enum_untagged() -> CaseSpec;
238 fn enum_variant_rename() -> CaseSpec;
240
241 fn untagged_with_null() -> CaseSpec;
245 fn untagged_newtype_variant() -> CaseSpec;
247 fn untagged_as_field() -> CaseSpec;
249
250 fn untagged_unit_only() -> CaseSpec;
252
253 fn box_wrapper() -> CaseSpec;
257 fn arc_wrapper() -> CaseSpec;
259 fn rc_wrapper() -> CaseSpec;
261 fn box_str() -> CaseSpec;
263 fn arc_str() -> CaseSpec;
265 fn rc_str() -> CaseSpec;
267 fn arc_slice() -> CaseSpec;
269
270 fn set_btree() -> CaseSpec;
274
275 fn scalar_integers_16() -> CaseSpec;
279 fn scalar_integers_128() -> CaseSpec;
281 fn scalar_integers_size() -> CaseSpec;
283
284 fn nonzero_integers() -> CaseSpec;
288 fn nonzero_integers_extended() -> CaseSpec;
290
291 fn cow_str() -> CaseSpec;
295
296 fn newtype_u64() -> CaseSpec;
300 fn newtype_string() -> CaseSpec;
302
303 fn char_scalar() -> CaseSpec;
307
308 fn hashset() -> CaseSpec;
312
313 fn vec_nested() -> CaseSpec;
317
318 fn bytes_vec_u8() -> CaseSpec;
322
323 fn array_fixed_size() -> CaseSpec;
327
328 fn skip_unknown_fields() -> CaseSpec;
332
333 fn string_escapes() -> CaseSpec;
337 fn string_escapes_extended() -> CaseSpec;
339
340 fn unit_struct() -> CaseSpec;
344
345 #[cfg(feature = "uuid")]
349 fn uuid() -> CaseSpec;
350
351 #[cfg(feature = "ulid")]
353 fn ulid() -> CaseSpec;
354
355 #[cfg(feature = "camino")]
357 fn camino_path() -> CaseSpec;
358
359 #[cfg(feature = "ordered-float")]
361 fn ordered_float() -> CaseSpec;
362
363 #[cfg(feature = "time")]
365 fn time_offset_datetime() -> CaseSpec;
366
367 #[cfg(feature = "jiff02")]
369 fn jiff_timestamp() -> CaseSpec;
370
371 #[cfg(feature = "jiff02")]
373 fn jiff_civil_datetime() -> CaseSpec;
374
375 #[cfg(feature = "chrono")]
377 fn chrono_datetime_utc() -> CaseSpec;
378
379 #[cfg(feature = "chrono")]
381 fn chrono_naive_datetime() -> CaseSpec;
382
383 #[cfg(feature = "chrono")]
385 fn chrono_naive_date() -> CaseSpec;
386
387 #[cfg(feature = "chrono")]
389 fn chrono_naive_time() -> CaseSpec;
390
391 #[cfg(feature = "chrono")]
393 fn chrono_in_vec() -> CaseSpec;
394
395 #[cfg(feature = "bytes")]
399 fn bytes_bytes() -> CaseSpec;
400
401 #[cfg(feature = "bytes")]
403 fn bytes_bytes_mut() -> CaseSpec;
404
405 #[cfg(feature = "bytestring")]
407 fn bytestring() -> CaseSpec;
408
409 #[cfg(feature = "compact_str")]
411 fn compact_string() -> CaseSpec;
412
413 #[cfg(feature = "smartstring")]
415 fn smartstring() -> CaseSpec;
416
417 #[cfg(feature = "facet-value")]
421 fn value_null() -> CaseSpec;
422
423 #[cfg(feature = "facet-value")]
425 fn value_bool() -> CaseSpec;
426
427 #[cfg(feature = "facet-value")]
429 fn value_integer() -> CaseSpec;
430
431 #[cfg(feature = "facet-value")]
433 fn value_float() -> CaseSpec;
434
435 #[cfg(feature = "facet-value")]
437 fn value_string() -> CaseSpec;
438
439 #[cfg(feature = "facet-value")]
441 fn value_array() -> CaseSpec;
442
443 #[cfg(feature = "facet-value")]
445 fn value_object() -> CaseSpec;
446}
447
448pub fn run_suite<S: FormatSuite + 'static>() {
451 for case in all_cases::<S>() {
452 match case.run() {
453 CaseOutcome::Passed => {}
454 CaseOutcome::Skipped(reason) => {
455 eprintln!(
456 "facet-format-suite: skipping {} for {} ({reason})",
457 case.id,
458 S::format_name()
459 );
460 }
461 CaseOutcome::Failed(msg) => {
462 panic!(
463 "facet-format-suite case {} ({}) failed: {msg}",
464 case.id, case.description
465 );
466 }
467 }
468 }
469}
470
471pub fn all_cases<S: FormatSuite + 'static>() -> Vec<SuiteCase> {
473 vec![
474 SuiteCase::new::<S, StructSingleField>(&CASE_STRUCT_SINGLE_FIELD, S::struct_single_field),
476 SuiteCase::new::<S, Vec<u64>>(&CASE_SEQUENCE_NUMBERS, S::sequence_numbers),
477 SuiteCase::new::<S, Vec<MixedScalar>>(
478 &CASE_SEQUENCE_MIXED_SCALARS,
479 S::sequence_mixed_scalars,
480 ),
481 SuiteCase::new::<S, NestedParent>(&CASE_STRUCT_NESTED, S::struct_nested),
482 SuiteCase::new::<S, ComplexEnum>(&CASE_ENUM_COMPLEX, S::enum_complex),
483 SuiteCase::new::<S, RenamedField>(&CASE_ATTR_RENAME_FIELD, S::attr_rename_field),
485 SuiteCase::new::<S, CamelCaseStruct>(&CASE_ATTR_RENAME_ALL_CAMEL, S::attr_rename_all_camel),
486 SuiteCase::new::<S, WithDefault>(&CASE_ATTR_DEFAULT_FIELD, S::attr_default_field),
487 SuiteCase::new::<S, StructLevelDefault>(&CASE_ATTR_DEFAULT_STRUCT, S::attr_default_struct),
488 SuiteCase::new::<S, WithDefaultFunction>(
489 &CASE_ATTR_DEFAULT_FUNCTION,
490 S::attr_default_function,
491 ),
492 SuiteCase::new::<S, WithOption>(&CASE_OPTION_NONE, S::option_none),
493 SuiteCase::new::<S, WithOption>(&CASE_OPTION_SOME, S::option_some),
494 SuiteCase::new::<S, WithOption>(&CASE_OPTION_NULL, S::option_null),
495 SuiteCase::new::<S, WithSkipSerializing>(
496 &CASE_ATTR_SKIP_SERIALIZING,
497 S::attr_skip_serializing,
498 ),
499 SuiteCase::new::<S, WithSkipSerializingIf>(
500 &CASE_ATTR_SKIP_SERIALIZING_IF,
501 S::attr_skip_serializing_if,
502 ),
503 SuiteCase::new::<S, WithSkip>(&CASE_ATTR_SKIP, S::attr_skip),
504 SuiteCase::new::<S, InternallyTagged>(
506 &CASE_ENUM_INTERNALLY_TAGGED,
507 S::enum_internally_tagged,
508 ),
509 SuiteCase::new::<S, AdjacentlyTagged>(
510 &CASE_ENUM_ADJACENTLY_TAGGED,
511 S::enum_adjacently_tagged,
512 ),
513 SuiteCase::new::<S, FlattenOuter>(&CASE_STRUCT_FLATTEN, S::struct_flatten),
515 SuiteCase::new::<S, UserRecord>(&CASE_TRANSPARENT_NEWTYPE, S::transparent_newtype),
516 SuiteCase::new::<S, FlattenOptionalSome>(
518 &CASE_FLATTEN_OPTIONAL_SOME,
519 S::flatten_optional_some,
520 ),
521 SuiteCase::new::<S, FlattenOptionalNone>(
522 &CASE_FLATTEN_OPTIONAL_NONE,
523 S::flatten_optional_none,
524 ),
525 SuiteCase::new::<S, FlattenOverlapping>(
526 &CASE_FLATTEN_OVERLAPPING_FIELDS_ERROR,
527 S::flatten_overlapping_fields_error,
528 ),
529 SuiteCase::new::<S, FlattenLevel1>(&CASE_FLATTEN_MULTILEVEL, S::flatten_multilevel),
530 SuiteCase::new::<S, FlattenMultipleEnums>(
531 &CASE_FLATTEN_MULTIPLE_ENUMS,
532 S::flatten_multiple_enums,
533 ),
534 SuiteCase::new::<S, DenyUnknownStruct>(&CASE_DENY_UNKNOWN_FIELDS, S::deny_unknown_fields),
536 SuiteCase::new::<S, ExpectsInteger>(
537 &CASE_ERROR_TYPE_MISMATCH_STRING_TO_INT,
538 S::error_type_mismatch_string_to_int,
539 ),
540 SuiteCase::new::<S, ExpectsArray>(
541 &CASE_ERROR_TYPE_MISMATCH_OBJECT_TO_ARRAY,
542 S::error_type_mismatch_object_to_array,
543 ),
544 SuiteCase::new::<S, RequiresAllFields>(
545 &CASE_ERROR_MISSING_REQUIRED_FIELD,
546 S::error_missing_required_field,
547 ),
548 SuiteCase::new::<S, WithAlias>(&CASE_ATTR_ALIAS, S::attr_alias),
550 SuiteCase::new::<S, RenameVsAlias>(
552 &CASE_ATTR_RENAME_VS_ALIAS,
553 S::attr_rename_vs_alias_precedence,
554 ),
555 SuiteCase::new::<S, RenameAllKebab>(&CASE_ATTR_RENAME_ALL_KEBAB, S::attr_rename_all_kebab),
556 SuiteCase::new::<S, RenameAllScreaming>(
557 &CASE_ATTR_RENAME_ALL_SCREAMING,
558 S::attr_rename_all_screaming,
559 ),
560 SuiteCase::new::<S, RenameUnicode>(&CASE_ATTR_RENAME_UNICODE, S::attr_rename_unicode),
561 SuiteCase::new::<S, RenameSpecialChars>(
562 &CASE_ATTR_RENAME_SPECIAL_CHARS,
563 S::attr_rename_special_chars,
564 ),
565 SuiteCase::new::<S, ProxyInt>(&CASE_PROXY_CONTAINER, S::proxy_container),
567 SuiteCase::new::<S, ProxyFieldLevel>(&CASE_PROXY_FIELD_LEVEL, S::proxy_field_level),
568 SuiteCase::new::<S, ProxyInt>(&CASE_PROXY_VALIDATION_ERROR, S::proxy_validation_error),
569 SuiteCase::new::<S, ProxyWithOption>(&CASE_PROXY_WITH_OPTION, S::proxy_with_option),
570 SuiteCase::new::<S, ProxyEnum>(&CASE_PROXY_WITH_ENUM, S::proxy_with_enum),
571 SuiteCase::new::<S, TransparentProxy>(
572 &CASE_PROXY_WITH_TRANSPARENT,
573 S::proxy_with_transparent,
574 ),
575 SuiteCase::new::<S, OpaqueProxyWrapper>(&CASE_OPAQUE_PROXY, S::opaque_proxy),
576 SuiteCase::new::<S, OpaqueProxyOptionWrapper>(
577 &CASE_OPAQUE_PROXY_OPTION,
578 S::opaque_proxy_option,
579 ),
580 SuiteCase::new::<S, OuterTransparent>(
582 &CASE_TRANSPARENT_MULTILEVEL,
583 S::transparent_multilevel,
584 ),
585 SuiteCase::new::<S, TransparentOption>(&CASE_TRANSPARENT_OPTION, S::transparent_option),
586 SuiteCase::new::<S, TransparentNonZero>(&CASE_TRANSPARENT_NONZERO, S::transparent_nonzero),
587 SuiteCase::new::<S, BoolWrapper>(&CASE_SCALAR_BOOL, S::scalar_bool),
589 SuiteCase::new::<S, IntegerTypes>(&CASE_SCALAR_INTEGERS, S::scalar_integers),
590 SuiteCase::new::<S, FloatTypes>(&CASE_SCALAR_FLOATS, S::scalar_floats),
591 SuiteCase::new::<S, FloatTypesScientific>(
592 &CASE_SCALAR_FLOATS_SCIENTIFIC,
593 S::scalar_floats_scientific,
594 ),
595 SuiteCase::new::<S, MapWrapper>(&CASE_MAP_STRING_KEYS, S::map_string_keys),
597 SuiteCase::new::<S, TupleWrapper>(&CASE_TUPLE_SIMPLE, S::tuple_simple),
598 SuiteCase::new::<S, NestedTupleWrapper>(&CASE_TUPLE_NESTED, S::tuple_nested),
599 SuiteCase::new::<S, EmptyTupleWrapper>(&CASE_TUPLE_EMPTY, S::tuple_empty),
600 SuiteCase::new::<S, SingleElementTupleWrapper>(
601 &CASE_TUPLE_SINGLE_ELEMENT,
602 S::tuple_single_element,
603 ),
604 SuiteCase::new::<S, TupleVariantEnum>(&CASE_TUPLE_STRUCT_VARIANT, S::tuple_struct_variant),
605 SuiteCase::new::<S, NewtypeVariantEnum>(
606 &CASE_TUPLE_NEWTYPE_VARIANT,
607 S::tuple_newtype_variant,
608 ),
609 SuiteCase::new::<S, UnitVariantEnum>(&CASE_ENUM_UNIT_VARIANT, S::enum_unit_variant),
611 SuiteCase::new::<S, NumericEnum>(&CASE_NUMERIC_ENUM, S::numeric_enum),
612 SuiteCase::new::<S, UntaggedEnum>(&CASE_ENUM_UNTAGGED, S::enum_untagged),
613 SuiteCase::new::<S, EnumVariantRename>(&CASE_ENUM_VARIANT_RENAME, S::enum_variant_rename),
614 SuiteCase::new::<S, SignedNumericEnum>(&CASE_SIGNED_NUMERIC_ENUM, S::signed_numeric_enum),
616 SuiteCase::new::<S, NumericEnum>(&CASE_INFERRED_NUMERIC_ENUM, S::inferred_numeric_enum),
617 SuiteCase::new::<S, UntaggedWithNull>(&CASE_UNTAGGED_WITH_NULL, S::untagged_with_null),
619 SuiteCase::new::<S, UntaggedNewtype>(
620 &CASE_UNTAGGED_NEWTYPE_VARIANT,
621 S::untagged_newtype_variant,
622 ),
623 SuiteCase::new::<S, UntaggedAsField>(&CASE_UNTAGGED_AS_FIELD, S::untagged_as_field),
624 SuiteCase::new::<S, UntaggedUnitOnly>(&CASE_UNTAGGED_UNIT_ONLY, S::untagged_unit_only),
625 SuiteCase::new::<S, BoxWrapper>(&CASE_BOX_WRAPPER, S::box_wrapper),
627 SuiteCase::new::<S, ArcWrapper>(&CASE_ARC_WRAPPER, S::arc_wrapper),
628 SuiteCase::new::<S, RcWrapper>(&CASE_RC_WRAPPER, S::rc_wrapper),
629 SuiteCase::new::<S, BoxStrWrapper>(&CASE_BOX_STR, S::box_str),
630 SuiteCase::new::<S, ArcStrWrapper>(&CASE_ARC_STR, S::arc_str),
631 SuiteCase::new::<S, RcStrWrapper>(&CASE_RC_STR, S::rc_str),
632 SuiteCase::new::<S, ArcSliceWrapper>(&CASE_ARC_SLICE, S::arc_slice),
633 SuiteCase::new::<S, SetWrapper>(&CASE_SET_BTREE, S::set_btree),
635 SuiteCase::new::<S, IntegerTypes16>(&CASE_SCALAR_INTEGERS_16, S::scalar_integers_16),
637 SuiteCase::new::<S, IntegerTypes128>(&CASE_SCALAR_INTEGERS_128, S::scalar_integers_128),
638 SuiteCase::new::<S, IntegerTypesSize>(&CASE_SCALAR_INTEGERS_SIZE, S::scalar_integers_size),
639 SuiteCase::new::<S, NonZeroTypes>(&CASE_NONZERO_INTEGERS, S::nonzero_integers),
641 SuiteCase::new::<S, NonZeroTypesExtended>(
642 &CASE_NONZERO_INTEGERS_EXTENDED,
643 S::nonzero_integers_extended,
644 ),
645 SuiteCase::new::<S, CowStrWrapper>(&CASE_COW_STR, S::cow_str),
647 SuiteCase::new::<S, BytesWrapper>(&CASE_BYTES_VEC_U8, S::bytes_vec_u8),
649 SuiteCase::new::<S, ArrayWrapper>(&CASE_ARRAY_FIXED_SIZE, S::array_fixed_size),
651 SuiteCase::new::<S, SkipUnknownStruct>(&CASE_SKIP_UNKNOWN_FIELDS, S::skip_unknown_fields),
653 SuiteCase::new::<S, StringEscapes>(&CASE_STRING_ESCAPES, S::string_escapes),
655 SuiteCase::new::<S, StringEscapesExtended>(
656 &CASE_STRING_ESCAPES_EXTENDED,
657 S::string_escapes_extended,
658 ),
659 SuiteCase::new::<S, UnitStruct>(&CASE_UNIT_STRUCT, S::unit_struct),
661 SuiteCase::new::<S, NewtypeU64Wrapper>(&CASE_NEWTYPE_U64, S::newtype_u64),
663 SuiteCase::new::<S, NewtypeStringWrapper>(&CASE_NEWTYPE_STRING, S::newtype_string),
664 SuiteCase::new::<S, CharWrapper>(&CASE_CHAR_SCALAR, S::char_scalar),
666 SuiteCase::new::<S, HashSetWrapper>(&CASE_HASHSET, S::hashset),
668 SuiteCase::new::<S, NestedVecWrapper>(&CASE_VEC_NESTED, S::vec_nested),
670 #[cfg(feature = "uuid")]
672 SuiteCase::new::<S, UuidWrapper>(&CASE_UUID, S::uuid),
673 #[cfg(feature = "ulid")]
674 SuiteCase::new::<S, UlidWrapper>(&CASE_ULID, S::ulid),
675 #[cfg(feature = "camino")]
676 SuiteCase::new::<S, CaminoWrapper>(&CASE_CAMINO_PATH, S::camino_path),
677 #[cfg(feature = "ordered-float")]
678 SuiteCase::new::<S, OrderedFloatWrapper>(&CASE_ORDERED_FLOAT, S::ordered_float),
679 #[cfg(feature = "time")]
680 SuiteCase::new::<S, TimeOffsetDateTimeWrapper>(
681 &CASE_TIME_OFFSET_DATETIME,
682 S::time_offset_datetime,
683 ),
684 #[cfg(feature = "jiff02")]
685 SuiteCase::new::<S, JiffTimestampWrapper>(&CASE_JIFF_TIMESTAMP, S::jiff_timestamp),
686 #[cfg(feature = "jiff02")]
687 SuiteCase::new::<S, JiffCivilDateTimeWrapper>(
688 &CASE_JIFF_CIVIL_DATETIME,
689 S::jiff_civil_datetime,
690 ),
691 #[cfg(feature = "chrono")]
692 SuiteCase::new::<S, ChronoDateTimeUtcWrapper>(
693 &CASE_CHRONO_DATETIME_UTC,
694 S::chrono_datetime_utc,
695 ),
696 #[cfg(feature = "chrono")]
697 SuiteCase::new::<S, ChronoNaiveDateTimeWrapper>(
698 &CASE_CHRONO_NAIVE_DATETIME,
699 S::chrono_naive_datetime,
700 ),
701 #[cfg(feature = "chrono")]
702 SuiteCase::new::<S, ChronoNaiveDateWrapper>(&CASE_CHRONO_NAIVE_DATE, S::chrono_naive_date),
703 #[cfg(feature = "chrono")]
704 SuiteCase::new::<S, ChronoNaiveTimeWrapper>(&CASE_CHRONO_NAIVE_TIME, S::chrono_naive_time),
705 #[cfg(feature = "chrono")]
706 SuiteCase::new::<S, ChronoInVecWrapper>(&CASE_CHRONO_IN_VEC, S::chrono_in_vec),
707 #[cfg(feature = "bytes")]
709 SuiteCase::new::<S, BytesBytesWrapper>(&CASE_BYTES_BYTES, S::bytes_bytes),
710 #[cfg(feature = "bytes")]
711 SuiteCase::new::<S, BytesBytesMutWrapper>(&CASE_BYTES_BYTES_MUT, S::bytes_bytes_mut),
712 #[cfg(feature = "bytestring")]
714 SuiteCase::new::<S, ByteStringWrapper>(&CASE_BYTESTRING, S::bytestring),
715 #[cfg(feature = "compact_str")]
716 SuiteCase::new::<S, CompactStringWrapper>(&CASE_COMPACT_STRING, S::compact_string),
717 #[cfg(feature = "smartstring")]
718 SuiteCase::new::<S, SmartStringWrapper>(&CASE_SMARTSTRING, S::smartstring),
719 #[cfg(feature = "facet-value")]
721 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_NULL, S::value_null),
722 #[cfg(feature = "facet-value")]
723 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_BOOL, S::value_bool),
724 #[cfg(feature = "facet-value")]
725 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_INTEGER, S::value_integer),
726 #[cfg(feature = "facet-value")]
727 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_FLOAT, S::value_float),
728 #[cfg(feature = "facet-value")]
729 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_STRING, S::value_string),
730 #[cfg(feature = "facet-value")]
731 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_ARRAY, S::value_array),
732 #[cfg(feature = "facet-value")]
733 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_OBJECT, S::value_object),
734 ]
735}
736
737#[derive(Debug, Clone, Copy, Default)]
739pub enum CompareMode {
740 #[default]
742 Reflection,
743 PartialEq,
745}
746
747#[derive(Debug, Clone)]
749pub struct CaseSpec {
750 payload: CasePayload,
751 note: Option<&'static str>,
752 roundtrip: RoundtripSpec,
753 compare_mode: CompareMode,
754}
755
756impl CaseSpec {
757 pub const fn from_bytes(input: &'static [u8]) -> Self {
759 Self {
760 payload: CasePayload::Input(input),
761 note: None,
762 roundtrip: RoundtripSpec::Enabled,
763 compare_mode: CompareMode::Reflection,
764 }
765 }
766
767 #[allow(clippy::should_implement_trait)]
769 pub fn from_str(input: &'static str) -> Self {
770 Self::from_bytes(input.as_bytes())
771 }
772
773 pub const fn skip(reason: &'static str) -> Self {
775 Self {
776 payload: CasePayload::Skip { reason },
777 note: None,
778 roundtrip: RoundtripSpec::Enabled,
779 compare_mode: CompareMode::Reflection,
780 }
781 }
782
783 pub fn with_note(mut self, note: &'static str) -> Self {
785 self.note = Some(note);
786 self
787 }
788
789 pub fn without_roundtrip(mut self, reason: &'static str) -> Self {
791 self.roundtrip = RoundtripSpec::Disabled { reason };
792 self
793 }
794
795 pub fn with_partial_eq(mut self) -> Self {
798 self.compare_mode = CompareMode::PartialEq;
799 self
800 }
801
802 pub fn expect_error(input: &'static str, error_contains: &'static str) -> Self {
804 Self {
805 payload: CasePayload::ExpectError {
806 input: input.as_bytes(),
807 error_contains,
808 },
809 note: None,
810 roundtrip: RoundtripSpec::Disabled {
811 reason: "error case",
812 },
813 compare_mode: CompareMode::Reflection,
814 }
815 }
816
817 pub fn from_bytes_vec(input: Vec<u8>) -> Self {
822 Self {
823 payload: CasePayload::DynamicInput(input),
824 note: None,
825 roundtrip: RoundtripSpec::Enabled,
826 compare_mode: CompareMode::Reflection,
827 }
828 }
829}
830
831#[derive(Debug, Clone)]
832enum CasePayload {
833 Input(&'static [u8]),
834 DynamicInput(Vec<u8>),
836 Skip {
837 reason: &'static str,
838 },
839 ExpectError {
841 input: &'static [u8],
842 error_contains: &'static str,
843 },
844}
845
846#[derive(Debug, Clone)]
847enum RoundtripSpec {
848 Enabled,
849 Disabled { reason: &'static str },
850}
851
852struct CaseDescriptor<T> {
853 id: &'static str,
854 description: &'static str,
855 expected: fn() -> T,
856}
857
858#[derive(Debug)]
859pub enum CaseOutcome {
860 Passed,
861 Skipped(&'static str),
862 Failed(String),
863}
864
865pub struct SuiteCase {
866 pub id: &'static str,
867 pub description: &'static str,
868 skip_reason: Option<&'static str>,
869 runner: Box<dyn Fn() -> CaseOutcome + Send + Sync + 'static>,
870 #[cfg(feature = "tokio")]
871 async_runner: Box<dyn Fn() -> CaseOutcome + Send + Sync + 'static>,
872}
873
874impl SuiteCase {
875 #[cfg(not(feature = "tokio"))]
876 fn new<S, T>(desc: &'static CaseDescriptor<T>, provider: fn() -> CaseSpec) -> Self
877 where
878 S: FormatSuite,
879 for<'facet> T: Facet<'facet>,
880 T: Debug + PartialEq + 'static,
881 {
882 let spec = provider();
883 let skip_reason = match spec.payload {
884 CasePayload::Skip { reason } => Some(reason),
885 _ => None,
886 };
887 let runner_spec = spec.clone();
888 let runner = move || execute_case::<S, T>(desc, runner_spec.clone());
889
890 Self {
891 id: desc.id,
892 description: desc.description,
893 skip_reason,
894 runner: Box::new(runner),
895 }
896 }
897
898 #[cfg(feature = "tokio")]
899 fn new<S, T>(desc: &'static CaseDescriptor<T>, provider: fn() -> CaseSpec) -> Self
900 where
901 S: FormatSuite + 'static,
902 for<'facet> T: Facet<'facet>,
903 T: Debug + PartialEq + 'static,
904 {
905 let spec = provider();
906 let skip_reason = match spec.payload {
907 CasePayload::Skip { reason } => Some(reason),
908 _ => None,
909 };
910 let runner_spec = spec.clone();
911 let runner = move || execute_case::<S, T>(desc, runner_spec.clone());
912
913 #[cfg(feature = "tokio")]
914 let async_runner = {
915 let async_spec = spec.clone();
916 Box::new(move || {
917 let spec = async_spec.clone();
918 let rt = tokio::runtime::Builder::new_current_thread()
920 .enable_all()
921 .build()
922 .expect("failed to create tokio runtime");
923 rt.block_on(execute_case_async::<S, T>(desc, spec))
924 }) as Box<dyn Fn() -> CaseOutcome + Send + Sync + 'static>
925 };
926
927 Self {
928 id: desc.id,
929 description: desc.description,
930 skip_reason,
931 runner: Box::new(runner),
932 #[cfg(feature = "tokio")]
933 async_runner,
934 }
935 }
936
937 pub fn run(&self) -> CaseOutcome {
938 (self.runner)()
939 }
940
941 #[cfg(feature = "tokio")]
944 pub fn run_async(&self) -> CaseOutcome {
945 (self.async_runner)()
946 }
947
948 pub fn skip_reason(&self) -> Option<&'static str> {
949 self.skip_reason
950 }
951}
952
953fn execute_case<S, T>(desc: &'static CaseDescriptor<T>, spec: CaseSpec) -> CaseOutcome
954where
955 S: FormatSuite,
956 for<'facet> T: Facet<'facet>,
957 T: Debug + PartialEq,
958{
959 let note = spec.note;
960 let compare_mode = spec.compare_mode;
961 let roundtrip_disabled_reason = match spec.roundtrip {
962 RoundtripSpec::Enabled => None,
963 RoundtripSpec::Disabled { reason } => Some(reason),
964 };
965 let highlight_language = S::highlight_language();
966 match spec.payload {
967 CasePayload::Skip { reason } => CaseOutcome::Skipped(reason),
968 CasePayload::Input(input) => {
969 let expected = (desc.expected)();
970 let actual = match S::deserialize::<T>(input) {
971 Ok(value) => value,
972 Err(err) => return CaseOutcome::Failed(err.to_string()),
973 };
974
975 emit_case_showcase::<S, T>(
976 desc,
977 note,
978 roundtrip_disabled_reason,
979 input,
980 highlight_language,
981 &actual,
982 );
983
984 let first_assert = panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
986 CompareMode::Reflection => {
987 assert_same!(
988 actual,
989 expected,
990 "facet-format-suite {} ({}) produced unexpected value",
991 desc.id,
992 desc.description
993 );
994 }
995 CompareMode::PartialEq => {
996 assert_eq!(
997 actual, expected,
998 "facet-format-suite {} ({}) produced unexpected value",
999 desc.id, desc.description
1000 );
1001 }
1002 }));
1003 if let Err(payload) = first_assert {
1004 return CaseOutcome::Failed(format_panic(payload));
1005 }
1006
1007 if roundtrip_disabled_reason.is_some() {
1008 return CaseOutcome::Passed;
1009 }
1010
1011 let Some(serialized) = S::serialize(&actual) else {
1012 return CaseOutcome::Passed;
1013 };
1014
1015 let serialized = match serialized {
1016 Ok(bytes) => bytes,
1017 Err(msg) => {
1018 return CaseOutcome::Failed(format!(
1019 "facet-format-suite {} ({}) serialization failed: {msg}",
1020 desc.id, desc.description
1021 ));
1022 }
1023 };
1024
1025 let roundtripped = match S::deserialize::<T>(&serialized) {
1026 Ok(value) => value,
1027 Err(err) => {
1028 return CaseOutcome::Failed(format!(
1029 "facet-format-suite {} ({}) round-trip deserialize failed: {err}",
1030 desc.id, desc.description
1031 ));
1032 }
1033 };
1034
1035 match panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1037 CompareMode::Reflection => {
1038 assert_same!(
1039 roundtripped,
1040 actual,
1041 "facet-format-suite {} ({}) round-trip mismatch",
1042 desc.id,
1043 desc.description
1044 );
1045 }
1046 CompareMode::PartialEq => {
1047 assert_eq!(
1048 roundtripped, actual,
1049 "facet-format-suite {} ({}) round-trip mismatch",
1050 desc.id, desc.description
1051 );
1052 }
1053 })) {
1054 Ok(_) => CaseOutcome::Passed,
1055 Err(payload) => CaseOutcome::Failed(format_panic(payload)),
1056 }
1057 }
1058 CasePayload::DynamicInput(ref input) => {
1059 let expected = (desc.expected)();
1060 let actual = match S::deserialize::<T>(input) {
1061 Ok(value) => value,
1062 Err(err) => return CaseOutcome::Failed(err.to_string()),
1063 };
1064
1065 emit_case_showcase_dynamic::<S, T>(
1066 desc,
1067 note,
1068 roundtrip_disabled_reason,
1069 input,
1070 &actual,
1071 );
1072
1073 let first_assert = panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1075 CompareMode::Reflection => {
1076 assert_same!(
1077 actual,
1078 expected,
1079 "facet-format-suite {} ({}) produced unexpected value",
1080 desc.id,
1081 desc.description
1082 );
1083 }
1084 CompareMode::PartialEq => {
1085 assert_eq!(
1086 actual, expected,
1087 "facet-format-suite {} ({}) produced unexpected value",
1088 desc.id, desc.description
1089 );
1090 }
1091 }));
1092 if let Err(payload) = first_assert {
1093 return CaseOutcome::Failed(format_panic(payload));
1094 }
1095
1096 if roundtrip_disabled_reason.is_some() {
1097 return CaseOutcome::Passed;
1098 }
1099
1100 let Some(serialized) = S::serialize(&actual) else {
1101 return CaseOutcome::Passed;
1102 };
1103
1104 let serialized = match serialized {
1105 Ok(bytes) => bytes,
1106 Err(msg) => {
1107 return CaseOutcome::Failed(format!(
1108 "facet-format-suite {} ({}) serialization failed: {msg}",
1109 desc.id, desc.description
1110 ));
1111 }
1112 };
1113
1114 let roundtripped = match S::deserialize::<T>(&serialized) {
1115 Ok(value) => value,
1116 Err(err) => {
1117 return CaseOutcome::Failed(format!(
1118 "facet-format-suite {} ({}) round-trip deserialize failed: {err}",
1119 desc.id, desc.description
1120 ));
1121 }
1122 };
1123
1124 match panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1126 CompareMode::Reflection => {
1127 assert_same!(
1128 roundtripped,
1129 actual,
1130 "facet-format-suite {} ({}) round-trip mismatch",
1131 desc.id,
1132 desc.description
1133 );
1134 }
1135 CompareMode::PartialEq => {
1136 assert_eq!(
1137 roundtripped, actual,
1138 "facet-format-suite {} ({}) round-trip mismatch",
1139 desc.id, desc.description
1140 );
1141 }
1142 })) {
1143 Ok(_) => CaseOutcome::Passed,
1144 Err(payload) => CaseOutcome::Failed(format_panic(payload)),
1145 }
1146 }
1147 CasePayload::ExpectError {
1148 input,
1149 error_contains,
1150 } => {
1151 emit_error_case_showcase::<S>(
1152 desc.id,
1153 desc.description,
1154 note,
1155 input,
1156 highlight_language,
1157 error_contains,
1158 );
1159
1160 match S::deserialize::<T>(input) {
1161 Ok(_) => CaseOutcome::Failed(format!(
1162 "facet-format-suite {} ({}) expected error containing '{}' but deserialization succeeded",
1163 desc.id, desc.description, error_contains
1164 )),
1165 Err(err) => {
1166 let err_str = err.to_string();
1167 if err_str.contains(error_contains) {
1168 CaseOutcome::Passed
1169 } else {
1170 CaseOutcome::Failed(format!(
1171 "facet-format-suite {} ({}) expected error containing '{}' but got: {}",
1172 desc.id, desc.description, error_contains, err_str
1173 ))
1174 }
1175 }
1176 }
1177 }
1178 }
1179}
1180
1181fn format_panic(payload: Box<dyn Any + Send>) -> String {
1182 if let Some(msg) = payload.downcast_ref::<&str>() {
1183 msg.to_string()
1184 } else if let Some(msg) = payload.downcast_ref::<String>() {
1185 msg.clone()
1186 } else {
1187 "panic with non-string payload".into()
1188 }
1189}
1190
1191#[cfg(feature = "tokio")]
1192async fn execute_case_async<S, T>(desc: &'static CaseDescriptor<T>, spec: CaseSpec) -> CaseOutcome
1193where
1194 S: FormatSuite,
1195 for<'facet> T: Facet<'facet>,
1196 T: Debug + PartialEq,
1197{
1198 let compare_mode = spec.compare_mode;
1199 match spec.payload {
1200 CasePayload::Skip { reason } => CaseOutcome::Skipped(reason),
1201 CasePayload::Input(input) => {
1202 let result = S::deserialize_async::<T>(input).await;
1204 let actual = match result {
1205 None => {
1206 return CaseOutcome::Skipped("async deserialization not supported");
1208 }
1209 Some(Ok(value)) => value,
1210 Some(Err(err)) => return CaseOutcome::Failed(err.to_string()),
1211 };
1212
1213 let expected = (desc.expected)();
1214
1215 let first_assert = panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1217 CompareMode::Reflection => {
1218 assert_same!(
1219 actual,
1220 expected,
1221 "facet-format-suite {} ({}) async produced unexpected value",
1222 desc.id,
1223 desc.description
1224 );
1225 }
1226 CompareMode::PartialEq => {
1227 assert_eq!(
1228 actual, expected,
1229 "facet-format-suite {} ({}) async produced unexpected value",
1230 desc.id, desc.description
1231 );
1232 }
1233 }));
1234 if let Err(payload) = first_assert {
1235 return CaseOutcome::Failed(format_panic(payload));
1236 }
1237
1238 CaseOutcome::Passed
1240 }
1241 CasePayload::DynamicInput(ref input) => {
1242 let result = S::deserialize_async::<T>(input).await;
1244 let actual = match result {
1245 None => {
1246 return CaseOutcome::Skipped("async deserialization not supported");
1248 }
1249 Some(Ok(value)) => value,
1250 Some(Err(err)) => return CaseOutcome::Failed(err.to_string()),
1251 };
1252
1253 let expected = (desc.expected)();
1254
1255 let first_assert = panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1257 CompareMode::Reflection => {
1258 assert_same!(
1259 actual,
1260 expected,
1261 "facet-format-suite {} ({}) async produced unexpected value",
1262 desc.id,
1263 desc.description
1264 );
1265 }
1266 CompareMode::PartialEq => {
1267 assert_eq!(
1268 actual, expected,
1269 "facet-format-suite {} ({}) async produced unexpected value",
1270 desc.id, desc.description
1271 );
1272 }
1273 }));
1274 if let Err(payload) = first_assert {
1275 return CaseOutcome::Failed(format_panic(payload));
1276 }
1277
1278 CaseOutcome::Passed
1280 }
1281 CasePayload::ExpectError {
1282 input,
1283 error_contains,
1284 } => {
1285 let result = S::deserialize_async::<T>(input).await;
1286 match result {
1287 None => CaseOutcome::Skipped("async deserialization not supported"),
1288 Some(Ok(_)) => CaseOutcome::Failed(format!(
1289 "facet-format-suite {} ({}) async expected error containing '{}' but deserialization succeeded",
1290 desc.id, desc.description, error_contains
1291 )),
1292 Some(Err(err)) => {
1293 let err_str = err.to_string();
1294 if err_str.contains(error_contains) {
1295 CaseOutcome::Passed
1296 } else {
1297 CaseOutcome::Failed(format!(
1298 "facet-format-suite {} ({}) async expected error containing '{}' but got: {}",
1299 desc.id, desc.description, error_contains, err_str
1300 ))
1301 }
1302 }
1303 }
1304 }
1305 }
1306}
1307
1308const CASE_STRUCT_SINGLE_FIELD: CaseDescriptor<StructSingleField> = CaseDescriptor {
1309 id: "struct::single_field",
1310 description: "single-field object parsed into StructSingleField",
1311 expected: || StructSingleField {
1312 name: "facet".into(),
1313 },
1314};
1315
1316const CASE_SEQUENCE_NUMBERS: CaseDescriptor<Vec<u64>> = CaseDescriptor {
1317 id: "sequence::numbers",
1318 description: "array of unsigned integers parsed into Vec<u64>",
1319 expected: || vec![1, 2, 3],
1320};
1321
1322const CASE_SEQUENCE_MIXED_SCALARS: CaseDescriptor<Vec<MixedScalar>> = CaseDescriptor {
1323 id: "sequence::mixed_scalars",
1324 description: "array of heterogeneous scalars parsed into Vec<MixedScalar>",
1325 expected: || {
1326 vec![
1327 MixedScalar::Signed(-1),
1328 MixedScalar::Float(4.625),
1329 MixedScalar::Null,
1330 MixedScalar::Bool(true),
1331 ]
1332 },
1333};
1334
1335const CASE_STRUCT_NESTED: CaseDescriptor<NestedParent> = CaseDescriptor {
1336 id: "struct::nested",
1337 description: "struct containing nested child and tag list",
1338 expected: || NestedParent {
1339 id: 42,
1340 child: NestedChild {
1341 code: "alpha".into(),
1342 active: true,
1343 },
1344 tags: vec!["core".into(), "json".into()],
1345 },
1346};
1347
1348const CASE_ENUM_COMPLEX: CaseDescriptor<ComplexEnum> = CaseDescriptor {
1349 id: "enum::complex",
1350 description: "enum with unit, tuple, and struct variants",
1351 expected: || ComplexEnum::Label {
1352 name: "facet".into(),
1353 level: 7,
1354 },
1355};
1356
1357const CASE_ATTR_RENAME_FIELD: CaseDescriptor<RenamedField> = CaseDescriptor {
1360 id: "attr::rename_field",
1361 description: "field with #[facet(rename = \"userName\")]",
1362 expected: || RenamedField {
1363 user_name: "alice".into(),
1364 age: 30,
1365 },
1366};
1367
1368const CASE_ATTR_RENAME_ALL_CAMEL: CaseDescriptor<CamelCaseStruct> = CaseDescriptor {
1369 id: "attr::rename_all_camel",
1370 description: "struct with #[facet(rename_all = \"camelCase\")]",
1371 expected: || CamelCaseStruct {
1372 first_name: "Jane".into(),
1373 last_name: "Doe".into(),
1374 is_active: true,
1375 },
1376};
1377
1378const CASE_ATTR_DEFAULT_FIELD: CaseDescriptor<WithDefault> = CaseDescriptor {
1379 id: "attr::default_field",
1380 description: "field with #[facet(default)] missing from input",
1381 expected: || WithDefault {
1382 required: "present".into(),
1383 optional_count: 0, },
1385};
1386
1387const CASE_ATTR_DEFAULT_STRUCT: CaseDescriptor<StructLevelDefault> = CaseDescriptor {
1388 id: "attr::default_struct",
1389 description: "struct-level #[facet(default)] with missing field uses Default",
1390 expected: || StructLevelDefault {
1391 count: 123,
1392 message: String::new(), },
1394};
1395
1396const CASE_ATTR_DEFAULT_FUNCTION: CaseDescriptor<WithDefaultFunction> = CaseDescriptor {
1397 id: "attr::default_function",
1398 description: "field with #[facet(default = expr)] uses custom default",
1399 expected: || WithDefaultFunction {
1400 magic_number: 42, name: "hello".into(),
1402 },
1403};
1404
1405const CASE_OPTION_NONE: CaseDescriptor<WithOption> = CaseDescriptor {
1406 id: "option::none",
1407 description: "Option<T> field missing from input becomes None",
1408 expected: || WithOption {
1409 name: "test".into(),
1410 nickname: None,
1411 },
1412};
1413
1414const CASE_OPTION_SOME: CaseDescriptor<WithOption> = CaseDescriptor {
1415 id: "option::some",
1416 description: "Option<T> field with Some value",
1417 expected: || WithOption {
1418 name: "test".into(),
1419 nickname: Some("nick".into()),
1420 },
1421};
1422
1423const CASE_OPTION_NULL: CaseDescriptor<WithOption> = CaseDescriptor {
1424 id: "option::null",
1425 description: "Option<T> field with explicit null becomes None",
1426 expected: || WithOption {
1427 name: "test".into(),
1428 nickname: None,
1429 },
1430};
1431
1432const CASE_ATTR_SKIP_SERIALIZING: CaseDescriptor<WithSkipSerializing> = CaseDescriptor {
1433 id: "attr::skip_serializing",
1434 description: "field with #[facet(skip_serializing)] not in output",
1435 expected: || WithSkipSerializing {
1436 visible: "shown".into(),
1437 hidden: String::new(), },
1439};
1440
1441const CASE_ATTR_SKIP_SERIALIZING_IF: CaseDescriptor<WithSkipSerializingIf> = CaseDescriptor {
1442 id: "attr::skip_serializing_if",
1443 description: "field with #[facet(skip_serializing_if = pred)] skipped when pred is true",
1444 expected: || WithSkipSerializingIf {
1445 name: "test".into(),
1446 optional_data: None, },
1448};
1449
1450const CASE_ATTR_SKIP: CaseDescriptor<WithSkip> = CaseDescriptor {
1451 id: "attr::skip",
1452 description: "field with #[facet(skip)] ignored for both ser and de",
1453 expected: || WithSkip {
1454 visible: "data".into(),
1455 internal: 0, },
1457};
1458
1459const CASE_ENUM_INTERNALLY_TAGGED: CaseDescriptor<InternallyTagged> = CaseDescriptor {
1462 id: "enum::internally_tagged",
1463 description: "internally tagged enum with #[facet(tag = \"type\")]",
1464 expected: || InternallyTagged::Circle { radius: 5.0 },
1465};
1466
1467const CASE_ENUM_ADJACENTLY_TAGGED: CaseDescriptor<AdjacentlyTagged> = CaseDescriptor {
1468 id: "enum::adjacently_tagged",
1469 description: "adjacently tagged enum with #[facet(tag = \"t\", content = \"c\")]",
1470 expected: || AdjacentlyTagged::Message("hello".into()),
1471};
1472
1473const CASE_STRUCT_FLATTEN: CaseDescriptor<FlattenOuter> = CaseDescriptor {
1476 id: "struct::flatten",
1477 description: "struct with #[facet(flatten)] flattening inner fields",
1478 expected: || FlattenOuter {
1479 name: "point".into(),
1480 coords: FlattenInner { x: 10, y: 20 },
1481 },
1482};
1483
1484const CASE_TRANSPARENT_NEWTYPE: CaseDescriptor<UserRecord> = CaseDescriptor {
1485 id: "attr::transparent",
1486 description: "struct containing #[facet(transparent)] newtype",
1487 expected: || UserRecord {
1488 id: UserId(42),
1489 name: "alice".into(),
1490 },
1491};
1492
1493const CASE_FLATTEN_OPTIONAL_SOME: CaseDescriptor<FlattenOptionalSome> = CaseDescriptor {
1496 id: "flatten::optional_some",
1497 description: "flattened field is Option<T> with Some value",
1498 expected: || FlattenOptionalSome {
1499 name: "test".into(),
1500 metadata: Some(FlattenMetadata {
1501 version: 1,
1502 author: "alice".into(),
1503 }),
1504 },
1505};
1506
1507const CASE_FLATTEN_OPTIONAL_NONE: CaseDescriptor<FlattenOptionalNone> = CaseDescriptor {
1508 id: "flatten::optional_none",
1509 description: "flattened field is Option<T> with None value",
1510 expected: || FlattenOptionalNone {
1511 name: "test".into(),
1512 metadata: None,
1513 },
1514};
1515
1516const CASE_FLATTEN_OVERLAPPING_FIELDS_ERROR: CaseDescriptor<FlattenOverlapping> = CaseDescriptor {
1517 id: "flatten::overlapping_fields_error",
1518 description: "two flattened structs with overlapping field names (error)",
1519 expected: || FlattenOverlapping {
1520 part_a: FlattenPartA {
1521 field_a: "a".into(),
1522 shared: 1,
1523 },
1524 part_b: FlattenPartB {
1525 field_b: "b".into(),
1526 shared: 2,
1527 },
1528 },
1529};
1530
1531const CASE_FLATTEN_MULTILEVEL: CaseDescriptor<FlattenLevel1> = CaseDescriptor {
1532 id: "flatten::multilevel",
1533 description: "three levels of nested flatten (A -> B -> C, all flattened)",
1534 expected: || FlattenLevel1 {
1535 top_field: "top".into(),
1536 level2: FlattenLevel2 {
1537 mid_field: 42,
1538 level3: FlattenLevel3 { deep_field: 100 },
1539 },
1540 },
1541};
1542
1543const CASE_FLATTEN_MULTIPLE_ENUMS: CaseDescriptor<FlattenMultipleEnums> = CaseDescriptor {
1544 id: "flatten::multiple_enums",
1545 description: "two different enums flattened into same struct",
1546 expected: || FlattenMultipleEnums {
1547 name: "service".into(),
1548 auth: FlattenAuthMethod::Password(FlattenAuthPassword {
1549 password: "secret".into(),
1550 }),
1551 transport: FlattenTransport::Tcp(FlattenTransportTcp { port: 8080 }),
1552 },
1553};
1554
1555const CASE_DENY_UNKNOWN_FIELDS: CaseDescriptor<DenyUnknownStruct> = CaseDescriptor {
1558 id: "error::deny_unknown_fields",
1559 description: "#[facet(deny_unknown_fields)] rejects input with extra fields",
1560 expected: || DenyUnknownStruct {
1561 foo: "abc".into(),
1562 bar: 42,
1563 },
1564};
1565
1566const CASE_ERROR_TYPE_MISMATCH_STRING_TO_INT: CaseDescriptor<ExpectsInteger> = CaseDescriptor {
1567 id: "error::type_mismatch_string_to_int",
1568 description: "type mismatch - string provided where integer expected",
1569 expected: || ExpectsInteger { value: 42 },
1570};
1571
1572const CASE_ERROR_TYPE_MISMATCH_OBJECT_TO_ARRAY: CaseDescriptor<ExpectsArray> = CaseDescriptor {
1573 id: "error::type_mismatch_object_to_array",
1574 description: "structure mismatch - object provided where array expected",
1575 expected: || ExpectsArray {
1576 items: vec![1, 2, 3],
1577 },
1578};
1579
1580const CASE_ERROR_MISSING_REQUIRED_FIELD: CaseDescriptor<RequiresAllFields> = CaseDescriptor {
1581 id: "error::missing_required_field",
1582 description: "missing required field error",
1583 expected: || RequiresAllFields {
1584 name: "Alice".into(),
1585 age: 30,
1586 email: "alice@example.com".into(),
1587 },
1588};
1589
1590const CASE_ATTR_ALIAS: CaseDescriptor<WithAlias> = CaseDescriptor {
1593 id: "attr::alias",
1594 description: "field with #[facet(alias = \"old_name\")] accepts alternative name",
1595 expected: || WithAlias {
1596 new_name: "value".into(),
1597 count: 5,
1598 },
1599};
1600
1601const CASE_ATTR_RENAME_VS_ALIAS: CaseDescriptor<RenameVsAlias> = CaseDescriptor {
1604 id: "attr::rename_vs_alias_precedence",
1605 description: "when both rename and alias present, rename takes precedence",
1606 expected: || RenameVsAlias {
1607 field: "test".into(),
1608 id: 1,
1609 },
1610};
1611
1612const CASE_ATTR_RENAME_ALL_KEBAB: CaseDescriptor<RenameAllKebab> = CaseDescriptor {
1613 id: "attr::rename_all_kebab",
1614 description: "struct with #[facet(rename_all = \"kebab-case\")]",
1615 expected: || RenameAllKebab {
1616 first_name: "John".into(),
1617 last_name: "Doe".into(),
1618 user_id: 42,
1619 },
1620};
1621
1622const CASE_ATTR_RENAME_ALL_SCREAMING: CaseDescriptor<RenameAllScreaming> = CaseDescriptor {
1623 id: "attr::rename_all_screaming",
1624 description: "struct with #[facet(rename_all = \"SCREAMING_SNAKE_CASE\")]",
1625 expected: || RenameAllScreaming {
1626 api_key: "secret-123".into(),
1627 max_retry_count: 5,
1628 },
1629};
1630
1631const CASE_ATTR_RENAME_UNICODE: CaseDescriptor<RenameUnicode> = CaseDescriptor {
1632 id: "attr::rename_unicode",
1633 description: "field with unicode (emoji) rename #[facet(rename = \"🎉\")]",
1634 expected: || RenameUnicode {
1635 celebration: "party".into(),
1636 },
1637};
1638
1639const CASE_ATTR_RENAME_SPECIAL_CHARS: CaseDescriptor<RenameSpecialChars> = CaseDescriptor {
1640 id: "attr::rename_special_chars",
1641 description: "field with special chars rename #[facet(rename = \"@type\")]",
1642 expected: || RenameSpecialChars {
1643 type_field: "node".into(),
1644 },
1645};
1646
1647const CASE_PROXY_CONTAINER: CaseDescriptor<ProxyInt> = CaseDescriptor {
1650 id: "proxy::container",
1651 description: "container-level #[facet(proxy = IntAsString)] deserializes int from string",
1652 expected: || ProxyInt { value: 42 },
1653};
1654
1655const CASE_PROXY_FIELD_LEVEL: CaseDescriptor<ProxyFieldLevel> = CaseDescriptor {
1656 id: "proxy::field_level",
1657 description: "field-level #[facet(proxy = IntAsString)] on individual field",
1658 expected: || ProxyFieldLevel {
1659 name: "test".into(),
1660 count: 100,
1661 },
1662};
1663
1664const CASE_PROXY_VALIDATION_ERROR: CaseDescriptor<ProxyInt> = CaseDescriptor {
1665 id: "proxy::validation_error",
1666 description: "proxy conversion error when validation fails (expects error)",
1667 expected: || ProxyInt { value: 0 }, };
1669
1670const CASE_PROXY_WITH_OPTION: CaseDescriptor<ProxyWithOption> = CaseDescriptor {
1671 id: "proxy::with_option",
1672 description: "proxy wrapping Option<T>",
1673 expected: || ProxyWithOption {
1674 name: "test".into(),
1675 count: Some(42),
1676 },
1677};
1678
1679const CASE_PROXY_WITH_ENUM: CaseDescriptor<ProxyEnum> = CaseDescriptor {
1680 id: "proxy::with_enum",
1681 description: "proxy on enum variant",
1682 expected: || ProxyEnum::Value(99),
1683};
1684
1685const CASE_PROXY_WITH_TRANSPARENT: CaseDescriptor<TransparentProxy> = CaseDescriptor {
1686 id: "proxy::with_transparent",
1687 description: "transparent wrapper with proxy",
1688 expected: || TransparentProxy(42),
1689};
1690
1691const CASE_OPAQUE_PROXY: CaseDescriptor<OpaqueProxyWrapper> = CaseDescriptor {
1692 id: "proxy::opaque",
1693 description: "#[facet(opaque, proxy = ...)] where target type doesn't implement Facet",
1694 expected: || OpaqueProxyWrapper {
1695 value: OpaqueType { inner: 42 },
1696 },
1697};
1698
1699const CASE_OPAQUE_PROXY_OPTION: CaseDescriptor<OpaqueProxyOptionWrapper> = CaseDescriptor {
1700 id: "proxy::opaque_option",
1701 description: "#[facet(opaque, proxy = ...)] on Option<OpaqueType>",
1702 expected: || OpaqueProxyOptionWrapper {
1703 value: Some(OpaqueType { inner: 99 }),
1704 },
1705};
1706
1707const CASE_TRANSPARENT_MULTILEVEL: CaseDescriptor<OuterTransparent> = CaseDescriptor {
1710 id: "transparent::multilevel",
1711 description: "transparent wrapping another transparent type",
1712 expected: || OuterTransparent(InnerTransparent(42)),
1713};
1714
1715const CASE_TRANSPARENT_OPTION: CaseDescriptor<TransparentOption> = CaseDescriptor {
1716 id: "transparent::option",
1717 description: "transparent wrapping Option<T>",
1718 expected: || TransparentOption(Some(99)),
1719};
1720
1721const CASE_TRANSPARENT_NONZERO: CaseDescriptor<TransparentNonZero> = CaseDescriptor {
1722 id: "transparent::nonzero",
1723 description: "transparent wrapping NonZero type",
1724 expected: || TransparentNonZero(std::num::NonZeroU32::new(42).unwrap()),
1725};
1726
1727const CASE_SCALAR_BOOL: CaseDescriptor<BoolWrapper> = CaseDescriptor {
1730 id: "scalar::bool",
1731 description: "boolean scalar values",
1732 expected: || BoolWrapper {
1733 yes: true,
1734 no: false,
1735 },
1736};
1737
1738const CASE_SCALAR_INTEGERS: CaseDescriptor<IntegerTypes> = CaseDescriptor {
1739 id: "scalar::integers",
1740 description: "various integer types (i8, u8, i32, u32, i64, u64)",
1741 expected: || IntegerTypes {
1742 signed_8: -128,
1743 unsigned_8: 255,
1744 signed_32: -2_147_483_648,
1745 unsigned_32: 4_294_967_295,
1746 signed_64: -9_223_372_036_854_775_808,
1747 unsigned_64: 18_446_744_073_709_551_615,
1748 },
1749};
1750
1751const CASE_SCALAR_FLOATS: CaseDescriptor<FloatTypes> = CaseDescriptor {
1752 id: "scalar::floats",
1753 description: "floating point types (f32, f64)",
1754 expected: || FloatTypes {
1755 float_32: 1.5,
1756 float_64: 2.25,
1757 },
1758};
1759
1760const CASE_SCALAR_FLOATS_SCIENTIFIC: CaseDescriptor<FloatTypesScientific> = CaseDescriptor {
1761 id: "scalar::floats_scientific",
1762 description: "floating point with scientific notation (1.23e10, -4.56e-7)",
1763 expected: || FloatTypesScientific {
1764 large: 1.23e10,
1765 small: -4.56e-7,
1766 positive_exp: 5e3,
1767 },
1768};
1769
1770const CASE_MAP_STRING_KEYS: CaseDescriptor<MapWrapper> = CaseDescriptor {
1773 id: "collection::map",
1774 description: "BTreeMap<String, i32> with string keys",
1775 expected: || {
1776 let mut map = std::collections::BTreeMap::new();
1777 map.insert("alpha".into(), 1);
1778 map.insert("beta".into(), 2);
1779 MapWrapper { data: map }
1780 },
1781};
1782
1783const CASE_TUPLE_SIMPLE: CaseDescriptor<TupleWrapper> = CaseDescriptor {
1784 id: "collection::tuple",
1785 description: "tuple (String, i32, bool)",
1786 expected: || TupleWrapper {
1787 triple: ("hello".into(), 42, true),
1788 },
1789};
1790
1791const CASE_TUPLE_NESTED: CaseDescriptor<NestedTupleWrapper> = CaseDescriptor {
1792 id: "collection::tuple_nested",
1793 description: "nested tuple ((i32, i32), (String, bool))",
1794 expected: || NestedTupleWrapper {
1795 outer: ((1, 2), ("test".into(), true)),
1796 },
1797};
1798
1799const CASE_TUPLE_EMPTY: CaseDescriptor<EmptyTupleWrapper> = CaseDescriptor {
1800 id: "collection::tuple_empty",
1801 description: "empty tuple () as a field",
1802 expected: || EmptyTupleWrapper {
1803 name: "test".into(),
1804 empty: (),
1805 },
1806};
1807
1808const CASE_TUPLE_SINGLE_ELEMENT: CaseDescriptor<SingleElementTupleWrapper> = CaseDescriptor {
1809 id: "collection::tuple_single_element",
1810 description: "single-element tuple (T,) as a field",
1811 expected: || SingleElementTupleWrapper {
1812 name: "test".into(),
1813 single: (42,),
1814 },
1815};
1816
1817const CASE_TUPLE_STRUCT_VARIANT: CaseDescriptor<TupleVariantEnum> = CaseDescriptor {
1818 id: "collection::tuple_struct_variant",
1819 description: "enum with tuple variant Variant(T, U)",
1820 expected: || TupleVariantEnum::Pair("test".into(), 42),
1821};
1822
1823const CASE_TUPLE_NEWTYPE_VARIANT: CaseDescriptor<NewtypeVariantEnum> = CaseDescriptor {
1824 id: "collection::tuple_newtype_variant",
1825 description: "enum with newtype variant Variant(T)",
1826 expected: || NewtypeVariantEnum::Some(99),
1827};
1828
1829const CASE_ENUM_UNIT_VARIANT: CaseDescriptor<UnitVariantEnum> = CaseDescriptor {
1832 id: "enum::unit_variant",
1833 description: "enum with unit variants",
1834 expected: || UnitVariantEnum::Active,
1835};
1836
1837const CASE_NUMERIC_ENUM: CaseDescriptor<NumericEnum> = CaseDescriptor {
1838 id: "enum::numeric",
1839 description: "#[facet(is_numeric)] enum matches by structure",
1840 expected: || NumericEnum::B,
1841};
1842
1843const CASE_ENUM_UNTAGGED: CaseDescriptor<UntaggedEnum> = CaseDescriptor {
1844 id: "enum::untagged",
1845 description: "#[facet(untagged)] enum matches by structure",
1846 expected: || UntaggedEnum::Point { x: 10, y: 20 },
1847};
1848
1849const CASE_ENUM_VARIANT_RENAME: CaseDescriptor<EnumVariantRename> = CaseDescriptor {
1850 id: "enum::variant_rename",
1851 description: "#[facet(rename = \"...\")] on enum variants",
1852 expected: || EnumVariantRename::Active,
1853};
1854
1855const CASE_SIGNED_NUMERIC_ENUM: CaseDescriptor<SignedNumericEnum> = CaseDescriptor {
1858 id: "enum::numeric::signed",
1859 description: "Numeric enum with signed discriminant",
1860 expected: || SignedNumericEnum::Z,
1861};
1862
1863const CASE_INFERRED_NUMERIC_ENUM: CaseDescriptor<NumericEnum> = CaseDescriptor {
1864 id: "enum::numeric::inferred",
1865 description: "Numeric enum that is inferred from string",
1866 expected: || NumericEnum::A,
1867};
1868
1869const CASE_UNTAGGED_WITH_NULL: CaseDescriptor<UntaggedWithNull> = CaseDescriptor {
1872 id: "untagged::with_null",
1873 description: "untagged enum with unit variant matching null",
1874 expected: || UntaggedWithNull::None,
1875};
1876
1877const CASE_UNTAGGED_NEWTYPE_VARIANT: CaseDescriptor<UntaggedNewtype> = CaseDescriptor {
1878 id: "untagged::newtype_variant",
1879 description: "untagged enum with newtype variants (discrimination)",
1880 expected: || UntaggedNewtype::String("test".into()),
1881};
1882
1883const CASE_UNTAGGED_AS_FIELD: CaseDescriptor<UntaggedAsField> = CaseDescriptor {
1884 id: "untagged::as_field",
1885 description: "untagged enum as struct field (nesting)",
1886 expected: || UntaggedAsField {
1887 name: "test".into(),
1888 value: UntaggedNewtype::Number(42),
1889 },
1890};
1891
1892const CASE_UNTAGGED_UNIT_ONLY: CaseDescriptor<UntaggedUnitOnly> = CaseDescriptor {
1893 id: "untagged::unit_only",
1894 description: "untagged enum with only unit variants (dataless)",
1895 expected: || UntaggedUnitOnly::Alpha,
1896};
1897
1898const CASE_BOX_WRAPPER: CaseDescriptor<BoxWrapper> = CaseDescriptor {
1901 id: "pointer::box",
1902 description: "Box<T> smart pointer",
1903 expected: || BoxWrapper {
1904 inner: Box::new(42),
1905 },
1906};
1907
1908const CASE_ARC_WRAPPER: CaseDescriptor<ArcWrapper> = CaseDescriptor {
1909 id: "pointer::arc",
1910 description: "Arc<T> smart pointer",
1911 expected: || ArcWrapper {
1912 inner: std::sync::Arc::new(42),
1913 },
1914};
1915
1916const CASE_RC_WRAPPER: CaseDescriptor<RcWrapper> = CaseDescriptor {
1917 id: "pointer::rc",
1918 description: "Rc<T> smart pointer",
1919 expected: || RcWrapper {
1920 inner: std::rc::Rc::new(42),
1921 },
1922};
1923
1924const CASE_BOX_STR: CaseDescriptor<BoxStrWrapper> = CaseDescriptor {
1925 id: "pointer::box_str",
1926 description: "Box<str> unsized smart pointer",
1927 expected: || BoxStrWrapper {
1928 inner: Box::from("hello world"),
1929 },
1930};
1931
1932const CASE_ARC_STR: CaseDescriptor<ArcStrWrapper> = CaseDescriptor {
1933 id: "pointer::arc_str",
1934 description: "Arc<str> unsized smart pointer",
1935 expected: || ArcStrWrapper {
1936 inner: std::sync::Arc::from("hello world"),
1937 },
1938};
1939
1940const CASE_RC_STR: CaseDescriptor<RcStrWrapper> = CaseDescriptor {
1941 id: "pointer::rc_str",
1942 description: "Rc<str> unsized smart pointer",
1943 expected: || RcStrWrapper {
1944 inner: std::rc::Rc::from("hello world"),
1945 },
1946};
1947
1948const CASE_ARC_SLICE: CaseDescriptor<ArcSliceWrapper> = CaseDescriptor {
1949 id: "pointer::arc_slice",
1950 description: "Arc<[T]> unsized slice smart pointer",
1951 expected: || ArcSliceWrapper {
1952 inner: std::sync::Arc::from([1i32, 2, 3, 4]),
1953 },
1954};
1955
1956const CASE_SET_BTREE: CaseDescriptor<SetWrapper> = CaseDescriptor {
1959 id: "collection::set",
1960 description: "BTreeSet<String>",
1961 expected: || {
1962 let mut set = std::collections::BTreeSet::new();
1963 set.insert("alpha".into());
1964 set.insert("beta".into());
1965 set.insert("gamma".into());
1966 SetWrapper { items: set }
1967 },
1968};
1969
1970const CASE_SCALAR_INTEGERS_16: CaseDescriptor<IntegerTypes16> = CaseDescriptor {
1973 id: "scalar::integers_16",
1974 description: "16-bit integer types (i16, u16)",
1975 expected: || IntegerTypes16 {
1976 signed_16: -32768,
1977 unsigned_16: 65535,
1978 },
1979};
1980
1981const CASE_SCALAR_INTEGERS_128: CaseDescriptor<IntegerTypes128> = CaseDescriptor {
1982 id: "scalar::integers_128",
1983 description: "128-bit integer types (i128, u128)",
1984 expected: || IntegerTypes128 {
1985 signed_128: -170_141_183_460_469_231_731_687_303_715_884_105_728,
1986 unsigned_128: 340_282_366_920_938_463_463_374_607_431_768_211_455,
1987 },
1988};
1989
1990const CASE_SCALAR_INTEGERS_SIZE: CaseDescriptor<IntegerTypesSize> = CaseDescriptor {
1991 id: "scalar::integers_size",
1992 description: "pointer-sized integer types (isize, usize)",
1993 expected: || IntegerTypesSize {
1994 signed_size: -1000,
1995 unsigned_size: 2000,
1996 },
1997};
1998
1999const CASE_NONZERO_INTEGERS: CaseDescriptor<NonZeroTypes> = CaseDescriptor {
2002 id: "scalar::nonzero",
2003 description: "NonZero integer types",
2004 expected: || NonZeroTypes {
2005 nz_u32: std::num::NonZeroU32::new(42).unwrap(),
2006 nz_i64: std::num::NonZeroI64::new(-100).unwrap(),
2007 },
2008};
2009
2010const CASE_NONZERO_INTEGERS_EXTENDED: CaseDescriptor<NonZeroTypesExtended> = CaseDescriptor {
2011 id: "scalar::nonzero_extended",
2012 description: "Extended NonZero integer types (8, 16, 128, size)",
2013 expected: || NonZeroTypesExtended {
2014 nz_u8: std::num::NonZeroU8::new(255).unwrap(),
2015 nz_i8: std::num::NonZeroI8::new(-128).unwrap(),
2016 nz_u16: std::num::NonZeroU16::new(65535).unwrap(),
2017 nz_i16: std::num::NonZeroI16::new(-32768).unwrap(),
2018 nz_u128: std::num::NonZeroU128::new(1).unwrap(),
2019 nz_i128: std::num::NonZeroI128::new(-1).unwrap(),
2020 nz_usize: std::num::NonZeroUsize::new(1000).unwrap(),
2021 nz_isize: std::num::NonZeroIsize::new(-500).unwrap(),
2022 },
2023};
2024
2025const CASE_COW_STR: CaseDescriptor<CowStrWrapper> = CaseDescriptor {
2028 id: "string::cow_str",
2029 description: "Cow<'static, str> string fields",
2030 expected: || CowStrWrapper {
2031 owned: std::borrow::Cow::Owned("hello world".to_string()),
2032 message: std::borrow::Cow::Borrowed("borrowed"),
2033 },
2034};
2035
2036const CASE_NEWTYPE_U64: CaseDescriptor<NewtypeU64Wrapper> = CaseDescriptor {
2039 id: "newtype::u64",
2040 description: "newtype wrapper around u64",
2041 expected: || NewtypeU64Wrapper {
2042 value: NewtypeU64(42),
2043 },
2044};
2045
2046const CASE_NEWTYPE_STRING: CaseDescriptor<NewtypeStringWrapper> = CaseDescriptor {
2047 id: "newtype::string",
2048 description: "newtype wrapper around String",
2049 expected: || NewtypeStringWrapper {
2050 value: NewtypeString("hello".into()),
2051 },
2052};
2053
2054const CASE_CHAR_SCALAR: CaseDescriptor<CharWrapper> = CaseDescriptor {
2057 id: "scalar::char",
2058 description: "char scalar type",
2059 expected: || CharWrapper {
2060 letter: 'A',
2061 emoji: '🦀',
2062 },
2063};
2064
2065const CASE_HASHSET: CaseDescriptor<HashSetWrapper> = CaseDescriptor {
2068 id: "collection::hashset",
2069 description: "HashSet<String>",
2070 expected: || {
2071 let mut set = std::collections::HashSet::new();
2072 set.insert("alpha".into());
2073 set.insert("beta".into());
2074 HashSetWrapper { items: set }
2075 },
2076};
2077
2078const CASE_VEC_NESTED: CaseDescriptor<NestedVecWrapper> = CaseDescriptor {
2081 id: "collection::vec_nested",
2082 description: "nested Vec<Vec<i32>>",
2083 expected: || NestedVecWrapper {
2084 matrix: vec![vec![1, 2], vec![3, 4, 5]],
2085 },
2086};
2087
2088const CASE_BYTES_VEC_U8: CaseDescriptor<BytesWrapper> = CaseDescriptor {
2091 id: "slice::bytes::vec_u8",
2092 description: "Vec<u8> binary data as array of numbers",
2093 expected: || BytesWrapper {
2094 data: vec![0, 128, 255, 42],
2095 },
2096};
2097
2098const CASE_ARRAY_FIXED_SIZE: CaseDescriptor<ArrayWrapper> = CaseDescriptor {
2101 id: "array::fixed_size",
2102 description: "[T; N] fixed-size array",
2103 expected: || ArrayWrapper { values: [1, 2, 3] },
2104};
2105
2106const CASE_SKIP_UNKNOWN_FIELDS: CaseDescriptor<SkipUnknownStruct> = CaseDescriptor {
2109 id: "behavior::skip_unknown_fields",
2110 description: "unknown fields are silently skipped by default",
2111 expected: || SkipUnknownStruct {
2112 known: "value".into(),
2113 },
2114};
2115
2116const CASE_STRING_ESCAPES: CaseDescriptor<StringEscapes> = CaseDescriptor {
2119 id: "string::escapes",
2120 description: "string with escape sequences (\\n, \\t, \\\", \\\\)",
2121 expected: || StringEscapes {
2122 text: "line1\nline2\ttab\"quote\\backslash".into(),
2123 },
2124};
2125
2126const CASE_STRING_ESCAPES_EXTENDED: CaseDescriptor<StringEscapesExtended> = CaseDescriptor {
2127 id: "string::escapes_extended",
2128 description: "string with extended escape sequences (\\b, \\f, \\r, \\u0001)",
2129 expected: || StringEscapesExtended {
2130 backspace: "hello\x08world".into(),
2131 formfeed: "page\x0Cbreak".into(),
2132 carriage_return: "line\rreturn".into(),
2133 control_char: "\x01".into(),
2134 },
2135};
2136
2137const CASE_UNIT_STRUCT: CaseDescriptor<UnitStruct> = CaseDescriptor {
2140 id: "unit::struct",
2141 description: "unit struct (zero-sized type)",
2142 expected: || UnitStruct,
2143};
2144
2145#[derive(Facet, Debug, Clone, PartialEq)]
2147#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2148pub struct StructSingleField {
2149 pub name: String,
2150}
2151
2152#[derive(Facet, Debug, Clone, PartialEq)]
2154#[facet(untagged)]
2155#[repr(u8)]
2156pub enum MixedScalar {
2157 Signed(i64),
2158 Float(f64),
2159 Bool(bool),
2160 Null,
2161}
2162
2163#[derive(Facet, Debug, Clone, PartialEq)]
2164#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2165pub struct NestedParent {
2166 pub id: u64,
2167 pub child: NestedChild,
2168 pub tags: Vec<String>,
2169}
2170
2171#[derive(Facet, Debug, Clone, PartialEq)]
2172#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2173pub struct NestedChild {
2174 pub code: String,
2175 pub active: bool,
2176}
2177
2178#[derive(Facet, Debug, Clone, PartialEq)]
2179#[repr(u8)]
2180pub enum ComplexEnum {
2181 Empty,
2182 Count(u64),
2183 Label { name: String, level: u8 },
2184}
2185
2186#[derive(Facet, Debug, Clone, PartialEq)]
2190pub struct RenamedField {
2191 #[facet(rename = "userName")]
2192 pub user_name: String,
2193 pub age: u32,
2194}
2195
2196#[derive(Facet, Debug, Clone, PartialEq)]
2198#[facet(rename_all = "camelCase")]
2199pub struct CamelCaseStruct {
2200 pub first_name: String,
2201 pub last_name: String,
2202 pub is_active: bool,
2203}
2204
2205#[derive(Facet, Debug, Clone, PartialEq)]
2207pub struct WithDefault {
2208 pub required: String,
2209 #[facet(default)]
2210 pub optional_count: u32,
2211}
2212
2213#[derive(Facet, Default, Debug, Clone, PartialEq)]
2215#[facet(default)]
2216pub struct StructLevelDefault {
2217 pub count: i32,
2218 pub message: String,
2219}
2220
2221pub fn custom_default_value() -> i32 {
2223 42
2224}
2225
2226#[derive(Facet, Debug, Clone, PartialEq)]
2228pub struct WithDefaultFunction {
2229 #[facet(default = custom_default_value())]
2230 pub magic_number: i32,
2231 pub name: String,
2232}
2233
2234#[derive(Facet, Debug, Clone, PartialEq)]
2236#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2237pub struct WithOption {
2238 pub name: String,
2239 pub nickname: Option<String>,
2240}
2241
2242#[derive(Facet, Debug, Clone, PartialEq)]
2244pub struct WithSkipSerializing {
2245 pub visible: String,
2246 #[facet(skip_serializing)]
2247 #[facet(default)]
2248 pub hidden: String,
2249}
2250
2251#[derive(Facet, Debug, Clone, PartialEq)]
2253pub struct WithSkipSerializingIf {
2254 pub name: String,
2255 #[facet(skip_serializing_if = Option::is_none)]
2256 pub optional_data: Option<String>,
2257}
2258
2259#[derive(Facet, Debug, Clone, PartialEq)]
2261pub struct WithSkip {
2262 pub visible: String,
2263 #[facet(skip)]
2264 #[facet(default)]
2265 pub internal: u32,
2266}
2267
2268#[derive(Facet, Debug, Clone, PartialEq)]
2272#[facet(tag = "type")]
2273#[repr(u8)]
2274pub enum InternallyTagged {
2275 Circle { radius: f64 },
2276 Rectangle { width: f64, height: f64 },
2277}
2278
2279#[derive(Facet, Debug, Clone, PartialEq)]
2281#[facet(tag = "t", content = "c")]
2282#[repr(u8)]
2283pub enum AdjacentlyTagged {
2284 Message(String),
2285 Count(u64),
2286}
2287
2288#[derive(Facet, Debug, Clone, PartialEq)]
2292pub struct FlattenInner {
2293 pub x: i32,
2294 pub y: i32,
2295}
2296
2297#[derive(Facet, Debug, Clone, PartialEq)]
2299pub struct FlattenOuter {
2300 pub name: String,
2301 #[facet(flatten)]
2302 pub coords: FlattenInner,
2303}
2304
2305#[derive(Facet, Debug, Clone, PartialEq)]
2307#[facet(transparent)]
2308pub struct UserId(pub u64);
2309
2310#[derive(Facet, Debug, Clone, PartialEq)]
2312pub struct UserRecord {
2313 pub id: UserId,
2314 pub name: String,
2315}
2316
2317#[derive(Facet, Debug, Clone, PartialEq)]
2321pub struct FlattenOptionalSome {
2322 pub name: String,
2323 #[facet(flatten)]
2324 pub metadata: Option<FlattenMetadata>,
2325}
2326
2327#[derive(Facet, Debug, Clone, PartialEq)]
2329pub struct FlattenOptionalNone {
2330 pub name: String,
2331 #[facet(flatten)]
2332 pub metadata: Option<FlattenMetadata>,
2333}
2334
2335#[derive(Facet, Debug, Clone, PartialEq)]
2337pub struct FlattenMetadata {
2338 pub version: i32,
2339 pub author: String,
2340}
2341
2342#[derive(Facet, Debug, Clone, PartialEq)]
2344pub struct FlattenPartA {
2345 pub field_a: String,
2346 pub shared: i32,
2347}
2348
2349#[derive(Facet, Debug, Clone, PartialEq)]
2351pub struct FlattenPartB {
2352 pub field_b: String,
2353 pub shared: i32,
2354}
2355
2356#[derive(Facet, Debug, Clone, PartialEq)]
2358pub struct FlattenOverlapping {
2359 #[facet(flatten)]
2360 pub part_a: FlattenPartA,
2361 #[facet(flatten)]
2362 pub part_b: FlattenPartB,
2363}
2364
2365#[derive(Facet, Debug, Clone, PartialEq)]
2367pub struct FlattenLevel3 {
2368 pub deep_field: i32,
2369}
2370
2371#[derive(Facet, Debug, Clone, PartialEq)]
2373pub struct FlattenLevel2 {
2374 pub mid_field: i32,
2375 #[facet(flatten)]
2376 pub level3: FlattenLevel3,
2377}
2378
2379#[derive(Facet, Debug, Clone, PartialEq)]
2381pub struct FlattenLevel1 {
2382 pub top_field: String,
2383 #[facet(flatten)]
2384 pub level2: FlattenLevel2,
2385}
2386
2387#[derive(Facet, Debug, Clone, PartialEq)]
2389pub struct FlattenAuthPassword {
2390 pub password: String,
2391}
2392
2393#[derive(Facet, Debug, Clone, PartialEq)]
2395pub struct FlattenAuthToken {
2396 pub token: String,
2397}
2398
2399#[allow(dead_code)]
2401#[derive(Facet, Debug, Clone, PartialEq)]
2402#[repr(u8)]
2403pub enum FlattenAuthMethod {
2404 Password(FlattenAuthPassword),
2405 Token(FlattenAuthToken),
2406}
2407
2408#[derive(Facet, Debug, Clone, PartialEq)]
2410pub struct FlattenTransportTcp {
2411 pub port: u16,
2412}
2413
2414#[derive(Facet, Debug, Clone, PartialEq)]
2416pub struct FlattenTransportUnix {
2417 pub socket: String,
2418}
2419
2420#[allow(dead_code)]
2422#[derive(Facet, Debug, Clone, PartialEq)]
2423#[repr(u8)]
2424pub enum FlattenTransport {
2425 Tcp(FlattenTransportTcp),
2426 Unix(FlattenTransportUnix),
2427}
2428
2429#[derive(Facet, Debug, Clone, PartialEq)]
2431pub struct FlattenMultipleEnums {
2432 pub name: String,
2433 #[facet(flatten)]
2434 pub auth: FlattenAuthMethod,
2435 #[facet(flatten)]
2436 pub transport: FlattenTransport,
2437}
2438
2439#[derive(Facet, Debug, Clone, PartialEq)]
2443#[facet(deny_unknown_fields)]
2444pub struct DenyUnknownStruct {
2445 pub foo: String,
2446 pub bar: i32,
2447}
2448
2449#[derive(Facet, Debug, Clone, PartialEq)]
2451pub struct ExpectsInteger {
2452 pub value: i32,
2453}
2454
2455#[derive(Facet, Debug, Clone, PartialEq)]
2457pub struct ExpectsArray {
2458 pub items: Vec<i32>,
2459}
2460
2461#[derive(Facet, Debug, Clone, PartialEq)]
2463pub struct RequiresAllFields {
2464 pub name: String,
2465 pub age: u32,
2466 pub email: String,
2467}
2468
2469#[derive(Facet, Debug, Clone, PartialEq)]
2471pub struct WithAlias {
2472 #[facet(alias = "old_name")]
2473 pub new_name: String,
2474 pub count: u32,
2475}
2476
2477#[derive(Facet, Debug, Clone, PartialEq)]
2481pub struct RenameVsAlias {
2482 #[facet(rename = "officialName")]
2483 #[facet(alias = "oldName")]
2484 pub field: String,
2485 pub id: u32,
2486}
2487
2488#[derive(Facet, Debug, Clone, PartialEq)]
2490#[facet(rename_all = "kebab-case")]
2491pub struct RenameAllKebab {
2492 pub first_name: String,
2493 pub last_name: String,
2494 pub user_id: u32,
2495}
2496
2497#[derive(Facet, Debug, Clone, PartialEq)]
2499#[facet(rename_all = "SCREAMING_SNAKE_CASE")]
2500pub struct RenameAllScreaming {
2501 pub api_key: String,
2502 pub max_retry_count: u32,
2503}
2504
2505#[derive(Facet, Clone, Debug, PartialEq)]
2507pub struct RenameUnicode {
2508 #[facet(rename = "🎉")]
2509 pub celebration: String,
2510}
2511
2512#[derive(Facet, Clone, Debug, PartialEq)]
2514pub struct RenameSpecialChars {
2515 #[facet(rename = "@type")]
2516 pub type_field: String,
2517}
2518
2519#[derive(Facet, Clone, Debug)]
2523#[facet(transparent)]
2524pub struct IntAsString(pub String);
2525
2526#[derive(Facet, Debug, Clone, PartialEq)]
2528#[facet(proxy = IntAsString)]
2529pub struct ProxyInt {
2530 pub value: i32,
2531}
2532
2533impl TryFrom<IntAsString> for ProxyInt {
2535 type Error = std::num::ParseIntError;
2536 fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2537 Ok(ProxyInt {
2538 value: proxy.0.parse()?,
2539 })
2540 }
2541}
2542
2543impl From<&ProxyInt> for IntAsString {
2545 fn from(v: &ProxyInt) -> Self {
2546 IntAsString(v.value.to_string())
2547 }
2548}
2549
2550#[derive(Facet, Debug, Clone, PartialEq)]
2552pub struct ProxyFieldLevel {
2553 pub name: String,
2554 #[facet(proxy = IntAsString)]
2555 pub count: i32,
2556}
2557
2558impl TryFrom<IntAsString> for i32 {
2560 type Error = std::num::ParseIntError;
2561 fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2562 proxy.0.parse()
2563 }
2564}
2565
2566impl From<&i32> for IntAsString {
2568 fn from(value: &i32) -> Self {
2569 IntAsString(value.to_string())
2570 }
2571}
2572
2573#[derive(Facet, Debug, Clone, PartialEq)]
2575pub struct ProxyWithOption {
2576 pub name: String,
2577 #[facet(proxy = IntAsString)]
2578 pub count: Option<i32>,
2579}
2580
2581impl TryFrom<IntAsString> for Option<i32> {
2583 type Error = std::num::ParseIntError;
2584 fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2585 if proxy.0.is_empty() {
2586 Ok(None)
2587 } else {
2588 Ok(Some(proxy.0.parse()?))
2589 }
2590 }
2591}
2592
2593impl From<&Option<i32>> for IntAsString {
2595 fn from(value: &Option<i32>) -> Self {
2596 match value {
2597 Some(v) => IntAsString(v.to_string()),
2598 None => IntAsString(String::new()),
2599 }
2600 }
2601}
2602
2603#[derive(Facet, Debug, Clone, PartialEq)]
2605#[repr(u8)]
2606pub enum ProxyEnum {
2607 None,
2608 #[facet(proxy = IntAsString)]
2609 Value(i32),
2610}
2611
2612#[derive(Facet, Debug, Clone, PartialEq)]
2614#[facet(transparent, proxy = IntAsString)]
2615pub struct TransparentProxy(pub i32);
2616
2617impl TryFrom<IntAsString> for TransparentProxy {
2619 type Error = std::num::ParseIntError;
2620 fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2621 Ok(TransparentProxy(proxy.0.parse()?))
2622 }
2623}
2624
2625impl From<&TransparentProxy> for IntAsString {
2627 fn from(value: &TransparentProxy) -> Self {
2628 IntAsString(value.0.to_string())
2629 }
2630}
2631
2632#[derive(Debug, Clone, PartialEq)]
2637pub struct OpaqueType {
2638 pub inner: u64,
2639}
2640
2641#[derive(Facet, Clone, Debug)]
2643pub struct OpaqueTypeProxy {
2644 pub inner: u64,
2645}
2646
2647impl From<OpaqueTypeProxy> for OpaqueType {
2649 fn from(proxy: OpaqueTypeProxy) -> Self {
2650 OpaqueType { inner: proxy.inner }
2651 }
2652}
2653
2654impl From<&OpaqueType> for OpaqueTypeProxy {
2656 fn from(v: &OpaqueType) -> Self {
2657 OpaqueTypeProxy { inner: v.inner }
2658 }
2659}
2660
2661#[derive(Facet, Debug, Clone, PartialEq)]
2663pub struct OpaqueProxyWrapper {
2664 #[facet(opaque, proxy = OpaqueTypeProxy)]
2665 pub value: OpaqueType,
2666}
2667
2668impl From<OpaqueTypeProxy> for Option<OpaqueType> {
2670 fn from(proxy: OpaqueTypeProxy) -> Self {
2671 Some(OpaqueType { inner: proxy.inner })
2672 }
2673}
2674
2675impl From<&Option<OpaqueType>> for OpaqueTypeProxy {
2677 fn from(v: &Option<OpaqueType>) -> Self {
2678 match v {
2679 Some(ot) => OpaqueTypeProxy { inner: ot.inner },
2680 None => OpaqueTypeProxy { inner: 0 },
2681 }
2682 }
2683}
2684
2685#[derive(Facet, Debug, Clone, PartialEq)]
2687pub struct OpaqueProxyOptionWrapper {
2688 #[facet(opaque, proxy = OpaqueTypeProxy)]
2689 pub value: Option<OpaqueType>,
2690}
2691
2692#[derive(Facet, Debug, Clone, PartialEq)]
2696#[facet(transparent)]
2697pub struct InnerTransparent(pub i32);
2698
2699#[derive(Facet, Debug, Clone, PartialEq)]
2701#[facet(transparent)]
2702pub struct OuterTransparent(pub InnerTransparent);
2703
2704#[derive(Facet, Debug, Clone, PartialEq)]
2706#[facet(transparent)]
2707pub struct TransparentOption(pub Option<i32>);
2708
2709#[derive(Facet, Debug, Clone, PartialEq)]
2711#[facet(transparent)]
2712pub struct TransparentNonZero(pub std::num::NonZeroU32);
2713
2714#[derive(Facet, Debug, Clone, PartialEq)]
2718#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2719pub struct BoolWrapper {
2720 pub yes: bool,
2721 pub no: bool,
2722}
2723
2724#[derive(Facet, Debug, Clone, PartialEq)]
2726#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2727pub struct IntegerTypes {
2728 pub signed_8: i8,
2729 pub unsigned_8: u8,
2730 pub signed_32: i32,
2731 pub unsigned_32: u32,
2732 pub signed_64: i64,
2733 pub unsigned_64: u64,
2734}
2735
2736#[derive(Facet, Debug, Clone, PartialEq)]
2738#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2739pub struct FloatTypes {
2740 pub float_32: f32,
2741 pub float_64: f64,
2742}
2743
2744#[derive(Facet, Debug, Clone, PartialEq)]
2746#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2747pub struct FloatTypesScientific {
2748 pub large: f64,
2749 pub small: f64,
2750 pub positive_exp: f64,
2751}
2752
2753#[derive(Facet, Debug, Clone, PartialEq)]
2757#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2758pub struct MapWrapper {
2759 pub data: std::collections::BTreeMap<String, i32>,
2760}
2761
2762#[derive(Facet, Debug, Clone, PartialEq)]
2764#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2765pub struct TupleWrapper {
2766 pub triple: (String, i32, bool),
2767}
2768
2769#[derive(Facet, Debug, Clone, PartialEq)]
2771#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2772pub struct NestedTupleWrapper {
2773 pub outer: ((i32, i32), (String, bool)),
2774}
2775
2776#[derive(Facet, Debug, Clone, PartialEq)]
2778#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2779pub struct EmptyTupleWrapper {
2780 pub name: String,
2781 pub empty: (),
2782}
2783
2784#[derive(Facet, Debug, Clone, PartialEq)]
2786#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2787pub struct SingleElementTupleWrapper {
2788 pub name: String,
2789 pub single: (i32,),
2790}
2791
2792#[derive(Facet, Debug, Clone, PartialEq)]
2796#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2797#[repr(u8)]
2798pub enum UnitVariantEnum {
2799 Active,
2800 Inactive,
2801 Pending,
2802}
2803
2804#[derive(Facet, Debug, Clone, PartialEq)]
2806#[repr(u8)]
2807pub enum EnumVariantRename {
2808 #[facet(rename = "enabled")]
2809 Active,
2810 #[facet(rename = "disabled")]
2811 Inactive,
2812}
2813
2814#[derive(Facet, Debug, Clone, PartialEq)]
2816#[facet(is_numeric)]
2817#[repr(u8)]
2818pub enum NumericEnum {
2819 A,
2820 B,
2821}
2822
2823#[derive(Facet, Debug, Clone, PartialEq)]
2825#[facet(is_numeric)]
2826#[repr(i16)]
2827pub enum SignedNumericEnum {
2828 Z = -1,
2829 A,
2830}
2831
2832#[derive(Facet, Debug, Clone, PartialEq)]
2834#[facet(untagged)]
2835#[repr(u8)]
2836pub enum UntaggedEnum {
2837 Point { x: i32, y: i32 },
2838 Value(i64),
2839}
2840
2841#[derive(Facet, Debug, Clone, PartialEq)]
2843#[facet(untagged)]
2844#[repr(u8)]
2845pub enum UntaggedWithNull {
2846 None,
2847 Some(i32),
2848}
2849
2850#[derive(Facet, Debug, Clone, PartialEq)]
2852#[facet(untagged)]
2853#[repr(u8)]
2854pub enum UntaggedNewtype {
2855 String(String),
2856 Number(u64),
2857 Bool(bool),
2858}
2859
2860#[derive(Facet, Debug, Clone, PartialEq)]
2862pub struct UntaggedAsField {
2863 pub name: String,
2864 pub value: UntaggedNewtype,
2865}
2866
2867#[derive(Facet, Debug, Clone, PartialEq)]
2870#[facet(untagged)]
2871#[repr(u8)]
2872pub enum UntaggedUnitOnly {
2873 Alpha,
2874 Beta,
2875 Gamma,
2876}
2877
2878#[derive(Facet, Debug, Clone, PartialEq)]
2880#[repr(u8)]
2881pub enum TupleVariantEnum {
2882 Empty,
2883 Pair(String, i32),
2884 Triple(bool, f64, String),
2885}
2886
2887#[derive(Facet, Debug, Clone, PartialEq)]
2889#[repr(u8)]
2890pub enum NewtypeVariantEnum {
2891 None,
2892 Some(i32),
2893}
2894
2895#[derive(Facet, Debug, Clone, PartialEq)]
2899pub struct BoxWrapper {
2900 pub inner: Box<i32>,
2901}
2902
2903#[derive(Facet, Debug, Clone, PartialEq)]
2905pub struct ArcWrapper {
2906 pub inner: std::sync::Arc<i32>,
2907}
2908
2909#[derive(Facet, Debug, Clone, PartialEq)]
2911pub struct RcWrapper {
2912 pub inner: std::rc::Rc<i32>,
2913}
2914
2915#[derive(Facet, Debug, Clone, PartialEq)]
2917pub struct BoxStrWrapper {
2918 pub inner: Box<str>,
2919}
2920
2921#[derive(Facet, Debug, Clone, PartialEq)]
2923pub struct ArcStrWrapper {
2924 pub inner: std::sync::Arc<str>,
2925}
2926
2927#[derive(Facet, Debug, Clone, PartialEq)]
2929pub struct RcStrWrapper {
2930 pub inner: std::rc::Rc<str>,
2931}
2932
2933#[derive(Facet, Debug, Clone, PartialEq)]
2935pub struct ArcSliceWrapper {
2936 pub inner: std::sync::Arc<[i32]>,
2937}
2938
2939#[derive(Facet, Debug, Clone, PartialEq)]
2943pub struct SetWrapper {
2944 pub items: std::collections::BTreeSet<String>,
2945}
2946
2947#[derive(Facet, Debug, Clone, PartialEq)]
2951pub struct IntegerTypes16 {
2952 pub signed_16: i16,
2953 pub unsigned_16: u16,
2954}
2955
2956#[derive(Facet, Debug, Clone, PartialEq)]
2958pub struct IntegerTypes128 {
2959 pub signed_128: i128,
2960 pub unsigned_128: u128,
2961}
2962
2963#[derive(Facet, Debug, Clone, PartialEq)]
2965pub struct IntegerTypesSize {
2966 pub signed_size: isize,
2967 pub unsigned_size: usize,
2968}
2969
2970#[derive(Facet, Debug, Clone, PartialEq)]
2974pub struct NonZeroTypes {
2975 pub nz_u32: std::num::NonZeroU32,
2976 pub nz_i64: std::num::NonZeroI64,
2977}
2978
2979#[derive(Facet, Debug, Clone, PartialEq)]
2981pub struct NonZeroTypesExtended {
2982 pub nz_u8: std::num::NonZeroU8,
2983 pub nz_i8: std::num::NonZeroI8,
2984 pub nz_u16: std::num::NonZeroU16,
2985 pub nz_i16: std::num::NonZeroI16,
2986 pub nz_u128: std::num::NonZeroU128,
2987 pub nz_i128: std::num::NonZeroI128,
2988 pub nz_usize: std::num::NonZeroUsize,
2989 pub nz_isize: std::num::NonZeroIsize,
2990}
2991
2992#[derive(Facet, Debug, Clone, PartialEq)]
2996pub struct CowStrWrapper {
2997 pub owned: std::borrow::Cow<'static, str>,
2998 pub message: std::borrow::Cow<'static, str>,
2999}
3000
3001#[derive(Facet, Debug, Clone, PartialEq)]
3005#[facet(transparent)]
3006pub struct NewtypeU64(pub u64);
3007
3008#[derive(Facet, Debug, Clone, PartialEq)]
3010pub struct NewtypeU64Wrapper {
3011 pub value: NewtypeU64,
3012}
3013
3014#[derive(Facet, Debug, Clone, PartialEq)]
3016#[facet(transparent)]
3017pub struct NewtypeString(pub String);
3018
3019#[derive(Facet, Debug, Clone, PartialEq)]
3021pub struct NewtypeStringWrapper {
3022 pub value: NewtypeString,
3023}
3024
3025#[derive(Facet, Debug, Clone, PartialEq)]
3029#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3030pub struct CharWrapper {
3031 pub letter: char,
3032 pub emoji: char,
3033}
3034
3035#[derive(Facet, Debug, Clone, PartialEq)]
3039#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3040pub struct HashSetWrapper {
3041 pub items: std::collections::HashSet<String>,
3042}
3043
3044#[derive(Facet, Debug, Clone, PartialEq)]
3048#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3049pub struct NestedVecWrapper {
3050 pub matrix: Vec<Vec<i32>>,
3051}
3052
3053#[derive(Facet, Debug, Clone, PartialEq)]
3057#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3058pub struct BytesWrapper {
3059 pub data: Vec<u8>,
3060}
3061
3062#[derive(Facet, Debug, Clone, PartialEq)]
3066pub struct ArrayWrapper {
3067 pub values: [u64; 3],
3068}
3069
3070#[derive(Facet, Debug, Clone, PartialEq)]
3074pub struct SkipUnknownStruct {
3075 pub known: String,
3076}
3077
3078#[derive(Facet, Debug, Clone, PartialEq)]
3082pub struct StringEscapes {
3083 pub text: String,
3084}
3085
3086#[derive(Facet, Debug, Clone, PartialEq)]
3088pub struct StringEscapesExtended {
3089 pub backspace: String,
3090 pub formfeed: String,
3091 pub carriage_return: String,
3092 pub control_char: String,
3093}
3094
3095#[derive(Facet, Debug, Clone, PartialEq)]
3099#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3100pub struct UnitStruct;
3101
3102#[cfg(feature = "uuid")]
3105const CASE_UUID: CaseDescriptor<UuidWrapper> = CaseDescriptor {
3106 id: "third_party::uuid",
3107 description: "uuid::Uuid type",
3108 expected: || UuidWrapper {
3109 id: uuid::Uuid::from_bytes([
3110 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
3111 0x00, 0x00,
3112 ]),
3113 },
3114};
3115
3116#[cfg(feature = "ulid")]
3117const CASE_ULID: CaseDescriptor<UlidWrapper> = CaseDescriptor {
3118 id: "third_party::ulid",
3119 description: "ulid::Ulid type",
3120 expected: || UlidWrapper {
3121 id: ulid::Ulid::from_string("01ARZ3NDEKTSV4RRFFQ69G5FAV").unwrap(),
3122 },
3123};
3124
3125#[cfg(feature = "camino")]
3126const CASE_CAMINO_PATH: CaseDescriptor<CaminoWrapper> = CaseDescriptor {
3127 id: "third_party::camino",
3128 description: "camino::Utf8PathBuf type",
3129 expected: || CaminoWrapper {
3130 path: camino::Utf8PathBuf::from("/home/user/documents"),
3131 },
3132};
3133
3134#[cfg(feature = "ordered-float")]
3135const CASE_ORDERED_FLOAT: CaseDescriptor<OrderedFloatWrapper> = CaseDescriptor {
3136 id: "third_party::ordered_float",
3137 description: "ordered_float::OrderedFloat type",
3138 expected: || OrderedFloatWrapper {
3139 value: ordered_float::OrderedFloat(1.23456),
3140 },
3141};
3142
3143#[cfg(feature = "time")]
3144const CASE_TIME_OFFSET_DATETIME: CaseDescriptor<TimeOffsetDateTimeWrapper> = CaseDescriptor {
3145 id: "third_party::time_offset_datetime",
3146 description: "time::OffsetDateTime type",
3147 expected: || TimeOffsetDateTimeWrapper {
3148 created_at: time::macros::datetime!(2023-01-15 12:34:56 UTC),
3149 },
3150};
3151
3152#[cfg(feature = "jiff02")]
3153const CASE_JIFF_TIMESTAMP: CaseDescriptor<JiffTimestampWrapper> = CaseDescriptor {
3154 id: "third_party::jiff_timestamp",
3155 description: "jiff::Timestamp type",
3156 expected: || JiffTimestampWrapper {
3157 created_at: "2023-12-31T11:30:00Z".parse().unwrap(),
3158 },
3159};
3160
3161#[cfg(feature = "jiff02")]
3162const CASE_JIFF_CIVIL_DATETIME: CaseDescriptor<JiffCivilDateTimeWrapper> = CaseDescriptor {
3163 id: "third_party::jiff_civil_datetime",
3164 description: "jiff::civil::DateTime type",
3165 expected: || JiffCivilDateTimeWrapper {
3166 created_at: "2024-06-19T15:22:45".parse().unwrap(),
3167 },
3168};
3169
3170#[cfg(feature = "chrono")]
3171const CASE_CHRONO_DATETIME_UTC: CaseDescriptor<ChronoDateTimeUtcWrapper> = CaseDescriptor {
3172 id: "third_party::chrono_datetime_utc",
3173 description: "chrono::DateTime<Utc> type",
3174 expected: || ChronoDateTimeUtcWrapper {
3175 created_at: chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2023, 1, 15, 12, 34, 56)
3176 .unwrap(),
3177 },
3178};
3179
3180#[cfg(feature = "chrono")]
3181const CASE_CHRONO_NAIVE_DATETIME: CaseDescriptor<ChronoNaiveDateTimeWrapper> = CaseDescriptor {
3182 id: "third_party::chrono_naive_datetime",
3183 description: "chrono::NaiveDateTime type",
3184 expected: || ChronoNaiveDateTimeWrapper {
3185 created_at: chrono::NaiveDate::from_ymd_opt(2023, 1, 15)
3186 .unwrap()
3187 .and_hms_opt(12, 34, 56)
3188 .unwrap(),
3189 },
3190};
3191
3192#[cfg(feature = "chrono")]
3193const CASE_CHRONO_NAIVE_DATE: CaseDescriptor<ChronoNaiveDateWrapper> = CaseDescriptor {
3194 id: "third_party::chrono_naive_date",
3195 description: "chrono::NaiveDate type",
3196 expected: || ChronoNaiveDateWrapper {
3197 birth_date: chrono::NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(),
3198 },
3199};
3200
3201#[cfg(feature = "chrono")]
3202const CASE_CHRONO_NAIVE_TIME: CaseDescriptor<ChronoNaiveTimeWrapper> = CaseDescriptor {
3203 id: "third_party::chrono_naive_time",
3204 description: "chrono::NaiveTime type",
3205 expected: || ChronoNaiveTimeWrapper {
3206 alarm_time: chrono::NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
3207 },
3208};
3209
3210#[cfg(feature = "chrono")]
3211const CASE_CHRONO_IN_VEC: CaseDescriptor<ChronoInVecWrapper> = CaseDescriptor {
3212 id: "third_party::chrono_in_vec",
3213 description: "Vec<chrono::DateTime<Utc>> - chrono in collections",
3214 expected: || ChronoInVecWrapper {
3215 timestamps: vec![
3216 chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2023, 1, 1, 0, 0, 0).unwrap(),
3217 chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2023, 6, 15, 12, 30, 0).unwrap(),
3218 ],
3219 },
3220};
3221
3222#[cfg(feature = "bytes")]
3225const CASE_BYTES_BYTES: CaseDescriptor<BytesBytesWrapper> = CaseDescriptor {
3226 id: "third_party::bytes_bytes",
3227 description: "bytes::Bytes type",
3228 expected: || BytesBytesWrapper {
3229 data: bytes::Bytes::from_static(&[1, 2, 3, 4, 255]),
3230 },
3231};
3232
3233#[cfg(feature = "bytes")]
3234const CASE_BYTES_BYTES_MUT: CaseDescriptor<BytesBytesMutWrapper> = CaseDescriptor {
3235 id: "third_party::bytes_bytes_mut",
3236 description: "bytes::BytesMut type",
3237 expected: || BytesBytesMutWrapper {
3238 data: bytes::BytesMut::from(&[1, 2, 3, 4, 255][..]),
3239 },
3240};
3241
3242#[cfg(feature = "bytestring")]
3245const CASE_BYTESTRING: CaseDescriptor<ByteStringWrapper> = CaseDescriptor {
3246 id: "third_party::bytestring",
3247 description: "bytestring::ByteString type",
3248 expected: || ByteStringWrapper {
3249 value: bytestring::ByteString::from("hello world"),
3250 },
3251};
3252
3253#[cfg(feature = "compact_str")]
3254const CASE_COMPACT_STRING: CaseDescriptor<CompactStringWrapper> = CaseDescriptor {
3255 id: "third_party::compact_string",
3256 description: "compact_str::CompactString type",
3257 expected: || CompactStringWrapper {
3258 value: compact_str::CompactString::from("hello world"),
3259 },
3260};
3261
3262#[cfg(feature = "smartstring")]
3263const CASE_SMARTSTRING: CaseDescriptor<SmartStringWrapper> = CaseDescriptor {
3264 id: "third_party::smartstring",
3265 description: "smartstring::SmartString type",
3266 expected: || SmartStringWrapper {
3267 value: smartstring::SmartString::from("hello world"),
3268 },
3269};
3270
3271#[cfg(feature = "facet-value")]
3274const CASE_VALUE_NULL: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3275 id: "value::null",
3276 description: "facet_value::Value - null",
3277 expected: || facet_value::Value::NULL,
3278};
3279
3280#[cfg(feature = "facet-value")]
3281const CASE_VALUE_BOOL: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3282 id: "value::bool",
3283 description: "facet_value::Value - bool",
3284 expected: || facet_value::Value::TRUE,
3285};
3286
3287#[cfg(feature = "facet-value")]
3288const CASE_VALUE_INTEGER: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3289 id: "value::integer",
3290 description: "facet_value::Value - integer",
3291 expected: || facet_value::Value::from(42i64),
3292};
3293
3294#[cfg(feature = "facet-value")]
3295const CASE_VALUE_FLOAT: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3296 id: "value::float",
3297 description: "facet_value::Value - float",
3298 expected: || facet_value::Value::from(2.5f64),
3299};
3300
3301#[cfg(feature = "facet-value")]
3302const CASE_VALUE_STRING: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3303 id: "value::string",
3304 description: "facet_value::Value - string",
3305 expected: || facet_value::Value::from("hello world"),
3306};
3307
3308#[cfg(feature = "facet-value")]
3309const CASE_VALUE_ARRAY: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3310 id: "value::array",
3311 description: "facet_value::Value - array",
3312 expected: || {
3313 facet_value::VArray::from_iter([
3314 facet_value::Value::from(1i64),
3315 facet_value::Value::from(2i64),
3316 facet_value::Value::from(3i64),
3317 ])
3318 .into()
3319 },
3320};
3321
3322#[cfg(feature = "facet-value")]
3323const CASE_VALUE_OBJECT: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3324 id: "value::object",
3325 description: "facet_value::Value - object",
3326 expected: || {
3327 let mut map = facet_value::VObject::new();
3328 map.insert("name", facet_value::Value::from("test"));
3329 map.insert("count", facet_value::Value::from(42i64));
3330 map.into()
3331 },
3332};
3333
3334#[cfg(feature = "uuid")]
3338#[derive(Facet, Debug, Clone, PartialEq)]
3339pub struct UuidWrapper {
3340 pub id: uuid::Uuid,
3341}
3342
3343#[cfg(feature = "ulid")]
3345#[derive(Facet, Debug, Clone, PartialEq)]
3346pub struct UlidWrapper {
3347 pub id: ulid::Ulid,
3348}
3349
3350#[cfg(feature = "camino")]
3352#[derive(Facet, Debug, Clone, PartialEq)]
3353pub struct CaminoWrapper {
3354 pub path: camino::Utf8PathBuf,
3355}
3356
3357#[cfg(feature = "ordered-float")]
3359#[derive(Facet, Debug, Clone, PartialEq)]
3360pub struct OrderedFloatWrapper {
3361 pub value: ordered_float::OrderedFloat<f64>,
3362}
3363
3364#[cfg(feature = "time")]
3366#[derive(Facet, Debug, Clone, PartialEq)]
3367pub struct TimeOffsetDateTimeWrapper {
3368 pub created_at: time::OffsetDateTime,
3369}
3370
3371#[cfg(feature = "jiff02")]
3373#[derive(Facet, Debug, Clone, PartialEq)]
3374pub struct JiffTimestampWrapper {
3375 pub created_at: jiff::Timestamp,
3376}
3377
3378#[cfg(feature = "jiff02")]
3380#[derive(Facet, Debug, Clone, PartialEq)]
3381pub struct JiffCivilDateTimeWrapper {
3382 pub created_at: jiff::civil::DateTime,
3383}
3384
3385#[cfg(feature = "chrono")]
3387#[derive(Facet, Debug, Clone, PartialEq)]
3388pub struct ChronoDateTimeUtcWrapper {
3389 pub created_at: chrono::DateTime<chrono::Utc>,
3390}
3391
3392#[cfg(feature = "chrono")]
3394#[derive(Facet, Debug, Clone, PartialEq)]
3395pub struct ChronoNaiveDateTimeWrapper {
3396 pub created_at: chrono::NaiveDateTime,
3397}
3398
3399#[cfg(feature = "chrono")]
3401#[derive(Facet, Debug, Clone, PartialEq)]
3402pub struct ChronoNaiveDateWrapper {
3403 pub birth_date: chrono::NaiveDate,
3404}
3405
3406#[cfg(feature = "chrono")]
3408#[derive(Facet, Debug, Clone, PartialEq)]
3409pub struct ChronoNaiveTimeWrapper {
3410 pub alarm_time: chrono::NaiveTime,
3411}
3412
3413#[cfg(feature = "chrono")]
3415#[derive(Facet, Debug, Clone, PartialEq)]
3416pub struct ChronoInVecWrapper {
3417 pub timestamps: Vec<chrono::DateTime<chrono::Utc>>,
3418}
3419
3420#[cfg(feature = "bytes")]
3424#[derive(Facet, Debug, Clone, PartialEq)]
3425pub struct BytesBytesWrapper {
3426 pub data: bytes::Bytes,
3427}
3428
3429#[cfg(feature = "bytes")]
3431#[derive(Facet, Debug, Clone, PartialEq)]
3432pub struct BytesBytesMutWrapper {
3433 pub data: bytes::BytesMut,
3434}
3435
3436#[cfg(feature = "bytestring")]
3440#[derive(Facet, Debug, Clone, PartialEq)]
3441pub struct ByteStringWrapper {
3442 pub value: bytestring::ByteString,
3443}
3444
3445#[cfg(feature = "compact_str")]
3447#[derive(Facet, Debug, Clone, PartialEq)]
3448pub struct CompactStringWrapper {
3449 pub value: compact_str::CompactString,
3450}
3451
3452#[cfg(feature = "smartstring")]
3454#[derive(Facet, Debug, Clone, PartialEq)]
3455pub struct SmartStringWrapper {
3456 pub value: smartstring::SmartString<smartstring::LazyCompact>,
3457}
3458
3459fn emit_case_showcase<S, T>(
3460 desc: &'static CaseDescriptor<T>,
3461 note: Option<&'static str>,
3462 roundtrip_disabled_reason: Option<&'static str>,
3463 input: &'static [u8],
3464 highlight_language: Option<&'static str>,
3465 actual: &T,
3466) where
3467 S: FormatSuite,
3468 for<'facet> T: Facet<'facet>,
3469 T: Debug,
3470{
3471 let (input_label, input_block) = match highlight_language {
3472 Some(language) => match highlight_payload(language, input) {
3473 Some(html) => (format!("Input highlighted via arborium ({language})"), html),
3474 None => (
3475 format!("Input (UTF-8, highlighting unavailable for {language})"),
3476 String::from_utf8_lossy(input).into_owned(),
3477 ),
3478 },
3479 None => (
3480 "Input (UTF-8)".to_string(),
3481 String::from_utf8_lossy(input).into_owned(),
3482 ),
3483 };
3484
3485 let pretty_output = format!(
3486 "{}",
3487 actual.pretty_with(PrettyPrinter::new().with_indent_size(2))
3488 );
3489 let note_line = note.map(|n| format!("note: {n}\n")).unwrap_or_default();
3490 let roundtrip_line = roundtrip_disabled_reason
3491 .map(|r| format!("roundtrip: disabled ({r})\n"))
3492 .unwrap_or_default();
3493
3494 println!(
3495 "{}",
3496 formatdoc!(
3497 "
3498 ── facet-format-suite :: {format_name} :: {case_id} ──
3499 description: {description}
3500 {note_line}{roundtrip_line}{input_label}:
3501 {input_block}
3502
3503 facet-pretty output:
3504 {pretty_output}
3505 ",
3506 format_name = S::format_name(),
3507 case_id = desc.id,
3508 description = desc.description,
3509 note_line = note_line,
3510 roundtrip_line = roundtrip_line,
3511 input_label = input_label,
3512 input_block = input_block,
3513 pretty_output = pretty_output,
3514 )
3515 );
3516}
3517
3518fn emit_error_case_showcase<S: FormatSuite>(
3519 case_id: &str,
3520 description: &str,
3521 note: Option<&'static str>,
3522 input: &[u8],
3523 highlight_language: Option<&'static str>,
3524 error_contains: &str,
3525) {
3526 let (input_label, input_block) = match highlight_language {
3527 Some(language) => match highlight_payload(language, input) {
3528 Some(html) => (format!("Input highlighted via arborium ({language})"), html),
3529 None => (
3530 format!("Input (UTF-8, highlighting unavailable for {language})"),
3531 String::from_utf8_lossy(input).into_owned(),
3532 ),
3533 },
3534 None => (
3535 "Input (UTF-8)".to_string(),
3536 String::from_utf8_lossy(input).into_owned(),
3537 ),
3538 };
3539
3540 let note_line = note.map(|n| format!("note: {n}\n")).unwrap_or_default();
3541
3542 println!(
3543 "{}",
3544 formatdoc!(
3545 "
3546 ── facet-format-suite :: {format_name} :: {case_id} ──
3547 description: {description}
3548 {note_line}expects error containing: \"{error_contains}\"
3549 {input_label}:
3550 {input_block}
3551 ",
3552 format_name = S::format_name(),
3553 case_id = case_id,
3554 description = description,
3555 note_line = note_line,
3556 error_contains = error_contains,
3557 input_label = input_label,
3558 input_block = input_block,
3559 )
3560 );
3561}
3562
3563fn highlight_payload(language: &str, input: &[u8]) -> Option<String> {
3564 let source = core::str::from_utf8(input).ok()?;
3565 let mut highlighter = Highlighter::new();
3566 highlighter.highlight(language, source).ok()
3567}
3568
3569#[cfg(feature = "msgpack")]
3577pub mod msgpack {
3578 use super::*;
3579
3580 pub fn serialize<T: serde::Serialize>(value: &T) -> Vec<u8> {
3585 rmp_serde::to_vec_named(value).expect("rmp-serde serialization failed")
3586 }
3587
3588 pub fn case_from_expected<T>(expected: &T) -> CaseSpec
3593 where
3594 T: serde::Serialize,
3595 {
3596 CaseSpec::from_bytes_vec(serialize(expected))
3597 }
3598
3599 pub fn struct_single_field_bytes() -> Vec<u8> {
3603 serialize(&StructSingleField {
3604 name: "facet".into(),
3605 })
3606 }
3607
3608 pub fn sequence_numbers_bytes() -> Vec<u8> {
3610 serialize(&vec![1u64, 2, 3])
3611 }
3612
3613 pub fn struct_nested_bytes() -> Vec<u8> {
3615 serialize(&NestedParent {
3616 id: 42,
3617 child: NestedChild {
3618 code: "alpha".into(),
3619 active: true,
3620 },
3621 tags: vec!["core".into(), "json".into()],
3622 })
3623 }
3624
3625 pub fn scalar_bool_bytes() -> Vec<u8> {
3627 serialize(&BoolWrapper {
3628 yes: true,
3629 no: false,
3630 })
3631 }
3632
3633 pub fn scalar_integers_bytes() -> Vec<u8> {
3635 serialize(&IntegerTypes {
3636 signed_8: -128,
3637 unsigned_8: 255,
3638 signed_32: -2_147_483_648,
3639 unsigned_32: 4_294_967_295,
3640 signed_64: -9_223_372_036_854_775_808,
3641 unsigned_64: 18_446_744_073_709_551_615,
3642 })
3643 }
3644
3645 pub fn scalar_floats_bytes() -> Vec<u8> {
3647 serialize(&FloatTypes {
3648 float_32: 1.5,
3649 float_64: 2.25,
3650 })
3651 }
3652
3653 pub fn map_string_keys_bytes() -> Vec<u8> {
3655 let mut map = std::collections::BTreeMap::new();
3656 map.insert("alpha".to_string(), 1);
3657 map.insert("beta".to_string(), 2);
3658 serialize(&MapWrapper { data: map })
3659 }
3660
3661 pub fn tuple_simple_bytes() -> Vec<u8> {
3663 serialize(&TupleWrapper {
3664 triple: ("hello".to_string(), 42, true),
3665 })
3666 }
3667
3668 pub fn tuple_nested_bytes() -> Vec<u8> {
3670 serialize(&NestedTupleWrapper {
3671 outer: ((1, 2), ("test".to_string(), true)),
3672 })
3673 }
3674
3675 pub fn option_none_bytes() -> Vec<u8> {
3677 serialize(&WithOption {
3678 name: "test".to_string(),
3679 nickname: None,
3680 })
3681 }
3682
3683 pub fn option_some_bytes() -> Vec<u8> {
3685 serialize(&WithOption {
3686 name: "test".to_string(),
3687 nickname: Some("nick".to_string()),
3688 })
3689 }
3690
3691 pub fn enum_unit_variant_bytes() -> Vec<u8> {
3693 serialize(&UnitVariantEnum::Active)
3694 }
3695
3696 pub fn vec_nested_bytes() -> Vec<u8> {
3698 serialize(&NestedVecWrapper {
3699 matrix: vec![vec![1, 2], vec![3, 4, 5]],
3700 })
3701 }
3702
3703 pub fn bytes_vec_u8_bytes() -> Vec<u8> {
3705 serialize(&BytesWrapper {
3706 data: vec![0xDE, 0xAD, 0xBE, 0xEF],
3707 })
3708 }
3709
3710 pub fn char_scalar_bytes() -> Vec<u8> {
3712 serialize(&CharWrapper {
3713 letter: 'A',
3714 emoji: '🦀',
3715 })
3716 }
3717
3718 pub fn unit_struct_bytes() -> Vec<u8> {
3720 serialize(&UnitStruct)
3721 }
3722
3723 pub fn hashset_bytes() -> Vec<u8> {
3725 let mut items = std::collections::HashSet::new();
3726 items.insert("alpha".to_string());
3727 items.insert("beta".to_string());
3728 items.insert("gamma".to_string());
3729 serialize(&HashSetWrapper { items })
3730 }
3731
3732 pub fn tuple_empty_bytes() -> Vec<u8> {
3734 serialize(&EmptyTupleWrapper {
3735 name: "test".to_string(),
3736 empty: (),
3737 })
3738 }
3739
3740 pub fn tuple_single_element_bytes() -> Vec<u8> {
3742 serialize(&SingleElementTupleWrapper {
3743 name: "test".to_string(),
3744 single: (42,),
3745 })
3746 }
3747}
3748
3749fn emit_case_showcase_dynamic<S, T>(
3751 desc: &'static CaseDescriptor<T>,
3752 note: Option<&'static str>,
3753 roundtrip_disabled_reason: Option<&'static str>,
3754 input: &[u8],
3755 actual: &T,
3756) where
3757 S: FormatSuite,
3758 for<'facet> T: Facet<'facet>,
3759 T: Debug,
3760{
3761 let input_block = format_hex_dump(input);
3763
3764 let pretty_output = format!(
3765 "{}",
3766 actual.pretty_with(PrettyPrinter::new().with_indent_size(2))
3767 );
3768 let note_line = note.map(|n| format!("note: {n}\n")).unwrap_or_default();
3769 let roundtrip_line = roundtrip_disabled_reason
3770 .map(|r| format!("roundtrip: disabled ({r})\n"))
3771 .unwrap_or_default();
3772
3773 println!(
3774 "{}",
3775 formatdoc!(
3776 "
3777 ── facet-format-suite :: {format_name} :: {case_id} ──
3778 description: {description}
3779 {note_line}{roundtrip_line}Input (binary, {len} bytes):
3780 {input_block}
3781
3782 facet-pretty output:
3783 {pretty_output}
3784 ",
3785 format_name = S::format_name(),
3786 case_id = desc.id,
3787 description = desc.description,
3788 note_line = note_line,
3789 roundtrip_line = roundtrip_line,
3790 len = input.len(),
3791 input_block = input_block,
3792 pretty_output = pretty_output,
3793 )
3794 );
3795}
3796
3797fn format_hex_dump(input: &[u8]) -> String {
3799 use std::fmt::Write;
3800 let mut output = String::new();
3801 for (i, chunk) in input.chunks(16).enumerate() {
3802 write!(output, "{:08x} ", i * 16).unwrap();
3804 for (j, byte) in chunk.iter().enumerate() {
3806 if j == 8 {
3807 output.push(' ');
3808 }
3809 write!(output, "{:02x} ", byte).unwrap();
3810 }
3811 for j in chunk.len()..16 {
3813 if j == 8 {
3814 output.push(' ');
3815 }
3816 output.push_str(" ");
3817 }
3818 output.push_str(" |");
3820 for byte in chunk {
3821 if byte.is_ascii_graphic() || *byte == b' ' {
3822 output.push(*byte as char);
3823 } else {
3824 output.push('.');
3825 }
3826 }
3827 output.push_str("|\n");
3828 }
3829 output
3830}