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 enum_untagged() -> CaseSpec;
232 fn enum_variant_rename() -> CaseSpec;
234
235 fn untagged_with_null() -> CaseSpec;
239 fn untagged_newtype_variant() -> CaseSpec;
241 fn untagged_as_field() -> CaseSpec;
243
244 fn untagged_unit_only() -> CaseSpec;
246
247 fn box_wrapper() -> CaseSpec;
251 fn arc_wrapper() -> CaseSpec;
253 fn rc_wrapper() -> CaseSpec;
255 fn box_str() -> CaseSpec;
257 fn arc_str() -> CaseSpec;
259 fn rc_str() -> CaseSpec;
261 fn arc_slice() -> CaseSpec;
263
264 fn set_btree() -> CaseSpec;
268
269 fn scalar_integers_16() -> CaseSpec;
273 fn scalar_integers_128() -> CaseSpec;
275 fn scalar_integers_size() -> CaseSpec;
277
278 fn nonzero_integers() -> CaseSpec;
282 fn nonzero_integers_extended() -> CaseSpec;
284
285 fn cow_str() -> CaseSpec;
289
290 fn newtype_u64() -> CaseSpec;
294 fn newtype_string() -> CaseSpec;
296
297 fn char_scalar() -> CaseSpec;
301
302 fn hashset() -> CaseSpec;
306
307 fn vec_nested() -> CaseSpec;
311
312 fn bytes_vec_u8() -> CaseSpec;
316
317 fn array_fixed_size() -> CaseSpec;
321
322 fn skip_unknown_fields() -> CaseSpec;
326
327 fn string_escapes() -> CaseSpec;
331 fn string_escapes_extended() -> CaseSpec;
333
334 fn unit_struct() -> CaseSpec;
338
339 #[cfg(feature = "uuid")]
343 fn uuid() -> CaseSpec;
344
345 #[cfg(feature = "ulid")]
347 fn ulid() -> CaseSpec;
348
349 #[cfg(feature = "camino")]
351 fn camino_path() -> CaseSpec;
352
353 #[cfg(feature = "ordered-float")]
355 fn ordered_float() -> CaseSpec;
356
357 #[cfg(feature = "time")]
359 fn time_offset_datetime() -> CaseSpec;
360
361 #[cfg(feature = "jiff02")]
363 fn jiff_timestamp() -> CaseSpec;
364
365 #[cfg(feature = "jiff02")]
367 fn jiff_civil_datetime() -> CaseSpec;
368
369 #[cfg(feature = "chrono")]
371 fn chrono_datetime_utc() -> CaseSpec;
372
373 #[cfg(feature = "chrono")]
375 fn chrono_naive_datetime() -> CaseSpec;
376
377 #[cfg(feature = "chrono")]
379 fn chrono_naive_date() -> CaseSpec;
380
381 #[cfg(feature = "chrono")]
383 fn chrono_naive_time() -> CaseSpec;
384
385 #[cfg(feature = "chrono")]
387 fn chrono_in_vec() -> CaseSpec;
388
389 #[cfg(feature = "bytes")]
393 fn bytes_bytes() -> CaseSpec;
394
395 #[cfg(feature = "bytes")]
397 fn bytes_bytes_mut() -> CaseSpec;
398
399 #[cfg(feature = "facet-value")]
403 fn value_null() -> CaseSpec;
404
405 #[cfg(feature = "facet-value")]
407 fn value_bool() -> CaseSpec;
408
409 #[cfg(feature = "facet-value")]
411 fn value_integer() -> CaseSpec;
412
413 #[cfg(feature = "facet-value")]
415 fn value_float() -> CaseSpec;
416
417 #[cfg(feature = "facet-value")]
419 fn value_string() -> CaseSpec;
420
421 #[cfg(feature = "facet-value")]
423 fn value_array() -> CaseSpec;
424
425 #[cfg(feature = "facet-value")]
427 fn value_object() -> CaseSpec;
428}
429
430pub fn run_suite<S: FormatSuite + 'static>() {
433 for case in all_cases::<S>() {
434 match case.run() {
435 CaseOutcome::Passed => {}
436 CaseOutcome::Skipped(reason) => {
437 eprintln!(
438 "facet-format-suite: skipping {} for {} ({reason})",
439 case.id,
440 S::format_name()
441 );
442 }
443 CaseOutcome::Failed(msg) => {
444 panic!(
445 "facet-format-suite case {} ({}) failed: {msg}",
446 case.id, case.description
447 );
448 }
449 }
450 }
451}
452
453pub fn all_cases<S: FormatSuite + 'static>() -> Vec<SuiteCase> {
455 vec![
456 SuiteCase::new::<S, StructSingleField>(&CASE_STRUCT_SINGLE_FIELD, S::struct_single_field),
458 SuiteCase::new::<S, Vec<u64>>(&CASE_SEQUENCE_NUMBERS, S::sequence_numbers),
459 SuiteCase::new::<S, Vec<MixedScalar>>(
460 &CASE_SEQUENCE_MIXED_SCALARS,
461 S::sequence_mixed_scalars,
462 ),
463 SuiteCase::new::<S, NestedParent>(&CASE_STRUCT_NESTED, S::struct_nested),
464 SuiteCase::new::<S, ComplexEnum>(&CASE_ENUM_COMPLEX, S::enum_complex),
465 SuiteCase::new::<S, RenamedField>(&CASE_ATTR_RENAME_FIELD, S::attr_rename_field),
467 SuiteCase::new::<S, CamelCaseStruct>(&CASE_ATTR_RENAME_ALL_CAMEL, S::attr_rename_all_camel),
468 SuiteCase::new::<S, WithDefault>(&CASE_ATTR_DEFAULT_FIELD, S::attr_default_field),
469 SuiteCase::new::<S, StructLevelDefault>(&CASE_ATTR_DEFAULT_STRUCT, S::attr_default_struct),
470 SuiteCase::new::<S, WithDefaultFunction>(
471 &CASE_ATTR_DEFAULT_FUNCTION,
472 S::attr_default_function,
473 ),
474 SuiteCase::new::<S, WithOption>(&CASE_OPTION_NONE, S::option_none),
475 SuiteCase::new::<S, WithOption>(&CASE_OPTION_SOME, S::option_some),
476 SuiteCase::new::<S, WithOption>(&CASE_OPTION_NULL, S::option_null),
477 SuiteCase::new::<S, WithSkipSerializing>(
478 &CASE_ATTR_SKIP_SERIALIZING,
479 S::attr_skip_serializing,
480 ),
481 SuiteCase::new::<S, WithSkipSerializingIf>(
482 &CASE_ATTR_SKIP_SERIALIZING_IF,
483 S::attr_skip_serializing_if,
484 ),
485 SuiteCase::new::<S, WithSkip>(&CASE_ATTR_SKIP, S::attr_skip),
486 SuiteCase::new::<S, InternallyTagged>(
488 &CASE_ENUM_INTERNALLY_TAGGED,
489 S::enum_internally_tagged,
490 ),
491 SuiteCase::new::<S, AdjacentlyTagged>(
492 &CASE_ENUM_ADJACENTLY_TAGGED,
493 S::enum_adjacently_tagged,
494 ),
495 SuiteCase::new::<S, FlattenOuter>(&CASE_STRUCT_FLATTEN, S::struct_flatten),
497 SuiteCase::new::<S, UserRecord>(&CASE_TRANSPARENT_NEWTYPE, S::transparent_newtype),
498 SuiteCase::new::<S, FlattenOptionalSome>(
500 &CASE_FLATTEN_OPTIONAL_SOME,
501 S::flatten_optional_some,
502 ),
503 SuiteCase::new::<S, FlattenOptionalNone>(
504 &CASE_FLATTEN_OPTIONAL_NONE,
505 S::flatten_optional_none,
506 ),
507 SuiteCase::new::<S, FlattenOverlapping>(
508 &CASE_FLATTEN_OVERLAPPING_FIELDS_ERROR,
509 S::flatten_overlapping_fields_error,
510 ),
511 SuiteCase::new::<S, FlattenLevel1>(&CASE_FLATTEN_MULTILEVEL, S::flatten_multilevel),
512 SuiteCase::new::<S, FlattenMultipleEnums>(
513 &CASE_FLATTEN_MULTIPLE_ENUMS,
514 S::flatten_multiple_enums,
515 ),
516 SuiteCase::new::<S, DenyUnknownStruct>(&CASE_DENY_UNKNOWN_FIELDS, S::deny_unknown_fields),
518 SuiteCase::new::<S, ExpectsInteger>(
519 &CASE_ERROR_TYPE_MISMATCH_STRING_TO_INT,
520 S::error_type_mismatch_string_to_int,
521 ),
522 SuiteCase::new::<S, ExpectsArray>(
523 &CASE_ERROR_TYPE_MISMATCH_OBJECT_TO_ARRAY,
524 S::error_type_mismatch_object_to_array,
525 ),
526 SuiteCase::new::<S, RequiresAllFields>(
527 &CASE_ERROR_MISSING_REQUIRED_FIELD,
528 S::error_missing_required_field,
529 ),
530 SuiteCase::new::<S, WithAlias>(&CASE_ATTR_ALIAS, S::attr_alias),
532 SuiteCase::new::<S, RenameVsAlias>(
534 &CASE_ATTR_RENAME_VS_ALIAS,
535 S::attr_rename_vs_alias_precedence,
536 ),
537 SuiteCase::new::<S, RenameAllKebab>(&CASE_ATTR_RENAME_ALL_KEBAB, S::attr_rename_all_kebab),
538 SuiteCase::new::<S, RenameAllScreaming>(
539 &CASE_ATTR_RENAME_ALL_SCREAMING,
540 S::attr_rename_all_screaming,
541 ),
542 SuiteCase::new::<S, RenameUnicode>(&CASE_ATTR_RENAME_UNICODE, S::attr_rename_unicode),
543 SuiteCase::new::<S, RenameSpecialChars>(
544 &CASE_ATTR_RENAME_SPECIAL_CHARS,
545 S::attr_rename_special_chars,
546 ),
547 SuiteCase::new::<S, ProxyInt>(&CASE_PROXY_CONTAINER, S::proxy_container),
549 SuiteCase::new::<S, ProxyFieldLevel>(&CASE_PROXY_FIELD_LEVEL, S::proxy_field_level),
550 SuiteCase::new::<S, ProxyInt>(&CASE_PROXY_VALIDATION_ERROR, S::proxy_validation_error),
551 SuiteCase::new::<S, ProxyWithOption>(&CASE_PROXY_WITH_OPTION, S::proxy_with_option),
552 SuiteCase::new::<S, ProxyEnum>(&CASE_PROXY_WITH_ENUM, S::proxy_with_enum),
553 SuiteCase::new::<S, TransparentProxy>(
554 &CASE_PROXY_WITH_TRANSPARENT,
555 S::proxy_with_transparent,
556 ),
557 SuiteCase::new::<S, OpaqueProxyWrapper>(&CASE_OPAQUE_PROXY, S::opaque_proxy),
558 SuiteCase::new::<S, OpaqueProxyOptionWrapper>(
559 &CASE_OPAQUE_PROXY_OPTION,
560 S::opaque_proxy_option,
561 ),
562 SuiteCase::new::<S, OuterTransparent>(
564 &CASE_TRANSPARENT_MULTILEVEL,
565 S::transparent_multilevel,
566 ),
567 SuiteCase::new::<S, TransparentOption>(&CASE_TRANSPARENT_OPTION, S::transparent_option),
568 SuiteCase::new::<S, TransparentNonZero>(&CASE_TRANSPARENT_NONZERO, S::transparent_nonzero),
569 SuiteCase::new::<S, BoolWrapper>(&CASE_SCALAR_BOOL, S::scalar_bool),
571 SuiteCase::new::<S, IntegerTypes>(&CASE_SCALAR_INTEGERS, S::scalar_integers),
572 SuiteCase::new::<S, FloatTypes>(&CASE_SCALAR_FLOATS, S::scalar_floats),
573 SuiteCase::new::<S, FloatTypesScientific>(
574 &CASE_SCALAR_FLOATS_SCIENTIFIC,
575 S::scalar_floats_scientific,
576 ),
577 SuiteCase::new::<S, MapWrapper>(&CASE_MAP_STRING_KEYS, S::map_string_keys),
579 SuiteCase::new::<S, TupleWrapper>(&CASE_TUPLE_SIMPLE, S::tuple_simple),
580 SuiteCase::new::<S, NestedTupleWrapper>(&CASE_TUPLE_NESTED, S::tuple_nested),
581 SuiteCase::new::<S, EmptyTupleWrapper>(&CASE_TUPLE_EMPTY, S::tuple_empty),
582 SuiteCase::new::<S, SingleElementTupleWrapper>(
583 &CASE_TUPLE_SINGLE_ELEMENT,
584 S::tuple_single_element,
585 ),
586 SuiteCase::new::<S, TupleVariantEnum>(&CASE_TUPLE_STRUCT_VARIANT, S::tuple_struct_variant),
587 SuiteCase::new::<S, NewtypeVariantEnum>(
588 &CASE_TUPLE_NEWTYPE_VARIANT,
589 S::tuple_newtype_variant,
590 ),
591 SuiteCase::new::<S, UnitVariantEnum>(&CASE_ENUM_UNIT_VARIANT, S::enum_unit_variant),
593 SuiteCase::new::<S, UntaggedEnum>(&CASE_ENUM_UNTAGGED, S::enum_untagged),
594 SuiteCase::new::<S, EnumVariantRename>(&CASE_ENUM_VARIANT_RENAME, S::enum_variant_rename),
595 SuiteCase::new::<S, UntaggedWithNull>(&CASE_UNTAGGED_WITH_NULL, S::untagged_with_null),
597 SuiteCase::new::<S, UntaggedNewtype>(
598 &CASE_UNTAGGED_NEWTYPE_VARIANT,
599 S::untagged_newtype_variant,
600 ),
601 SuiteCase::new::<S, UntaggedAsField>(&CASE_UNTAGGED_AS_FIELD, S::untagged_as_field),
602 SuiteCase::new::<S, UntaggedUnitOnly>(&CASE_UNTAGGED_UNIT_ONLY, S::untagged_unit_only),
603 SuiteCase::new::<S, BoxWrapper>(&CASE_BOX_WRAPPER, S::box_wrapper),
605 SuiteCase::new::<S, ArcWrapper>(&CASE_ARC_WRAPPER, S::arc_wrapper),
606 SuiteCase::new::<S, RcWrapper>(&CASE_RC_WRAPPER, S::rc_wrapper),
607 SuiteCase::new::<S, BoxStrWrapper>(&CASE_BOX_STR, S::box_str),
608 SuiteCase::new::<S, ArcStrWrapper>(&CASE_ARC_STR, S::arc_str),
609 SuiteCase::new::<S, RcStrWrapper>(&CASE_RC_STR, S::rc_str),
610 SuiteCase::new::<S, ArcSliceWrapper>(&CASE_ARC_SLICE, S::arc_slice),
611 SuiteCase::new::<S, SetWrapper>(&CASE_SET_BTREE, S::set_btree),
613 SuiteCase::new::<S, IntegerTypes16>(&CASE_SCALAR_INTEGERS_16, S::scalar_integers_16),
615 SuiteCase::new::<S, IntegerTypes128>(&CASE_SCALAR_INTEGERS_128, S::scalar_integers_128),
616 SuiteCase::new::<S, IntegerTypesSize>(&CASE_SCALAR_INTEGERS_SIZE, S::scalar_integers_size),
617 SuiteCase::new::<S, NonZeroTypes>(&CASE_NONZERO_INTEGERS, S::nonzero_integers),
619 SuiteCase::new::<S, NonZeroTypesExtended>(
620 &CASE_NONZERO_INTEGERS_EXTENDED,
621 S::nonzero_integers_extended,
622 ),
623 SuiteCase::new::<S, CowStrWrapper>(&CASE_COW_STR, S::cow_str),
625 SuiteCase::new::<S, BytesWrapper>(&CASE_BYTES_VEC_U8, S::bytes_vec_u8),
627 SuiteCase::new::<S, ArrayWrapper>(&CASE_ARRAY_FIXED_SIZE, S::array_fixed_size),
629 SuiteCase::new::<S, SkipUnknownStruct>(&CASE_SKIP_UNKNOWN_FIELDS, S::skip_unknown_fields),
631 SuiteCase::new::<S, StringEscapes>(&CASE_STRING_ESCAPES, S::string_escapes),
633 SuiteCase::new::<S, StringEscapesExtended>(
634 &CASE_STRING_ESCAPES_EXTENDED,
635 S::string_escapes_extended,
636 ),
637 SuiteCase::new::<S, UnitStruct>(&CASE_UNIT_STRUCT, S::unit_struct),
639 SuiteCase::new::<S, NewtypeU64Wrapper>(&CASE_NEWTYPE_U64, S::newtype_u64),
641 SuiteCase::new::<S, NewtypeStringWrapper>(&CASE_NEWTYPE_STRING, S::newtype_string),
642 SuiteCase::new::<S, CharWrapper>(&CASE_CHAR_SCALAR, S::char_scalar),
644 SuiteCase::new::<S, HashSetWrapper>(&CASE_HASHSET, S::hashset),
646 SuiteCase::new::<S, NestedVecWrapper>(&CASE_VEC_NESTED, S::vec_nested),
648 #[cfg(feature = "uuid")]
650 SuiteCase::new::<S, UuidWrapper>(&CASE_UUID, S::uuid),
651 #[cfg(feature = "ulid")]
652 SuiteCase::new::<S, UlidWrapper>(&CASE_ULID, S::ulid),
653 #[cfg(feature = "camino")]
654 SuiteCase::new::<S, CaminoWrapper>(&CASE_CAMINO_PATH, S::camino_path),
655 #[cfg(feature = "ordered-float")]
656 SuiteCase::new::<S, OrderedFloatWrapper>(&CASE_ORDERED_FLOAT, S::ordered_float),
657 #[cfg(feature = "time")]
658 SuiteCase::new::<S, TimeOffsetDateTimeWrapper>(
659 &CASE_TIME_OFFSET_DATETIME,
660 S::time_offset_datetime,
661 ),
662 #[cfg(feature = "jiff02")]
663 SuiteCase::new::<S, JiffTimestampWrapper>(&CASE_JIFF_TIMESTAMP, S::jiff_timestamp),
664 #[cfg(feature = "jiff02")]
665 SuiteCase::new::<S, JiffCivilDateTimeWrapper>(
666 &CASE_JIFF_CIVIL_DATETIME,
667 S::jiff_civil_datetime,
668 ),
669 #[cfg(feature = "chrono")]
670 SuiteCase::new::<S, ChronoDateTimeUtcWrapper>(
671 &CASE_CHRONO_DATETIME_UTC,
672 S::chrono_datetime_utc,
673 ),
674 #[cfg(feature = "chrono")]
675 SuiteCase::new::<S, ChronoNaiveDateTimeWrapper>(
676 &CASE_CHRONO_NAIVE_DATETIME,
677 S::chrono_naive_datetime,
678 ),
679 #[cfg(feature = "chrono")]
680 SuiteCase::new::<S, ChronoNaiveDateWrapper>(&CASE_CHRONO_NAIVE_DATE, S::chrono_naive_date),
681 #[cfg(feature = "chrono")]
682 SuiteCase::new::<S, ChronoNaiveTimeWrapper>(&CASE_CHRONO_NAIVE_TIME, S::chrono_naive_time),
683 #[cfg(feature = "chrono")]
684 SuiteCase::new::<S, ChronoInVecWrapper>(&CASE_CHRONO_IN_VEC, S::chrono_in_vec),
685 #[cfg(feature = "bytes")]
687 SuiteCase::new::<S, BytesBytesWrapper>(&CASE_BYTES_BYTES, S::bytes_bytes),
688 #[cfg(feature = "bytes")]
689 SuiteCase::new::<S, BytesBytesMutWrapper>(&CASE_BYTES_BYTES_MUT, S::bytes_bytes_mut),
690 #[cfg(feature = "facet-value")]
692 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_NULL, S::value_null),
693 #[cfg(feature = "facet-value")]
694 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_BOOL, S::value_bool),
695 #[cfg(feature = "facet-value")]
696 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_INTEGER, S::value_integer),
697 #[cfg(feature = "facet-value")]
698 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_FLOAT, S::value_float),
699 #[cfg(feature = "facet-value")]
700 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_STRING, S::value_string),
701 #[cfg(feature = "facet-value")]
702 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_ARRAY, S::value_array),
703 #[cfg(feature = "facet-value")]
704 SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_OBJECT, S::value_object),
705 ]
706}
707
708#[derive(Debug, Clone, Copy, Default)]
710pub enum CompareMode {
711 #[default]
713 Reflection,
714 PartialEq,
716}
717
718#[derive(Debug, Clone)]
720pub struct CaseSpec {
721 payload: CasePayload,
722 note: Option<&'static str>,
723 roundtrip: RoundtripSpec,
724 compare_mode: CompareMode,
725}
726
727impl CaseSpec {
728 pub const fn from_bytes(input: &'static [u8]) -> Self {
730 Self {
731 payload: CasePayload::Input(input),
732 note: None,
733 roundtrip: RoundtripSpec::Enabled,
734 compare_mode: CompareMode::Reflection,
735 }
736 }
737
738 #[allow(clippy::should_implement_trait)]
740 pub fn from_str(input: &'static str) -> Self {
741 Self::from_bytes(input.as_bytes())
742 }
743
744 pub const fn skip(reason: &'static str) -> Self {
746 Self {
747 payload: CasePayload::Skip { reason },
748 note: None,
749 roundtrip: RoundtripSpec::Enabled,
750 compare_mode: CompareMode::Reflection,
751 }
752 }
753
754 pub fn with_note(mut self, note: &'static str) -> Self {
756 self.note = Some(note);
757 self
758 }
759
760 pub fn without_roundtrip(mut self, reason: &'static str) -> Self {
762 self.roundtrip = RoundtripSpec::Disabled { reason };
763 self
764 }
765
766 pub fn with_partial_eq(mut self) -> Self {
769 self.compare_mode = CompareMode::PartialEq;
770 self
771 }
772
773 pub fn expect_error(input: &'static str, error_contains: &'static str) -> Self {
775 Self {
776 payload: CasePayload::ExpectError {
777 input: input.as_bytes(),
778 error_contains,
779 },
780 note: None,
781 roundtrip: RoundtripSpec::Disabled {
782 reason: "error case",
783 },
784 compare_mode: CompareMode::Reflection,
785 }
786 }
787}
788
789#[derive(Debug, Clone)]
790enum CasePayload {
791 Input(&'static [u8]),
792 Skip {
793 reason: &'static str,
794 },
795 ExpectError {
797 input: &'static [u8],
798 error_contains: &'static str,
799 },
800}
801
802#[derive(Debug, Clone)]
803enum RoundtripSpec {
804 Enabled,
805 Disabled { reason: &'static str },
806}
807
808struct CaseDescriptor<T> {
809 id: &'static str,
810 description: &'static str,
811 expected: fn() -> T,
812}
813
814#[derive(Debug)]
815pub enum CaseOutcome {
816 Passed,
817 Skipped(&'static str),
818 Failed(String),
819}
820
821pub struct SuiteCase {
822 pub id: &'static str,
823 pub description: &'static str,
824 skip_reason: Option<&'static str>,
825 runner: Box<dyn Fn() -> CaseOutcome + Send + Sync + 'static>,
826 #[cfg(feature = "tokio")]
827 async_runner: Box<dyn Fn() -> CaseOutcome + Send + Sync + 'static>,
828}
829
830impl SuiteCase {
831 #[cfg(not(feature = "tokio"))]
832 fn new<S, T>(desc: &'static CaseDescriptor<T>, provider: fn() -> CaseSpec) -> Self
833 where
834 S: FormatSuite,
835 for<'facet> T: Facet<'facet>,
836 T: Debug + PartialEq + 'static,
837 {
838 let spec = provider();
839 let skip_reason = match spec.payload {
840 CasePayload::Skip { reason } => Some(reason),
841 _ => None,
842 };
843 let runner_spec = spec.clone();
844 let runner = move || execute_case::<S, T>(desc, runner_spec.clone());
845
846 Self {
847 id: desc.id,
848 description: desc.description,
849 skip_reason,
850 runner: Box::new(runner),
851 }
852 }
853
854 #[cfg(feature = "tokio")]
855 fn new<S, T>(desc: &'static CaseDescriptor<T>, provider: fn() -> CaseSpec) -> Self
856 where
857 S: FormatSuite + 'static,
858 for<'facet> T: Facet<'facet>,
859 T: Debug + PartialEq + 'static,
860 {
861 let spec = provider();
862 let skip_reason = match spec.payload {
863 CasePayload::Skip { reason } => Some(reason),
864 _ => None,
865 };
866 let runner_spec = spec.clone();
867 let runner = move || execute_case::<S, T>(desc, runner_spec.clone());
868
869 #[cfg(feature = "tokio")]
870 let async_runner = {
871 let async_spec = spec.clone();
872 Box::new(move || {
873 let spec = async_spec.clone();
874 let rt = tokio::runtime::Builder::new_current_thread()
876 .enable_all()
877 .build()
878 .expect("failed to create tokio runtime");
879 rt.block_on(execute_case_async::<S, T>(desc, spec))
880 }) as Box<dyn Fn() -> CaseOutcome + Send + Sync + 'static>
881 };
882
883 Self {
884 id: desc.id,
885 description: desc.description,
886 skip_reason,
887 runner: Box::new(runner),
888 #[cfg(feature = "tokio")]
889 async_runner,
890 }
891 }
892
893 pub fn run(&self) -> CaseOutcome {
894 (self.runner)()
895 }
896
897 #[cfg(feature = "tokio")]
900 pub fn run_async(&self) -> CaseOutcome {
901 (self.async_runner)()
902 }
903
904 pub fn skip_reason(&self) -> Option<&'static str> {
905 self.skip_reason
906 }
907}
908
909fn execute_case<S, T>(desc: &'static CaseDescriptor<T>, spec: CaseSpec) -> CaseOutcome
910where
911 S: FormatSuite,
912 for<'facet> T: Facet<'facet>,
913 T: Debug + PartialEq,
914{
915 let note = spec.note;
916 let compare_mode = spec.compare_mode;
917 let roundtrip_disabled_reason = match spec.roundtrip {
918 RoundtripSpec::Enabled => None,
919 RoundtripSpec::Disabled { reason } => Some(reason),
920 };
921 let highlight_language = S::highlight_language();
922 match spec.payload {
923 CasePayload::Skip { reason } => CaseOutcome::Skipped(reason),
924 CasePayload::Input(input) => {
925 let expected = (desc.expected)();
926 let actual = match S::deserialize::<T>(input) {
927 Ok(value) => value,
928 Err(err) => return CaseOutcome::Failed(err.to_string()),
929 };
930
931 emit_case_showcase::<S, T>(
932 desc,
933 note,
934 roundtrip_disabled_reason,
935 input,
936 highlight_language,
937 &actual,
938 );
939
940 let first_assert = panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
942 CompareMode::Reflection => {
943 assert_same!(
944 actual,
945 expected,
946 "facet-format-suite {} ({}) produced unexpected value",
947 desc.id,
948 desc.description
949 );
950 }
951 CompareMode::PartialEq => {
952 assert_eq!(
953 actual, expected,
954 "facet-format-suite {} ({}) produced unexpected value",
955 desc.id, desc.description
956 );
957 }
958 }));
959 if let Err(payload) = first_assert {
960 return CaseOutcome::Failed(format_panic(payload));
961 }
962
963 if roundtrip_disabled_reason.is_some() {
964 return CaseOutcome::Passed;
965 }
966
967 let Some(serialized) = S::serialize(&actual) else {
968 return CaseOutcome::Passed;
969 };
970
971 let serialized = match serialized {
972 Ok(bytes) => bytes,
973 Err(msg) => {
974 return CaseOutcome::Failed(format!(
975 "facet-format-suite {} ({}) serialization failed: {msg}",
976 desc.id, desc.description
977 ));
978 }
979 };
980
981 let roundtripped = match S::deserialize::<T>(&serialized) {
982 Ok(value) => value,
983 Err(err) => {
984 return CaseOutcome::Failed(format!(
985 "facet-format-suite {} ({}) round-trip deserialize failed: {err}",
986 desc.id, desc.description
987 ));
988 }
989 };
990
991 match panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
993 CompareMode::Reflection => {
994 assert_same!(
995 roundtripped,
996 actual,
997 "facet-format-suite {} ({}) round-trip mismatch",
998 desc.id,
999 desc.description
1000 );
1001 }
1002 CompareMode::PartialEq => {
1003 assert_eq!(
1004 roundtripped, actual,
1005 "facet-format-suite {} ({}) round-trip mismatch",
1006 desc.id, desc.description
1007 );
1008 }
1009 })) {
1010 Ok(_) => CaseOutcome::Passed,
1011 Err(payload) => CaseOutcome::Failed(format_panic(payload)),
1012 }
1013 }
1014 CasePayload::ExpectError {
1015 input,
1016 error_contains,
1017 } => {
1018 emit_error_case_showcase::<S>(
1019 desc.id,
1020 desc.description,
1021 note,
1022 input,
1023 highlight_language,
1024 error_contains,
1025 );
1026
1027 match S::deserialize::<T>(input) {
1028 Ok(_) => CaseOutcome::Failed(format!(
1029 "facet-format-suite {} ({}) expected error containing '{}' but deserialization succeeded",
1030 desc.id, desc.description, error_contains
1031 )),
1032 Err(err) => {
1033 let err_str = err.to_string();
1034 if err_str.contains(error_contains) {
1035 CaseOutcome::Passed
1036 } else {
1037 CaseOutcome::Failed(format!(
1038 "facet-format-suite {} ({}) expected error containing '{}' but got: {}",
1039 desc.id, desc.description, error_contains, err_str
1040 ))
1041 }
1042 }
1043 }
1044 }
1045 }
1046}
1047
1048fn format_panic(payload: Box<dyn Any + Send>) -> String {
1049 if let Some(msg) = payload.downcast_ref::<&str>() {
1050 msg.to_string()
1051 } else if let Some(msg) = payload.downcast_ref::<String>() {
1052 msg.clone()
1053 } else {
1054 "panic with non-string payload".into()
1055 }
1056}
1057
1058#[cfg(feature = "tokio")]
1059async fn execute_case_async<S, T>(desc: &'static CaseDescriptor<T>, spec: CaseSpec) -> CaseOutcome
1060where
1061 S: FormatSuite,
1062 for<'facet> T: Facet<'facet>,
1063 T: Debug + PartialEq,
1064{
1065 let compare_mode = spec.compare_mode;
1066 match spec.payload {
1067 CasePayload::Skip { reason } => CaseOutcome::Skipped(reason),
1068 CasePayload::Input(input) => {
1069 let result = S::deserialize_async::<T>(input).await;
1071 let actual = match result {
1072 None => {
1073 return CaseOutcome::Skipped("async deserialization not supported");
1075 }
1076 Some(Ok(value)) => value,
1077 Some(Err(err)) => return CaseOutcome::Failed(err.to_string()),
1078 };
1079
1080 let expected = (desc.expected)();
1081
1082 let first_assert = panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1084 CompareMode::Reflection => {
1085 assert_same!(
1086 actual,
1087 expected,
1088 "facet-format-suite {} ({}) async produced unexpected value",
1089 desc.id,
1090 desc.description
1091 );
1092 }
1093 CompareMode::PartialEq => {
1094 assert_eq!(
1095 actual, expected,
1096 "facet-format-suite {} ({}) async produced unexpected value",
1097 desc.id, desc.description
1098 );
1099 }
1100 }));
1101 if let Err(payload) = first_assert {
1102 return CaseOutcome::Failed(format_panic(payload));
1103 }
1104
1105 CaseOutcome::Passed
1107 }
1108 CasePayload::ExpectError {
1109 input,
1110 error_contains,
1111 } => {
1112 let result = S::deserialize_async::<T>(input).await;
1113 match result {
1114 None => CaseOutcome::Skipped("async deserialization not supported"),
1115 Some(Ok(_)) => CaseOutcome::Failed(format!(
1116 "facet-format-suite {} ({}) async expected error containing '{}' but deserialization succeeded",
1117 desc.id, desc.description, error_contains
1118 )),
1119 Some(Err(err)) => {
1120 let err_str = err.to_string();
1121 if err_str.contains(error_contains) {
1122 CaseOutcome::Passed
1123 } else {
1124 CaseOutcome::Failed(format!(
1125 "facet-format-suite {} ({}) async expected error containing '{}' but got: {}",
1126 desc.id, desc.description, error_contains, err_str
1127 ))
1128 }
1129 }
1130 }
1131 }
1132 }
1133}
1134
1135const CASE_STRUCT_SINGLE_FIELD: CaseDescriptor<StructSingleField> = CaseDescriptor {
1136 id: "struct::single_field",
1137 description: "single-field object parsed into StructSingleField",
1138 expected: || StructSingleField {
1139 name: "facet".into(),
1140 },
1141};
1142
1143const CASE_SEQUENCE_NUMBERS: CaseDescriptor<Vec<u64>> = CaseDescriptor {
1144 id: "sequence::numbers",
1145 description: "array of unsigned integers parsed into Vec<u64>",
1146 expected: || vec![1, 2, 3],
1147};
1148
1149const CASE_SEQUENCE_MIXED_SCALARS: CaseDescriptor<Vec<MixedScalar>> = CaseDescriptor {
1150 id: "sequence::mixed_scalars",
1151 description: "array of heterogeneous scalars parsed into Vec<MixedScalar>",
1152 expected: || {
1153 vec![
1154 MixedScalar::Signed(-1),
1155 MixedScalar::Float(4.625),
1156 MixedScalar::Null,
1157 MixedScalar::Bool(true),
1158 ]
1159 },
1160};
1161
1162const CASE_STRUCT_NESTED: CaseDescriptor<NestedParent> = CaseDescriptor {
1163 id: "struct::nested",
1164 description: "struct containing nested child and tag list",
1165 expected: || NestedParent {
1166 id: 42,
1167 child: NestedChild {
1168 code: "alpha".into(),
1169 active: true,
1170 },
1171 tags: vec!["core".into(), "json".into()],
1172 },
1173};
1174
1175const CASE_ENUM_COMPLEX: CaseDescriptor<ComplexEnum> = CaseDescriptor {
1176 id: "enum::complex",
1177 description: "enum with unit, tuple, and struct variants",
1178 expected: || ComplexEnum::Label {
1179 name: "facet".into(),
1180 level: 7,
1181 },
1182};
1183
1184const CASE_ATTR_RENAME_FIELD: CaseDescriptor<RenamedField> = CaseDescriptor {
1187 id: "attr::rename_field",
1188 description: "field with #[facet(rename = \"userName\")]",
1189 expected: || RenamedField {
1190 user_name: "alice".into(),
1191 age: 30,
1192 },
1193};
1194
1195const CASE_ATTR_RENAME_ALL_CAMEL: CaseDescriptor<CamelCaseStruct> = CaseDescriptor {
1196 id: "attr::rename_all_camel",
1197 description: "struct with #[facet(rename_all = \"camelCase\")]",
1198 expected: || CamelCaseStruct {
1199 first_name: "Jane".into(),
1200 last_name: "Doe".into(),
1201 is_active: true,
1202 },
1203};
1204
1205const CASE_ATTR_DEFAULT_FIELD: CaseDescriptor<WithDefault> = CaseDescriptor {
1206 id: "attr::default_field",
1207 description: "field with #[facet(default)] missing from input",
1208 expected: || WithDefault {
1209 required: "present".into(),
1210 optional_count: 0, },
1212};
1213
1214const CASE_ATTR_DEFAULT_STRUCT: CaseDescriptor<StructLevelDefault> = CaseDescriptor {
1215 id: "attr::default_struct",
1216 description: "struct-level #[facet(default)] with missing field uses Default",
1217 expected: || StructLevelDefault {
1218 count: 123,
1219 message: String::new(), },
1221};
1222
1223const CASE_ATTR_DEFAULT_FUNCTION: CaseDescriptor<WithDefaultFunction> = CaseDescriptor {
1224 id: "attr::default_function",
1225 description: "field with #[facet(default = expr)] uses custom default",
1226 expected: || WithDefaultFunction {
1227 magic_number: 42, name: "hello".into(),
1229 },
1230};
1231
1232const CASE_OPTION_NONE: CaseDescriptor<WithOption> = CaseDescriptor {
1233 id: "option::none",
1234 description: "Option<T> field missing from input becomes None",
1235 expected: || WithOption {
1236 name: "test".into(),
1237 nickname: None,
1238 },
1239};
1240
1241const CASE_OPTION_SOME: CaseDescriptor<WithOption> = CaseDescriptor {
1242 id: "option::some",
1243 description: "Option<T> field with Some value",
1244 expected: || WithOption {
1245 name: "test".into(),
1246 nickname: Some("nick".into()),
1247 },
1248};
1249
1250const CASE_OPTION_NULL: CaseDescriptor<WithOption> = CaseDescriptor {
1251 id: "option::null",
1252 description: "Option<T> field with explicit null becomes None",
1253 expected: || WithOption {
1254 name: "test".into(),
1255 nickname: None,
1256 },
1257};
1258
1259const CASE_ATTR_SKIP_SERIALIZING: CaseDescriptor<WithSkipSerializing> = CaseDescriptor {
1260 id: "attr::skip_serializing",
1261 description: "field with #[facet(skip_serializing)] not in output",
1262 expected: || WithSkipSerializing {
1263 visible: "shown".into(),
1264 hidden: String::new(), },
1266};
1267
1268const CASE_ATTR_SKIP_SERIALIZING_IF: CaseDescriptor<WithSkipSerializingIf> = CaseDescriptor {
1269 id: "attr::skip_serializing_if",
1270 description: "field with #[facet(skip_serializing_if = pred)] skipped when pred is true",
1271 expected: || WithSkipSerializingIf {
1272 name: "test".into(),
1273 optional_data: None, },
1275};
1276
1277const CASE_ATTR_SKIP: CaseDescriptor<WithSkip> = CaseDescriptor {
1278 id: "attr::skip",
1279 description: "field with #[facet(skip)] ignored for both ser and de",
1280 expected: || WithSkip {
1281 visible: "data".into(),
1282 internal: 0, },
1284};
1285
1286const CASE_ENUM_INTERNALLY_TAGGED: CaseDescriptor<InternallyTagged> = CaseDescriptor {
1289 id: "enum::internally_tagged",
1290 description: "internally tagged enum with #[facet(tag = \"type\")]",
1291 expected: || InternallyTagged::Circle { radius: 5.0 },
1292};
1293
1294const CASE_ENUM_ADJACENTLY_TAGGED: CaseDescriptor<AdjacentlyTagged> = CaseDescriptor {
1295 id: "enum::adjacently_tagged",
1296 description: "adjacently tagged enum with #[facet(tag = \"t\", content = \"c\")]",
1297 expected: || AdjacentlyTagged::Message("hello".into()),
1298};
1299
1300const CASE_STRUCT_FLATTEN: CaseDescriptor<FlattenOuter> = CaseDescriptor {
1303 id: "struct::flatten",
1304 description: "struct with #[facet(flatten)] flattening inner fields",
1305 expected: || FlattenOuter {
1306 name: "point".into(),
1307 coords: FlattenInner { x: 10, y: 20 },
1308 },
1309};
1310
1311const CASE_TRANSPARENT_NEWTYPE: CaseDescriptor<UserRecord> = CaseDescriptor {
1312 id: "attr::transparent",
1313 description: "struct containing #[facet(transparent)] newtype",
1314 expected: || UserRecord {
1315 id: UserId(42),
1316 name: "alice".into(),
1317 },
1318};
1319
1320const CASE_FLATTEN_OPTIONAL_SOME: CaseDescriptor<FlattenOptionalSome> = CaseDescriptor {
1323 id: "flatten::optional_some",
1324 description: "flattened field is Option<T> with Some value",
1325 expected: || FlattenOptionalSome {
1326 name: "test".into(),
1327 metadata: Some(FlattenMetadata {
1328 version: 1,
1329 author: "alice".into(),
1330 }),
1331 },
1332};
1333
1334const CASE_FLATTEN_OPTIONAL_NONE: CaseDescriptor<FlattenOptionalNone> = CaseDescriptor {
1335 id: "flatten::optional_none",
1336 description: "flattened field is Option<T> with None value",
1337 expected: || FlattenOptionalNone {
1338 name: "test".into(),
1339 metadata: None,
1340 },
1341};
1342
1343const CASE_FLATTEN_OVERLAPPING_FIELDS_ERROR: CaseDescriptor<FlattenOverlapping> = CaseDescriptor {
1344 id: "flatten::overlapping_fields_error",
1345 description: "two flattened structs with overlapping field names (error)",
1346 expected: || FlattenOverlapping {
1347 part_a: FlattenPartA {
1348 field_a: "a".into(),
1349 shared: 1,
1350 },
1351 part_b: FlattenPartB {
1352 field_b: "b".into(),
1353 shared: 2,
1354 },
1355 },
1356};
1357
1358const CASE_FLATTEN_MULTILEVEL: CaseDescriptor<FlattenLevel1> = CaseDescriptor {
1359 id: "flatten::multilevel",
1360 description: "three levels of nested flatten (A -> B -> C, all flattened)",
1361 expected: || FlattenLevel1 {
1362 top_field: "top".into(),
1363 level2: FlattenLevel2 {
1364 mid_field: 42,
1365 level3: FlattenLevel3 { deep_field: 100 },
1366 },
1367 },
1368};
1369
1370const CASE_FLATTEN_MULTIPLE_ENUMS: CaseDescriptor<FlattenMultipleEnums> = CaseDescriptor {
1371 id: "flatten::multiple_enums",
1372 description: "two different enums flattened into same struct",
1373 expected: || FlattenMultipleEnums {
1374 name: "service".into(),
1375 auth: FlattenAuthMethod::Password(FlattenAuthPassword {
1376 password: "secret".into(),
1377 }),
1378 transport: FlattenTransport::Tcp(FlattenTransportTcp { port: 8080 }),
1379 },
1380};
1381
1382const CASE_DENY_UNKNOWN_FIELDS: CaseDescriptor<DenyUnknownStruct> = CaseDescriptor {
1385 id: "error::deny_unknown_fields",
1386 description: "#[facet(deny_unknown_fields)] rejects input with extra fields",
1387 expected: || DenyUnknownStruct {
1388 foo: "abc".into(),
1389 bar: 42,
1390 },
1391};
1392
1393const CASE_ERROR_TYPE_MISMATCH_STRING_TO_INT: CaseDescriptor<ExpectsInteger> = CaseDescriptor {
1394 id: "error::type_mismatch_string_to_int",
1395 description: "type mismatch - string provided where integer expected",
1396 expected: || ExpectsInteger { value: 42 },
1397};
1398
1399const CASE_ERROR_TYPE_MISMATCH_OBJECT_TO_ARRAY: CaseDescriptor<ExpectsArray> = CaseDescriptor {
1400 id: "error::type_mismatch_object_to_array",
1401 description: "structure mismatch - object provided where array expected",
1402 expected: || ExpectsArray {
1403 items: vec![1, 2, 3],
1404 },
1405};
1406
1407const CASE_ERROR_MISSING_REQUIRED_FIELD: CaseDescriptor<RequiresAllFields> = CaseDescriptor {
1408 id: "error::missing_required_field",
1409 description: "missing required field error",
1410 expected: || RequiresAllFields {
1411 name: "Alice".into(),
1412 age: 30,
1413 email: "alice@example.com".into(),
1414 },
1415};
1416
1417const CASE_ATTR_ALIAS: CaseDescriptor<WithAlias> = CaseDescriptor {
1420 id: "attr::alias",
1421 description: "field with #[facet(alias = \"old_name\")] accepts alternative name",
1422 expected: || WithAlias {
1423 new_name: "value".into(),
1424 count: 5,
1425 },
1426};
1427
1428const CASE_ATTR_RENAME_VS_ALIAS: CaseDescriptor<RenameVsAlias> = CaseDescriptor {
1431 id: "attr::rename_vs_alias_precedence",
1432 description: "when both rename and alias present, rename takes precedence",
1433 expected: || RenameVsAlias {
1434 field: "test".into(),
1435 id: 1,
1436 },
1437};
1438
1439const CASE_ATTR_RENAME_ALL_KEBAB: CaseDescriptor<RenameAllKebab> = CaseDescriptor {
1440 id: "attr::rename_all_kebab",
1441 description: "struct with #[facet(rename_all = \"kebab-case\")]",
1442 expected: || RenameAllKebab {
1443 first_name: "John".into(),
1444 last_name: "Doe".into(),
1445 user_id: 42,
1446 },
1447};
1448
1449const CASE_ATTR_RENAME_ALL_SCREAMING: CaseDescriptor<RenameAllScreaming> = CaseDescriptor {
1450 id: "attr::rename_all_screaming",
1451 description: "struct with #[facet(rename_all = \"SCREAMING_SNAKE_CASE\")]",
1452 expected: || RenameAllScreaming {
1453 api_key: "secret-123".into(),
1454 max_retry_count: 5,
1455 },
1456};
1457
1458const CASE_ATTR_RENAME_UNICODE: CaseDescriptor<RenameUnicode> = CaseDescriptor {
1459 id: "attr::rename_unicode",
1460 description: "field with unicode (emoji) rename #[facet(rename = \"🎉\")]",
1461 expected: || RenameUnicode {
1462 celebration: "party".into(),
1463 },
1464};
1465
1466const CASE_ATTR_RENAME_SPECIAL_CHARS: CaseDescriptor<RenameSpecialChars> = CaseDescriptor {
1467 id: "attr::rename_special_chars",
1468 description: "field with special chars rename #[facet(rename = \"@type\")]",
1469 expected: || RenameSpecialChars {
1470 type_field: "node".into(),
1471 },
1472};
1473
1474const CASE_PROXY_CONTAINER: CaseDescriptor<ProxyInt> = CaseDescriptor {
1477 id: "proxy::container",
1478 description: "container-level #[facet(proxy = IntAsString)] deserializes int from string",
1479 expected: || ProxyInt { value: 42 },
1480};
1481
1482const CASE_PROXY_FIELD_LEVEL: CaseDescriptor<ProxyFieldLevel> = CaseDescriptor {
1483 id: "proxy::field_level",
1484 description: "field-level #[facet(proxy = IntAsString)] on individual field",
1485 expected: || ProxyFieldLevel {
1486 name: "test".into(),
1487 count: 100,
1488 },
1489};
1490
1491const CASE_PROXY_VALIDATION_ERROR: CaseDescriptor<ProxyInt> = CaseDescriptor {
1492 id: "proxy::validation_error",
1493 description: "proxy conversion error when validation fails (expects error)",
1494 expected: || ProxyInt { value: 0 }, };
1496
1497const CASE_PROXY_WITH_OPTION: CaseDescriptor<ProxyWithOption> = CaseDescriptor {
1498 id: "proxy::with_option",
1499 description: "proxy wrapping Option<T>",
1500 expected: || ProxyWithOption {
1501 name: "test".into(),
1502 count: Some(42),
1503 },
1504};
1505
1506const CASE_PROXY_WITH_ENUM: CaseDescriptor<ProxyEnum> = CaseDescriptor {
1507 id: "proxy::with_enum",
1508 description: "proxy on enum variant",
1509 expected: || ProxyEnum::Value(99),
1510};
1511
1512const CASE_PROXY_WITH_TRANSPARENT: CaseDescriptor<TransparentProxy> = CaseDescriptor {
1513 id: "proxy::with_transparent",
1514 description: "transparent wrapper with proxy",
1515 expected: || TransparentProxy(42),
1516};
1517
1518const CASE_OPAQUE_PROXY: CaseDescriptor<OpaqueProxyWrapper> = CaseDescriptor {
1519 id: "proxy::opaque",
1520 description: "#[facet(opaque, proxy = ...)] where target type doesn't implement Facet",
1521 expected: || OpaqueProxyWrapper {
1522 value: OpaqueType { inner: 42 },
1523 },
1524};
1525
1526const CASE_OPAQUE_PROXY_OPTION: CaseDescriptor<OpaqueProxyOptionWrapper> = CaseDescriptor {
1527 id: "proxy::opaque_option",
1528 description: "#[facet(opaque, proxy = ...)] on Option<OpaqueType>",
1529 expected: || OpaqueProxyOptionWrapper {
1530 value: Some(OpaqueType { inner: 99 }),
1531 },
1532};
1533
1534const CASE_TRANSPARENT_MULTILEVEL: CaseDescriptor<OuterTransparent> = CaseDescriptor {
1537 id: "transparent::multilevel",
1538 description: "transparent wrapping another transparent type",
1539 expected: || OuterTransparent(InnerTransparent(42)),
1540};
1541
1542const CASE_TRANSPARENT_OPTION: CaseDescriptor<TransparentOption> = CaseDescriptor {
1543 id: "transparent::option",
1544 description: "transparent wrapping Option<T>",
1545 expected: || TransparentOption(Some(99)),
1546};
1547
1548const CASE_TRANSPARENT_NONZERO: CaseDescriptor<TransparentNonZero> = CaseDescriptor {
1549 id: "transparent::nonzero",
1550 description: "transparent wrapping NonZero type",
1551 expected: || TransparentNonZero(std::num::NonZeroU32::new(42).unwrap()),
1552};
1553
1554const CASE_SCALAR_BOOL: CaseDescriptor<BoolWrapper> = CaseDescriptor {
1557 id: "scalar::bool",
1558 description: "boolean scalar values",
1559 expected: || BoolWrapper {
1560 yes: true,
1561 no: false,
1562 },
1563};
1564
1565const CASE_SCALAR_INTEGERS: CaseDescriptor<IntegerTypes> = CaseDescriptor {
1566 id: "scalar::integers",
1567 description: "various integer types (i8, u8, i32, u32, i64, u64)",
1568 expected: || IntegerTypes {
1569 signed_8: -128,
1570 unsigned_8: 255,
1571 signed_32: -2_147_483_648,
1572 unsigned_32: 4_294_967_295,
1573 signed_64: -9_223_372_036_854_775_808,
1574 unsigned_64: 18_446_744_073_709_551_615,
1575 },
1576};
1577
1578const CASE_SCALAR_FLOATS: CaseDescriptor<FloatTypes> = CaseDescriptor {
1579 id: "scalar::floats",
1580 description: "floating point types (f32, f64)",
1581 expected: || FloatTypes {
1582 float_32: 1.5,
1583 float_64: 2.25,
1584 },
1585};
1586
1587const CASE_SCALAR_FLOATS_SCIENTIFIC: CaseDescriptor<FloatTypesScientific> = CaseDescriptor {
1588 id: "scalar::floats_scientific",
1589 description: "floating point with scientific notation (1.23e10, -4.56e-7)",
1590 expected: || FloatTypesScientific {
1591 large: 1.23e10,
1592 small: -4.56e-7,
1593 positive_exp: 5e3,
1594 },
1595};
1596
1597const CASE_MAP_STRING_KEYS: CaseDescriptor<MapWrapper> = CaseDescriptor {
1600 id: "collection::map",
1601 description: "BTreeMap<String, i32> with string keys",
1602 expected: || {
1603 let mut map = std::collections::BTreeMap::new();
1604 map.insert("alpha".into(), 1);
1605 map.insert("beta".into(), 2);
1606 MapWrapper { data: map }
1607 },
1608};
1609
1610const CASE_TUPLE_SIMPLE: CaseDescriptor<TupleWrapper> = CaseDescriptor {
1611 id: "collection::tuple",
1612 description: "tuple (String, i32, bool)",
1613 expected: || TupleWrapper {
1614 triple: ("hello".into(), 42, true),
1615 },
1616};
1617
1618const CASE_TUPLE_NESTED: CaseDescriptor<NestedTupleWrapper> = CaseDescriptor {
1619 id: "collection::tuple_nested",
1620 description: "nested tuple ((i32, i32), (String, bool))",
1621 expected: || NestedTupleWrapper {
1622 outer: ((1, 2), ("test".into(), true)),
1623 },
1624};
1625
1626const CASE_TUPLE_EMPTY: CaseDescriptor<EmptyTupleWrapper> = CaseDescriptor {
1627 id: "collection::tuple_empty",
1628 description: "empty tuple () as a field",
1629 expected: || EmptyTupleWrapper {
1630 name: "test".into(),
1631 empty: (),
1632 },
1633};
1634
1635const CASE_TUPLE_SINGLE_ELEMENT: CaseDescriptor<SingleElementTupleWrapper> = CaseDescriptor {
1636 id: "collection::tuple_single_element",
1637 description: "single-element tuple (T,) as a field",
1638 expected: || SingleElementTupleWrapper {
1639 name: "test".into(),
1640 single: (42,),
1641 },
1642};
1643
1644const CASE_TUPLE_STRUCT_VARIANT: CaseDescriptor<TupleVariantEnum> = CaseDescriptor {
1645 id: "collection::tuple_struct_variant",
1646 description: "enum with tuple variant Variant(T, U)",
1647 expected: || TupleVariantEnum::Pair("test".into(), 42),
1648};
1649
1650const CASE_TUPLE_NEWTYPE_VARIANT: CaseDescriptor<NewtypeVariantEnum> = CaseDescriptor {
1651 id: "collection::tuple_newtype_variant",
1652 description: "enum with newtype variant Variant(T)",
1653 expected: || NewtypeVariantEnum::Some(99),
1654};
1655
1656const CASE_ENUM_UNIT_VARIANT: CaseDescriptor<UnitVariantEnum> = CaseDescriptor {
1659 id: "enum::unit_variant",
1660 description: "enum with unit variants",
1661 expected: || UnitVariantEnum::Active,
1662};
1663
1664const CASE_ENUM_UNTAGGED: CaseDescriptor<UntaggedEnum> = CaseDescriptor {
1665 id: "enum::untagged",
1666 description: "#[facet(untagged)] enum matches by structure",
1667 expected: || UntaggedEnum::Point { x: 10, y: 20 },
1668};
1669
1670const CASE_ENUM_VARIANT_RENAME: CaseDescriptor<EnumVariantRename> = CaseDescriptor {
1671 id: "enum::variant_rename",
1672 description: "#[facet(rename = \"...\")] on enum variants",
1673 expected: || EnumVariantRename::Active,
1674};
1675
1676const CASE_UNTAGGED_WITH_NULL: CaseDescriptor<UntaggedWithNull> = CaseDescriptor {
1679 id: "untagged::with_null",
1680 description: "untagged enum with unit variant matching null",
1681 expected: || UntaggedWithNull::None,
1682};
1683
1684const CASE_UNTAGGED_NEWTYPE_VARIANT: CaseDescriptor<UntaggedNewtype> = CaseDescriptor {
1685 id: "untagged::newtype_variant",
1686 description: "untagged enum with newtype variants (discrimination)",
1687 expected: || UntaggedNewtype::String("test".into()),
1688};
1689
1690const CASE_UNTAGGED_AS_FIELD: CaseDescriptor<UntaggedAsField> = CaseDescriptor {
1691 id: "untagged::as_field",
1692 description: "untagged enum as struct field (nesting)",
1693 expected: || UntaggedAsField {
1694 name: "test".into(),
1695 value: UntaggedNewtype::Number(42),
1696 },
1697};
1698
1699const CASE_UNTAGGED_UNIT_ONLY: CaseDescriptor<UntaggedUnitOnly> = CaseDescriptor {
1700 id: "untagged::unit_only",
1701 description: "untagged enum with only unit variants (dataless)",
1702 expected: || UntaggedUnitOnly::Alpha,
1703};
1704
1705const CASE_BOX_WRAPPER: CaseDescriptor<BoxWrapper> = CaseDescriptor {
1708 id: "pointer::box",
1709 description: "Box<T> smart pointer",
1710 expected: || BoxWrapper {
1711 inner: Box::new(42),
1712 },
1713};
1714
1715const CASE_ARC_WRAPPER: CaseDescriptor<ArcWrapper> = CaseDescriptor {
1716 id: "pointer::arc",
1717 description: "Arc<T> smart pointer",
1718 expected: || ArcWrapper {
1719 inner: std::sync::Arc::new(42),
1720 },
1721};
1722
1723const CASE_RC_WRAPPER: CaseDescriptor<RcWrapper> = CaseDescriptor {
1724 id: "pointer::rc",
1725 description: "Rc<T> smart pointer",
1726 expected: || RcWrapper {
1727 inner: std::rc::Rc::new(42),
1728 },
1729};
1730
1731const CASE_BOX_STR: CaseDescriptor<BoxStrWrapper> = CaseDescriptor {
1732 id: "pointer::box_str",
1733 description: "Box<str> unsized smart pointer",
1734 expected: || BoxStrWrapper {
1735 inner: Box::from("hello world"),
1736 },
1737};
1738
1739const CASE_ARC_STR: CaseDescriptor<ArcStrWrapper> = CaseDescriptor {
1740 id: "pointer::arc_str",
1741 description: "Arc<str> unsized smart pointer",
1742 expected: || ArcStrWrapper {
1743 inner: std::sync::Arc::from("hello world"),
1744 },
1745};
1746
1747const CASE_RC_STR: CaseDescriptor<RcStrWrapper> = CaseDescriptor {
1748 id: "pointer::rc_str",
1749 description: "Rc<str> unsized smart pointer",
1750 expected: || RcStrWrapper {
1751 inner: std::rc::Rc::from("hello world"),
1752 },
1753};
1754
1755const CASE_ARC_SLICE: CaseDescriptor<ArcSliceWrapper> = CaseDescriptor {
1756 id: "pointer::arc_slice",
1757 description: "Arc<[T]> unsized slice smart pointer",
1758 expected: || ArcSliceWrapper {
1759 inner: std::sync::Arc::from([1i32, 2, 3, 4]),
1760 },
1761};
1762
1763const CASE_SET_BTREE: CaseDescriptor<SetWrapper> = CaseDescriptor {
1766 id: "collection::set",
1767 description: "BTreeSet<String>",
1768 expected: || {
1769 let mut set = std::collections::BTreeSet::new();
1770 set.insert("alpha".into());
1771 set.insert("beta".into());
1772 set.insert("gamma".into());
1773 SetWrapper { items: set }
1774 },
1775};
1776
1777const CASE_SCALAR_INTEGERS_16: CaseDescriptor<IntegerTypes16> = CaseDescriptor {
1780 id: "scalar::integers_16",
1781 description: "16-bit integer types (i16, u16)",
1782 expected: || IntegerTypes16 {
1783 signed_16: -32768,
1784 unsigned_16: 65535,
1785 },
1786};
1787
1788const CASE_SCALAR_INTEGERS_128: CaseDescriptor<IntegerTypes128> = CaseDescriptor {
1789 id: "scalar::integers_128",
1790 description: "128-bit integer types (i128, u128)",
1791 expected: || IntegerTypes128 {
1792 signed_128: -170_141_183_460_469_231_731_687_303_715_884_105_728,
1793 unsigned_128: 340_282_366_920_938_463_463_374_607_431_768_211_455,
1794 },
1795};
1796
1797const CASE_SCALAR_INTEGERS_SIZE: CaseDescriptor<IntegerTypesSize> = CaseDescriptor {
1798 id: "scalar::integers_size",
1799 description: "pointer-sized integer types (isize, usize)",
1800 expected: || IntegerTypesSize {
1801 signed_size: -1000,
1802 unsigned_size: 2000,
1803 },
1804};
1805
1806const CASE_NONZERO_INTEGERS: CaseDescriptor<NonZeroTypes> = CaseDescriptor {
1809 id: "scalar::nonzero",
1810 description: "NonZero integer types",
1811 expected: || NonZeroTypes {
1812 nz_u32: std::num::NonZeroU32::new(42).unwrap(),
1813 nz_i64: std::num::NonZeroI64::new(-100).unwrap(),
1814 },
1815};
1816
1817const CASE_NONZERO_INTEGERS_EXTENDED: CaseDescriptor<NonZeroTypesExtended> = CaseDescriptor {
1818 id: "scalar::nonzero_extended",
1819 description: "Extended NonZero integer types (8, 16, 128, size)",
1820 expected: || NonZeroTypesExtended {
1821 nz_u8: std::num::NonZeroU8::new(255).unwrap(),
1822 nz_i8: std::num::NonZeroI8::new(-128).unwrap(),
1823 nz_u16: std::num::NonZeroU16::new(65535).unwrap(),
1824 nz_i16: std::num::NonZeroI16::new(-32768).unwrap(),
1825 nz_u128: std::num::NonZeroU128::new(1).unwrap(),
1826 nz_i128: std::num::NonZeroI128::new(-1).unwrap(),
1827 nz_usize: std::num::NonZeroUsize::new(1000).unwrap(),
1828 nz_isize: std::num::NonZeroIsize::new(-500).unwrap(),
1829 },
1830};
1831
1832const CASE_COW_STR: CaseDescriptor<CowStrWrapper> = CaseDescriptor {
1835 id: "string::cow_str",
1836 description: "Cow<'static, str> string fields",
1837 expected: || CowStrWrapper {
1838 owned: std::borrow::Cow::Owned("hello world".to_string()),
1839 message: std::borrow::Cow::Borrowed("borrowed"),
1840 },
1841};
1842
1843const CASE_NEWTYPE_U64: CaseDescriptor<NewtypeU64Wrapper> = CaseDescriptor {
1846 id: "newtype::u64",
1847 description: "newtype wrapper around u64",
1848 expected: || NewtypeU64Wrapper {
1849 value: NewtypeU64(42),
1850 },
1851};
1852
1853const CASE_NEWTYPE_STRING: CaseDescriptor<NewtypeStringWrapper> = CaseDescriptor {
1854 id: "newtype::string",
1855 description: "newtype wrapper around String",
1856 expected: || NewtypeStringWrapper {
1857 value: NewtypeString("hello".into()),
1858 },
1859};
1860
1861const CASE_CHAR_SCALAR: CaseDescriptor<CharWrapper> = CaseDescriptor {
1864 id: "scalar::char",
1865 description: "char scalar type",
1866 expected: || CharWrapper {
1867 letter: 'A',
1868 emoji: '🦀',
1869 },
1870};
1871
1872const CASE_HASHSET: CaseDescriptor<HashSetWrapper> = CaseDescriptor {
1875 id: "collection::hashset",
1876 description: "HashSet<String>",
1877 expected: || {
1878 let mut set = std::collections::HashSet::new();
1879 set.insert("alpha".into());
1880 set.insert("beta".into());
1881 HashSetWrapper { items: set }
1882 },
1883};
1884
1885const CASE_VEC_NESTED: CaseDescriptor<NestedVecWrapper> = CaseDescriptor {
1888 id: "collection::vec_nested",
1889 description: "nested Vec<Vec<i32>>",
1890 expected: || NestedVecWrapper {
1891 matrix: vec![vec![1, 2], vec![3, 4, 5]],
1892 },
1893};
1894
1895const CASE_BYTES_VEC_U8: CaseDescriptor<BytesWrapper> = CaseDescriptor {
1898 id: "slice::bytes::vec_u8",
1899 description: "Vec<u8> binary data as array of numbers",
1900 expected: || BytesWrapper {
1901 data: vec![0, 128, 255, 42],
1902 },
1903};
1904
1905const CASE_ARRAY_FIXED_SIZE: CaseDescriptor<ArrayWrapper> = CaseDescriptor {
1908 id: "array::fixed_size",
1909 description: "[T; N] fixed-size array",
1910 expected: || ArrayWrapper { values: [1, 2, 3] },
1911};
1912
1913const CASE_SKIP_UNKNOWN_FIELDS: CaseDescriptor<SkipUnknownStruct> = CaseDescriptor {
1916 id: "behavior::skip_unknown_fields",
1917 description: "unknown fields are silently skipped by default",
1918 expected: || SkipUnknownStruct {
1919 known: "value".into(),
1920 },
1921};
1922
1923const CASE_STRING_ESCAPES: CaseDescriptor<StringEscapes> = CaseDescriptor {
1926 id: "string::escapes",
1927 description: "string with escape sequences (\\n, \\t, \\\", \\\\)",
1928 expected: || StringEscapes {
1929 text: "line1\nline2\ttab\"quote\\backslash".into(),
1930 },
1931};
1932
1933const CASE_STRING_ESCAPES_EXTENDED: CaseDescriptor<StringEscapesExtended> = CaseDescriptor {
1934 id: "string::escapes_extended",
1935 description: "string with extended escape sequences (\\b, \\f, \\r, \\u0001)",
1936 expected: || StringEscapesExtended {
1937 backspace: "hello\x08world".into(),
1938 formfeed: "page\x0Cbreak".into(),
1939 carriage_return: "line\rreturn".into(),
1940 control_char: "\x01".into(),
1941 },
1942};
1943
1944const CASE_UNIT_STRUCT: CaseDescriptor<UnitStruct> = CaseDescriptor {
1947 id: "unit::struct",
1948 description: "unit struct (zero-sized type)",
1949 expected: || UnitStruct,
1950};
1951
1952#[derive(Facet, Debug, Clone, PartialEq)]
1954pub struct StructSingleField {
1955 pub name: String,
1956}
1957
1958#[derive(Facet, Debug, Clone, PartialEq)]
1960#[facet(untagged)]
1961#[repr(u8)]
1962pub enum MixedScalar {
1963 Signed(i64),
1964 Float(f64),
1965 Bool(bool),
1966 Null,
1967}
1968
1969#[derive(Facet, Debug, Clone, PartialEq)]
1970pub struct NestedParent {
1971 pub id: u64,
1972 pub child: NestedChild,
1973 pub tags: Vec<String>,
1974}
1975
1976#[derive(Facet, Debug, Clone, PartialEq)]
1977pub struct NestedChild {
1978 pub code: String,
1979 pub active: bool,
1980}
1981
1982#[derive(Facet, Debug, Clone, PartialEq)]
1983#[repr(u8)]
1984pub enum ComplexEnum {
1985 Empty,
1986 Count(u64),
1987 Label { name: String, level: u8 },
1988}
1989
1990#[derive(Facet, Debug, Clone, PartialEq)]
1994pub struct RenamedField {
1995 #[facet(rename = "userName")]
1996 pub user_name: String,
1997 pub age: u32,
1998}
1999
2000#[derive(Facet, Debug, Clone, PartialEq)]
2002#[facet(rename_all = "camelCase")]
2003pub struct CamelCaseStruct {
2004 pub first_name: String,
2005 pub last_name: String,
2006 pub is_active: bool,
2007}
2008
2009#[derive(Facet, Debug, Clone, PartialEq)]
2011pub struct WithDefault {
2012 pub required: String,
2013 #[facet(default)]
2014 pub optional_count: u32,
2015}
2016
2017#[derive(Facet, Default, Debug, Clone, PartialEq)]
2019#[facet(default)]
2020pub struct StructLevelDefault {
2021 pub count: i32,
2022 pub message: String,
2023}
2024
2025pub fn custom_default_value() -> i32 {
2027 42
2028}
2029
2030#[derive(Facet, Debug, Clone, PartialEq)]
2032pub struct WithDefaultFunction {
2033 #[facet(default = custom_default_value())]
2034 pub magic_number: i32,
2035 pub name: String,
2036}
2037
2038#[derive(Facet, Debug, Clone, PartialEq)]
2040pub struct WithOption {
2041 pub name: String,
2042 pub nickname: Option<String>,
2043}
2044
2045#[derive(Facet, Debug, Clone, PartialEq)]
2047pub struct WithSkipSerializing {
2048 pub visible: String,
2049 #[facet(skip_serializing)]
2050 #[facet(default)]
2051 pub hidden: String,
2052}
2053
2054#[derive(Facet, Debug, Clone, PartialEq)]
2056pub struct WithSkipSerializingIf {
2057 pub name: String,
2058 #[facet(skip_serializing_if = Option::is_none)]
2059 pub optional_data: Option<String>,
2060}
2061
2062#[derive(Facet, Debug, Clone, PartialEq)]
2064pub struct WithSkip {
2065 pub visible: String,
2066 #[facet(skip)]
2067 #[facet(default)]
2068 pub internal: u32,
2069}
2070
2071#[derive(Facet, Debug, Clone, PartialEq)]
2075#[facet(tag = "type")]
2076#[repr(u8)]
2077pub enum InternallyTagged {
2078 Circle { radius: f64 },
2079 Rectangle { width: f64, height: f64 },
2080}
2081
2082#[derive(Facet, Debug, Clone, PartialEq)]
2084#[facet(tag = "t", content = "c")]
2085#[repr(u8)]
2086pub enum AdjacentlyTagged {
2087 Message(String),
2088 Count(u64),
2089}
2090
2091#[derive(Facet, Debug, Clone, PartialEq)]
2095pub struct FlattenInner {
2096 pub x: i32,
2097 pub y: i32,
2098}
2099
2100#[derive(Facet, Debug, Clone, PartialEq)]
2102pub struct FlattenOuter {
2103 pub name: String,
2104 #[facet(flatten)]
2105 pub coords: FlattenInner,
2106}
2107
2108#[derive(Facet, Debug, Clone, PartialEq)]
2110#[facet(transparent)]
2111pub struct UserId(pub u64);
2112
2113#[derive(Facet, Debug, Clone, PartialEq)]
2115pub struct UserRecord {
2116 pub id: UserId,
2117 pub name: String,
2118}
2119
2120#[derive(Facet, Debug, Clone, PartialEq)]
2124pub struct FlattenOptionalSome {
2125 pub name: String,
2126 #[facet(flatten)]
2127 pub metadata: Option<FlattenMetadata>,
2128}
2129
2130#[derive(Facet, Debug, Clone, PartialEq)]
2132pub struct FlattenOptionalNone {
2133 pub name: String,
2134 #[facet(flatten)]
2135 pub metadata: Option<FlattenMetadata>,
2136}
2137
2138#[derive(Facet, Debug, Clone, PartialEq)]
2140pub struct FlattenMetadata {
2141 pub version: i32,
2142 pub author: String,
2143}
2144
2145#[derive(Facet, Debug, Clone, PartialEq)]
2147pub struct FlattenPartA {
2148 pub field_a: String,
2149 pub shared: i32,
2150}
2151
2152#[derive(Facet, Debug, Clone, PartialEq)]
2154pub struct FlattenPartB {
2155 pub field_b: String,
2156 pub shared: i32,
2157}
2158
2159#[derive(Facet, Debug, Clone, PartialEq)]
2161pub struct FlattenOverlapping {
2162 #[facet(flatten)]
2163 pub part_a: FlattenPartA,
2164 #[facet(flatten)]
2165 pub part_b: FlattenPartB,
2166}
2167
2168#[derive(Facet, Debug, Clone, PartialEq)]
2170pub struct FlattenLevel3 {
2171 pub deep_field: i32,
2172}
2173
2174#[derive(Facet, Debug, Clone, PartialEq)]
2176pub struct FlattenLevel2 {
2177 pub mid_field: i32,
2178 #[facet(flatten)]
2179 pub level3: FlattenLevel3,
2180}
2181
2182#[derive(Facet, Debug, Clone, PartialEq)]
2184pub struct FlattenLevel1 {
2185 pub top_field: String,
2186 #[facet(flatten)]
2187 pub level2: FlattenLevel2,
2188}
2189
2190#[derive(Facet, Debug, Clone, PartialEq)]
2192pub struct FlattenAuthPassword {
2193 pub password: String,
2194}
2195
2196#[derive(Facet, Debug, Clone, PartialEq)]
2198pub struct FlattenAuthToken {
2199 pub token: String,
2200}
2201
2202#[allow(dead_code)]
2204#[derive(Facet, Debug, Clone, PartialEq)]
2205#[repr(u8)]
2206pub enum FlattenAuthMethod {
2207 Password(FlattenAuthPassword),
2208 Token(FlattenAuthToken),
2209}
2210
2211#[derive(Facet, Debug, Clone, PartialEq)]
2213pub struct FlattenTransportTcp {
2214 pub port: u16,
2215}
2216
2217#[derive(Facet, Debug, Clone, PartialEq)]
2219pub struct FlattenTransportUnix {
2220 pub socket: String,
2221}
2222
2223#[allow(dead_code)]
2225#[derive(Facet, Debug, Clone, PartialEq)]
2226#[repr(u8)]
2227pub enum FlattenTransport {
2228 Tcp(FlattenTransportTcp),
2229 Unix(FlattenTransportUnix),
2230}
2231
2232#[derive(Facet, Debug, Clone, PartialEq)]
2234pub struct FlattenMultipleEnums {
2235 pub name: String,
2236 #[facet(flatten)]
2237 pub auth: FlattenAuthMethod,
2238 #[facet(flatten)]
2239 pub transport: FlattenTransport,
2240}
2241
2242#[derive(Facet, Debug, Clone, PartialEq)]
2246#[facet(deny_unknown_fields)]
2247pub struct DenyUnknownStruct {
2248 pub foo: String,
2249 pub bar: i32,
2250}
2251
2252#[derive(Facet, Debug, Clone, PartialEq)]
2254pub struct ExpectsInteger {
2255 pub value: i32,
2256}
2257
2258#[derive(Facet, Debug, Clone, PartialEq)]
2260pub struct ExpectsArray {
2261 pub items: Vec<i32>,
2262}
2263
2264#[derive(Facet, Debug, Clone, PartialEq)]
2266pub struct RequiresAllFields {
2267 pub name: String,
2268 pub age: u32,
2269 pub email: String,
2270}
2271
2272#[derive(Facet, Debug, Clone, PartialEq)]
2274pub struct WithAlias {
2275 #[facet(alias = "old_name")]
2276 pub new_name: String,
2277 pub count: u32,
2278}
2279
2280#[derive(Facet, Debug, Clone, PartialEq)]
2284pub struct RenameVsAlias {
2285 #[facet(rename = "officialName")]
2286 #[facet(alias = "oldName")]
2287 pub field: String,
2288 pub id: u32,
2289}
2290
2291#[derive(Facet, Debug, Clone, PartialEq)]
2293#[facet(rename_all = "kebab-case")]
2294pub struct RenameAllKebab {
2295 pub first_name: String,
2296 pub last_name: String,
2297 pub user_id: u32,
2298}
2299
2300#[derive(Facet, Debug, Clone, PartialEq)]
2302#[facet(rename_all = "SCREAMING_SNAKE_CASE")]
2303pub struct RenameAllScreaming {
2304 pub api_key: String,
2305 pub max_retry_count: u32,
2306}
2307
2308#[derive(Facet, Clone, Debug, PartialEq)]
2310pub struct RenameUnicode {
2311 #[facet(rename = "🎉")]
2312 pub celebration: String,
2313}
2314
2315#[derive(Facet, Clone, Debug, PartialEq)]
2317pub struct RenameSpecialChars {
2318 #[facet(rename = "@type")]
2319 pub type_field: String,
2320}
2321
2322#[derive(Facet, Clone, Debug)]
2326#[facet(transparent)]
2327pub struct IntAsString(pub String);
2328
2329#[derive(Facet, Debug, Clone, PartialEq)]
2331#[facet(proxy = IntAsString)]
2332pub struct ProxyInt {
2333 pub value: i32,
2334}
2335
2336impl TryFrom<IntAsString> for ProxyInt {
2338 type Error = std::num::ParseIntError;
2339 fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2340 Ok(ProxyInt {
2341 value: proxy.0.parse()?,
2342 })
2343 }
2344}
2345
2346impl From<&ProxyInt> for IntAsString {
2348 fn from(v: &ProxyInt) -> Self {
2349 IntAsString(v.value.to_string())
2350 }
2351}
2352
2353#[derive(Facet, Debug, Clone, PartialEq)]
2355pub struct ProxyFieldLevel {
2356 pub name: String,
2357 #[facet(proxy = IntAsString)]
2358 pub count: i32,
2359}
2360
2361impl TryFrom<IntAsString> for i32 {
2363 type Error = std::num::ParseIntError;
2364 fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2365 proxy.0.parse()
2366 }
2367}
2368
2369impl From<&i32> for IntAsString {
2371 fn from(value: &i32) -> Self {
2372 IntAsString(value.to_string())
2373 }
2374}
2375
2376#[derive(Facet, Debug, Clone, PartialEq)]
2378pub struct ProxyWithOption {
2379 pub name: String,
2380 #[facet(proxy = IntAsString)]
2381 pub count: Option<i32>,
2382}
2383
2384impl TryFrom<IntAsString> for Option<i32> {
2386 type Error = std::num::ParseIntError;
2387 fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2388 if proxy.0.is_empty() {
2389 Ok(None)
2390 } else {
2391 Ok(Some(proxy.0.parse()?))
2392 }
2393 }
2394}
2395
2396impl From<&Option<i32>> for IntAsString {
2398 fn from(value: &Option<i32>) -> Self {
2399 match value {
2400 Some(v) => IntAsString(v.to_string()),
2401 None => IntAsString(String::new()),
2402 }
2403 }
2404}
2405
2406#[derive(Facet, Debug, Clone, PartialEq)]
2408#[repr(u8)]
2409pub enum ProxyEnum {
2410 None,
2411 #[facet(proxy = IntAsString)]
2412 Value(i32),
2413}
2414
2415#[derive(Facet, Debug, Clone, PartialEq)]
2417#[facet(transparent, proxy = IntAsString)]
2418pub struct TransparentProxy(pub i32);
2419
2420impl TryFrom<IntAsString> for TransparentProxy {
2422 type Error = std::num::ParseIntError;
2423 fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2424 Ok(TransparentProxy(proxy.0.parse()?))
2425 }
2426}
2427
2428impl From<&TransparentProxy> for IntAsString {
2430 fn from(value: &TransparentProxy) -> Self {
2431 IntAsString(value.0.to_string())
2432 }
2433}
2434
2435#[derive(Debug, Clone, PartialEq)]
2440pub struct OpaqueType {
2441 pub inner: u64,
2442}
2443
2444#[derive(Facet, Clone, Debug)]
2446pub struct OpaqueTypeProxy {
2447 pub inner: u64,
2448}
2449
2450impl From<OpaqueTypeProxy> for OpaqueType {
2452 fn from(proxy: OpaqueTypeProxy) -> Self {
2453 OpaqueType { inner: proxy.inner }
2454 }
2455}
2456
2457impl From<&OpaqueType> for OpaqueTypeProxy {
2459 fn from(v: &OpaqueType) -> Self {
2460 OpaqueTypeProxy { inner: v.inner }
2461 }
2462}
2463
2464#[derive(Facet, Debug, Clone, PartialEq)]
2466pub struct OpaqueProxyWrapper {
2467 #[facet(opaque, proxy = OpaqueTypeProxy)]
2468 pub value: OpaqueType,
2469}
2470
2471impl From<OpaqueTypeProxy> for Option<OpaqueType> {
2473 fn from(proxy: OpaqueTypeProxy) -> Self {
2474 Some(OpaqueType { inner: proxy.inner })
2475 }
2476}
2477
2478impl From<&Option<OpaqueType>> for OpaqueTypeProxy {
2480 fn from(v: &Option<OpaqueType>) -> Self {
2481 match v {
2482 Some(ot) => OpaqueTypeProxy { inner: ot.inner },
2483 None => OpaqueTypeProxy { inner: 0 },
2484 }
2485 }
2486}
2487
2488#[derive(Facet, Debug, Clone, PartialEq)]
2490pub struct OpaqueProxyOptionWrapper {
2491 #[facet(opaque, proxy = OpaqueTypeProxy)]
2492 pub value: Option<OpaqueType>,
2493}
2494
2495#[derive(Facet, Debug, Clone, PartialEq)]
2499#[facet(transparent)]
2500pub struct InnerTransparent(pub i32);
2501
2502#[derive(Facet, Debug, Clone, PartialEq)]
2504#[facet(transparent)]
2505pub struct OuterTransparent(pub InnerTransparent);
2506
2507#[derive(Facet, Debug, Clone, PartialEq)]
2509#[facet(transparent)]
2510pub struct TransparentOption(pub Option<i32>);
2511
2512#[derive(Facet, Debug, Clone, PartialEq)]
2514#[facet(transparent)]
2515pub struct TransparentNonZero(pub std::num::NonZeroU32);
2516
2517#[derive(Facet, Debug, Clone, PartialEq)]
2521pub struct BoolWrapper {
2522 pub yes: bool,
2523 pub no: bool,
2524}
2525
2526#[derive(Facet, Debug, Clone, PartialEq)]
2528pub struct IntegerTypes {
2529 pub signed_8: i8,
2530 pub unsigned_8: u8,
2531 pub signed_32: i32,
2532 pub unsigned_32: u32,
2533 pub signed_64: i64,
2534 pub unsigned_64: u64,
2535}
2536
2537#[derive(Facet, Debug, Clone, PartialEq)]
2539pub struct FloatTypes {
2540 pub float_32: f32,
2541 pub float_64: f64,
2542}
2543
2544#[derive(Facet, Debug, Clone, PartialEq)]
2546pub struct FloatTypesScientific {
2547 pub large: f64,
2548 pub small: f64,
2549 pub positive_exp: f64,
2550}
2551
2552#[derive(Facet, Debug, Clone, PartialEq)]
2556pub struct MapWrapper {
2557 pub data: std::collections::BTreeMap<String, i32>,
2558}
2559
2560#[derive(Facet, Debug, Clone, PartialEq)]
2562pub struct TupleWrapper {
2563 pub triple: (String, i32, bool),
2564}
2565
2566#[derive(Facet, Debug, Clone, PartialEq)]
2568pub struct NestedTupleWrapper {
2569 pub outer: ((i32, i32), (String, bool)),
2570}
2571
2572#[derive(Facet, Debug, Clone, PartialEq)]
2574pub struct EmptyTupleWrapper {
2575 pub name: String,
2576 pub empty: (),
2577}
2578
2579#[derive(Facet, Debug, Clone, PartialEq)]
2581pub struct SingleElementTupleWrapper {
2582 pub name: String,
2583 pub single: (i32,),
2584}
2585
2586#[derive(Facet, Debug, Clone, PartialEq)]
2590#[repr(u8)]
2591pub enum UnitVariantEnum {
2592 Active,
2593 Inactive,
2594 Pending,
2595}
2596
2597#[derive(Facet, Debug, Clone, PartialEq)]
2599#[repr(u8)]
2600pub enum EnumVariantRename {
2601 #[facet(rename = "enabled")]
2602 Active,
2603 #[facet(rename = "disabled")]
2604 Inactive,
2605}
2606
2607#[derive(Facet, Debug, Clone, PartialEq)]
2609#[facet(untagged)]
2610#[repr(u8)]
2611pub enum UntaggedEnum {
2612 Point { x: i32, y: i32 },
2613 Value(i64),
2614}
2615
2616#[derive(Facet, Debug, Clone, PartialEq)]
2618#[facet(untagged)]
2619#[repr(u8)]
2620pub enum UntaggedWithNull {
2621 None,
2622 Some(i32),
2623}
2624
2625#[derive(Facet, Debug, Clone, PartialEq)]
2627#[facet(untagged)]
2628#[repr(u8)]
2629pub enum UntaggedNewtype {
2630 String(String),
2631 Number(u64),
2632 Bool(bool),
2633}
2634
2635#[derive(Facet, Debug, Clone, PartialEq)]
2637pub struct UntaggedAsField {
2638 pub name: String,
2639 pub value: UntaggedNewtype,
2640}
2641
2642#[derive(Facet, Debug, Clone, PartialEq)]
2645#[facet(untagged)]
2646#[repr(u8)]
2647pub enum UntaggedUnitOnly {
2648 Alpha,
2649 Beta,
2650 Gamma,
2651}
2652
2653#[derive(Facet, Debug, Clone, PartialEq)]
2655#[repr(u8)]
2656pub enum TupleVariantEnum {
2657 Empty,
2658 Pair(String, i32),
2659 Triple(bool, f64, String),
2660}
2661
2662#[derive(Facet, Debug, Clone, PartialEq)]
2664#[repr(u8)]
2665pub enum NewtypeVariantEnum {
2666 None,
2667 Some(i32),
2668}
2669
2670#[derive(Facet, Debug, Clone, PartialEq)]
2674pub struct BoxWrapper {
2675 pub inner: Box<i32>,
2676}
2677
2678#[derive(Facet, Debug, Clone, PartialEq)]
2680pub struct ArcWrapper {
2681 pub inner: std::sync::Arc<i32>,
2682}
2683
2684#[derive(Facet, Debug, Clone, PartialEq)]
2686pub struct RcWrapper {
2687 pub inner: std::rc::Rc<i32>,
2688}
2689
2690#[derive(Facet, Debug, Clone, PartialEq)]
2692pub struct BoxStrWrapper {
2693 pub inner: Box<str>,
2694}
2695
2696#[derive(Facet, Debug, Clone, PartialEq)]
2698pub struct ArcStrWrapper {
2699 pub inner: std::sync::Arc<str>,
2700}
2701
2702#[derive(Facet, Debug, Clone, PartialEq)]
2704pub struct RcStrWrapper {
2705 pub inner: std::rc::Rc<str>,
2706}
2707
2708#[derive(Facet, Debug, Clone, PartialEq)]
2710pub struct ArcSliceWrapper {
2711 pub inner: std::sync::Arc<[i32]>,
2712}
2713
2714#[derive(Facet, Debug, Clone, PartialEq)]
2718pub struct SetWrapper {
2719 pub items: std::collections::BTreeSet<String>,
2720}
2721
2722#[derive(Facet, Debug, Clone, PartialEq)]
2726pub struct IntegerTypes16 {
2727 pub signed_16: i16,
2728 pub unsigned_16: u16,
2729}
2730
2731#[derive(Facet, Debug, Clone, PartialEq)]
2733pub struct IntegerTypes128 {
2734 pub signed_128: i128,
2735 pub unsigned_128: u128,
2736}
2737
2738#[derive(Facet, Debug, Clone, PartialEq)]
2740pub struct IntegerTypesSize {
2741 pub signed_size: isize,
2742 pub unsigned_size: usize,
2743}
2744
2745#[derive(Facet, Debug, Clone, PartialEq)]
2749pub struct NonZeroTypes {
2750 pub nz_u32: std::num::NonZeroU32,
2751 pub nz_i64: std::num::NonZeroI64,
2752}
2753
2754#[derive(Facet, Debug, Clone, PartialEq)]
2756pub struct NonZeroTypesExtended {
2757 pub nz_u8: std::num::NonZeroU8,
2758 pub nz_i8: std::num::NonZeroI8,
2759 pub nz_u16: std::num::NonZeroU16,
2760 pub nz_i16: std::num::NonZeroI16,
2761 pub nz_u128: std::num::NonZeroU128,
2762 pub nz_i128: std::num::NonZeroI128,
2763 pub nz_usize: std::num::NonZeroUsize,
2764 pub nz_isize: std::num::NonZeroIsize,
2765}
2766
2767#[derive(Facet, Debug, Clone, PartialEq)]
2771pub struct CowStrWrapper {
2772 pub owned: std::borrow::Cow<'static, str>,
2773 pub message: std::borrow::Cow<'static, str>,
2774}
2775
2776#[derive(Facet, Debug, Clone, PartialEq)]
2780#[facet(transparent)]
2781pub struct NewtypeU64(pub u64);
2782
2783#[derive(Facet, Debug, Clone, PartialEq)]
2785pub struct NewtypeU64Wrapper {
2786 pub value: NewtypeU64,
2787}
2788
2789#[derive(Facet, Debug, Clone, PartialEq)]
2791#[facet(transparent)]
2792pub struct NewtypeString(pub String);
2793
2794#[derive(Facet, Debug, Clone, PartialEq)]
2796pub struct NewtypeStringWrapper {
2797 pub value: NewtypeString,
2798}
2799
2800#[derive(Facet, Debug, Clone, PartialEq)]
2804pub struct CharWrapper {
2805 pub letter: char,
2806 pub emoji: char,
2807}
2808
2809#[derive(Facet, Debug, Clone, PartialEq)]
2813pub struct HashSetWrapper {
2814 pub items: std::collections::HashSet<String>,
2815}
2816
2817#[derive(Facet, Debug, Clone, PartialEq)]
2821pub struct NestedVecWrapper {
2822 pub matrix: Vec<Vec<i32>>,
2823}
2824
2825#[derive(Facet, Debug, Clone, PartialEq)]
2829pub struct BytesWrapper {
2830 pub data: Vec<u8>,
2831}
2832
2833#[derive(Facet, Debug, Clone, PartialEq)]
2837pub struct ArrayWrapper {
2838 pub values: [u64; 3],
2839}
2840
2841#[derive(Facet, Debug, Clone, PartialEq)]
2845pub struct SkipUnknownStruct {
2846 pub known: String,
2847}
2848
2849#[derive(Facet, Debug, Clone, PartialEq)]
2853pub struct StringEscapes {
2854 pub text: String,
2855}
2856
2857#[derive(Facet, Debug, Clone, PartialEq)]
2859pub struct StringEscapesExtended {
2860 pub backspace: String,
2861 pub formfeed: String,
2862 pub carriage_return: String,
2863 pub control_char: String,
2864}
2865
2866#[derive(Facet, Debug, Clone, PartialEq)]
2870pub struct UnitStruct;
2871
2872#[cfg(feature = "uuid")]
2875const CASE_UUID: CaseDescriptor<UuidWrapper> = CaseDescriptor {
2876 id: "third_party::uuid",
2877 description: "uuid::Uuid type",
2878 expected: || UuidWrapper {
2879 id: uuid::Uuid::from_bytes([
2880 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
2881 0x00, 0x00,
2882 ]),
2883 },
2884};
2885
2886#[cfg(feature = "ulid")]
2887const CASE_ULID: CaseDescriptor<UlidWrapper> = CaseDescriptor {
2888 id: "third_party::ulid",
2889 description: "ulid::Ulid type",
2890 expected: || UlidWrapper {
2891 id: ulid::Ulid::from_string("01ARZ3NDEKTSV4RRFFQ69G5FAV").unwrap(),
2892 },
2893};
2894
2895#[cfg(feature = "camino")]
2896const CASE_CAMINO_PATH: CaseDescriptor<CaminoWrapper> = CaseDescriptor {
2897 id: "third_party::camino",
2898 description: "camino::Utf8PathBuf type",
2899 expected: || CaminoWrapper {
2900 path: camino::Utf8PathBuf::from("/home/user/documents"),
2901 },
2902};
2903
2904#[cfg(feature = "ordered-float")]
2905const CASE_ORDERED_FLOAT: CaseDescriptor<OrderedFloatWrapper> = CaseDescriptor {
2906 id: "third_party::ordered_float",
2907 description: "ordered_float::OrderedFloat type",
2908 expected: || OrderedFloatWrapper {
2909 value: ordered_float::OrderedFloat(1.23456),
2910 },
2911};
2912
2913#[cfg(feature = "time")]
2914const CASE_TIME_OFFSET_DATETIME: CaseDescriptor<TimeOffsetDateTimeWrapper> = CaseDescriptor {
2915 id: "third_party::time_offset_datetime",
2916 description: "time::OffsetDateTime type",
2917 expected: || TimeOffsetDateTimeWrapper {
2918 created_at: time::macros::datetime!(2023-01-15 12:34:56 UTC),
2919 },
2920};
2921
2922#[cfg(feature = "jiff02")]
2923const CASE_JIFF_TIMESTAMP: CaseDescriptor<JiffTimestampWrapper> = CaseDescriptor {
2924 id: "third_party::jiff_timestamp",
2925 description: "jiff::Timestamp type",
2926 expected: || JiffTimestampWrapper {
2927 created_at: "2023-12-31T11:30:00Z".parse().unwrap(),
2928 },
2929};
2930
2931#[cfg(feature = "jiff02")]
2932const CASE_JIFF_CIVIL_DATETIME: CaseDescriptor<JiffCivilDateTimeWrapper> = CaseDescriptor {
2933 id: "third_party::jiff_civil_datetime",
2934 description: "jiff::civil::DateTime type",
2935 expected: || JiffCivilDateTimeWrapper {
2936 created_at: "2024-06-19T15:22:45".parse().unwrap(),
2937 },
2938};
2939
2940#[cfg(feature = "chrono")]
2941const CASE_CHRONO_DATETIME_UTC: CaseDescriptor<ChronoDateTimeUtcWrapper> = CaseDescriptor {
2942 id: "third_party::chrono_datetime_utc",
2943 description: "chrono::DateTime<Utc> type",
2944 expected: || ChronoDateTimeUtcWrapper {
2945 created_at: chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2023, 1, 15, 12, 34, 56)
2946 .unwrap(),
2947 },
2948};
2949
2950#[cfg(feature = "chrono")]
2951const CASE_CHRONO_NAIVE_DATETIME: CaseDescriptor<ChronoNaiveDateTimeWrapper> = CaseDescriptor {
2952 id: "third_party::chrono_naive_datetime",
2953 description: "chrono::NaiveDateTime type",
2954 expected: || ChronoNaiveDateTimeWrapper {
2955 created_at: chrono::NaiveDate::from_ymd_opt(2023, 1, 15)
2956 .unwrap()
2957 .and_hms_opt(12, 34, 56)
2958 .unwrap(),
2959 },
2960};
2961
2962#[cfg(feature = "chrono")]
2963const CASE_CHRONO_NAIVE_DATE: CaseDescriptor<ChronoNaiveDateWrapper> = CaseDescriptor {
2964 id: "third_party::chrono_naive_date",
2965 description: "chrono::NaiveDate type",
2966 expected: || ChronoNaiveDateWrapper {
2967 birth_date: chrono::NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(),
2968 },
2969};
2970
2971#[cfg(feature = "chrono")]
2972const CASE_CHRONO_NAIVE_TIME: CaseDescriptor<ChronoNaiveTimeWrapper> = CaseDescriptor {
2973 id: "third_party::chrono_naive_time",
2974 description: "chrono::NaiveTime type",
2975 expected: || ChronoNaiveTimeWrapper {
2976 alarm_time: chrono::NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
2977 },
2978};
2979
2980#[cfg(feature = "chrono")]
2981const CASE_CHRONO_IN_VEC: CaseDescriptor<ChronoInVecWrapper> = CaseDescriptor {
2982 id: "third_party::chrono_in_vec",
2983 description: "Vec<chrono::DateTime<Utc>> - chrono in collections",
2984 expected: || ChronoInVecWrapper {
2985 timestamps: vec![
2986 chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2023, 1, 1, 0, 0, 0).unwrap(),
2987 chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2023, 6, 15, 12, 30, 0).unwrap(),
2988 ],
2989 },
2990};
2991
2992#[cfg(feature = "bytes")]
2995const CASE_BYTES_BYTES: CaseDescriptor<BytesBytesWrapper> = CaseDescriptor {
2996 id: "third_party::bytes_bytes",
2997 description: "bytes::Bytes type",
2998 expected: || BytesBytesWrapper {
2999 data: bytes::Bytes::from_static(&[1, 2, 3, 4, 255]),
3000 },
3001};
3002
3003#[cfg(feature = "bytes")]
3004const CASE_BYTES_BYTES_MUT: CaseDescriptor<BytesBytesMutWrapper> = CaseDescriptor {
3005 id: "third_party::bytes_bytes_mut",
3006 description: "bytes::BytesMut type",
3007 expected: || BytesBytesMutWrapper {
3008 data: bytes::BytesMut::from(&[1, 2, 3, 4, 255][..]),
3009 },
3010};
3011
3012#[cfg(feature = "facet-value")]
3015const CASE_VALUE_NULL: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3016 id: "value::null",
3017 description: "facet_value::Value - null",
3018 expected: || facet_value::Value::NULL,
3019};
3020
3021#[cfg(feature = "facet-value")]
3022const CASE_VALUE_BOOL: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3023 id: "value::bool",
3024 description: "facet_value::Value - bool",
3025 expected: || facet_value::Value::TRUE,
3026};
3027
3028#[cfg(feature = "facet-value")]
3029const CASE_VALUE_INTEGER: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3030 id: "value::integer",
3031 description: "facet_value::Value - integer",
3032 expected: || facet_value::Value::from(42i64),
3033};
3034
3035#[cfg(feature = "facet-value")]
3036const CASE_VALUE_FLOAT: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3037 id: "value::float",
3038 description: "facet_value::Value - float",
3039 expected: || facet_value::Value::from(2.5f64),
3040};
3041
3042#[cfg(feature = "facet-value")]
3043const CASE_VALUE_STRING: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3044 id: "value::string",
3045 description: "facet_value::Value - string",
3046 expected: || facet_value::Value::from("hello world"),
3047};
3048
3049#[cfg(feature = "facet-value")]
3050const CASE_VALUE_ARRAY: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3051 id: "value::array",
3052 description: "facet_value::Value - array",
3053 expected: || {
3054 facet_value::VArray::from_iter([
3055 facet_value::Value::from(1i64),
3056 facet_value::Value::from(2i64),
3057 facet_value::Value::from(3i64),
3058 ])
3059 .into()
3060 },
3061};
3062
3063#[cfg(feature = "facet-value")]
3064const CASE_VALUE_OBJECT: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3065 id: "value::object",
3066 description: "facet_value::Value - object",
3067 expected: || {
3068 let mut map = facet_value::VObject::new();
3069 map.insert("name", facet_value::Value::from("test"));
3070 map.insert("count", facet_value::Value::from(42i64));
3071 map.into()
3072 },
3073};
3074
3075#[cfg(feature = "uuid")]
3079#[derive(Facet, Debug, Clone, PartialEq)]
3080pub struct UuidWrapper {
3081 pub id: uuid::Uuid,
3082}
3083
3084#[cfg(feature = "ulid")]
3086#[derive(Facet, Debug, Clone, PartialEq)]
3087pub struct UlidWrapper {
3088 pub id: ulid::Ulid,
3089}
3090
3091#[cfg(feature = "camino")]
3093#[derive(Facet, Debug, Clone, PartialEq)]
3094pub struct CaminoWrapper {
3095 pub path: camino::Utf8PathBuf,
3096}
3097
3098#[cfg(feature = "ordered-float")]
3100#[derive(Facet, Debug, Clone, PartialEq)]
3101pub struct OrderedFloatWrapper {
3102 pub value: ordered_float::OrderedFloat<f64>,
3103}
3104
3105#[cfg(feature = "time")]
3107#[derive(Facet, Debug, Clone, PartialEq)]
3108pub struct TimeOffsetDateTimeWrapper {
3109 pub created_at: time::OffsetDateTime,
3110}
3111
3112#[cfg(feature = "jiff02")]
3114#[derive(Facet, Debug, Clone, PartialEq)]
3115pub struct JiffTimestampWrapper {
3116 pub created_at: jiff::Timestamp,
3117}
3118
3119#[cfg(feature = "jiff02")]
3121#[derive(Facet, Debug, Clone, PartialEq)]
3122pub struct JiffCivilDateTimeWrapper {
3123 pub created_at: jiff::civil::DateTime,
3124}
3125
3126#[cfg(feature = "chrono")]
3128#[derive(Facet, Debug, Clone, PartialEq)]
3129pub struct ChronoDateTimeUtcWrapper {
3130 pub created_at: chrono::DateTime<chrono::Utc>,
3131}
3132
3133#[cfg(feature = "chrono")]
3135#[derive(Facet, Debug, Clone, PartialEq)]
3136pub struct ChronoNaiveDateTimeWrapper {
3137 pub created_at: chrono::NaiveDateTime,
3138}
3139
3140#[cfg(feature = "chrono")]
3142#[derive(Facet, Debug, Clone, PartialEq)]
3143pub struct ChronoNaiveDateWrapper {
3144 pub birth_date: chrono::NaiveDate,
3145}
3146
3147#[cfg(feature = "chrono")]
3149#[derive(Facet, Debug, Clone, PartialEq)]
3150pub struct ChronoNaiveTimeWrapper {
3151 pub alarm_time: chrono::NaiveTime,
3152}
3153
3154#[cfg(feature = "chrono")]
3156#[derive(Facet, Debug, Clone, PartialEq)]
3157pub struct ChronoInVecWrapper {
3158 pub timestamps: Vec<chrono::DateTime<chrono::Utc>>,
3159}
3160
3161#[cfg(feature = "bytes")]
3165#[derive(Facet, Debug, Clone, PartialEq)]
3166pub struct BytesBytesWrapper {
3167 pub data: bytes::Bytes,
3168}
3169
3170#[cfg(feature = "bytes")]
3172#[derive(Facet, Debug, Clone, PartialEq)]
3173pub struct BytesBytesMutWrapper {
3174 pub data: bytes::BytesMut,
3175}
3176
3177fn emit_case_showcase<S, T>(
3178 desc: &'static CaseDescriptor<T>,
3179 note: Option<&'static str>,
3180 roundtrip_disabled_reason: Option<&'static str>,
3181 input: &'static [u8],
3182 highlight_language: Option<&'static str>,
3183 actual: &T,
3184) where
3185 S: FormatSuite,
3186 for<'facet> T: Facet<'facet>,
3187 T: Debug,
3188{
3189 let (input_label, input_block) = match highlight_language {
3190 Some(language) => match highlight_payload(language, input) {
3191 Some(html) => (format!("Input highlighted via arborium ({language})"), html),
3192 None => (
3193 format!("Input (UTF-8, highlighting unavailable for {language})"),
3194 String::from_utf8_lossy(input).into_owned(),
3195 ),
3196 },
3197 None => (
3198 "Input (UTF-8)".to_string(),
3199 String::from_utf8_lossy(input).into_owned(),
3200 ),
3201 };
3202
3203 let pretty_output = format!(
3204 "{}",
3205 actual.pretty_with(PrettyPrinter::new().with_indent_size(2))
3206 );
3207 let note_line = note.map(|n| format!("note: {n}\n")).unwrap_or_default();
3208 let roundtrip_line = roundtrip_disabled_reason
3209 .map(|r| format!("roundtrip: disabled ({r})\n"))
3210 .unwrap_or_default();
3211
3212 println!(
3213 "{}",
3214 formatdoc!(
3215 "
3216 ── facet-format-suite :: {format_name} :: {case_id} ──
3217 description: {description}
3218 {note_line}{roundtrip_line}{input_label}:
3219 {input_block}
3220
3221 facet-pretty output:
3222 {pretty_output}
3223 ",
3224 format_name = S::format_name(),
3225 case_id = desc.id,
3226 description = desc.description,
3227 note_line = note_line,
3228 roundtrip_line = roundtrip_line,
3229 input_label = input_label,
3230 input_block = input_block,
3231 pretty_output = pretty_output,
3232 )
3233 );
3234}
3235
3236fn emit_error_case_showcase<S: FormatSuite>(
3237 case_id: &str,
3238 description: &str,
3239 note: Option<&'static str>,
3240 input: &[u8],
3241 highlight_language: Option<&'static str>,
3242 error_contains: &str,
3243) {
3244 let (input_label, input_block) = match highlight_language {
3245 Some(language) => match highlight_payload(language, input) {
3246 Some(html) => (format!("Input highlighted via arborium ({language})"), html),
3247 None => (
3248 format!("Input (UTF-8, highlighting unavailable for {language})"),
3249 String::from_utf8_lossy(input).into_owned(),
3250 ),
3251 },
3252 None => (
3253 "Input (UTF-8)".to_string(),
3254 String::from_utf8_lossy(input).into_owned(),
3255 ),
3256 };
3257
3258 let note_line = note.map(|n| format!("note: {n}\n")).unwrap_or_default();
3259
3260 println!(
3261 "{}",
3262 formatdoc!(
3263 "
3264 ── facet-format-suite :: {format_name} :: {case_id} ──
3265 description: {description}
3266 {note_line}expects error containing: \"{error_contains}\"
3267 {input_label}:
3268 {input_block}
3269 ",
3270 format_name = S::format_name(),
3271 case_id = case_id,
3272 description = description,
3273 note_line = note_line,
3274 error_contains = error_contains,
3275 input_label = input_label,
3276 input_block = input_block,
3277 )
3278 );
3279}
3280
3281fn highlight_payload(language: &str, input: &[u8]) -> Option<String> {
3282 let source = core::str::from_utf8(input).ok()?;
3283 let mut highlighter = Highlighter::new();
3284 highlighter.highlight(language, source).ok()
3285}