facet_format_suite/
lib.rs

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
13/// Trait every format variant implements to participate in the suite.
14///
15/// Each method returning a [`CaseSpec`] corresponds to a canonical test case.
16/// When the suite adds a new case, the trait sprouts another required method,
17/// forcing every format crate to acknowledge and implement it.
18///
19/// The [`FormatSuite::deserialize`] hook is intentionally generic over every `T: Facet` – in
20/// the end state it will invoke the shared `FormatDeserializer` to produce a
21/// typed value, not just raw events.
22pub trait FormatSuite {
23    /// Parser/deserializer specific error type.
24    type Error: Debug + Display;
25
26    /// Human-readable name for diagnostics.
27    fn format_name() -> &'static str;
28
29    /// Optional syntax highlighter language name (Arborium).
30    fn highlight_language() -> Option<&'static str> {
31        None
32    }
33
34    /// Attempt to deserialize `input` into the requested Facet type.
35    fn deserialize<T>(input: &[u8]) -> Result<T, Self::Error>
36    where
37        for<'facet> T: Facet<'facet>,
38        T: Debug;
39
40    /// Optional serialization hook used for round-trip testing.
41    ///
42    /// If implemented (returns `Some`), the suite will:
43    /// 1) deserialize the canonical input into `T`
44    /// 2) serialize that value back into the format
45    /// 3) deserialize again into `T`
46    /// 4) `assert_same!` that the round-tripped value matches the first one.
47    ///
48    /// Returning `None` disables round-trip checks for the format.
49    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    /// Optional async deserialization hook for streaming formats.
59    ///
60    /// If implemented (returns `Some`), the suite will run async variants
61    /// of each test case using this method.
62    ///
63    /// Returning `None` indicates the format doesn't support async deserialization.
64    #[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    /// Case: simple object with a single string field.
77    fn struct_single_field() -> CaseSpec;
78    /// Case: homogeneous sequence of unsigned integers.
79    fn sequence_numbers() -> CaseSpec;
80    /// Case: heterogeneous scalar sequence represented as an untagged enum.
81    fn sequence_mixed_scalars() -> CaseSpec;
82    /// Case: nested struct with child object and tags.
83    fn struct_nested() -> CaseSpec;
84    /// Case: enum with multiple variant styles.
85    fn enum_complex() -> CaseSpec;
86
87    // ── Attribute tests ──
88
89    /// Case: field with `#[facet(rename = "...")]` attribute.
90    fn attr_rename_field() -> CaseSpec;
91    /// Case: container with `#[facet(rename_all = "camelCase")]` attribute.
92    fn attr_rename_all_camel() -> CaseSpec;
93    /// Case: field with `#[facet(default)]` attribute.
94    fn attr_default_field() -> CaseSpec;
95    /// Case: struct-level `#[facet(default)]` allowing all fields to be missing.
96    fn attr_default_struct() -> CaseSpec;
97    /// Case: field with `#[facet(default = expr)]` using a custom default expression.
98    fn attr_default_function() -> CaseSpec;
99    /// Case: `Option<T>` field with `None` value (missing in input).
100    fn option_none() -> CaseSpec;
101    /// Case: `Option<T>` field with `Some` value.
102    fn option_some() -> CaseSpec;
103    /// Case: `Option<T>` field with explicit `null` value.
104    fn option_null() -> CaseSpec;
105    /// Case: `#[facet(skip_serializing)]` field.
106    fn attr_skip_serializing() -> CaseSpec;
107    /// Case: `#[facet(skip_serializing_if = predicate)]` field.
108    fn attr_skip_serializing_if() -> CaseSpec;
109    /// Case: `#[facet(skip)]` field (skipped for both ser and de).
110    fn attr_skip() -> CaseSpec;
111
112    // ── Enum tagging tests ──
113
114    /// Case: internally tagged enum `#[facet(tag = "type")]`.
115    fn enum_internally_tagged() -> CaseSpec;
116    /// Case: adjacently tagged enum `#[facet(tag = "t", content = "c")]`.
117    fn enum_adjacently_tagged() -> CaseSpec;
118
119    // ── Advanced tests ──
120
121    /// Case: flattened struct `#[facet(flatten)]`.
122    fn struct_flatten() -> CaseSpec;
123    /// Case: transparent newtype `#[facet(transparent)]`.
124    fn transparent_newtype() -> CaseSpec;
125
126    // ── Flatten variation tests ──
127
128    /// Case: flattened field is `Option<T>` with `Some` value.
129    fn flatten_optional_some() -> CaseSpec;
130    /// Case: flattened field is `Option<T>` with `None` value.
131    fn flatten_optional_none() -> CaseSpec;
132    /// Case: two flattened structs with overlapping field names (error).
133    fn flatten_overlapping_fields_error() -> CaseSpec;
134    /// Case: three levels of nested flatten (A -> B -> C, all flattened).
135    fn flatten_multilevel() -> CaseSpec;
136    /// Case: two different enums flattened into same struct.
137    fn flatten_multiple_enums() -> CaseSpec;
138
139    // ── Error cases ──
140
141    /// Case: `#[facet(deny_unknown_fields)]` rejects unknown fields.
142    fn deny_unknown_fields() -> CaseSpec;
143    /// Case: type mismatch - string provided where integer expected.
144    fn error_type_mismatch_string_to_int() -> CaseSpec;
145    /// Case: structure mismatch - object provided where array expected.
146    fn error_type_mismatch_object_to_array() -> CaseSpec;
147    /// Case: missing required field error.
148    fn error_missing_required_field() -> CaseSpec;
149
150    // ── Alias tests ──
151
152    /// Case: field with `#[facet(alias = "...")]` accepts alternative name.
153    fn attr_alias() -> CaseSpec;
154
155    // ── Attribute precedence tests ──
156
157    /// Case: field with both rename and alias - rename takes precedence.
158    fn attr_rename_vs_alias_precedence() -> CaseSpec;
159    /// Case: struct with `#[facet(rename_all = "kebab-case")]`.
160    fn attr_rename_all_kebab() -> CaseSpec;
161    /// Case: struct with `#[facet(rename_all = "SCREAMING_SNAKE_CASE")]`.
162    fn attr_rename_all_screaming() -> CaseSpec;
163    /// Case: field with unicode (emoji) rename `#[facet(rename = "🎉")]`.
164    fn attr_rename_unicode() -> CaseSpec;
165    /// Case: field with special symbol chars in rename `#[facet(rename = "@type")]`.
166    fn attr_rename_special_chars() -> CaseSpec;
167
168    // ── Proxy tests ──
169
170    /// Case: container-level `#[facet(proxy = ...)]` for custom serialization.
171    fn proxy_container() -> CaseSpec;
172    /// Case: field-level `#[facet(proxy = ...)]` on individual field.
173    fn proxy_field_level() -> CaseSpec;
174    /// Case: proxy conversion error handling (validation failure).
175    fn proxy_validation_error() -> CaseSpec;
176    /// Case: proxy wrapping `Option<T>`.
177    fn proxy_with_option() -> CaseSpec;
178    /// Case: proxy on enum variants.
179    fn proxy_with_enum() -> CaseSpec;
180    /// Case: interaction between proxy and transparent.
181    fn proxy_with_transparent() -> CaseSpec;
182
183    /// Case: `#[facet(opaque, proxy = ...)]` where target type doesn't implement Facet.
184    fn opaque_proxy() -> CaseSpec;
185
186    /// Case: `#[facet(opaque, proxy = ...)]` on `Option<OpaqueType>`.
187    fn opaque_proxy_option() -> CaseSpec;
188
189    // ── Transparent tests ──
190
191    /// Case: transparent wrapping another transparent type (multilevel).
192    fn transparent_multilevel() -> CaseSpec;
193    /// Case: transparent wrapping `Option<T>`.
194    fn transparent_option() -> CaseSpec;
195    /// Case: transparent wrapping NonZero types.
196    fn transparent_nonzero() -> CaseSpec;
197
198    // ── Scalar tests ──
199
200    /// Case: boolean scalar value.
201    fn scalar_bool() -> CaseSpec;
202    /// Case: various integer types.
203    fn scalar_integers() -> CaseSpec;
204    /// Case: floating point types.
205    fn scalar_floats() -> CaseSpec;
206    /// Case: floating point with scientific notation.
207    fn scalar_floats_scientific() -> CaseSpec;
208
209    // ── Collection tests ──
210
211    /// Case: `HashMap<String, T>`.
212    fn map_string_keys() -> CaseSpec;
213    /// Case: tuple types.
214    fn tuple_simple() -> CaseSpec;
215    /// Case: nested tuple types.
216    fn tuple_nested() -> CaseSpec;
217    /// Case: empty tuple `()` as a field.
218    fn tuple_empty() -> CaseSpec;
219    /// Case: single-element tuple `(T,)` as a field.
220    fn tuple_single_element() -> CaseSpec;
221    /// Case: enum with tuple variant `Variant(T, U)`.
222    fn tuple_struct_variant() -> CaseSpec;
223    /// Case: enum with newtype variant `Variant(T)`.
224    fn tuple_newtype_variant() -> CaseSpec;
225
226    // ── Enum variant tests ──
227
228    /// Case: unit enum variant.
229    fn enum_unit_variant() -> CaseSpec;
230    /// Case: numeric enum.
231    fn numeric_enum() -> CaseSpec;
232    /// Case: signed numeric enum.
233    fn signed_numeric_enum() -> CaseSpec;
234    /// Case: numeric enum.
235    fn inferred_numeric_enum() -> CaseSpec;
236    /// Case: untagged enum.
237    fn enum_untagged() -> CaseSpec;
238    /// Case: enum with renamed variants `#[facet(rename = "...")]`.
239    fn enum_variant_rename() -> CaseSpec;
240
241    // ── Untagged enum variation tests ──
242
243    /// Case: untagged enum with unit variant matching null.
244    fn untagged_with_null() -> CaseSpec;
245    /// Case: untagged enum with newtype variants (discrimination test).
246    fn untagged_newtype_variant() -> CaseSpec;
247    /// Case: untagged enum as struct field (nesting test).
248    fn untagged_as_field() -> CaseSpec;
249
250    /// Case: untagged enum with only unit variants (dataless enum).
251    fn untagged_unit_only() -> CaseSpec;
252
253    // ── Smart pointer tests ──
254
255    /// Case: `Box<T>` smart pointer.
256    fn box_wrapper() -> CaseSpec;
257    /// Case: `Arc<T>` smart pointer.
258    fn arc_wrapper() -> CaseSpec;
259    /// Case: `Rc<T>` smart pointer.
260    fn rc_wrapper() -> CaseSpec;
261    /// Case: `Box<str>` unsized smart pointer.
262    fn box_str() -> CaseSpec;
263    /// Case: `Arc<str>` unsized smart pointer.
264    fn arc_str() -> CaseSpec;
265    /// Case: `Rc<str>` unsized smart pointer.
266    fn rc_str() -> CaseSpec;
267    /// Case: `Arc<[T]>` unsized slice smart pointer.
268    fn arc_slice() -> CaseSpec;
269
270    // ── Set tests ──
271
272    /// Case: `BTreeSet<T>`.
273    fn set_btree() -> CaseSpec;
274
275    // ── Extended numeric tests ──
276
277    /// Case: i16, u16 integers.
278    fn scalar_integers_16() -> CaseSpec;
279    /// Case: i128, u128 integers.
280    fn scalar_integers_128() -> CaseSpec;
281    /// Case: isize, usize integers.
282    fn scalar_integers_size() -> CaseSpec;
283
284    // ── NonZero tests ──
285
286    /// Case: NonZero integer types.
287    fn nonzero_integers() -> CaseSpec;
288    /// Case: Extended NonZero integer types (8, 16, 128, size).
289    fn nonzero_integers_extended() -> CaseSpec;
290
291    // ── Borrowed string tests ──
292
293    /// Case: Cow<'static, str> field.
294    fn cow_str() -> CaseSpec;
295
296    // ── Newtype tests ──
297
298    /// Case: newtype wrapper around u64.
299    fn newtype_u64() -> CaseSpec;
300    /// Case: newtype wrapper around String.
301    fn newtype_string() -> CaseSpec;
302
303    // ── Char tests ──
304
305    /// Case: char scalar type.
306    fn char_scalar() -> CaseSpec;
307
308    // ── HashSet tests ──
309
310    /// Case: `HashSet<T>`.
311    fn hashset() -> CaseSpec;
312
313    // ── Nested collection tests ──
314
315    /// Case: nested `Vec<Vec<T>>`.
316    fn vec_nested() -> CaseSpec;
317
318    // ── Bytes/binary data tests ──
319
320    /// Case: `Vec<u8>` binary data as array of numbers.
321    fn bytes_vec_u8() -> CaseSpec;
322
323    // ── Fixed-size array tests ──
324
325    /// Case: `[T; N]` fixed-size array.
326    fn array_fixed_size() -> CaseSpec;
327
328    // ── Unknown field handling tests ──
329
330    /// Case: unknown fields are silently skipped by default.
331    fn skip_unknown_fields() -> CaseSpec;
332
333    // ── String escape tests ──
334
335    /// Case: string with escape sequences (\n, \t, \", \\).
336    fn string_escapes() -> CaseSpec;
337    /// Case: string with extended escape sequences (\b, \f, \r, \u0001).
338    fn string_escapes_extended() -> CaseSpec;
339
340    // ── Unit type tests ──
341
342    /// Case: unit struct (zero-sized type).
343    fn unit_struct() -> CaseSpec;
344
345    // ── Third-party type tests ──
346
347    /// Case: uuid::Uuid type.
348    #[cfg(feature = "uuid")]
349    fn uuid() -> CaseSpec;
350
351    /// Case: ulid::Ulid type.
352    #[cfg(feature = "ulid")]
353    fn ulid() -> CaseSpec;
354
355    /// Case: camino::Utf8PathBuf type.
356    #[cfg(feature = "camino")]
357    fn camino_path() -> CaseSpec;
358
359    /// Case: ordered_float::OrderedFloat type.
360    #[cfg(feature = "ordered-float")]
361    fn ordered_float() -> CaseSpec;
362
363    /// Case: time::OffsetDateTime type.
364    #[cfg(feature = "time")]
365    fn time_offset_datetime() -> CaseSpec;
366
367    /// Case: jiff::Timestamp type.
368    #[cfg(feature = "jiff02")]
369    fn jiff_timestamp() -> CaseSpec;
370
371    /// Case: jiff::civil::DateTime type.
372    #[cfg(feature = "jiff02")]
373    fn jiff_civil_datetime() -> CaseSpec;
374
375    /// Case: `chrono::DateTime<Utc>` type.
376    #[cfg(feature = "chrono")]
377    fn chrono_datetime_utc() -> CaseSpec;
378
379    /// Case: chrono::NaiveDateTime type.
380    #[cfg(feature = "chrono")]
381    fn chrono_naive_datetime() -> CaseSpec;
382
383    /// Case: chrono::NaiveDate type.
384    #[cfg(feature = "chrono")]
385    fn chrono_naive_date() -> CaseSpec;
386
387    /// Case: chrono::NaiveTime type.
388    #[cfg(feature = "chrono")]
389    fn chrono_naive_time() -> CaseSpec;
390
391    /// Case: `Vec<chrono::DateTime<Utc>>` - chrono in collections.
392    #[cfg(feature = "chrono")]
393    fn chrono_in_vec() -> CaseSpec;
394
395    // ── Bytes crate tests ──
396
397    /// Case: `bytes::Bytes` type.
398    #[cfg(feature = "bytes")]
399    fn bytes_bytes() -> CaseSpec;
400
401    /// Case: `bytes::BytesMut` type.
402    #[cfg(feature = "bytes")]
403    fn bytes_bytes_mut() -> CaseSpec;
404
405    /// Case: `bytestring::ByteString` type.
406    #[cfg(feature = "bytestring")]
407    fn bytestring() -> CaseSpec;
408
409    /// Case: `compact_str::CompactString` type.
410    #[cfg(feature = "compact_str")]
411    fn compact_string() -> CaseSpec;
412
413    /// Case: `smartstring::SmartString` type.
414    #[cfg(feature = "smartstring")]
415    fn smartstring() -> CaseSpec;
416
417    // ── Dynamic value tests ──
418
419    /// Case: `facet_value::Value` dynamic type - null.
420    #[cfg(feature = "facet-value")]
421    fn value_null() -> CaseSpec;
422
423    /// Case: `facet_value::Value` dynamic type - bool.
424    #[cfg(feature = "facet-value")]
425    fn value_bool() -> CaseSpec;
426
427    /// Case: `facet_value::Value` dynamic type - integer.
428    #[cfg(feature = "facet-value")]
429    fn value_integer() -> CaseSpec;
430
431    /// Case: `facet_value::Value` dynamic type - float.
432    #[cfg(feature = "facet-value")]
433    fn value_float() -> CaseSpec;
434
435    /// Case: `facet_value::Value` dynamic type - string.
436    #[cfg(feature = "facet-value")]
437    fn value_string() -> CaseSpec;
438
439    /// Case: `facet_value::Value` dynamic type - array.
440    #[cfg(feature = "facet-value")]
441    fn value_array() -> CaseSpec;
442
443    /// Case: `facet_value::Value` dynamic type - object.
444    #[cfg(feature = "facet-value")]
445    fn value_object() -> CaseSpec;
446}
447
448/// Execute suite cases; kept for convenience, but formats should register each
449/// case individually via [`all_cases`].
450pub 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
471/// Enumerate every canonical case with its typed descriptor.
472pub fn all_cases<S: FormatSuite + 'static>() -> Vec<SuiteCase> {
473    vec![
474        // Core cases
475        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        // Attribute cases
484        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        // Enum tagging cases
505        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        // Advanced cases
514        SuiteCase::new::<S, FlattenOuter>(&CASE_STRUCT_FLATTEN, S::struct_flatten),
515        SuiteCase::new::<S, UserRecord>(&CASE_TRANSPARENT_NEWTYPE, S::transparent_newtype),
516        // Flatten variation cases
517        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        // Error cases
535        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        // Alias cases
549        SuiteCase::new::<S, WithAlias>(&CASE_ATTR_ALIAS, S::attr_alias),
550        // Attribute precedence cases
551        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        // Proxy cases
566        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        // Transparent cases
581        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        // Scalar cases
588        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        // Collection cases
596        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        // Enum variant cases
610        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        // Numeric enum variation cases
615        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        // Untagged enum variation cases
618        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        // Smart pointer cases
626        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        // Set cases
634        SuiteCase::new::<S, SetWrapper>(&CASE_SET_BTREE, S::set_btree),
635        // Extended numeric cases
636        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        // NonZero cases
640        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        // Borrowed string cases
646        SuiteCase::new::<S, CowStrWrapper>(&CASE_COW_STR, S::cow_str),
647        // Bytes/binary data cases
648        SuiteCase::new::<S, BytesWrapper>(&CASE_BYTES_VEC_U8, S::bytes_vec_u8),
649        // Fixed-size array cases
650        SuiteCase::new::<S, ArrayWrapper>(&CASE_ARRAY_FIXED_SIZE, S::array_fixed_size),
651        // Unknown field handling cases
652        SuiteCase::new::<S, SkipUnknownStruct>(&CASE_SKIP_UNKNOWN_FIELDS, S::skip_unknown_fields),
653        // String escape cases
654        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        // Unit type cases
660        SuiteCase::new::<S, UnitStruct>(&CASE_UNIT_STRUCT, S::unit_struct),
661        // Newtype cases
662        SuiteCase::new::<S, NewtypeU64Wrapper>(&CASE_NEWTYPE_U64, S::newtype_u64),
663        SuiteCase::new::<S, NewtypeStringWrapper>(&CASE_NEWTYPE_STRING, S::newtype_string),
664        // Char cases
665        SuiteCase::new::<S, CharWrapper>(&CASE_CHAR_SCALAR, S::char_scalar),
666        // HashSet cases
667        SuiteCase::new::<S, HashSetWrapper>(&CASE_HASHSET, S::hashset),
668        // Nested collection cases
669        SuiteCase::new::<S, NestedVecWrapper>(&CASE_VEC_NESTED, S::vec_nested),
670        // Third-party type cases
671        #[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        // Bytes crate cases
708        #[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        // String optimization crate cases
713        #[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        // Dynamic value cases
720        #[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/// How to compare the deserialized value against the expected value.
738#[derive(Debug, Clone, Copy, Default)]
739pub enum CompareMode {
740    /// Use `assert_same!` (reflection-based comparison) - default for most types.
741    #[default]
742    Reflection,
743    /// Use `assert_eq!` (PartialEq comparison) - required for types containing opaque fields.
744    PartialEq,
745}
746
747/// Specification returned by each trait method.
748#[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    /// Provide raw bytes for the case input.
758    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    /// Convenience for UTF-8 inputs.
768    #[allow(clippy::should_implement_trait)]
769    pub fn from_str(input: &'static str) -> Self {
770        Self::from_bytes(input.as_bytes())
771    }
772
773    /// Mark the case as skipped for this format, documenting the reason.
774    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    /// Attach an optional note for diagnostics.
784    pub fn with_note(mut self, note: &'static str) -> Self {
785        self.note = Some(note);
786        self
787    }
788
789    /// Disable round-trip checks for this case, documenting the reason.
790    pub fn without_roundtrip(mut self, reason: &'static str) -> Self {
791        self.roundtrip = RoundtripSpec::Disabled { reason };
792        self
793    }
794
795    /// Use PartialEq comparison instead of reflection-based comparison.
796    /// Required for types containing opaque fields that can't be compared via reflection.
797    pub fn with_partial_eq(mut self) -> Self {
798        self.compare_mode = CompareMode::PartialEq;
799        self
800    }
801
802    /// Expect deserialization to fail with an error containing the given substring.
803    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    /// Provide dynamically-generated bytes for the case input.
818    ///
819    /// Use this for binary formats where input is generated at runtime
820    /// (e.g., from a reference implementation like rmp-serde for MsgPack).
821    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    /// Dynamic input: bytes generated at runtime (e.g., from a reference implementation).
835    DynamicInput(Vec<u8>),
836    Skip {
837        reason: &'static str,
838    },
839    /// Expect deserialization to fail with an error containing the given substring.
840    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                // Use current_thread runtime to avoid Send requirements
919                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    /// Run the async version of this test case.
942    /// Uses a current-thread tokio runtime internally.
943    #[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            // Compare deserialized value against expected
985            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            // Compare round-tripped value against original
1036            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            // Compare deserialized value against expected
1074            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            // Compare round-tripped value against original
1125            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            // Try async deserialization
1203            let result = S::deserialize_async::<T>(input).await;
1204            let actual = match result {
1205                None => {
1206                    // Format doesn't support async - skip this test
1207                    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            // Compare deserialized value against expected
1216            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            // Note: We skip round-trip testing for async - it's tested by sync version
1239            CaseOutcome::Passed
1240        }
1241        CasePayload::DynamicInput(ref input) => {
1242            // Try async deserialization with dynamic input
1243            let result = S::deserialize_async::<T>(input).await;
1244            let actual = match result {
1245                None => {
1246                    // Format doesn't support async - skip this test
1247                    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            // Compare deserialized value against expected
1256            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            // Note: We skip round-trip testing for async - it's tested by sync version
1279            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
1357// ── Attribute test case descriptors ──
1358
1359const 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, // default value
1384    },
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(), // Default::default() for String
1393    },
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, // from custom_default_value()
1401        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(), // default, not in input
1438    },
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, // is_none returns true, so field is skipped on serialize
1447    },
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, // always uses default (u32::default())
1456    },
1457};
1458
1459// ── Enum tagging case descriptors ──
1460
1461const 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
1473// ── Advanced case descriptors ──
1474
1475const 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
1493// ── Flatten variation case descriptors ──
1494
1495const 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
1555// ── Error case descriptors ──
1556
1557const 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
1590// ── Alias case descriptors ──
1591
1592const 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
1601// ── Attribute precedence case descriptors ──
1602
1603const 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
1647// ── Proxy case descriptors ──
1648
1649const 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 }, // Not used for error cases
1668};
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
1707// ── Transparent case descriptors ──
1708
1709const 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
1727// ── Scalar case descriptors ──
1728
1729const 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
1770// ── Collection case descriptors ──
1771
1772const 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
1829// ── Enum variant case descriptors ──
1830
1831const 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
1855// ── Numeric enum variation case descriptors ──
1856
1857const 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
1869// ── Untagged enum variation case descriptors ──
1870
1871const 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
1898// ── Smart pointer case descriptors ──
1899
1900const 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
1956// ── Set case descriptors ──
1957
1958const 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
1970// ── Extended numeric case descriptors ──
1971
1972const 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
1999// ── NonZero case descriptors ──
2000
2001const 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
2025// ── Borrowed string case descriptors ──
2026
2027const 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
2036// ── Newtype case descriptors ──
2037
2038const 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
2054// ── Char case descriptors ──
2055
2056const 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
2065// ── HashSet case descriptors ──
2066
2067const 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
2078// ── Nested collection case descriptors ──
2079
2080const 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
2088// ── Bytes/binary data case descriptors ──
2089
2090const 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
2098// ── Fixed-size array case descriptors ──
2099
2100const 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
2106// ── Unknown field handling case descriptors ──
2107
2108const 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
2116// ── String escape case descriptors ──
2117
2118const 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
2137// ── Unit type case descriptors ──
2138
2139const CASE_UNIT_STRUCT: CaseDescriptor<UnitStruct> = CaseDescriptor {
2140    id: "unit::struct",
2141    description: "unit struct (zero-sized type)",
2142    expected: || UnitStruct,
2143};
2144
2145/// Shared fixture type for the struct case.
2146#[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/// Shared fixture type for the mixed scalars case.
2153#[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// ── Attribute test fixtures ──
2187
2188/// Fixture for `#[facet(rename = "...")]` test.
2189#[derive(Facet, Debug, Clone, PartialEq)]
2190pub struct RenamedField {
2191    #[facet(rename = "userName")]
2192    pub user_name: String,
2193    pub age: u32,
2194}
2195
2196/// Fixture for `#[facet(rename_all = "camelCase")]` test.
2197#[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/// Fixture for `#[facet(default)]` test.
2206#[derive(Facet, Debug, Clone, PartialEq)]
2207pub struct WithDefault {
2208    pub required: String,
2209    #[facet(default)]
2210    pub optional_count: u32,
2211}
2212
2213/// Fixture for struct-level `#[facet(default)]` test.
2214#[derive(Facet, Default, Debug, Clone, PartialEq)]
2215#[facet(default)]
2216pub struct StructLevelDefault {
2217    pub count: i32,
2218    pub message: String,
2219}
2220
2221/// Default value function for `WithDefaultFunction`.
2222pub fn custom_default_value() -> i32 {
2223    42
2224}
2225
2226/// Fixture for `#[facet(default = expr)]` test.
2227#[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/// Fixture for `Option<T>` with `None`.
2235#[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/// Fixture for `#[facet(skip_serializing)]` test.
2243#[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/// Fixture for `#[facet(skip_serializing_if = predicate)]` test.
2252#[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/// Fixture for `#[facet(skip)]` test (skipped for both ser and de).
2260#[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// ── Enum tagging fixtures ──
2269
2270/// Internally tagged enum `#[facet(tag = "type")]`.
2271#[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/// Adjacently tagged enum `#[facet(tag = "t", content = "c")]`.
2280#[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// ── Advanced fixtures ──
2289
2290/// Inner struct for flatten test.
2291#[derive(Facet, Debug, Clone, PartialEq)]
2292pub struct FlattenInner {
2293    pub x: i32,
2294    pub y: i32,
2295}
2296
2297/// Outer struct with `#[facet(flatten)]`.
2298#[derive(Facet, Debug, Clone, PartialEq)]
2299pub struct FlattenOuter {
2300    pub name: String,
2301    #[facet(flatten)]
2302    pub coords: FlattenInner,
2303}
2304
2305/// Transparent newtype wrapper.
2306#[derive(Facet, Debug, Clone, PartialEq)]
2307#[facet(transparent)]
2308pub struct UserId(pub u64);
2309
2310/// Struct containing a transparent newtype.
2311#[derive(Facet, Debug, Clone, PartialEq)]
2312pub struct UserRecord {
2313    pub id: UserId,
2314    pub name: String,
2315}
2316
2317// ── Flatten variation fixtures ──
2318
2319/// Struct for flatten with optional flattened field (Some case).
2320#[derive(Facet, Debug, Clone, PartialEq)]
2321pub struct FlattenOptionalSome {
2322    pub name: String,
2323    #[facet(flatten)]
2324    pub metadata: Option<FlattenMetadata>,
2325}
2326
2327/// Struct for flatten with optional flattened field (None case).
2328#[derive(Facet, Debug, Clone, PartialEq)]
2329pub struct FlattenOptionalNone {
2330    pub name: String,
2331    #[facet(flatten)]
2332    pub metadata: Option<FlattenMetadata>,
2333}
2334
2335/// Metadata struct for flatten optional tests.
2336#[derive(Facet, Debug, Clone, PartialEq)]
2337pub struct FlattenMetadata {
2338    pub version: i32,
2339    pub author: String,
2340}
2341
2342/// First flattened struct with overlapping fields.
2343#[derive(Facet, Debug, Clone, PartialEq)]
2344pub struct FlattenPartA {
2345    pub field_a: String,
2346    pub shared: i32,
2347}
2348
2349/// Second flattened struct with overlapping fields.
2350#[derive(Facet, Debug, Clone, PartialEq)]
2351pub struct FlattenPartB {
2352    pub field_b: String,
2353    pub shared: i32,
2354}
2355
2356/// Container with two flattened structs that have overlapping field names.
2357#[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/// Deepest level for three-level flatten test.
2366#[derive(Facet, Debug, Clone, PartialEq)]
2367pub struct FlattenLevel3 {
2368    pub deep_field: i32,
2369}
2370
2371/// Middle level for three-level flatten test.
2372#[derive(Facet, Debug, Clone, PartialEq)]
2373pub struct FlattenLevel2 {
2374    pub mid_field: i32,
2375    #[facet(flatten)]
2376    pub level3: FlattenLevel3,
2377}
2378
2379/// Top level for three-level flatten test.
2380#[derive(Facet, Debug, Clone, PartialEq)]
2381pub struct FlattenLevel1 {
2382    pub top_field: String,
2383    #[facet(flatten)]
2384    pub level2: FlattenLevel2,
2385}
2386
2387/// Password auth data for multiple flattened enums test.
2388#[derive(Facet, Debug, Clone, PartialEq)]
2389pub struct FlattenAuthPassword {
2390    pub password: String,
2391}
2392
2393/// Token auth data for multiple flattened enums test.
2394#[derive(Facet, Debug, Clone, PartialEq)]
2395pub struct FlattenAuthToken {
2396    pub token: String,
2397}
2398
2399/// Auth method enum for multiple flattened enums test.
2400#[allow(dead_code)]
2401#[derive(Facet, Debug, Clone, PartialEq)]
2402#[repr(u8)]
2403pub enum FlattenAuthMethod {
2404    Password(FlattenAuthPassword),
2405    Token(FlattenAuthToken),
2406}
2407
2408/// TCP transport data for multiple flattened enums test.
2409#[derive(Facet, Debug, Clone, PartialEq)]
2410pub struct FlattenTransportTcp {
2411    pub port: u16,
2412}
2413
2414/// Unix transport data for multiple flattened enums test.
2415#[derive(Facet, Debug, Clone, PartialEq)]
2416pub struct FlattenTransportUnix {
2417    pub socket: String,
2418}
2419
2420/// Transport enum for multiple flattened enums test.
2421#[allow(dead_code)]
2422#[derive(Facet, Debug, Clone, PartialEq)]
2423#[repr(u8)]
2424pub enum FlattenTransport {
2425    Tcp(FlattenTransportTcp),
2426    Unix(FlattenTransportUnix),
2427}
2428
2429/// Container with two different flattened enums.
2430#[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// ── Error test fixtures ──
2440
2441/// Fixture for `#[facet(deny_unknown_fields)]` test.
2442#[derive(Facet, Debug, Clone, PartialEq)]
2443#[facet(deny_unknown_fields)]
2444pub struct DenyUnknownStruct {
2445    pub foo: String,
2446    pub bar: i32,
2447}
2448
2449/// Fixture for type mismatch error (string to int).
2450#[derive(Facet, Debug, Clone, PartialEq)]
2451pub struct ExpectsInteger {
2452    pub value: i32,
2453}
2454
2455/// Fixture for structure mismatch error (object to array).
2456#[derive(Facet, Debug, Clone, PartialEq)]
2457pub struct ExpectsArray {
2458    pub items: Vec<i32>,
2459}
2460
2461/// Fixture for missing required field error.
2462#[derive(Facet, Debug, Clone, PartialEq)]
2463pub struct RequiresAllFields {
2464    pub name: String,
2465    pub age: u32,
2466    pub email: String,
2467}
2468
2469/// Fixture for `#[facet(alias = "...")]` test.
2470#[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// ── Attribute precedence test fixtures ──
2478
2479/// Fixture for testing rename vs alias precedence (rename should win).
2480#[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/// Fixture for `#[facet(rename_all = "kebab-case")]` test.
2489#[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/// Fixture for `#[facet(rename_all = "SCREAMING_SNAKE_CASE")]` test.
2498#[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/// Struct with unicode (emoji) field name via rename.
2506#[derive(Facet, Clone, Debug, PartialEq)]
2507pub struct RenameUnicode {
2508    #[facet(rename = "🎉")]
2509    pub celebration: String,
2510}
2511
2512/// Struct with special characters in field name via rename.
2513#[derive(Facet, Clone, Debug, PartialEq)]
2514pub struct RenameSpecialChars {
2515    #[facet(rename = "@type")]
2516    pub type_field: String,
2517}
2518
2519// ── Proxy test fixtures ──
2520
2521/// Proxy type that wraps a string for serialization.
2522#[derive(Facet, Clone, Debug)]
2523#[facet(transparent)]
2524pub struct IntAsString(pub String);
2525
2526/// Target type that uses the proxy for serialization.
2527#[derive(Facet, Debug, Clone, PartialEq)]
2528#[facet(proxy = IntAsString)]
2529pub struct ProxyInt {
2530    pub value: i32,
2531}
2532
2533/// Convert from proxy (deserialization): string -> ProxyInt
2534impl 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
2543/// Convert to proxy (serialization): ProxyInt -> string
2544impl From<&ProxyInt> for IntAsString {
2545    fn from(v: &ProxyInt) -> Self {
2546        IntAsString(v.value.to_string())
2547    }
2548}
2549
2550/// Struct with field-level proxy (tests field-level vs container-level).
2551#[derive(Facet, Debug, Clone, PartialEq)]
2552pub struct ProxyFieldLevel {
2553    pub name: String,
2554    #[facet(proxy = IntAsString)]
2555    pub count: i32,
2556}
2557
2558/// Convert from proxy for field-level proxy.
2559impl 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
2566/// Convert to proxy for field-level proxy.
2567impl From<&i32> for IntAsString {
2568    fn from(value: &i32) -> Self {
2569        IntAsString(value.to_string())
2570    }
2571}
2572
2573/// Struct with proxy wrapping `Option<T>`.
2574#[derive(Facet, Debug, Clone, PartialEq)]
2575pub struct ProxyWithOption {
2576    pub name: String,
2577    #[facet(proxy = IntAsString)]
2578    pub count: Option<i32>,
2579}
2580
2581/// Convert from proxy for `Option<i32>`.
2582impl 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
2593/// Convert to proxy for `Option<i32>`.
2594impl 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/// Enum with proxy on a newtype variant.
2604#[derive(Facet, Debug, Clone, PartialEq)]
2605#[repr(u8)]
2606pub enum ProxyEnum {
2607    None,
2608    #[facet(proxy = IntAsString)]
2609    Value(i32),
2610}
2611
2612/// Transparent wrapper with proxy.
2613#[derive(Facet, Debug, Clone, PartialEq)]
2614#[facet(transparent, proxy = IntAsString)]
2615pub struct TransparentProxy(pub i32);
2616
2617/// Convert from proxy for TransparentProxy.
2618impl 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
2625/// Convert to proxy for TransparentProxy.
2626impl From<&TransparentProxy> for IntAsString {
2627    fn from(value: &TransparentProxy) -> Self {
2628        IntAsString(value.0.to_string())
2629    }
2630}
2631
2632// ── Opaque proxy test fixtures ──
2633
2634/// An opaque type that does NOT implement Facet.
2635/// This tests the `#[facet(opaque, proxy = ...)]` pattern.
2636#[derive(Debug, Clone, PartialEq)]
2637pub struct OpaqueType {
2638    pub inner: u64,
2639}
2640
2641/// Proxy type for OpaqueType that implements Facet.
2642#[derive(Facet, Clone, Debug)]
2643pub struct OpaqueTypeProxy {
2644    pub inner: u64,
2645}
2646
2647/// Convert from proxy (deserialization): OpaqueTypeProxy -> OpaqueType
2648impl From<OpaqueTypeProxy> for OpaqueType {
2649    fn from(proxy: OpaqueTypeProxy) -> Self {
2650        OpaqueType { inner: proxy.inner }
2651    }
2652}
2653
2654/// Convert to proxy (serialization): &OpaqueType -> OpaqueTypeProxy
2655impl From<&OpaqueType> for OpaqueTypeProxy {
2656    fn from(v: &OpaqueType) -> Self {
2657        OpaqueTypeProxy { inner: v.inner }
2658    }
2659}
2660
2661/// Wrapper struct with an opaque field using proxy.
2662#[derive(Facet, Debug, Clone, PartialEq)]
2663pub struct OpaqueProxyWrapper {
2664    #[facet(opaque, proxy = OpaqueTypeProxy)]
2665    pub value: OpaqueType,
2666}
2667
2668/// Convert from proxy for `Option<OpaqueType>`
2669impl From<OpaqueTypeProxy> for Option<OpaqueType> {
2670    fn from(proxy: OpaqueTypeProxy) -> Self {
2671        Some(OpaqueType { inner: proxy.inner })
2672    }
2673}
2674
2675/// Convert to proxy for `Option<OpaqueType>`
2676impl 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/// Wrapper struct with an optional opaque field using proxy.
2686#[derive(Facet, Debug, Clone, PartialEq)]
2687pub struct OpaqueProxyOptionWrapper {
2688    #[facet(opaque, proxy = OpaqueTypeProxy)]
2689    pub value: Option<OpaqueType>,
2690}
2691
2692// ── Transparent test fixtures ──
2693
2694/// Inner transparent wrapper.
2695#[derive(Facet, Debug, Clone, PartialEq)]
2696#[facet(transparent)]
2697pub struct InnerTransparent(pub i32);
2698
2699/// Outer transparent wrapper wrapping another transparent type.
2700#[derive(Facet, Debug, Clone, PartialEq)]
2701#[facet(transparent)]
2702pub struct OuterTransparent(pub InnerTransparent);
2703
2704/// Transparent wrapper around `Option<T>`.
2705#[derive(Facet, Debug, Clone, PartialEq)]
2706#[facet(transparent)]
2707pub struct TransparentOption(pub Option<i32>);
2708
2709/// Transparent wrapper around NonZero type.
2710#[derive(Facet, Debug, Clone, PartialEq)]
2711#[facet(transparent)]
2712pub struct TransparentNonZero(pub std::num::NonZeroU32);
2713
2714// ── Scalar test fixtures ──
2715
2716/// Fixture for boolean scalar test.
2717#[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/// Fixture for integer scalar test.
2725#[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/// Fixture for float scalar test.
2737#[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/// Fixture for scientific notation float test.
2745#[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// ── Collection test fixtures ──
2754
2755/// Fixture for BTreeMap test.
2756#[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/// Fixture for tuple test.
2763#[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/// Fixture for nested tuple test.
2770#[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/// Fixture for empty tuple test.
2777#[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/// Fixture for single-element tuple test.
2785#[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// ── Enum variant test fixtures ──
2793
2794/// Unit variant enum.
2795#[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/// Enum with renamed variants.
2805#[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/// Numeric enum with a u8 discriminant.
2815#[derive(Facet, Debug, Clone, PartialEq)]
2816#[facet(is_numeric)]
2817#[repr(u8)]
2818pub enum NumericEnum {
2819    A,
2820    B,
2821}
2822
2823/// Numeric enum with a u8 discriminant.
2824#[derive(Facet, Debug, Clone, PartialEq)]
2825#[facet(is_numeric)]
2826#[repr(i16)]
2827pub enum SignedNumericEnum {
2828    Z = -1,
2829    A,
2830}
2831
2832/// Untagged enum that matches by structure.
2833#[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/// Untagged enum with unit variant that matches null.
2842#[derive(Facet, Debug, Clone, PartialEq)]
2843#[facet(untagged)]
2844#[repr(u8)]
2845pub enum UntaggedWithNull {
2846    None,
2847    Some(i32),
2848}
2849
2850/// Untagged enum with newtype variants for discrimination testing.
2851#[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/// Struct containing an untagged enum field (nesting test).
2861#[derive(Facet, Debug, Clone, PartialEq)]
2862pub struct UntaggedAsField {
2863    pub name: String,
2864    pub value: UntaggedNewtype,
2865}
2866
2867/// Untagged enum with only unit variants (dataless enum).
2868/// Tests issue #1228: deserializing untagged dataless enums from strings.
2869#[derive(Facet, Debug, Clone, PartialEq)]
2870#[facet(untagged)]
2871#[repr(u8)]
2872pub enum UntaggedUnitOnly {
2873    Alpha,
2874    Beta,
2875    Gamma,
2876}
2877
2878/// Enum with tuple variant (multiple fields).
2879#[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/// Enum with newtype variant (single field).
2888#[derive(Facet, Debug, Clone, PartialEq)]
2889#[repr(u8)]
2890pub enum NewtypeVariantEnum {
2891    None,
2892    Some(i32),
2893}
2894
2895// ── Smart pointer test fixtures ──
2896
2897/// Fixture for `Box<T>` test.
2898#[derive(Facet, Debug, Clone, PartialEq)]
2899pub struct BoxWrapper {
2900    pub inner: Box<i32>,
2901}
2902
2903/// Fixture for `Arc<T>` test.
2904#[derive(Facet, Debug, Clone, PartialEq)]
2905pub struct ArcWrapper {
2906    pub inner: std::sync::Arc<i32>,
2907}
2908
2909/// Fixture for `Rc<T>` test.
2910#[derive(Facet, Debug, Clone, PartialEq)]
2911pub struct RcWrapper {
2912    pub inner: std::rc::Rc<i32>,
2913}
2914
2915/// Fixture for `Box<str>` test.
2916#[derive(Facet, Debug, Clone, PartialEq)]
2917pub struct BoxStrWrapper {
2918    pub inner: Box<str>,
2919}
2920
2921/// Fixture for `Arc<str>` test.
2922#[derive(Facet, Debug, Clone, PartialEq)]
2923pub struct ArcStrWrapper {
2924    pub inner: std::sync::Arc<str>,
2925}
2926
2927/// Fixture for `Rc<str>` test.
2928#[derive(Facet, Debug, Clone, PartialEq)]
2929pub struct RcStrWrapper {
2930    pub inner: std::rc::Rc<str>,
2931}
2932
2933/// Fixture for `Arc<[T]>` test.
2934#[derive(Facet, Debug, Clone, PartialEq)]
2935pub struct ArcSliceWrapper {
2936    pub inner: std::sync::Arc<[i32]>,
2937}
2938
2939// ── Set test fixtures ──
2940
2941/// Fixture for BTreeSet test.
2942#[derive(Facet, Debug, Clone, PartialEq)]
2943pub struct SetWrapper {
2944    pub items: std::collections::BTreeSet<String>,
2945}
2946
2947// ── Extended numeric test fixtures ──
2948
2949/// Fixture for 16-bit integer test.
2950#[derive(Facet, Debug, Clone, PartialEq)]
2951pub struct IntegerTypes16 {
2952    pub signed_16: i16,
2953    pub unsigned_16: u16,
2954}
2955
2956/// Fixture for 128-bit integer test.
2957#[derive(Facet, Debug, Clone, PartialEq)]
2958pub struct IntegerTypes128 {
2959    pub signed_128: i128,
2960    pub unsigned_128: u128,
2961}
2962
2963/// Fixture for pointer-sized integer test.
2964#[derive(Facet, Debug, Clone, PartialEq)]
2965pub struct IntegerTypesSize {
2966    pub signed_size: isize,
2967    pub unsigned_size: usize,
2968}
2969
2970// ── NonZero test fixtures ──
2971
2972/// Fixture for NonZero integer test.
2973#[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/// Fixture for extended NonZero integer test.
2980#[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// ── Borrowed string test fixtures ──
2993
2994/// Fixture for Cow<'static, str> test.
2995#[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// ── Newtype test fixtures ──
3002
3003/// Newtype wrapper around u64.
3004#[derive(Facet, Debug, Clone, PartialEq)]
3005#[facet(transparent)]
3006pub struct NewtypeU64(pub u64);
3007
3008/// Fixture containing newtype u64.
3009#[derive(Facet, Debug, Clone, PartialEq)]
3010pub struct NewtypeU64Wrapper {
3011    pub value: NewtypeU64,
3012}
3013
3014/// Newtype wrapper around String.
3015#[derive(Facet, Debug, Clone, PartialEq)]
3016#[facet(transparent)]
3017pub struct NewtypeString(pub String);
3018
3019/// Fixture containing newtype String.
3020#[derive(Facet, Debug, Clone, PartialEq)]
3021pub struct NewtypeStringWrapper {
3022    pub value: NewtypeString,
3023}
3024
3025// ── Char test fixtures ──
3026
3027/// Fixture for char scalar test.
3028#[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// ── HashSet test fixtures ──
3036
3037/// Fixture for HashSet test.
3038#[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// ── Nested collection test fixtures ──
3045
3046/// Fixture for nested Vec test.
3047#[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// ── Bytes/binary data test fixtures ──
3054
3055/// Fixture for `Vec<u8>` binary data test.
3056#[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// ── Fixed-size array test fixtures ──
3063
3064/// Fixture for fixed-size array test.
3065#[derive(Facet, Debug, Clone, PartialEq)]
3066pub struct ArrayWrapper {
3067    pub values: [u64; 3],
3068}
3069
3070// ── Unknown field handling test fixtures ──
3071
3072/// Fixture for skip_unknown_fields test (no deny_unknown_fields attribute).
3073#[derive(Facet, Debug, Clone, PartialEq)]
3074pub struct SkipUnknownStruct {
3075    pub known: String,
3076}
3077
3078// ── String escape test fixtures ──
3079
3080/// Fixture for string escape test.
3081#[derive(Facet, Debug, Clone, PartialEq)]
3082pub struct StringEscapes {
3083    pub text: String,
3084}
3085
3086/// Fixture for extended string escape test.
3087#[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// ── Unit type test fixtures ──
3096
3097/// Fixture for unit struct test (zero-sized type).
3098#[derive(Facet, Debug, Clone, PartialEq)]
3099#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3100pub struct UnitStruct;
3101
3102// ── Third-party type case descriptors ──
3103
3104#[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// ── Bytes crate case descriptors ──
3223
3224#[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// ── String optimization crate case descriptors ──
3243
3244#[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// ── Dynamic value case descriptors ──
3272
3273#[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// ── Third-party type test fixtures ──
3335
3336/// Fixture for uuid::Uuid test.
3337#[cfg(feature = "uuid")]
3338#[derive(Facet, Debug, Clone, PartialEq)]
3339pub struct UuidWrapper {
3340    pub id: uuid::Uuid,
3341}
3342
3343/// Fixture for ulid::Ulid test.
3344#[cfg(feature = "ulid")]
3345#[derive(Facet, Debug, Clone, PartialEq)]
3346pub struct UlidWrapper {
3347    pub id: ulid::Ulid,
3348}
3349
3350/// Fixture for camino::Utf8PathBuf test.
3351#[cfg(feature = "camino")]
3352#[derive(Facet, Debug, Clone, PartialEq)]
3353pub struct CaminoWrapper {
3354    pub path: camino::Utf8PathBuf,
3355}
3356
3357/// Fixture for ordered_float::OrderedFloat test.
3358#[cfg(feature = "ordered-float")]
3359#[derive(Facet, Debug, Clone, PartialEq)]
3360pub struct OrderedFloatWrapper {
3361    pub value: ordered_float::OrderedFloat<f64>,
3362}
3363
3364/// Fixture for time::OffsetDateTime test.
3365#[cfg(feature = "time")]
3366#[derive(Facet, Debug, Clone, PartialEq)]
3367pub struct TimeOffsetDateTimeWrapper {
3368    pub created_at: time::OffsetDateTime,
3369}
3370
3371/// Fixture for jiff::Timestamp test.
3372#[cfg(feature = "jiff02")]
3373#[derive(Facet, Debug, Clone, PartialEq)]
3374pub struct JiffTimestampWrapper {
3375    pub created_at: jiff::Timestamp,
3376}
3377
3378/// Fixture for jiff::civil::DateTime test.
3379#[cfg(feature = "jiff02")]
3380#[derive(Facet, Debug, Clone, PartialEq)]
3381pub struct JiffCivilDateTimeWrapper {
3382    pub created_at: jiff::civil::DateTime,
3383}
3384
3385/// Fixture for `chrono::DateTime<Utc>` test.
3386#[cfg(feature = "chrono")]
3387#[derive(Facet, Debug, Clone, PartialEq)]
3388pub struct ChronoDateTimeUtcWrapper {
3389    pub created_at: chrono::DateTime<chrono::Utc>,
3390}
3391
3392/// Fixture for chrono::NaiveDateTime test.
3393#[cfg(feature = "chrono")]
3394#[derive(Facet, Debug, Clone, PartialEq)]
3395pub struct ChronoNaiveDateTimeWrapper {
3396    pub created_at: chrono::NaiveDateTime,
3397}
3398
3399/// Fixture for chrono::NaiveDate test.
3400#[cfg(feature = "chrono")]
3401#[derive(Facet, Debug, Clone, PartialEq)]
3402pub struct ChronoNaiveDateWrapper {
3403    pub birth_date: chrono::NaiveDate,
3404}
3405
3406/// Fixture for chrono::NaiveTime test.
3407#[cfg(feature = "chrono")]
3408#[derive(Facet, Debug, Clone, PartialEq)]
3409pub struct ChronoNaiveTimeWrapper {
3410    pub alarm_time: chrono::NaiveTime,
3411}
3412
3413/// Fixture for chrono in collections test.
3414#[cfg(feature = "chrono")]
3415#[derive(Facet, Debug, Clone, PartialEq)]
3416pub struct ChronoInVecWrapper {
3417    pub timestamps: Vec<chrono::DateTime<chrono::Utc>>,
3418}
3419
3420// ── Bytes crate test fixtures ──
3421
3422/// Fixture for `bytes::Bytes` test.
3423#[cfg(feature = "bytes")]
3424#[derive(Facet, Debug, Clone, PartialEq)]
3425pub struct BytesBytesWrapper {
3426    pub data: bytes::Bytes,
3427}
3428
3429/// Fixture for `bytes::BytesMut` test.
3430#[cfg(feature = "bytes")]
3431#[derive(Facet, Debug, Clone, PartialEq)]
3432pub struct BytesBytesMutWrapper {
3433    pub data: bytes::BytesMut,
3434}
3435
3436// ── String optimization crate test fixtures ──
3437
3438/// Fixture for `bytestring::ByteString` test.
3439#[cfg(feature = "bytestring")]
3440#[derive(Facet, Debug, Clone, PartialEq)]
3441pub struct ByteStringWrapper {
3442    pub value: bytestring::ByteString,
3443}
3444
3445/// Fixture for `compact_str::CompactString` test.
3446#[cfg(feature = "compact_str")]
3447#[derive(Facet, Debug, Clone, PartialEq)]
3448pub struct CompactStringWrapper {
3449    pub value: compact_str::CompactString,
3450}
3451
3452/// Fixture for `smartstring::SmartString` test.
3453#[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// ── MsgPack interoperability helpers ──
3570
3571/// Generate MsgPack bytes using `rmp-serde` for interoperability testing.
3572///
3573/// This module provides functions to serialize test fixtures using the
3574/// reference MsgPack implementation, allowing our parser to be tested
3575/// against known-good input.
3576#[cfg(feature = "msgpack")]
3577pub mod msgpack {
3578    use super::*;
3579
3580    /// Serialize a value to MsgPack bytes using rmp-serde.
3581    ///
3582    /// Uses `to_vec_named` to serialize structs as maps with field names,
3583    /// which is the format our parser expects.
3584    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    /// Create a CaseSpec with MsgPack input from the expected value.
3589    ///
3590    /// This serializes the expected value using rmp-serde (reference impl)
3591    /// and returns a CaseSpec that will test our deserializer against it.
3592    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    // ── Pre-built test inputs ──
3600
3601    /// MsgPack bytes for StructSingleField { name: "facet" }
3602    pub fn struct_single_field_bytes() -> Vec<u8> {
3603        serialize(&StructSingleField {
3604            name: "facet".into(),
3605        })
3606    }
3607
3608    /// MsgPack bytes for sequence [1, 2, 3]
3609    pub fn sequence_numbers_bytes() -> Vec<u8> {
3610        serialize(&vec![1u64, 2, 3])
3611    }
3612
3613    /// MsgPack bytes for NestedParent
3614    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    /// MsgPack bytes for BoolWrapper
3626    pub fn scalar_bool_bytes() -> Vec<u8> {
3627        serialize(&BoolWrapper {
3628            yes: true,
3629            no: false,
3630        })
3631    }
3632
3633    /// MsgPack bytes for IntegerTypes
3634    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    /// MsgPack bytes for FloatTypes
3646    pub fn scalar_floats_bytes() -> Vec<u8> {
3647        serialize(&FloatTypes {
3648            float_32: 1.5,
3649            float_64: 2.25,
3650        })
3651    }
3652
3653    /// MsgPack bytes for MapWrapper (HashMap/BTreeMap)
3654    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    /// MsgPack bytes for TupleWrapper
3662    pub fn tuple_simple_bytes() -> Vec<u8> {
3663        serialize(&TupleWrapper {
3664            triple: ("hello".to_string(), 42, true),
3665        })
3666    }
3667
3668    /// MsgPack bytes for NestedTupleWrapper
3669    pub fn tuple_nested_bytes() -> Vec<u8> {
3670        serialize(&NestedTupleWrapper {
3671            outer: ((1, 2), ("test".to_string(), true)),
3672        })
3673    }
3674
3675    /// MsgPack bytes for WithOption (None case)
3676    pub fn option_none_bytes() -> Vec<u8> {
3677        serialize(&WithOption {
3678            name: "test".to_string(),
3679            nickname: None,
3680        })
3681    }
3682
3683    /// MsgPack bytes for WithOption (Some case)
3684    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    /// MsgPack bytes for UnitVariantEnum
3692    pub fn enum_unit_variant_bytes() -> Vec<u8> {
3693        serialize(&UnitVariantEnum::Active)
3694    }
3695
3696    /// MsgPack bytes for NestedVecWrapper
3697    pub fn vec_nested_bytes() -> Vec<u8> {
3698        serialize(&NestedVecWrapper {
3699            matrix: vec![vec![1, 2], vec![3, 4, 5]],
3700        })
3701    }
3702
3703    /// MsgPack bytes for BytesWrapper
3704    pub fn bytes_vec_u8_bytes() -> Vec<u8> {
3705        serialize(&BytesWrapper {
3706            data: vec![0xDE, 0xAD, 0xBE, 0xEF],
3707        })
3708    }
3709
3710    /// MsgPack bytes for CharWrapper
3711    pub fn char_scalar_bytes() -> Vec<u8> {
3712        serialize(&CharWrapper {
3713            letter: 'A',
3714            emoji: '🦀',
3715        })
3716    }
3717
3718    /// MsgPack bytes for UnitStruct
3719    pub fn unit_struct_bytes() -> Vec<u8> {
3720        serialize(&UnitStruct)
3721    }
3722
3723    /// MsgPack bytes for HashSetWrapper
3724    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    /// MsgPack bytes for EmptyTupleWrapper
3733    pub fn tuple_empty_bytes() -> Vec<u8> {
3734        serialize(&EmptyTupleWrapper {
3735            name: "test".to_string(),
3736            empty: (),
3737        })
3738    }
3739
3740    /// MsgPack bytes for SingleElementTupleWrapper  
3741    pub fn tuple_single_element_bytes() -> Vec<u8> {
3742        serialize(&SingleElementTupleWrapper {
3743            name: "test".to_string(),
3744            single: (42,),
3745        })
3746    }
3747}
3748
3749/// Display showcase for dynamic input (binary formats).
3750fn 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    // Format binary input as hex dump
3762    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
3797/// Format bytes as a hex dump.
3798fn 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        // Offset
3803        write!(output, "{:08x}  ", i * 16).unwrap();
3804        // Hex bytes
3805        for (j, byte) in chunk.iter().enumerate() {
3806            if j == 8 {
3807                output.push(' ');
3808            }
3809            write!(output, "{:02x} ", byte).unwrap();
3810        }
3811        // Padding for incomplete lines
3812        for j in chunk.len()..16 {
3813            if j == 8 {
3814                output.push(' ');
3815            }
3816            output.push_str("   ");
3817        }
3818        // ASCII representation
3819        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}