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    // ── Network type tests ──
210
211    /// Case: `std::net::IpAddr` (IPv4 variant).
212    #[cfg(feature = "net")]
213    fn net_ip_addr_v4() -> CaseSpec;
214    /// Case: `std::net::IpAddr` (IPv6 variant).
215    #[cfg(feature = "net")]
216    fn net_ip_addr_v6() -> CaseSpec;
217    /// Case: `std::net::Ipv4Addr`.
218    #[cfg(feature = "net")]
219    fn net_ipv4_addr() -> CaseSpec;
220    /// Case: `std::net::Ipv6Addr`.
221    #[cfg(feature = "net")]
222    fn net_ipv6_addr() -> CaseSpec;
223    /// Case: `std::net::SocketAddr` (IPv4 variant).
224    #[cfg(feature = "net")]
225    fn net_socket_addr_v4() -> CaseSpec;
226    /// Case: `std::net::SocketAddr` (IPv6 variant).
227    #[cfg(feature = "net")]
228    fn net_socket_addr_v6() -> CaseSpec;
229    /// Case: `std::net::SocketAddrV4`.
230    #[cfg(feature = "net")]
231    fn net_socket_addr_v4_explicit() -> CaseSpec;
232    /// Case: `std::net::SocketAddrV6`.
233    #[cfg(feature = "net")]
234    fn net_socket_addr_v6_explicit() -> CaseSpec;
235
236    // ── Collection tests ──
237
238    /// Case: `HashMap<String, T>`.
239    fn map_string_keys() -> CaseSpec;
240    /// Case: tuple types.
241    fn tuple_simple() -> CaseSpec;
242    /// Case: nested tuple types.
243    fn tuple_nested() -> CaseSpec;
244    /// Case: empty tuple `()` as a field.
245    fn tuple_empty() -> CaseSpec;
246    /// Case: single-element tuple `(T,)` as a field.
247    fn tuple_single_element() -> CaseSpec;
248    /// Case: enum with tuple variant `Variant(T, U)`.
249    fn tuple_struct_variant() -> CaseSpec;
250    /// Case: enum with newtype variant `Variant(T)`.
251    fn tuple_newtype_variant() -> CaseSpec;
252
253    // ── Enum variant tests ──
254
255    /// Case: unit enum variant.
256    fn enum_unit_variant() -> CaseSpec;
257    /// Case: numeric enum.
258    fn numeric_enum() -> CaseSpec;
259    /// Case: signed numeric enum.
260    fn signed_numeric_enum() -> CaseSpec;
261    /// Case: numeric enum.
262    fn inferred_numeric_enum() -> CaseSpec;
263    /// Case: untagged enum.
264    fn enum_untagged() -> CaseSpec;
265    /// Case: enum with renamed variants `#[facet(rename = "...")]`.
266    fn enum_variant_rename() -> CaseSpec;
267
268    // ── Untagged enum variation tests ──
269
270    /// Case: untagged enum with unit variant matching null.
271    fn untagged_with_null() -> CaseSpec;
272    /// Case: untagged enum with newtype variants (discrimination test).
273    fn untagged_newtype_variant() -> CaseSpec;
274    /// Case: untagged enum as struct field (nesting test).
275    fn untagged_as_field() -> CaseSpec;
276
277    /// Case: untagged enum with only unit variants (dataless enum).
278    fn untagged_unit_only() -> CaseSpec;
279
280    // ── Smart pointer tests ──
281
282    /// Case: `Box<T>` smart pointer.
283    fn box_wrapper() -> CaseSpec;
284    /// Case: `Arc<T>` smart pointer.
285    fn arc_wrapper() -> CaseSpec;
286    /// Case: `Rc<T>` smart pointer.
287    fn rc_wrapper() -> CaseSpec;
288    /// Case: `Box<str>` unsized smart pointer.
289    fn box_str() -> CaseSpec;
290    /// Case: `Arc<str>` unsized smart pointer.
291    fn arc_str() -> CaseSpec;
292    /// Case: `Rc<str>` unsized smart pointer.
293    fn rc_str() -> CaseSpec;
294    /// Case: `Arc<[T]>` unsized slice smart pointer.
295    fn arc_slice() -> CaseSpec;
296
297    // ── Yoke tests ──
298
299    /// Case: `Yoke<Cow<'static, str>, Arc<str>>` zero-copy smart pointer.
300    #[cfg(feature = "yoke")]
301    fn yoke_cow_str() -> CaseSpec;
302
303    /// Case: Custom type deriving Yokeable and using #[facet(try_from_ref)].
304    #[cfg(feature = "yoke")]
305    fn yoke_custom() -> CaseSpec;
306
307    // ── Set tests ──
308
309    /// Case: `BTreeSet<T>`.
310    fn set_btree() -> CaseSpec;
311
312    // ── Extended numeric tests ──
313
314    /// Case: i16, u16 integers.
315    fn scalar_integers_16() -> CaseSpec;
316    /// Case: i128, u128 integers.
317    fn scalar_integers_128() -> CaseSpec;
318    /// Case: isize, usize integers.
319    fn scalar_integers_size() -> CaseSpec;
320
321    // ── NonZero tests ──
322
323    /// Case: NonZero integer types.
324    fn nonzero_integers() -> CaseSpec;
325    /// Case: Extended NonZero integer types (8, 16, 128, size).
326    fn nonzero_integers_extended() -> CaseSpec;
327
328    // ── Borrowed string tests ──
329
330    /// Case: Cow<'static, str> field.
331    fn cow_str() -> CaseSpec;
332
333    // ── Newtype tests ──
334
335    /// Case: newtype wrapper around u64.
336    fn newtype_u64() -> CaseSpec;
337    /// Case: newtype wrapper around String.
338    fn newtype_string() -> CaseSpec;
339
340    // ── Char tests ──
341
342    /// Case: char scalar type.
343    fn char_scalar() -> CaseSpec;
344
345    // ── HashSet tests ──
346
347    /// Case: `HashSet<T>`.
348    fn hashset() -> CaseSpec;
349
350    // ── Nested collection tests ──
351
352    /// Case: nested `Vec<Vec<T>>`.
353    fn vec_nested() -> CaseSpec;
354
355    // ── Bytes/binary data tests ──
356
357    /// Case: `Vec<u8>` binary data as array of numbers.
358    fn bytes_vec_u8() -> CaseSpec;
359
360    // ── Fixed-size array tests ──
361
362    /// Case: `[T; N]` fixed-size array.
363    fn array_fixed_size() -> CaseSpec;
364
365    // ── Unknown field handling tests ──
366
367    /// Case: unknown fields are silently skipped by default.
368    fn skip_unknown_fields() -> CaseSpec;
369
370    // ── String escape tests ──
371
372    /// Case: string with escape sequences (\n, \t, \", \\).
373    fn string_escapes() -> CaseSpec;
374    /// Case: string with extended escape sequences (\b, \f, \r, \u0001).
375    fn string_escapes_extended() -> CaseSpec;
376
377    // ── Unit type tests ──
378
379    /// Case: unit struct (zero-sized type).
380    fn unit_struct() -> CaseSpec;
381
382    // ── Third-party type tests ──
383
384    /// Case: uuid::Uuid type.
385    #[cfg(feature = "uuid")]
386    fn uuid() -> CaseSpec;
387
388    /// Case: ulid::Ulid type.
389    #[cfg(feature = "ulid")]
390    fn ulid() -> CaseSpec;
391
392    /// Case: camino::Utf8PathBuf type.
393    #[cfg(feature = "camino")]
394    fn camino_path() -> CaseSpec;
395
396    /// Case: ordered_float::OrderedFloat type.
397    #[cfg(feature = "ordered-float")]
398    fn ordered_float() -> CaseSpec;
399
400    /// Case: rust_decimal::Decimal type.
401    #[cfg(feature = "rust_decimal")]
402    fn rust_decimal() -> CaseSpec;
403
404    /// Case: time::OffsetDateTime type.
405    #[cfg(feature = "time")]
406    fn time_offset_datetime() -> CaseSpec;
407
408    /// Case: jiff::Timestamp type.
409    #[cfg(feature = "jiff02")]
410    fn jiff_timestamp() -> CaseSpec;
411
412    /// Case: jiff::civil::DateTime type.
413    #[cfg(feature = "jiff02")]
414    fn jiff_civil_datetime() -> CaseSpec;
415
416    /// Case: `chrono::DateTime<Utc>` type.
417    #[cfg(feature = "chrono")]
418    fn chrono_datetime_utc() -> CaseSpec;
419
420    /// Case: chrono::NaiveDateTime type.
421    #[cfg(feature = "chrono")]
422    fn chrono_naive_datetime() -> CaseSpec;
423
424    /// Case: chrono::NaiveDate type.
425    #[cfg(feature = "chrono")]
426    fn chrono_naive_date() -> CaseSpec;
427
428    /// Case: chrono::NaiveTime type.
429    #[cfg(feature = "chrono")]
430    fn chrono_naive_time() -> CaseSpec;
431
432    /// Case: `Vec<chrono::DateTime<Utc>>` - chrono in collections.
433    #[cfg(feature = "chrono")]
434    fn chrono_in_vec() -> CaseSpec;
435
436    /// Case: `chrono::Duration` type - serializes as (secs, nanos) tuple.
437    #[cfg(feature = "chrono")]
438    fn chrono_duration() -> CaseSpec;
439
440    /// Case: `chrono::Duration` with negative value - tests signed (secs, nanos) handling.
441    #[cfg(feature = "chrono")]
442    fn chrono_duration_negative() -> CaseSpec;
443
444    // ── Standard library time types ──
445
446    /// Case: `core::time::Duration` type - serializes as (secs, nanos) tuple.
447    fn std_duration() -> CaseSpec;
448
449    // ── Bytes crate tests ──
450
451    /// Case: `bytes::Bytes` type.
452    #[cfg(feature = "bytes")]
453    fn bytes_bytes() -> CaseSpec;
454
455    /// Case: `bytes::BytesMut` type.
456    #[cfg(feature = "bytes")]
457    fn bytes_bytes_mut() -> CaseSpec;
458
459    /// Case: `bytestring::ByteString` type.
460    #[cfg(feature = "bytestring")]
461    fn bytestring() -> CaseSpec;
462
463    /// Case: `compact_str::CompactString` type.
464    #[cfg(feature = "compact_str")]
465    fn compact_string() -> CaseSpec;
466
467    /// Case: `smartstring::SmartString` type.
468    #[cfg(feature = "smartstring")]
469    fn smartstring() -> CaseSpec;
470
471    /// Case: `smol_str::SmolStr` type.
472    #[cfg(feature = "smol_str")]
473    fn smol_str() -> CaseSpec;
474
475    /// Case: `iddqd::IdHashMap` type.
476    #[cfg(feature = "iddqd")]
477    fn iddqd_id_hash_map() -> CaseSpec;
478
479    /// Case: `iddqd::IdOrdMap` type.
480    #[cfg(feature = "iddqd")]
481    fn iddqd_id_ord_map() -> CaseSpec;
482
483    /// Case: `iddqd::BiHashMap` type.
484    #[cfg(feature = "iddqd")]
485    fn iddqd_bi_hash_map() -> CaseSpec;
486
487    /// Case: `iddqd::TriHashMap` type.
488    #[cfg(feature = "iddqd")]
489    fn iddqd_tri_hash_map() -> CaseSpec;
490
491    // ── Dynamic value tests ──
492
493    /// Case: `facet_value::Value` dynamic type - null.
494    #[cfg(feature = "facet-value")]
495    fn value_null() -> CaseSpec;
496
497    /// Case: `facet_value::Value` dynamic type - bool.
498    #[cfg(feature = "facet-value")]
499    fn value_bool() -> CaseSpec;
500
501    /// Case: `facet_value::Value` dynamic type - integer.
502    #[cfg(feature = "facet-value")]
503    fn value_integer() -> CaseSpec;
504
505    /// Case: `facet_value::Value` dynamic type - float.
506    #[cfg(feature = "facet-value")]
507    fn value_float() -> CaseSpec;
508
509    /// Case: `facet_value::Value` dynamic type - string.
510    #[cfg(feature = "facet-value")]
511    fn value_string() -> CaseSpec;
512
513    /// Case: `facet_value::Value` dynamic type - array.
514    #[cfg(feature = "facet-value")]
515    fn value_array() -> CaseSpec;
516
517    /// Case: `facet_value::Value` dynamic type - object.
518    #[cfg(feature = "facet-value")]
519    fn value_object() -> CaseSpec;
520}
521
522/// Execute suite cases; kept for convenience, but formats should register each
523/// case individually via [`all_cases`].
524pub fn run_suite<S: FormatSuite + 'static>() {
525    for case in all_cases::<S>() {
526        match case.run() {
527            CaseOutcome::Passed => {}
528            CaseOutcome::Skipped(reason) => {
529                eprintln!(
530                    "facet-format-suite: skipping {} for {} ({reason})",
531                    case.id,
532                    S::format_name()
533                );
534            }
535            CaseOutcome::Failed(msg) => {
536                panic!(
537                    "facet-format-suite case {} ({}) failed: {msg}",
538                    case.id, case.description
539                );
540            }
541        }
542    }
543}
544
545/// Enumerate every canonical case with its typed descriptor.
546pub fn all_cases<S: FormatSuite + 'static>() -> Vec<SuiteCase> {
547    vec![
548        // Core cases
549        SuiteCase::new::<S, StructSingleField>(&CASE_STRUCT_SINGLE_FIELD, S::struct_single_field),
550        SuiteCase::new::<S, Vec<u64>>(&CASE_SEQUENCE_NUMBERS, S::sequence_numbers),
551        SuiteCase::new::<S, Vec<MixedScalar>>(
552            &CASE_SEQUENCE_MIXED_SCALARS,
553            S::sequence_mixed_scalars,
554        ),
555        SuiteCase::new::<S, NestedParent>(&CASE_STRUCT_NESTED, S::struct_nested),
556        SuiteCase::new::<S, ComplexEnum>(&CASE_ENUM_COMPLEX, S::enum_complex),
557        // Attribute cases
558        SuiteCase::new::<S, RenamedField>(&CASE_ATTR_RENAME_FIELD, S::attr_rename_field),
559        SuiteCase::new::<S, CamelCaseStruct>(&CASE_ATTR_RENAME_ALL_CAMEL, S::attr_rename_all_camel),
560        SuiteCase::new::<S, WithDefault>(&CASE_ATTR_DEFAULT_FIELD, S::attr_default_field),
561        SuiteCase::new::<S, StructLevelDefault>(&CASE_ATTR_DEFAULT_STRUCT, S::attr_default_struct),
562        SuiteCase::new::<S, WithDefaultFunction>(
563            &CASE_ATTR_DEFAULT_FUNCTION,
564            S::attr_default_function,
565        ),
566        SuiteCase::new::<S, WithOption>(&CASE_OPTION_NONE, S::option_none),
567        SuiteCase::new::<S, WithOption>(&CASE_OPTION_SOME, S::option_some),
568        SuiteCase::new::<S, WithOption>(&CASE_OPTION_NULL, S::option_null),
569        SuiteCase::new::<S, WithSkipSerializing>(
570            &CASE_ATTR_SKIP_SERIALIZING,
571            S::attr_skip_serializing,
572        ),
573        SuiteCase::new::<S, WithSkipSerializingIf>(
574            &CASE_ATTR_SKIP_SERIALIZING_IF,
575            S::attr_skip_serializing_if,
576        ),
577        SuiteCase::new::<S, WithSkip>(&CASE_ATTR_SKIP, S::attr_skip),
578        // Enum tagging cases
579        SuiteCase::new::<S, InternallyTagged>(
580            &CASE_ENUM_INTERNALLY_TAGGED,
581            S::enum_internally_tagged,
582        ),
583        SuiteCase::new::<S, AdjacentlyTagged>(
584            &CASE_ENUM_ADJACENTLY_TAGGED,
585            S::enum_adjacently_tagged,
586        ),
587        // Advanced cases
588        SuiteCase::new::<S, FlattenOuter>(&CASE_STRUCT_FLATTEN, S::struct_flatten),
589        SuiteCase::new::<S, UserRecord>(&CASE_TRANSPARENT_NEWTYPE, S::transparent_newtype),
590        // Flatten variation cases
591        SuiteCase::new::<S, FlattenOptionalSome>(
592            &CASE_FLATTEN_OPTIONAL_SOME,
593            S::flatten_optional_some,
594        ),
595        SuiteCase::new::<S, FlattenOptionalNone>(
596            &CASE_FLATTEN_OPTIONAL_NONE,
597            S::flatten_optional_none,
598        ),
599        SuiteCase::new::<S, FlattenOverlapping>(
600            &CASE_FLATTEN_OVERLAPPING_FIELDS_ERROR,
601            S::flatten_overlapping_fields_error,
602        ),
603        SuiteCase::new::<S, FlattenLevel1>(&CASE_FLATTEN_MULTILEVEL, S::flatten_multilevel),
604        SuiteCase::new::<S, FlattenMultipleEnums>(
605            &CASE_FLATTEN_MULTIPLE_ENUMS,
606            S::flatten_multiple_enums,
607        ),
608        // Error cases
609        SuiteCase::new::<S, DenyUnknownStruct>(&CASE_DENY_UNKNOWN_FIELDS, S::deny_unknown_fields),
610        SuiteCase::new::<S, ExpectsInteger>(
611            &CASE_ERROR_TYPE_MISMATCH_STRING_TO_INT,
612            S::error_type_mismatch_string_to_int,
613        ),
614        SuiteCase::new::<S, ExpectsArray>(
615            &CASE_ERROR_TYPE_MISMATCH_OBJECT_TO_ARRAY,
616            S::error_type_mismatch_object_to_array,
617        ),
618        SuiteCase::new::<S, RequiresAllFields>(
619            &CASE_ERROR_MISSING_REQUIRED_FIELD,
620            S::error_missing_required_field,
621        ),
622        // Alias cases
623        SuiteCase::new::<S, WithAlias>(&CASE_ATTR_ALIAS, S::attr_alias),
624        // Attribute precedence cases
625        SuiteCase::new::<S, RenameVsAlias>(
626            &CASE_ATTR_RENAME_VS_ALIAS,
627            S::attr_rename_vs_alias_precedence,
628        ),
629        SuiteCase::new::<S, RenameAllKebab>(&CASE_ATTR_RENAME_ALL_KEBAB, S::attr_rename_all_kebab),
630        SuiteCase::new::<S, RenameAllScreaming>(
631            &CASE_ATTR_RENAME_ALL_SCREAMING,
632            S::attr_rename_all_screaming,
633        ),
634        SuiteCase::new::<S, RenameUnicode>(&CASE_ATTR_RENAME_UNICODE, S::attr_rename_unicode),
635        SuiteCase::new::<S, RenameSpecialChars>(
636            &CASE_ATTR_RENAME_SPECIAL_CHARS,
637            S::attr_rename_special_chars,
638        ),
639        // Proxy cases
640        SuiteCase::new::<S, ProxyInt>(&CASE_PROXY_CONTAINER, S::proxy_container),
641        SuiteCase::new::<S, ProxyFieldLevel>(&CASE_PROXY_FIELD_LEVEL, S::proxy_field_level),
642        SuiteCase::new::<S, ProxyInt>(&CASE_PROXY_VALIDATION_ERROR, S::proxy_validation_error),
643        SuiteCase::new::<S, ProxyWithOption>(&CASE_PROXY_WITH_OPTION, S::proxy_with_option),
644        SuiteCase::new::<S, ProxyEnum>(&CASE_PROXY_WITH_ENUM, S::proxy_with_enum),
645        SuiteCase::new::<S, TransparentProxy>(
646            &CASE_PROXY_WITH_TRANSPARENT,
647            S::proxy_with_transparent,
648        ),
649        SuiteCase::new::<S, OpaqueProxyWrapper>(&CASE_OPAQUE_PROXY, S::opaque_proxy),
650        SuiteCase::new::<S, OpaqueProxyOptionWrapper>(
651            &CASE_OPAQUE_PROXY_OPTION,
652            S::opaque_proxy_option,
653        ),
654        // Transparent cases
655        SuiteCase::new::<S, OuterTransparent>(
656            &CASE_TRANSPARENT_MULTILEVEL,
657            S::transparent_multilevel,
658        ),
659        SuiteCase::new::<S, TransparentOption>(&CASE_TRANSPARENT_OPTION, S::transparent_option),
660        SuiteCase::new::<S, TransparentNonZero>(&CASE_TRANSPARENT_NONZERO, S::transparent_nonzero),
661        // Scalar cases
662        SuiteCase::new::<S, BoolWrapper>(&CASE_SCALAR_BOOL, S::scalar_bool),
663        SuiteCase::new::<S, IntegerTypes>(&CASE_SCALAR_INTEGERS, S::scalar_integers),
664        SuiteCase::new::<S, FloatTypes>(&CASE_SCALAR_FLOATS, S::scalar_floats),
665        SuiteCase::new::<S, FloatTypesScientific>(
666            &CASE_SCALAR_FLOATS_SCIENTIFIC,
667            S::scalar_floats_scientific,
668        ),
669        // Network type cases
670        #[cfg(feature = "net")]
671        SuiteCase::new::<S, IpAddrV4Wrapper>(&CASE_NET_IP_ADDR_V4, S::net_ip_addr_v4),
672        #[cfg(feature = "net")]
673        SuiteCase::new::<S, IpAddrV6Wrapper>(&CASE_NET_IP_ADDR_V6, S::net_ip_addr_v6),
674        #[cfg(feature = "net")]
675        SuiteCase::new::<S, Ipv4AddrWrapper>(&CASE_NET_IPV4_ADDR, S::net_ipv4_addr),
676        #[cfg(feature = "net")]
677        SuiteCase::new::<S, Ipv6AddrWrapper>(&CASE_NET_IPV6_ADDR, S::net_ipv6_addr),
678        #[cfg(feature = "net")]
679        SuiteCase::new::<S, SocketAddrV4Wrapper>(&CASE_NET_SOCKET_ADDR_V4, S::net_socket_addr_v4),
680        #[cfg(feature = "net")]
681        SuiteCase::new::<S, SocketAddrV6Wrapper>(&CASE_NET_SOCKET_ADDR_V6, S::net_socket_addr_v6),
682        #[cfg(feature = "net")]
683        SuiteCase::new::<S, SocketAddrV4ExplicitWrapper>(
684            &CASE_NET_SOCKET_ADDR_V4_EXPLICIT,
685            S::net_socket_addr_v4_explicit,
686        ),
687        #[cfg(feature = "net")]
688        SuiteCase::new::<S, SocketAddrV6ExplicitWrapper>(
689            &CASE_NET_SOCKET_ADDR_V6_EXPLICIT,
690            S::net_socket_addr_v6_explicit,
691        ),
692        // Collection cases
693        SuiteCase::new::<S, MapWrapper>(&CASE_MAP_STRING_KEYS, S::map_string_keys),
694        SuiteCase::new::<S, TupleWrapper>(&CASE_TUPLE_SIMPLE, S::tuple_simple),
695        SuiteCase::new::<S, NestedTupleWrapper>(&CASE_TUPLE_NESTED, S::tuple_nested),
696        SuiteCase::new::<S, EmptyTupleWrapper>(&CASE_TUPLE_EMPTY, S::tuple_empty),
697        SuiteCase::new::<S, SingleElementTupleWrapper>(
698            &CASE_TUPLE_SINGLE_ELEMENT,
699            S::tuple_single_element,
700        ),
701        SuiteCase::new::<S, TupleVariantEnum>(&CASE_TUPLE_STRUCT_VARIANT, S::tuple_struct_variant),
702        SuiteCase::new::<S, NewtypeVariantEnum>(
703            &CASE_TUPLE_NEWTYPE_VARIANT,
704            S::tuple_newtype_variant,
705        ),
706        // Enum variant cases
707        SuiteCase::new::<S, UnitVariantEnum>(&CASE_ENUM_UNIT_VARIANT, S::enum_unit_variant),
708        SuiteCase::new::<S, NumericEnum>(&CASE_NUMERIC_ENUM, S::numeric_enum),
709        SuiteCase::new::<S, UntaggedEnum>(&CASE_ENUM_UNTAGGED, S::enum_untagged),
710        SuiteCase::new::<S, EnumVariantRename>(&CASE_ENUM_VARIANT_RENAME, S::enum_variant_rename),
711        // Numeric enum variation cases
712        SuiteCase::new::<S, SignedNumericEnum>(&CASE_SIGNED_NUMERIC_ENUM, S::signed_numeric_enum),
713        SuiteCase::new::<S, NumericEnum>(&CASE_INFERRED_NUMERIC_ENUM, S::inferred_numeric_enum),
714        // Untagged enum variation cases
715        SuiteCase::new::<S, UntaggedWithNull>(&CASE_UNTAGGED_WITH_NULL, S::untagged_with_null),
716        SuiteCase::new::<S, UntaggedNewtype>(
717            &CASE_UNTAGGED_NEWTYPE_VARIANT,
718            S::untagged_newtype_variant,
719        ),
720        SuiteCase::new::<S, UntaggedAsField>(&CASE_UNTAGGED_AS_FIELD, S::untagged_as_field),
721        SuiteCase::new::<S, UntaggedUnitOnly>(&CASE_UNTAGGED_UNIT_ONLY, S::untagged_unit_only),
722        // Smart pointer cases
723        SuiteCase::new::<S, BoxWrapper>(&CASE_BOX_WRAPPER, S::box_wrapper),
724        SuiteCase::new::<S, ArcWrapper>(&CASE_ARC_WRAPPER, S::arc_wrapper),
725        SuiteCase::new::<S, RcWrapper>(&CASE_RC_WRAPPER, S::rc_wrapper),
726        SuiteCase::new::<S, BoxStrWrapper>(&CASE_BOX_STR, S::box_str),
727        SuiteCase::new::<S, ArcStrWrapper>(&CASE_ARC_STR, S::arc_str),
728        SuiteCase::new::<S, RcStrWrapper>(&CASE_RC_STR, S::rc_str),
729        SuiteCase::new::<S, ArcSliceWrapper>(&CASE_ARC_SLICE, S::arc_slice),
730        // Yoke cases
731        #[cfg(feature = "yoke")]
732        SuiteCase::new::<S, YokeWrapper>(&CASE_YOKE_COW_STR, S::yoke_cow_str),
733        #[cfg(feature = "yoke")]
734        SuiteCase::new::<S, YokingWrapper>(&CASE_YOKE_CUSTOM, S::yoke_custom),
735        // Set cases
736        SuiteCase::new::<S, SetWrapper>(&CASE_SET_BTREE, S::set_btree),
737        // Extended numeric cases
738        SuiteCase::new::<S, IntegerTypes16>(&CASE_SCALAR_INTEGERS_16, S::scalar_integers_16),
739        SuiteCase::new::<S, IntegerTypes128>(&CASE_SCALAR_INTEGERS_128, S::scalar_integers_128),
740        SuiteCase::new::<S, IntegerTypesSize>(&CASE_SCALAR_INTEGERS_SIZE, S::scalar_integers_size),
741        // NonZero cases
742        SuiteCase::new::<S, NonZeroTypes>(&CASE_NONZERO_INTEGERS, S::nonzero_integers),
743        SuiteCase::new::<S, NonZeroTypesExtended>(
744            &CASE_NONZERO_INTEGERS_EXTENDED,
745            S::nonzero_integers_extended,
746        ),
747        // Borrowed string cases
748        SuiteCase::new::<S, CowStrWrapper>(&CASE_COW_STR, S::cow_str),
749        // Bytes/binary data cases
750        SuiteCase::new::<S, BytesWrapper>(&CASE_BYTES_VEC_U8, S::bytes_vec_u8),
751        // Fixed-size array cases
752        SuiteCase::new::<S, ArrayWrapper>(&CASE_ARRAY_FIXED_SIZE, S::array_fixed_size),
753        // Unknown field handling cases
754        SuiteCase::new::<S, SkipUnknownStruct>(&CASE_SKIP_UNKNOWN_FIELDS, S::skip_unknown_fields),
755        // String escape cases
756        SuiteCase::new::<S, StringEscapes>(&CASE_STRING_ESCAPES, S::string_escapes),
757        SuiteCase::new::<S, StringEscapesExtended>(
758            &CASE_STRING_ESCAPES_EXTENDED,
759            S::string_escapes_extended,
760        ),
761        // Unit type cases
762        SuiteCase::new::<S, UnitStruct>(&CASE_UNIT_STRUCT, S::unit_struct),
763        // Newtype cases
764        SuiteCase::new::<S, NewtypeU64Wrapper>(&CASE_NEWTYPE_U64, S::newtype_u64),
765        SuiteCase::new::<S, NewtypeStringWrapper>(&CASE_NEWTYPE_STRING, S::newtype_string),
766        // Char cases
767        SuiteCase::new::<S, CharWrapper>(&CASE_CHAR_SCALAR, S::char_scalar),
768        // HashSet cases
769        SuiteCase::new::<S, HashSetWrapper>(&CASE_HASHSET, S::hashset),
770        // Nested collection cases
771        SuiteCase::new::<S, NestedVecWrapper>(&CASE_VEC_NESTED, S::vec_nested),
772        // Third-party type cases
773        #[cfg(feature = "uuid")]
774        SuiteCase::new::<S, UuidWrapper>(&CASE_UUID, S::uuid),
775        #[cfg(feature = "ulid")]
776        SuiteCase::new::<S, UlidWrapper>(&CASE_ULID, S::ulid),
777        #[cfg(feature = "camino")]
778        SuiteCase::new::<S, CaminoWrapper>(&CASE_CAMINO_PATH, S::camino_path),
779        #[cfg(feature = "ordered-float")]
780        SuiteCase::new::<S, OrderedFloatWrapper>(&CASE_ORDERED_FLOAT, S::ordered_float),
781        #[cfg(feature = "rust_decimal")]
782        SuiteCase::new::<S, RustDecimalWrapper>(&CASE_RUST_DECIMAL, S::rust_decimal),
783        #[cfg(feature = "time")]
784        SuiteCase::new::<S, TimeOffsetDateTimeWrapper>(
785            &CASE_TIME_OFFSET_DATETIME,
786            S::time_offset_datetime,
787        ),
788        #[cfg(feature = "jiff02")]
789        SuiteCase::new::<S, JiffTimestampWrapper>(&CASE_JIFF_TIMESTAMP, S::jiff_timestamp),
790        #[cfg(feature = "jiff02")]
791        SuiteCase::new::<S, JiffCivilDateTimeWrapper>(
792            &CASE_JIFF_CIVIL_DATETIME,
793            S::jiff_civil_datetime,
794        ),
795        #[cfg(feature = "chrono")]
796        SuiteCase::new::<S, ChronoDateTimeUtcWrapper>(
797            &CASE_CHRONO_DATETIME_UTC,
798            S::chrono_datetime_utc,
799        ),
800        #[cfg(feature = "chrono")]
801        SuiteCase::new::<S, ChronoNaiveDateTimeWrapper>(
802            &CASE_CHRONO_NAIVE_DATETIME,
803            S::chrono_naive_datetime,
804        ),
805        #[cfg(feature = "chrono")]
806        SuiteCase::new::<S, ChronoNaiveDateWrapper>(&CASE_CHRONO_NAIVE_DATE, S::chrono_naive_date),
807        #[cfg(feature = "chrono")]
808        SuiteCase::new::<S, ChronoNaiveTimeWrapper>(&CASE_CHRONO_NAIVE_TIME, S::chrono_naive_time),
809        #[cfg(feature = "chrono")]
810        SuiteCase::new::<S, ChronoInVecWrapper>(&CASE_CHRONO_IN_VEC, S::chrono_in_vec),
811        #[cfg(feature = "chrono")]
812        SuiteCase::new::<S, ChronoDurationWrapper>(&CASE_CHRONO_DURATION, S::chrono_duration),
813        #[cfg(feature = "chrono")]
814        SuiteCase::new::<S, ChronoDurationNegativeWrapper>(
815            &CASE_CHRONO_DURATION_NEGATIVE,
816            S::chrono_duration_negative,
817        ),
818        // Standard library time cases
819        SuiteCase::new::<S, StdDurationWrapper>(&CASE_STD_DURATION, S::std_duration),
820        // Bytes crate cases
821        #[cfg(feature = "bytes")]
822        SuiteCase::new::<S, BytesBytesWrapper>(&CASE_BYTES_BYTES, S::bytes_bytes),
823        #[cfg(feature = "bytes")]
824        SuiteCase::new::<S, BytesBytesMutWrapper>(&CASE_BYTES_BYTES_MUT, S::bytes_bytes_mut),
825        // String optimization crate cases
826        #[cfg(feature = "bytestring")]
827        SuiteCase::new::<S, ByteStringWrapper>(&CASE_BYTESTRING, S::bytestring),
828        #[cfg(feature = "compact_str")]
829        SuiteCase::new::<S, CompactStringWrapper>(&CASE_COMPACT_STRING, S::compact_string),
830        #[cfg(feature = "smartstring")]
831        SuiteCase::new::<S, SmartStringWrapper>(&CASE_SMARTSTRING, S::smartstring),
832        #[cfg(feature = "smol_str")]
833        SuiteCase::new::<S, SmolStrWrapper>(&CASE_SMOL_STR, S::smol_str),
834        #[cfg(feature = "iddqd")]
835        SuiteCase::new::<S, IddqdIdHashMapWrapper>(&CASE_IDDQD_ID_HASH_MAP, S::iddqd_id_hash_map),
836        #[cfg(feature = "iddqd")]
837        SuiteCase::new::<S, IddqdIdOrdMapWrapper>(&CASE_IDDQD_ID_ORD_MAP, S::iddqd_id_ord_map),
838        #[cfg(feature = "iddqd")]
839        SuiteCase::new::<S, IddqdBiHashMapWrapper>(&CASE_IDDQD_BI_HASH_MAP, S::iddqd_bi_hash_map),
840        #[cfg(feature = "iddqd")]
841        SuiteCase::new::<S, IddqdTriHashMapWrapper>(
842            &CASE_IDDQD_TRI_HASH_MAP,
843            S::iddqd_tri_hash_map,
844        ),
845        // Dynamic value cases
846        #[cfg(feature = "facet-value")]
847        SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_NULL, S::value_null),
848        #[cfg(feature = "facet-value")]
849        SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_BOOL, S::value_bool),
850        #[cfg(feature = "facet-value")]
851        SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_INTEGER, S::value_integer),
852        #[cfg(feature = "facet-value")]
853        SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_FLOAT, S::value_float),
854        #[cfg(feature = "facet-value")]
855        SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_STRING, S::value_string),
856        #[cfg(feature = "facet-value")]
857        SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_ARRAY, S::value_array),
858        #[cfg(feature = "facet-value")]
859        SuiteCase::new::<S, facet_value::Value>(&CASE_VALUE_OBJECT, S::value_object),
860    ]
861}
862
863/// How to compare the deserialized value against the expected value.
864#[derive(Debug, Clone, Copy, Default)]
865pub enum CompareMode {
866    /// Use `assert_same!` (reflection-based comparison) - default for most types.
867    #[default]
868    Reflection,
869    /// Use `assert_eq!` (PartialEq comparison) - required for types containing opaque fields.
870    PartialEq,
871}
872
873/// Specification returned by each trait method.
874#[derive(Debug, Clone)]
875pub struct CaseSpec {
876    payload: CasePayload,
877    note: Option<&'static str>,
878    roundtrip: RoundtripSpec,
879    compare_mode: CompareMode,
880}
881
882impl CaseSpec {
883    /// Provide raw bytes for the case input.
884    pub const fn from_bytes(input: &'static [u8]) -> Self {
885        Self {
886            payload: CasePayload::Input(input),
887            note: None,
888            roundtrip: RoundtripSpec::Enabled,
889            compare_mode: CompareMode::Reflection,
890        }
891    }
892
893    /// Convenience for UTF-8 inputs.
894    #[allow(clippy::should_implement_trait)]
895    pub const fn from_str(input: &'static str) -> Self {
896        Self::from_bytes(input.as_bytes())
897    }
898
899    /// Mark the case as skipped for this format, documenting the reason.
900    pub const fn skip(reason: &'static str) -> Self {
901        Self {
902            payload: CasePayload::Skip { reason },
903            note: None,
904            roundtrip: RoundtripSpec::Enabled,
905            compare_mode: CompareMode::Reflection,
906        }
907    }
908
909    /// Attach an optional note for diagnostics.
910    pub const fn with_note(mut self, note: &'static str) -> Self {
911        self.note = Some(note);
912        self
913    }
914
915    /// Disable round-trip checks for this case, documenting the reason.
916    pub const fn without_roundtrip(mut self, reason: &'static str) -> Self {
917        self.roundtrip = RoundtripSpec::Disabled { reason };
918        self
919    }
920
921    /// Use PartialEq comparison instead of reflection-based comparison.
922    /// Required for types containing opaque fields that can't be compared via reflection.
923    pub const fn with_partial_eq(mut self) -> Self {
924        self.compare_mode = CompareMode::PartialEq;
925        self
926    }
927
928    /// Expect deserialization to fail with an error containing the given substring.
929    pub const fn expect_error(input: &'static str, error_contains: &'static str) -> Self {
930        Self {
931            payload: CasePayload::ExpectError {
932                input: input.as_bytes(),
933                error_contains,
934            },
935            note: None,
936            roundtrip: RoundtripSpec::Disabled {
937                reason: "error case",
938            },
939            compare_mode: CompareMode::Reflection,
940        }
941    }
942
943    /// Provide dynamically-generated bytes for the case input.
944    ///
945    /// Use this for binary formats where input is generated at runtime
946    /// (e.g., from a reference implementation like rmp-serde for MsgPack).
947    pub const fn from_bytes_vec(input: Vec<u8>) -> Self {
948        Self {
949            payload: CasePayload::DynamicInput(input),
950            note: None,
951            roundtrip: RoundtripSpec::Enabled,
952            compare_mode: CompareMode::Reflection,
953        }
954    }
955}
956
957#[derive(Debug, Clone)]
958enum CasePayload {
959    Input(&'static [u8]),
960    /// Dynamic input: bytes generated at runtime (e.g., from a reference implementation).
961    DynamicInput(Vec<u8>),
962    Skip {
963        reason: &'static str,
964    },
965    /// Expect deserialization to fail with an error containing the given substring.
966    ExpectError {
967        input: &'static [u8],
968        error_contains: &'static str,
969    },
970}
971
972#[derive(Debug, Clone)]
973enum RoundtripSpec {
974    Enabled,
975    Disabled { reason: &'static str },
976}
977
978struct CaseDescriptor<T> {
979    id: &'static str,
980    description: &'static str,
981    expected: fn() -> T,
982}
983
984#[derive(Debug)]
985pub enum CaseOutcome {
986    Passed,
987    Skipped(&'static str),
988    Failed(String),
989}
990
991pub struct SuiteCase {
992    pub id: &'static str,
993    pub description: &'static str,
994    skip_reason: Option<&'static str>,
995    runner: Box<dyn Fn() -> CaseOutcome + Send + Sync + 'static>,
996    #[cfg(feature = "tokio")]
997    async_runner: Box<dyn Fn() -> CaseOutcome + Send + Sync + 'static>,
998}
999
1000impl SuiteCase {
1001    #[cfg(not(feature = "tokio"))]
1002    fn new<S, T>(desc: &'static CaseDescriptor<T>, provider: fn() -> CaseSpec) -> Self
1003    where
1004        S: FormatSuite,
1005        for<'facet> T: Facet<'facet>,
1006        T: Debug + PartialEq + 'static,
1007    {
1008        let spec = provider();
1009        let skip_reason = match spec.payload {
1010            CasePayload::Skip { reason } => Some(reason),
1011            _ => None,
1012        };
1013        let runner_spec = spec.clone();
1014        let runner = move || execute_case::<S, T>(desc, runner_spec.clone());
1015
1016        Self {
1017            id: desc.id,
1018            description: desc.description,
1019            skip_reason,
1020            runner: Box::new(runner),
1021        }
1022    }
1023
1024    #[cfg(feature = "tokio")]
1025    fn new<S, T>(desc: &'static CaseDescriptor<T>, provider: fn() -> CaseSpec) -> Self
1026    where
1027        S: FormatSuite + 'static,
1028        for<'facet> T: Facet<'facet>,
1029        T: Debug + PartialEq + 'static,
1030    {
1031        let spec = provider();
1032        let skip_reason = match spec.payload {
1033            CasePayload::Skip { reason } => Some(reason),
1034            _ => None,
1035        };
1036        let runner_spec = spec.clone();
1037        let runner = move || execute_case::<S, T>(desc, runner_spec.clone());
1038
1039        #[cfg(feature = "tokio")]
1040        let async_runner = {
1041            let async_spec = spec.clone();
1042            Box::new(move || {
1043                let spec = async_spec.clone();
1044                // Use current_thread runtime to avoid Send requirements
1045                let rt = tokio::runtime::Builder::new_current_thread()
1046                    .enable_all()
1047                    .build()
1048                    .expect("failed to create tokio runtime");
1049                rt.block_on(execute_case_async::<S, T>(desc, spec))
1050            }) as Box<dyn Fn() -> CaseOutcome + Send + Sync + 'static>
1051        };
1052
1053        Self {
1054            id: desc.id,
1055            description: desc.description,
1056            skip_reason,
1057            runner: Box::new(runner),
1058            #[cfg(feature = "tokio")]
1059            async_runner,
1060        }
1061    }
1062
1063    pub fn run(&self) -> CaseOutcome {
1064        (self.runner)()
1065    }
1066
1067    /// Run the async version of this test case.
1068    /// Uses a current-thread tokio runtime internally.
1069    #[cfg(feature = "tokio")]
1070    pub fn run_async(&self) -> CaseOutcome {
1071        (self.async_runner)()
1072    }
1073
1074    pub const fn skip_reason(&self) -> Option<&'static str> {
1075        self.skip_reason
1076    }
1077}
1078
1079fn execute_case<S, T>(desc: &'static CaseDescriptor<T>, spec: CaseSpec) -> CaseOutcome
1080where
1081    S: FormatSuite,
1082    for<'facet> T: Facet<'facet>,
1083    T: Debug + PartialEq,
1084{
1085    let note = spec.note;
1086    let compare_mode = spec.compare_mode;
1087    let roundtrip_disabled_reason = match spec.roundtrip {
1088        RoundtripSpec::Enabled => None,
1089        RoundtripSpec::Disabled { reason } => Some(reason),
1090    };
1091    let highlight_language = S::highlight_language();
1092    match spec.payload {
1093        CasePayload::Skip { reason } => CaseOutcome::Skipped(reason),
1094        CasePayload::Input(input) => {
1095            let expected = (desc.expected)();
1096            tracing::info!("=================== Deserializing");
1097            let actual = match S::deserialize::<T>(input) {
1098                Ok(value) => value,
1099                Err(err) => return CaseOutcome::Failed(err.to_string()),
1100            };
1101
1102            tracing::info!("=================== Emitting showcase");
1103            emit_case_showcase::<S, T>(
1104                desc,
1105                note,
1106                roundtrip_disabled_reason,
1107                input,
1108                highlight_language,
1109                &actual,
1110            );
1111
1112            tracing::info!("=================== Comparing deserialized value");
1113            // Compare deserialized value against expected
1114            let first_assert = panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1115                CompareMode::Reflection => {
1116                    assert_same!(
1117                        actual,
1118                        expected,
1119                        "facet-format-suite {} ({}) produced unexpected value",
1120                        desc.id,
1121                        desc.description
1122                    );
1123                }
1124                CompareMode::PartialEq => {
1125                    assert_eq!(
1126                        actual, expected,
1127                        "facet-format-suite {} ({}) produced unexpected value",
1128                        desc.id, desc.description
1129                    );
1130                }
1131            }));
1132            if let Err(payload) = first_assert {
1133                return CaseOutcome::Failed(format_panic(payload));
1134            }
1135
1136            if roundtrip_disabled_reason.is_some() {
1137                return CaseOutcome::Passed;
1138            }
1139
1140            tracing::info!("=================== Serializing");
1141            let Some(serialized) = S::serialize(&actual) else {
1142                return CaseOutcome::Passed;
1143            };
1144
1145            let serialized = match serialized {
1146                Ok(bytes) => bytes,
1147                Err(msg) => {
1148                    return CaseOutcome::Failed(format!(
1149                        "facet-format-suite {} ({}) serialization failed: {msg}",
1150                        desc.id, desc.description
1151                    ));
1152                }
1153            };
1154
1155            if let Ok(serialized) = std::str::from_utf8(&serialized) {
1156                tracing::info!(%serialized, "=================== Serialized result");
1157            }
1158
1159            let roundtripped = match S::deserialize::<T>(&serialized) {
1160                Ok(value) => value,
1161                Err(err) => {
1162                    return CaseOutcome::Failed(format!(
1163                        "facet-format-suite {} ({}) round-trip deserialize failed: {err}",
1164                        desc.id, desc.description
1165                    ));
1166                }
1167            };
1168
1169            // Compare round-tripped value against original
1170            match panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1171                CompareMode::Reflection => {
1172                    assert_same!(
1173                        roundtripped,
1174                        actual,
1175                        "facet-format-suite {} ({}) round-trip mismatch",
1176                        desc.id,
1177                        desc.description
1178                    );
1179                }
1180                CompareMode::PartialEq => {
1181                    assert_eq!(
1182                        roundtripped, actual,
1183                        "facet-format-suite {} ({}) round-trip mismatch",
1184                        desc.id, desc.description
1185                    );
1186                }
1187            })) {
1188                Ok(_) => CaseOutcome::Passed,
1189                Err(payload) => CaseOutcome::Failed(format_panic(payload)),
1190            }
1191        }
1192        CasePayload::DynamicInput(ref input) => {
1193            let expected = (desc.expected)();
1194            let actual = match S::deserialize::<T>(input) {
1195                Ok(value) => value,
1196                Err(err) => return CaseOutcome::Failed(err.to_string()),
1197            };
1198
1199            emit_case_showcase_dynamic::<S, T>(
1200                desc,
1201                note,
1202                roundtrip_disabled_reason,
1203                input,
1204                &actual,
1205            );
1206
1207            // Compare deserialized value against expected
1208            let first_assert = panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1209                CompareMode::Reflection => {
1210                    assert_same!(
1211                        actual,
1212                        expected,
1213                        "facet-format-suite {} ({}) produced unexpected value",
1214                        desc.id,
1215                        desc.description
1216                    );
1217                }
1218                CompareMode::PartialEq => {
1219                    assert_eq!(
1220                        actual, expected,
1221                        "facet-format-suite {} ({}) produced unexpected value",
1222                        desc.id, desc.description
1223                    );
1224                }
1225            }));
1226            if let Err(payload) = first_assert {
1227                return CaseOutcome::Failed(format_panic(payload));
1228            }
1229
1230            if roundtrip_disabled_reason.is_some() {
1231                return CaseOutcome::Passed;
1232            }
1233
1234            let Some(serialized) = S::serialize(&actual) else {
1235                return CaseOutcome::Passed;
1236            };
1237
1238            let serialized = match serialized {
1239                Ok(bytes) => bytes,
1240                Err(msg) => {
1241                    return CaseOutcome::Failed(format!(
1242                        "facet-format-suite {} ({}) serialization failed: {msg}",
1243                        desc.id, desc.description
1244                    ));
1245                }
1246            };
1247
1248            let roundtripped = match S::deserialize::<T>(&serialized) {
1249                Ok(value) => value,
1250                Err(err) => {
1251                    return CaseOutcome::Failed(format!(
1252                        "facet-format-suite {} ({}) round-trip deserialize failed: {err}",
1253                        desc.id, desc.description
1254                    ));
1255                }
1256            };
1257
1258            // Compare round-tripped value against original
1259            match panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1260                CompareMode::Reflection => {
1261                    assert_same!(
1262                        roundtripped,
1263                        actual,
1264                        "facet-format-suite {} ({}) round-trip mismatch",
1265                        desc.id,
1266                        desc.description
1267                    );
1268                }
1269                CompareMode::PartialEq => {
1270                    assert_eq!(
1271                        roundtripped, actual,
1272                        "facet-format-suite {} ({}) round-trip mismatch",
1273                        desc.id, desc.description
1274                    );
1275                }
1276            })) {
1277                Ok(_) => CaseOutcome::Passed,
1278                Err(payload) => CaseOutcome::Failed(format_panic(payload)),
1279            }
1280        }
1281        CasePayload::ExpectError {
1282            input,
1283            error_contains,
1284        } => {
1285            emit_error_case_showcase::<S>(
1286                desc.id,
1287                desc.description,
1288                note,
1289                input,
1290                highlight_language,
1291                error_contains,
1292            );
1293
1294            match S::deserialize::<T>(input) {
1295                Ok(_) => CaseOutcome::Failed(format!(
1296                    "facet-format-suite {} ({}) expected error containing '{}' but deserialization succeeded",
1297                    desc.id, desc.description, error_contains
1298                )),
1299                Err(err) => {
1300                    let err_str = err.to_string();
1301                    if err_str.contains(error_contains) {
1302                        CaseOutcome::Passed
1303                    } else {
1304                        CaseOutcome::Failed(format!(
1305                            "facet-format-suite {} ({}) expected error containing '{}' but got: {}",
1306                            desc.id, desc.description, error_contains, err_str
1307                        ))
1308                    }
1309                }
1310            }
1311        }
1312    }
1313}
1314
1315fn format_panic(payload: Box<dyn Any + Send>) -> String {
1316    if let Some(msg) = payload.downcast_ref::<&str>() {
1317        msg.to_string()
1318    } else if let Some(msg) = payload.downcast_ref::<String>() {
1319        msg.clone()
1320    } else {
1321        "panic with non-string payload".into()
1322    }
1323}
1324
1325#[cfg(feature = "tokio")]
1326async fn execute_case_async<S, T>(desc: &'static CaseDescriptor<T>, spec: CaseSpec) -> CaseOutcome
1327where
1328    S: FormatSuite,
1329    for<'facet> T: Facet<'facet>,
1330    T: Debug + PartialEq,
1331{
1332    let compare_mode = spec.compare_mode;
1333    match spec.payload {
1334        CasePayload::Skip { reason } => CaseOutcome::Skipped(reason),
1335        CasePayload::Input(input) => {
1336            // Try async deserialization
1337            let result = S::deserialize_async::<T>(input).await;
1338            let actual = match result {
1339                None => {
1340                    // Format doesn't support async - skip this test
1341                    return CaseOutcome::Skipped("async deserialization not supported");
1342                }
1343                Some(Ok(value)) => value,
1344                Some(Err(err)) => return CaseOutcome::Failed(err.to_string()),
1345            };
1346
1347            let expected = (desc.expected)();
1348
1349            // Compare deserialized value against expected
1350            let first_assert = panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1351                CompareMode::Reflection => {
1352                    assert_same!(
1353                        actual,
1354                        expected,
1355                        "facet-format-suite {} ({}) async produced unexpected value",
1356                        desc.id,
1357                        desc.description
1358                    );
1359                }
1360                CompareMode::PartialEq => {
1361                    assert_eq!(
1362                        actual, expected,
1363                        "facet-format-suite {} ({}) async produced unexpected value",
1364                        desc.id, desc.description
1365                    );
1366                }
1367            }));
1368            if let Err(payload) = first_assert {
1369                return CaseOutcome::Failed(format_panic(payload));
1370            }
1371
1372            // Note: We skip round-trip testing for async - it's tested by sync version
1373            CaseOutcome::Passed
1374        }
1375        CasePayload::DynamicInput(ref input) => {
1376            // Try async deserialization with dynamic input
1377            let result = S::deserialize_async::<T>(input).await;
1378            let actual = match result {
1379                None => {
1380                    // Format doesn't support async - skip this test
1381                    return CaseOutcome::Skipped("async deserialization not supported");
1382                }
1383                Some(Ok(value)) => value,
1384                Some(Err(err)) => return CaseOutcome::Failed(err.to_string()),
1385            };
1386
1387            let expected = (desc.expected)();
1388
1389            // Compare deserialized value against expected
1390            let first_assert = panic::catch_unwind(AssertUnwindSafe(|| match compare_mode {
1391                CompareMode::Reflection => {
1392                    assert_same!(
1393                        actual,
1394                        expected,
1395                        "facet-format-suite {} ({}) async produced unexpected value",
1396                        desc.id,
1397                        desc.description
1398                    );
1399                }
1400                CompareMode::PartialEq => {
1401                    assert_eq!(
1402                        actual, expected,
1403                        "facet-format-suite {} ({}) async produced unexpected value",
1404                        desc.id, desc.description
1405                    );
1406                }
1407            }));
1408            if let Err(payload) = first_assert {
1409                return CaseOutcome::Failed(format_panic(payload));
1410            }
1411
1412            // Note: We skip round-trip testing for async - it's tested by sync version
1413            CaseOutcome::Passed
1414        }
1415        CasePayload::ExpectError {
1416            input,
1417            error_contains,
1418        } => {
1419            let result = S::deserialize_async::<T>(input).await;
1420            match result {
1421                None => CaseOutcome::Skipped("async deserialization not supported"),
1422                Some(Ok(_)) => CaseOutcome::Failed(format!(
1423                    "facet-format-suite {} ({}) async expected error containing '{}' but deserialization succeeded",
1424                    desc.id, desc.description, error_contains
1425                )),
1426                Some(Err(err)) => {
1427                    let err_str = err.to_string();
1428                    if err_str.contains(error_contains) {
1429                        CaseOutcome::Passed
1430                    } else {
1431                        CaseOutcome::Failed(format!(
1432                            "facet-format-suite {} ({}) async expected error containing '{}' but got: {}",
1433                            desc.id, desc.description, error_contains, err_str
1434                        ))
1435                    }
1436                }
1437            }
1438        }
1439    }
1440}
1441
1442const CASE_STRUCT_SINGLE_FIELD: CaseDescriptor<StructSingleField> = CaseDescriptor {
1443    id: "struct::single_field",
1444    description: "single-field object parsed into StructSingleField",
1445    expected: || StructSingleField {
1446        name: "facet".into(),
1447    },
1448};
1449
1450const CASE_SEQUENCE_NUMBERS: CaseDescriptor<Vec<u64>> = CaseDescriptor {
1451    id: "sequence::numbers",
1452    description: "array of unsigned integers parsed into Vec<u64>",
1453    expected: || vec![1, 2, 3],
1454};
1455
1456const CASE_SEQUENCE_MIXED_SCALARS: CaseDescriptor<Vec<MixedScalar>> = CaseDescriptor {
1457    id: "sequence::mixed_scalars",
1458    description: "array of heterogeneous scalars parsed into Vec<MixedScalar>",
1459    expected: || {
1460        vec![
1461            MixedScalar::Signed(-1),
1462            MixedScalar::Float(4.625),
1463            MixedScalar::Null,
1464            MixedScalar::Bool(true),
1465        ]
1466    },
1467};
1468
1469const CASE_STRUCT_NESTED: CaseDescriptor<NestedParent> = CaseDescriptor {
1470    id: "struct::nested",
1471    description: "struct containing nested child and tag list",
1472    expected: || NestedParent {
1473        id: 42,
1474        child: NestedChild {
1475            code: "alpha".into(),
1476            active: true,
1477        },
1478        tags: vec!["core".into(), "json".into()],
1479    },
1480};
1481
1482const CASE_ENUM_COMPLEX: CaseDescriptor<ComplexEnum> = CaseDescriptor {
1483    id: "enum::complex",
1484    description: "enum with unit, tuple, and struct variants",
1485    expected: || ComplexEnum::Label {
1486        name: "facet".into(),
1487        level: 7,
1488    },
1489};
1490
1491// ── Attribute test case descriptors ──
1492
1493const CASE_ATTR_RENAME_FIELD: CaseDescriptor<RenamedField> = CaseDescriptor {
1494    id: "attr::rename_field",
1495    description: "field with #[facet(rename = \"userName\")]",
1496    expected: || RenamedField {
1497        user_name: "alice".into(),
1498        age: 30,
1499    },
1500};
1501
1502const CASE_ATTR_RENAME_ALL_CAMEL: CaseDescriptor<CamelCaseStruct> = CaseDescriptor {
1503    id: "attr::rename_all_camel",
1504    description: "struct with #[facet(rename_all = \"camelCase\")]",
1505    expected: || CamelCaseStruct {
1506        first_name: "Jane".into(),
1507        last_name: "Doe".into(),
1508        is_active: true,
1509    },
1510};
1511
1512const CASE_ATTR_DEFAULT_FIELD: CaseDescriptor<WithDefault> = CaseDescriptor {
1513    id: "attr::default_field",
1514    description: "field with #[facet(default)] missing from input",
1515    expected: || WithDefault {
1516        required: "present".into(),
1517        optional_count: 0, // default value
1518    },
1519};
1520
1521const CASE_ATTR_DEFAULT_STRUCT: CaseDescriptor<StructLevelDefault> = CaseDescriptor {
1522    id: "attr::default_struct",
1523    description: "struct-level #[facet(default)] with missing field uses Default",
1524    expected: || StructLevelDefault {
1525        count: 123,
1526        message: String::new(), // Default::default() for String
1527    },
1528};
1529
1530const CASE_ATTR_DEFAULT_FUNCTION: CaseDescriptor<WithDefaultFunction> = CaseDescriptor {
1531    id: "attr::default_function",
1532    description: "field with #[facet(default = expr)] uses custom default",
1533    expected: || WithDefaultFunction {
1534        magic_number: 42, // from custom_default_value()
1535        name: "hello".into(),
1536    },
1537};
1538
1539const CASE_OPTION_NONE: CaseDescriptor<WithOption> = CaseDescriptor {
1540    id: "option::none",
1541    description: "Option<T> field missing from input becomes None",
1542    expected: || WithOption {
1543        name: "test".into(),
1544        nickname: None,
1545    },
1546};
1547
1548const CASE_OPTION_SOME: CaseDescriptor<WithOption> = CaseDescriptor {
1549    id: "option::some",
1550    description: "Option<T> field with Some value",
1551    expected: || WithOption {
1552        name: "test".into(),
1553        nickname: Some("nick".into()),
1554    },
1555};
1556
1557const CASE_OPTION_NULL: CaseDescriptor<WithOption> = CaseDescriptor {
1558    id: "option::null",
1559    description: "Option<T> field with explicit null becomes None",
1560    expected: || WithOption {
1561        name: "test".into(),
1562        nickname: None,
1563    },
1564};
1565
1566const CASE_ATTR_SKIP_SERIALIZING: CaseDescriptor<WithSkipSerializing> = CaseDescriptor {
1567    id: "attr::skip_serializing",
1568    description: "field with #[facet(skip_serializing)] not in output",
1569    expected: || WithSkipSerializing {
1570        visible: "shown".into(),
1571        hidden: String::new(), // default, not in input
1572    },
1573};
1574
1575const CASE_ATTR_SKIP_SERIALIZING_IF: CaseDescriptor<WithSkipSerializingIf> = CaseDescriptor {
1576    id: "attr::skip_serializing_if",
1577    description: "field with #[facet(skip_serializing_if = pred)] skipped when pred is true",
1578    expected: || WithSkipSerializingIf {
1579        name: "test".into(),
1580        optional_data: None, // is_none returns true, so field is skipped on serialize
1581    },
1582};
1583
1584const CASE_ATTR_SKIP: CaseDescriptor<WithSkip> = CaseDescriptor {
1585    id: "attr::skip",
1586    description: "field with #[facet(skip)] ignored for both ser and de",
1587    expected: || WithSkip {
1588        visible: "data".into(),
1589        internal: 0, // always uses default (u32::default())
1590    },
1591};
1592
1593// ── Enum tagging case descriptors ──
1594
1595const CASE_ENUM_INTERNALLY_TAGGED: CaseDescriptor<InternallyTagged> = CaseDescriptor {
1596    id: "enum::internally_tagged",
1597    description: "internally tagged enum with #[facet(tag = \"type\")]",
1598    expected: || InternallyTagged::Circle { radius: 5.0 },
1599};
1600
1601const CASE_ENUM_ADJACENTLY_TAGGED: CaseDescriptor<AdjacentlyTagged> = CaseDescriptor {
1602    id: "enum::adjacently_tagged",
1603    description: "adjacently tagged enum with #[facet(tag = \"t\", content = \"c\")]",
1604    expected: || AdjacentlyTagged::Message("hello".into()),
1605};
1606
1607// ── Advanced case descriptors ──
1608
1609const CASE_STRUCT_FLATTEN: CaseDescriptor<FlattenOuter> = CaseDescriptor {
1610    id: "struct::flatten",
1611    description: "struct with #[facet(flatten)] flattening inner fields",
1612    expected: || FlattenOuter {
1613        name: "point".into(),
1614        coords: FlattenInner { x: 10, y: 20 },
1615    },
1616};
1617
1618const CASE_TRANSPARENT_NEWTYPE: CaseDescriptor<UserRecord> = CaseDescriptor {
1619    id: "attr::transparent",
1620    description: "struct containing #[facet(transparent)] newtype",
1621    expected: || UserRecord {
1622        id: UserId(42),
1623        name: "alice".into(),
1624    },
1625};
1626
1627// ── Flatten variation case descriptors ──
1628
1629const CASE_FLATTEN_OPTIONAL_SOME: CaseDescriptor<FlattenOptionalSome> = CaseDescriptor {
1630    id: "flatten::optional_some",
1631    description: "flattened field is Option<T> with Some value",
1632    expected: || FlattenOptionalSome {
1633        name: "test".into(),
1634        metadata: Some(FlattenMetadata {
1635            version: 1,
1636            author: "alice".into(),
1637        }),
1638    },
1639};
1640
1641const CASE_FLATTEN_OPTIONAL_NONE: CaseDescriptor<FlattenOptionalNone> = CaseDescriptor {
1642    id: "flatten::optional_none",
1643    description: "flattened field is Option<T> with None value",
1644    expected: || FlattenOptionalNone {
1645        name: "test".into(),
1646        metadata: None,
1647    },
1648};
1649
1650const CASE_FLATTEN_OVERLAPPING_FIELDS_ERROR: CaseDescriptor<FlattenOverlapping> = CaseDescriptor {
1651    id: "flatten::overlapping_fields_error",
1652    description: "two flattened structs with overlapping field names (error)",
1653    expected: || FlattenOverlapping {
1654        part_a: FlattenPartA {
1655            field_a: "a".into(),
1656            shared: 1,
1657        },
1658        part_b: FlattenPartB {
1659            field_b: "b".into(),
1660            shared: 2,
1661        },
1662    },
1663};
1664
1665const CASE_FLATTEN_MULTILEVEL: CaseDescriptor<FlattenLevel1> = CaseDescriptor {
1666    id: "flatten::multilevel",
1667    description: "three levels of nested flatten (A -> B -> C, all flattened)",
1668    expected: || FlattenLevel1 {
1669        top_field: "top".into(),
1670        level2: FlattenLevel2 {
1671            mid_field: 42,
1672            level3: FlattenLevel3 { deep_field: 100 },
1673        },
1674    },
1675};
1676
1677const CASE_FLATTEN_MULTIPLE_ENUMS: CaseDescriptor<FlattenMultipleEnums> = CaseDescriptor {
1678    id: "flatten::multiple_enums",
1679    description: "two different enums flattened into same struct",
1680    expected: || FlattenMultipleEnums {
1681        name: "service".into(),
1682        auth: FlattenAuthMethod::Password(FlattenAuthPassword {
1683            password: "secret".into(),
1684        }),
1685        transport: FlattenTransport::Tcp(FlattenTransportTcp { port: 8080 }),
1686    },
1687};
1688
1689// ── Error case descriptors ──
1690
1691const CASE_DENY_UNKNOWN_FIELDS: CaseDescriptor<DenyUnknownStruct> = CaseDescriptor {
1692    id: "error::deny_unknown_fields",
1693    description: "#[facet(deny_unknown_fields)] rejects input with extra fields",
1694    expected: || DenyUnknownStruct {
1695        foo: "abc".into(),
1696        bar: 42,
1697    },
1698};
1699
1700const CASE_ERROR_TYPE_MISMATCH_STRING_TO_INT: CaseDescriptor<ExpectsInteger> = CaseDescriptor {
1701    id: "error::type_mismatch_string_to_int",
1702    description: "type mismatch - string provided where integer expected",
1703    expected: || ExpectsInteger { value: 42 },
1704};
1705
1706const CASE_ERROR_TYPE_MISMATCH_OBJECT_TO_ARRAY: CaseDescriptor<ExpectsArray> = CaseDescriptor {
1707    id: "error::type_mismatch_object_to_array",
1708    description: "structure mismatch - object provided where array expected",
1709    expected: || ExpectsArray {
1710        items: vec![1, 2, 3],
1711    },
1712};
1713
1714const CASE_ERROR_MISSING_REQUIRED_FIELD: CaseDescriptor<RequiresAllFields> = CaseDescriptor {
1715    id: "error::missing_required_field",
1716    description: "missing required field error",
1717    expected: || RequiresAllFields {
1718        name: "Alice".into(),
1719        age: 30,
1720        email: "alice@example.com".into(),
1721    },
1722};
1723
1724// ── Alias case descriptors ──
1725
1726const CASE_ATTR_ALIAS: CaseDescriptor<WithAlias> = CaseDescriptor {
1727    id: "attr::alias",
1728    description: "field with #[facet(alias = \"old_name\")] accepts alternative name",
1729    expected: || WithAlias {
1730        new_name: "value".into(),
1731        count: 5,
1732    },
1733};
1734
1735// ── Attribute precedence case descriptors ──
1736
1737const CASE_ATTR_RENAME_VS_ALIAS: CaseDescriptor<RenameVsAlias> = CaseDescriptor {
1738    id: "attr::rename_vs_alias_precedence",
1739    description: "when both rename and alias present, rename takes precedence",
1740    expected: || RenameVsAlias {
1741        field: "test".into(),
1742        id: 1,
1743    },
1744};
1745
1746const CASE_ATTR_RENAME_ALL_KEBAB: CaseDescriptor<RenameAllKebab> = CaseDescriptor {
1747    id: "attr::rename_all_kebab",
1748    description: "struct with #[facet(rename_all = \"kebab-case\")]",
1749    expected: || RenameAllKebab {
1750        first_name: "John".into(),
1751        last_name: "Doe".into(),
1752        user_id: 42,
1753    },
1754};
1755
1756const CASE_ATTR_RENAME_ALL_SCREAMING: CaseDescriptor<RenameAllScreaming> = CaseDescriptor {
1757    id: "attr::rename_all_screaming",
1758    description: "struct with #[facet(rename_all = \"SCREAMING_SNAKE_CASE\")]",
1759    expected: || RenameAllScreaming {
1760        api_key: "secret-123".into(),
1761        max_retry_count: 5,
1762    },
1763};
1764
1765const CASE_ATTR_RENAME_UNICODE: CaseDescriptor<RenameUnicode> = CaseDescriptor {
1766    id: "attr::rename_unicode",
1767    description: "field with unicode (emoji) rename #[facet(rename = \"🎉\")]",
1768    expected: || RenameUnicode {
1769        celebration: "party".into(),
1770    },
1771};
1772
1773const CASE_ATTR_RENAME_SPECIAL_CHARS: CaseDescriptor<RenameSpecialChars> = CaseDescriptor {
1774    id: "attr::rename_special_chars",
1775    description: "field with special chars rename #[facet(rename = \"@type\")]",
1776    expected: || RenameSpecialChars {
1777        type_field: "node".into(),
1778    },
1779};
1780
1781// ── Proxy case descriptors ──
1782
1783const CASE_PROXY_CONTAINER: CaseDescriptor<ProxyInt> = CaseDescriptor {
1784    id: "proxy::container",
1785    description: "container-level #[facet(proxy = IntAsString)] deserializes int from string",
1786    expected: || ProxyInt { value: 42 },
1787};
1788
1789const CASE_PROXY_FIELD_LEVEL: CaseDescriptor<ProxyFieldLevel> = CaseDescriptor {
1790    id: "proxy::field_level",
1791    description: "field-level #[facet(proxy = IntAsString)] on individual field",
1792    expected: || ProxyFieldLevel {
1793        name: "test".into(),
1794        count: 100,
1795    },
1796};
1797
1798const CASE_PROXY_VALIDATION_ERROR: CaseDescriptor<ProxyInt> = CaseDescriptor {
1799    id: "proxy::validation_error",
1800    description: "proxy conversion error when validation fails (expects error)",
1801    expected: || ProxyInt { value: 0 }, // Not used for error cases
1802};
1803
1804const CASE_PROXY_WITH_OPTION: CaseDescriptor<ProxyWithOption> = CaseDescriptor {
1805    id: "proxy::with_option",
1806    description: "proxy wrapping Option<T>",
1807    expected: || ProxyWithOption {
1808        name: "test".into(),
1809        count: Some(42),
1810    },
1811};
1812
1813const CASE_PROXY_WITH_ENUM: CaseDescriptor<ProxyEnum> = CaseDescriptor {
1814    id: "proxy::with_enum",
1815    description: "proxy on enum variant",
1816    expected: || ProxyEnum::Value(99),
1817};
1818
1819const CASE_PROXY_WITH_TRANSPARENT: CaseDescriptor<TransparentProxy> = CaseDescriptor {
1820    id: "proxy::with_transparent",
1821    description: "transparent wrapper with proxy",
1822    expected: || TransparentProxy(42),
1823};
1824
1825const CASE_OPAQUE_PROXY: CaseDescriptor<OpaqueProxyWrapper> = CaseDescriptor {
1826    id: "proxy::opaque",
1827    description: "#[facet(opaque, proxy = ...)] where target type doesn't implement Facet",
1828    expected: || OpaqueProxyWrapper {
1829        value: OpaqueType { inner: 42 },
1830    },
1831};
1832
1833const CASE_OPAQUE_PROXY_OPTION: CaseDescriptor<OpaqueProxyOptionWrapper> = CaseDescriptor {
1834    id: "proxy::opaque_option",
1835    description: "#[facet(opaque, proxy = ...)] on Option<OpaqueType>",
1836    expected: || OpaqueProxyOptionWrapper {
1837        value: Some(OpaqueType { inner: 99 }),
1838    },
1839};
1840
1841// ── Transparent case descriptors ──
1842
1843const CASE_TRANSPARENT_MULTILEVEL: CaseDescriptor<OuterTransparent> = CaseDescriptor {
1844    id: "transparent::multilevel",
1845    description: "transparent wrapping another transparent type",
1846    expected: || OuterTransparent(InnerTransparent(42)),
1847};
1848
1849const CASE_TRANSPARENT_OPTION: CaseDescriptor<TransparentOption> = CaseDescriptor {
1850    id: "transparent::option",
1851    description: "transparent wrapping Option<T>",
1852    expected: || TransparentOption(Some(99)),
1853};
1854
1855const CASE_TRANSPARENT_NONZERO: CaseDescriptor<TransparentNonZero> = CaseDescriptor {
1856    id: "transparent::nonzero",
1857    description: "transparent wrapping NonZero type",
1858    expected: || TransparentNonZero(std::num::NonZeroU32::new(42).unwrap()),
1859};
1860
1861// ── Scalar case descriptors ──
1862
1863const CASE_SCALAR_BOOL: CaseDescriptor<BoolWrapper> = CaseDescriptor {
1864    id: "scalar::bool",
1865    description: "boolean scalar values",
1866    expected: || BoolWrapper {
1867        yes: true,
1868        no: false,
1869    },
1870};
1871
1872const CASE_SCALAR_INTEGERS: CaseDescriptor<IntegerTypes> = CaseDescriptor {
1873    id: "scalar::integers",
1874    description: "various integer types (i8, u8, i32, u32, i64, u64)",
1875    expected: || IntegerTypes {
1876        signed_8: -128,
1877        unsigned_8: 255,
1878        signed_32: -2_147_483_648,
1879        unsigned_32: 4_294_967_295,
1880        signed_64: -9_223_372_036_854_775_808,
1881        unsigned_64: 18_446_744_073_709_551_615,
1882    },
1883};
1884
1885const CASE_SCALAR_FLOATS: CaseDescriptor<FloatTypes> = CaseDescriptor {
1886    id: "scalar::floats",
1887    description: "floating point types (f32, f64)",
1888    expected: || FloatTypes {
1889        float_32: 1.5,
1890        float_64: 2.25,
1891    },
1892};
1893
1894const CASE_SCALAR_FLOATS_SCIENTIFIC: CaseDescriptor<FloatTypesScientific> = CaseDescriptor {
1895    id: "scalar::floats_scientific",
1896    description: "floating point with scientific notation (1.23e10, -4.56e-7)",
1897    expected: || FloatTypesScientific {
1898        large: 1.23e10,
1899        small: -4.56e-7,
1900        positive_exp: 5e3,
1901    },
1902};
1903
1904// ── Network type case descriptors ──
1905
1906#[cfg(feature = "net")]
1907const CASE_NET_IP_ADDR_V4: CaseDescriptor<IpAddrV4Wrapper> = CaseDescriptor {
1908    id: "net::ip_addr_v4",
1909    description: "std::net::IpAddr (IPv4 variant)",
1910    expected: || IpAddrV4Wrapper {
1911        addr: std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 1)),
1912    },
1913};
1914
1915#[cfg(feature = "net")]
1916const CASE_NET_IP_ADDR_V6: CaseDescriptor<IpAddrV6Wrapper> = CaseDescriptor {
1917    id: "net::ip_addr_v6",
1918    description: "std::net::IpAddr (IPv6 variant)",
1919    expected: || IpAddrV6Wrapper {
1920        addr: std::net::IpAddr::V6(std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)),
1921    },
1922};
1923
1924#[cfg(feature = "net")]
1925const CASE_NET_IPV4_ADDR: CaseDescriptor<Ipv4AddrWrapper> = CaseDescriptor {
1926    id: "net::ipv4_addr",
1927    description: "std::net::Ipv4Addr",
1928    expected: || Ipv4AddrWrapper {
1929        addr: std::net::Ipv4Addr::new(127, 0, 0, 1),
1930    },
1931};
1932
1933#[cfg(feature = "net")]
1934const CASE_NET_IPV6_ADDR: CaseDescriptor<Ipv6AddrWrapper> = CaseDescriptor {
1935    id: "net::ipv6_addr",
1936    description: "std::net::Ipv6Addr",
1937    expected: || Ipv6AddrWrapper {
1938        addr: std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1),
1939    },
1940};
1941
1942#[cfg(feature = "net")]
1943const CASE_NET_SOCKET_ADDR_V4: CaseDescriptor<SocketAddrV4Wrapper> = CaseDescriptor {
1944    id: "net::socket_addr_v4",
1945    description: "std::net::SocketAddr (IPv4 variant)",
1946    expected: || SocketAddrV4Wrapper {
1947        addr: std::net::SocketAddr::new(
1948            std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 1)),
1949            8080,
1950        ),
1951    },
1952};
1953
1954#[cfg(feature = "net")]
1955const CASE_NET_SOCKET_ADDR_V6: CaseDescriptor<SocketAddrV6Wrapper> = CaseDescriptor {
1956    id: "net::socket_addr_v6",
1957    description: "std::net::SocketAddr (IPv6 variant)",
1958    expected: || SocketAddrV6Wrapper {
1959        addr: std::net::SocketAddr::new(
1960            std::net::IpAddr::V6(std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)),
1961            443,
1962        ),
1963    },
1964};
1965
1966#[cfg(feature = "net")]
1967const CASE_NET_SOCKET_ADDR_V4_EXPLICIT: CaseDescriptor<SocketAddrV4ExplicitWrapper> =
1968    CaseDescriptor {
1969        id: "net::socket_addr_v4_explicit",
1970        description: "std::net::SocketAddrV4 (explicit type)",
1971        expected: || SocketAddrV4ExplicitWrapper {
1972            addr: std::net::SocketAddrV4::new(std::net::Ipv4Addr::new(10, 0, 0, 1), 3000),
1973        },
1974    };
1975
1976#[cfg(feature = "net")]
1977const CASE_NET_SOCKET_ADDR_V6_EXPLICIT: CaseDescriptor<SocketAddrV6ExplicitWrapper> =
1978    CaseDescriptor {
1979        id: "net::socket_addr_v6_explicit",
1980        description: "std::net::SocketAddrV6 (explicit type)",
1981        expected: || SocketAddrV6ExplicitWrapper {
1982            addr: std::net::SocketAddrV6::new(
1983                std::net::Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
1984                9000,
1985                0,
1986                0,
1987            ),
1988        },
1989    };
1990
1991// ── Collection case descriptors ──
1992
1993const CASE_MAP_STRING_KEYS: CaseDescriptor<MapWrapper> = CaseDescriptor {
1994    id: "collection::map",
1995    description: "BTreeMap<String, i32> with string keys",
1996    expected: || {
1997        let mut map = std::collections::BTreeMap::new();
1998        map.insert("alpha".into(), 1);
1999        map.insert("beta".into(), 2);
2000        MapWrapper { data: map }
2001    },
2002};
2003
2004const CASE_TUPLE_SIMPLE: CaseDescriptor<TupleWrapper> = CaseDescriptor {
2005    id: "collection::tuple",
2006    description: "tuple (String, i32, bool)",
2007    expected: || TupleWrapper {
2008        triple: ("hello".into(), 42, true),
2009    },
2010};
2011
2012const CASE_TUPLE_NESTED: CaseDescriptor<NestedTupleWrapper> = CaseDescriptor {
2013    id: "collection::tuple_nested",
2014    description: "nested tuple ((i32, i32), (String, bool))",
2015    expected: || NestedTupleWrapper {
2016        outer: ((1, 2), ("test".into(), true)),
2017    },
2018};
2019
2020const CASE_TUPLE_EMPTY: CaseDescriptor<EmptyTupleWrapper> = CaseDescriptor {
2021    id: "collection::tuple_empty",
2022    description: "empty tuple () as a field",
2023    expected: || EmptyTupleWrapper {
2024        name: "test".into(),
2025        empty: (),
2026    },
2027};
2028
2029const CASE_TUPLE_SINGLE_ELEMENT: CaseDescriptor<SingleElementTupleWrapper> = CaseDescriptor {
2030    id: "collection::tuple_single_element",
2031    description: "single-element tuple (T,) as a field",
2032    expected: || SingleElementTupleWrapper {
2033        name: "test".into(),
2034        single: (42,),
2035    },
2036};
2037
2038const CASE_TUPLE_STRUCT_VARIANT: CaseDescriptor<TupleVariantEnum> = CaseDescriptor {
2039    id: "collection::tuple_struct_variant",
2040    description: "enum with tuple variant Variant(T, U)",
2041    expected: || TupleVariantEnum::Pair("test".into(), 42),
2042};
2043
2044const CASE_TUPLE_NEWTYPE_VARIANT: CaseDescriptor<NewtypeVariantEnum> = CaseDescriptor {
2045    id: "collection::tuple_newtype_variant",
2046    description: "enum with newtype variant Variant(T)",
2047    expected: || NewtypeVariantEnum::Some(99),
2048};
2049
2050// ── Enum variant case descriptors ──
2051
2052const CASE_ENUM_UNIT_VARIANT: CaseDescriptor<UnitVariantEnum> = CaseDescriptor {
2053    id: "enum::unit_variant",
2054    description: "enum with unit variants",
2055    expected: || UnitVariantEnum::Active,
2056};
2057
2058const CASE_NUMERIC_ENUM: CaseDescriptor<NumericEnum> = CaseDescriptor {
2059    id: "enum::numeric",
2060    description: "#[facet(is_numeric)] enum matches by structure",
2061    expected: || NumericEnum::B,
2062};
2063
2064const CASE_ENUM_UNTAGGED: CaseDescriptor<UntaggedEnum> = CaseDescriptor {
2065    id: "enum::untagged",
2066    description: "#[facet(untagged)] enum matches by structure",
2067    expected: || UntaggedEnum::Point { x: 10, y: 20 },
2068};
2069
2070const CASE_ENUM_VARIANT_RENAME: CaseDescriptor<EnumVariantRename> = CaseDescriptor {
2071    id: "enum::variant_rename",
2072    description: "#[facet(rename = \"...\")] on enum variants",
2073    expected: || EnumVariantRename::Active,
2074};
2075
2076// ── Numeric enum variation case descriptors ──
2077
2078const CASE_SIGNED_NUMERIC_ENUM: CaseDescriptor<SignedNumericEnum> = CaseDescriptor {
2079    id: "enum::numeric::signed",
2080    description: "Numeric enum with signed discriminant",
2081    expected: || SignedNumericEnum::Z,
2082};
2083
2084const CASE_INFERRED_NUMERIC_ENUM: CaseDescriptor<NumericEnum> = CaseDescriptor {
2085    id: "enum::numeric::inferred",
2086    description: "Numeric enum that is inferred from string",
2087    expected: || NumericEnum::A,
2088};
2089
2090// ── Untagged enum variation case descriptors ──
2091
2092const CASE_UNTAGGED_WITH_NULL: CaseDescriptor<UntaggedWithNull> = CaseDescriptor {
2093    id: "untagged::with_null",
2094    description: "untagged enum with unit variant matching null",
2095    expected: || UntaggedWithNull::None,
2096};
2097
2098const CASE_UNTAGGED_NEWTYPE_VARIANT: CaseDescriptor<UntaggedNewtype> = CaseDescriptor {
2099    id: "untagged::newtype_variant",
2100    description: "untagged enum with newtype variants (discrimination)",
2101    expected: || UntaggedNewtype::String("test".into()),
2102};
2103
2104const CASE_UNTAGGED_AS_FIELD: CaseDescriptor<UntaggedAsField> = CaseDescriptor {
2105    id: "untagged::as_field",
2106    description: "untagged enum as struct field (nesting)",
2107    expected: || UntaggedAsField {
2108        name: "test".into(),
2109        value: UntaggedNewtype::Number(42),
2110    },
2111};
2112
2113const CASE_UNTAGGED_UNIT_ONLY: CaseDescriptor<UntaggedUnitOnly> = CaseDescriptor {
2114    id: "untagged::unit_only",
2115    description: "untagged enum with only unit variants (dataless)",
2116    expected: || UntaggedUnitOnly::Alpha,
2117};
2118
2119// ── Smart pointer case descriptors ──
2120
2121const CASE_BOX_WRAPPER: CaseDescriptor<BoxWrapper> = CaseDescriptor {
2122    id: "pointer::box",
2123    description: "Box<T> smart pointer",
2124    expected: || BoxWrapper {
2125        inner: Box::new(42),
2126    },
2127};
2128
2129const CASE_ARC_WRAPPER: CaseDescriptor<ArcWrapper> = CaseDescriptor {
2130    id: "pointer::arc",
2131    description: "Arc<T> smart pointer",
2132    expected: || ArcWrapper {
2133        inner: std::sync::Arc::new(42),
2134    },
2135};
2136
2137const CASE_RC_WRAPPER: CaseDescriptor<RcWrapper> = CaseDescriptor {
2138    id: "pointer::rc",
2139    description: "Rc<T> smart pointer",
2140    expected: || RcWrapper {
2141        inner: std::rc::Rc::new(42),
2142    },
2143};
2144
2145const CASE_BOX_STR: CaseDescriptor<BoxStrWrapper> = CaseDescriptor {
2146    id: "pointer::box_str",
2147    description: "Box<str> unsized smart pointer",
2148    expected: || BoxStrWrapper {
2149        inner: Box::from("hello world"),
2150    },
2151};
2152
2153const CASE_ARC_STR: CaseDescriptor<ArcStrWrapper> = CaseDescriptor {
2154    id: "pointer::arc_str",
2155    description: "Arc<str> unsized smart pointer",
2156    expected: || ArcStrWrapper {
2157        inner: std::sync::Arc::from("hello world"),
2158    },
2159};
2160
2161const CASE_RC_STR: CaseDescriptor<RcStrWrapper> = CaseDescriptor {
2162    id: "pointer::rc_str",
2163    description: "Rc<str> unsized smart pointer",
2164    expected: || RcStrWrapper {
2165        inner: std::rc::Rc::from("hello world"),
2166    },
2167};
2168
2169const CASE_ARC_SLICE: CaseDescriptor<ArcSliceWrapper> = CaseDescriptor {
2170    id: "pointer::arc_slice",
2171    description: "Arc<[T]> unsized slice smart pointer",
2172    expected: || ArcSliceWrapper {
2173        inner: std::sync::Arc::from([1i32, 2, 3, 4]),
2174    },
2175};
2176
2177// ── Yoke case descriptors ──
2178
2179#[cfg(feature = "yoke")]
2180const CASE_YOKE_COW_STR: CaseDescriptor<YokeWrapper> = CaseDescriptor {
2181    id: "pointer::yoke_cow_str",
2182    description: "Yoke<Cow<'static, str>, Arc<str>> zero-copy smart pointer",
2183    expected: || YokeWrapper {
2184        value: yoke::Yoke::<std::borrow::Cow<'static, str>, std::sync::Arc<str>>::attach_to_cart(
2185            std::sync::Arc::from("hello yoke"),
2186            |s| std::borrow::Cow::Borrowed(s),
2187        ),
2188    },
2189};
2190
2191#[cfg(feature = "yoke")]
2192const CASE_YOKE_CUSTOM: CaseDescriptor<YokingWrapper> = CaseDescriptor {
2193    id: "pointer::yoke_custom",
2194    description: "Custom type deriving Yokeable and using #[facet(try_from_ref)]",
2195    expected: || YokingWrapper {
2196        value: yoke::Yoke::<SplittingYoker<'static>, std::sync::Arc<str>>::attach_to_cart(
2197            std::sync::Arc::from("hello|yoke"),
2198            |s| SplittingYoker::try_from_ref(s).unwrap(),
2199        ),
2200    },
2201};
2202
2203// ── Set case descriptors ──
2204
2205const CASE_SET_BTREE: CaseDescriptor<SetWrapper> = CaseDescriptor {
2206    id: "collection::set",
2207    description: "BTreeSet<String>",
2208    expected: || {
2209        let mut set = std::collections::BTreeSet::new();
2210        set.insert("alpha".into());
2211        set.insert("beta".into());
2212        set.insert("gamma".into());
2213        SetWrapper { items: set }
2214    },
2215};
2216
2217// ── Extended numeric case descriptors ──
2218
2219const CASE_SCALAR_INTEGERS_16: CaseDescriptor<IntegerTypes16> = CaseDescriptor {
2220    id: "scalar::integers_16",
2221    description: "16-bit integer types (i16, u16)",
2222    expected: || IntegerTypes16 {
2223        signed_16: -32768,
2224        unsigned_16: 65535,
2225    },
2226};
2227
2228const CASE_SCALAR_INTEGERS_128: CaseDescriptor<IntegerTypes128> = CaseDescriptor {
2229    id: "scalar::integers_128",
2230    description: "128-bit integer types (i128, u128)",
2231    expected: || IntegerTypes128 {
2232        signed_128: -170_141_183_460_469_231_731_687_303_715_884_105_728,
2233        unsigned_128: 340_282_366_920_938_463_463_374_607_431_768_211_455,
2234    },
2235};
2236
2237const CASE_SCALAR_INTEGERS_SIZE: CaseDescriptor<IntegerTypesSize> = CaseDescriptor {
2238    id: "scalar::integers_size",
2239    description: "pointer-sized integer types (isize, usize)",
2240    expected: || IntegerTypesSize {
2241        signed_size: -1000,
2242        unsigned_size: 2000,
2243    },
2244};
2245
2246// ── NonZero case descriptors ──
2247
2248const CASE_NONZERO_INTEGERS: CaseDescriptor<NonZeroTypes> = CaseDescriptor {
2249    id: "scalar::nonzero",
2250    description: "NonZero integer types",
2251    expected: || NonZeroTypes {
2252        nz_u32: std::num::NonZeroU32::new(42).unwrap(),
2253        nz_i64: std::num::NonZeroI64::new(-100).unwrap(),
2254    },
2255};
2256
2257const CASE_NONZERO_INTEGERS_EXTENDED: CaseDescriptor<NonZeroTypesExtended> = CaseDescriptor {
2258    id: "scalar::nonzero_extended",
2259    description: "Extended NonZero integer types (8, 16, 128, size)",
2260    expected: || NonZeroTypesExtended {
2261        nz_u8: std::num::NonZeroU8::new(255).unwrap(),
2262        nz_i8: std::num::NonZeroI8::new(-128).unwrap(),
2263        nz_u16: std::num::NonZeroU16::new(65535).unwrap(),
2264        nz_i16: std::num::NonZeroI16::new(-32768).unwrap(),
2265        nz_u128: std::num::NonZeroU128::new(1).unwrap(),
2266        nz_i128: std::num::NonZeroI128::new(-1).unwrap(),
2267        nz_usize: std::num::NonZeroUsize::new(1000).unwrap(),
2268        nz_isize: std::num::NonZeroIsize::new(-500).unwrap(),
2269    },
2270};
2271
2272// ── Borrowed string case descriptors ──
2273
2274const CASE_COW_STR: CaseDescriptor<CowStrWrapper> = CaseDescriptor {
2275    id: "string::cow_str",
2276    description: "Cow<'static, str> string fields",
2277    expected: || CowStrWrapper {
2278        owned: std::borrow::Cow::Owned("hello world".to_string()),
2279        message: std::borrow::Cow::Borrowed("borrowed"),
2280    },
2281};
2282
2283// ── Newtype case descriptors ──
2284
2285const CASE_NEWTYPE_U64: CaseDescriptor<NewtypeU64Wrapper> = CaseDescriptor {
2286    id: "newtype::u64",
2287    description: "newtype wrapper around u64",
2288    expected: || NewtypeU64Wrapper {
2289        value: NewtypeU64(42),
2290    },
2291};
2292
2293const CASE_NEWTYPE_STRING: CaseDescriptor<NewtypeStringWrapper> = CaseDescriptor {
2294    id: "newtype::string",
2295    description: "newtype wrapper around String",
2296    expected: || NewtypeStringWrapper {
2297        value: NewtypeString("hello".into()),
2298    },
2299};
2300
2301// ── Char case descriptors ──
2302
2303const CASE_CHAR_SCALAR: CaseDescriptor<CharWrapper> = CaseDescriptor {
2304    id: "scalar::char",
2305    description: "char scalar type",
2306    expected: || CharWrapper {
2307        letter: 'A',
2308        emoji: '🦀',
2309    },
2310};
2311
2312// ── HashSet case descriptors ──
2313
2314const CASE_HASHSET: CaseDescriptor<HashSetWrapper> = CaseDescriptor {
2315    id: "collection::hashset",
2316    description: "HashSet<String>",
2317    expected: || {
2318        let mut set = std::collections::HashSet::new();
2319        set.insert("alpha".into());
2320        set.insert("beta".into());
2321        HashSetWrapper { items: set }
2322    },
2323};
2324
2325// ── Nested collection case descriptors ──
2326
2327const CASE_VEC_NESTED: CaseDescriptor<NestedVecWrapper> = CaseDescriptor {
2328    id: "collection::vec_nested",
2329    description: "nested Vec<Vec<i32>>",
2330    expected: || NestedVecWrapper {
2331        matrix: vec![vec![1, 2], vec![3, 4, 5]],
2332    },
2333};
2334
2335// ── Bytes/binary data case descriptors ──
2336
2337const CASE_BYTES_VEC_U8: CaseDescriptor<BytesWrapper> = CaseDescriptor {
2338    id: "slice::bytes::vec_u8",
2339    description: "Vec<u8> binary data as array of numbers",
2340    expected: || BytesWrapper {
2341        data: vec![0, 128, 255, 42],
2342    },
2343};
2344
2345// ── Fixed-size array case descriptors ──
2346
2347const CASE_ARRAY_FIXED_SIZE: CaseDescriptor<ArrayWrapper> = CaseDescriptor {
2348    id: "array::fixed_size",
2349    description: "[T; N] fixed-size array",
2350    expected: || ArrayWrapper { values: [1, 2, 3] },
2351};
2352
2353// ── Unknown field handling case descriptors ──
2354
2355const CASE_SKIP_UNKNOWN_FIELDS: CaseDescriptor<SkipUnknownStruct> = CaseDescriptor {
2356    id: "behavior::skip_unknown_fields",
2357    description: "unknown fields are silently skipped by default",
2358    expected: || SkipUnknownStruct {
2359        known: "value".into(),
2360    },
2361};
2362
2363// ── String escape case descriptors ──
2364
2365const CASE_STRING_ESCAPES: CaseDescriptor<StringEscapes> = CaseDescriptor {
2366    id: "string::escapes",
2367    description: "string with escape sequences (\\n, \\t, \\\", \\\\)",
2368    expected: || StringEscapes {
2369        text: "line1\nline2\ttab\"quote\\backslash".into(),
2370    },
2371};
2372
2373const CASE_STRING_ESCAPES_EXTENDED: CaseDescriptor<StringEscapesExtended> = CaseDescriptor {
2374    id: "string::escapes_extended",
2375    description: "string with extended escape sequences (\\b, \\f, \\r, \\u0001)",
2376    expected: || StringEscapesExtended {
2377        backspace: "hello\x08world".into(),
2378        formfeed: "page\x0Cbreak".into(),
2379        carriage_return: "line\rreturn".into(),
2380        control_char: "\x01".into(),
2381    },
2382};
2383
2384// ── Unit type case descriptors ──
2385
2386const CASE_UNIT_STRUCT: CaseDescriptor<UnitStruct> = CaseDescriptor {
2387    id: "unit::struct",
2388    description: "unit struct (zero-sized type)",
2389    expected: || UnitStruct,
2390};
2391
2392/// Shared fixture type for the struct case.
2393#[derive(Facet, Debug, Clone, PartialEq)]
2394#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2395pub struct StructSingleField {
2396    pub name: String,
2397}
2398
2399/// Shared fixture type for the mixed scalars case.
2400#[derive(Facet, Debug, Clone, PartialEq)]
2401#[facet(untagged)]
2402#[repr(u8)]
2403pub enum MixedScalar {
2404    Signed(i64),
2405    Float(f64),
2406    Bool(bool),
2407    Null,
2408}
2409
2410#[derive(Facet, Debug, Clone, PartialEq)]
2411#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2412pub struct NestedParent {
2413    pub id: u64,
2414    pub child: NestedChild,
2415    pub tags: Vec<String>,
2416}
2417
2418#[derive(Facet, Debug, Clone, PartialEq)]
2419#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2420pub struct NestedChild {
2421    pub code: String,
2422    pub active: bool,
2423}
2424
2425#[derive(Facet, Debug, Clone, PartialEq)]
2426#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2427#[repr(u8)]
2428pub enum ComplexEnum {
2429    Empty,
2430    Count(u64),
2431    Label { name: String, level: u8 },
2432}
2433
2434// ── Attribute test fixtures ──
2435
2436/// Fixture for `#[facet(rename = "...")]` test.
2437#[derive(Facet, Debug, Clone, PartialEq)]
2438pub struct RenamedField {
2439    #[facet(rename = "userName")]
2440    pub user_name: String,
2441    pub age: u32,
2442}
2443
2444/// Fixture for `#[facet(rename_all = "camelCase")]` test.
2445#[derive(Facet, Debug, Clone, PartialEq)]
2446#[facet(rename_all = "camelCase")]
2447pub struct CamelCaseStruct {
2448    pub first_name: String,
2449    pub last_name: String,
2450    pub is_active: bool,
2451}
2452
2453/// Fixture for `#[facet(default)]` test.
2454#[derive(Facet, Debug, Clone, PartialEq)]
2455pub struct WithDefault {
2456    pub required: String,
2457    #[facet(default)]
2458    pub optional_count: u32,
2459}
2460
2461/// Fixture for struct-level `#[facet(default)]` test.
2462#[derive(Facet, Default, Debug, Clone, PartialEq)]
2463#[facet(default)]
2464pub struct StructLevelDefault {
2465    pub count: i32,
2466    pub message: String,
2467}
2468
2469/// Default value function for `WithDefaultFunction`.
2470pub const fn custom_default_value() -> i32 {
2471    42
2472}
2473
2474/// Fixture for `#[facet(default = expr)]` test.
2475#[derive(Facet, Debug, Clone, PartialEq)]
2476pub struct WithDefaultFunction {
2477    #[facet(default = custom_default_value())]
2478    pub magic_number: i32,
2479    pub name: String,
2480}
2481
2482/// Fixture for `Option<T>` with `None`.
2483#[derive(Facet, Debug, Clone, PartialEq)]
2484#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2485pub struct WithOption {
2486    pub name: String,
2487    pub nickname: Option<String>,
2488}
2489
2490/// Fixture for `#[facet(skip_serializing)]` test.
2491#[derive(Facet, Debug, Clone, PartialEq)]
2492pub struct WithSkipSerializing {
2493    pub visible: String,
2494    #[facet(skip_serializing)]
2495    #[facet(default)]
2496    pub hidden: String,
2497}
2498
2499/// Fixture for `#[facet(skip_serializing_if = predicate)]` test.
2500#[derive(Facet, Debug, Clone, PartialEq)]
2501pub struct WithSkipSerializingIf {
2502    pub name: String,
2503    #[facet(skip_serializing_if = Option::is_none)]
2504    pub optional_data: Option<String>,
2505}
2506
2507/// Fixture for `#[facet(skip)]` test (skipped for both ser and de).
2508#[derive(Facet, Debug, Clone, PartialEq)]
2509pub struct WithSkip {
2510    pub visible: String,
2511    #[facet(skip)]
2512    #[facet(default)]
2513    pub internal: u32,
2514}
2515
2516// ── Enum tagging fixtures ──
2517
2518/// Internally tagged enum `#[facet(tag = "type")]`.
2519#[derive(Facet, Debug, Clone, PartialEq)]
2520#[facet(tag = "type")]
2521#[repr(u8)]
2522pub enum InternallyTagged {
2523    Circle { radius: f64 },
2524    Rectangle { width: f64, height: f64 },
2525}
2526
2527/// Adjacently tagged enum `#[facet(tag = "t", content = "c")]`.
2528#[derive(Facet, Debug, Clone, PartialEq)]
2529#[facet(tag = "t", content = "c")]
2530#[repr(u8)]
2531pub enum AdjacentlyTagged {
2532    Message(String),
2533    Count(u64),
2534}
2535
2536// ── Advanced fixtures ──
2537
2538/// Inner struct for flatten test.
2539#[derive(Facet, Debug, Clone, PartialEq)]
2540pub struct FlattenInner {
2541    pub x: i32,
2542    pub y: i32,
2543}
2544
2545/// Outer struct with `#[facet(flatten)]`.
2546#[derive(Facet, Debug, Clone, PartialEq)]
2547pub struct FlattenOuter {
2548    pub name: String,
2549    #[facet(flatten)]
2550    pub coords: FlattenInner,
2551}
2552
2553/// Transparent newtype wrapper.
2554#[derive(Facet, Debug, Clone, PartialEq)]
2555#[facet(transparent)]
2556pub struct UserId(pub u64);
2557
2558/// Struct containing a transparent newtype.
2559#[derive(Facet, Debug, Clone, PartialEq)]
2560pub struct UserRecord {
2561    pub id: UserId,
2562    pub name: String,
2563}
2564
2565// ── Flatten variation fixtures ──
2566
2567/// Struct for flatten with optional flattened field (Some case).
2568#[derive(Facet, Debug, Clone, PartialEq)]
2569pub struct FlattenOptionalSome {
2570    pub name: String,
2571    #[facet(flatten)]
2572    pub metadata: Option<FlattenMetadata>,
2573}
2574
2575/// Struct for flatten with optional flattened field (None case).
2576#[derive(Facet, Debug, Clone, PartialEq)]
2577pub struct FlattenOptionalNone {
2578    pub name: String,
2579    #[facet(flatten)]
2580    pub metadata: Option<FlattenMetadata>,
2581}
2582
2583/// Metadata struct for flatten optional tests.
2584#[derive(Facet, Debug, Clone, PartialEq)]
2585pub struct FlattenMetadata {
2586    pub version: i32,
2587    pub author: String,
2588}
2589
2590/// First flattened struct with overlapping fields.
2591#[derive(Facet, Debug, Clone, PartialEq)]
2592pub struct FlattenPartA {
2593    pub field_a: String,
2594    pub shared: i32,
2595}
2596
2597/// Second flattened struct with overlapping fields.
2598#[derive(Facet, Debug, Clone, PartialEq)]
2599pub struct FlattenPartB {
2600    pub field_b: String,
2601    pub shared: i32,
2602}
2603
2604/// Container with two flattened structs that have overlapping field names.
2605#[derive(Facet, Debug, Clone, PartialEq)]
2606pub struct FlattenOverlapping {
2607    #[facet(flatten)]
2608    pub part_a: FlattenPartA,
2609    #[facet(flatten)]
2610    pub part_b: FlattenPartB,
2611}
2612
2613/// Deepest level for three-level flatten test.
2614#[derive(Facet, Debug, Clone, PartialEq)]
2615pub struct FlattenLevel3 {
2616    pub deep_field: i32,
2617}
2618
2619/// Middle level for three-level flatten test.
2620#[derive(Facet, Debug, Clone, PartialEq)]
2621pub struct FlattenLevel2 {
2622    pub mid_field: i32,
2623    #[facet(flatten)]
2624    pub level3: FlattenLevel3,
2625}
2626
2627/// Top level for three-level flatten test.
2628#[derive(Facet, Debug, Clone, PartialEq)]
2629pub struct FlattenLevel1 {
2630    pub top_field: String,
2631    #[facet(flatten)]
2632    pub level2: FlattenLevel2,
2633}
2634
2635/// Password auth data for multiple flattened enums test.
2636#[derive(Facet, Debug, Clone, PartialEq)]
2637pub struct FlattenAuthPassword {
2638    pub password: String,
2639}
2640
2641/// Token auth data for multiple flattened enums test.
2642#[derive(Facet, Debug, Clone, PartialEq)]
2643pub struct FlattenAuthToken {
2644    pub token: String,
2645}
2646
2647/// Auth method enum for multiple flattened enums test.
2648#[allow(dead_code)]
2649#[derive(Facet, Debug, Clone, PartialEq)]
2650#[repr(u8)]
2651pub enum FlattenAuthMethod {
2652    Password(FlattenAuthPassword),
2653    Token(FlattenAuthToken),
2654}
2655
2656/// TCP transport data for multiple flattened enums test.
2657#[derive(Facet, Debug, Clone, PartialEq)]
2658pub struct FlattenTransportTcp {
2659    pub port: u16,
2660}
2661
2662/// Unix transport data for multiple flattened enums test.
2663#[derive(Facet, Debug, Clone, PartialEq)]
2664pub struct FlattenTransportUnix {
2665    pub socket: String,
2666}
2667
2668/// Transport enum for multiple flattened enums test.
2669#[allow(dead_code)]
2670#[derive(Facet, Debug, Clone, PartialEq)]
2671#[repr(u8)]
2672pub enum FlattenTransport {
2673    Tcp(FlattenTransportTcp),
2674    Unix(FlattenTransportUnix),
2675}
2676
2677/// Container with two different flattened enums.
2678#[derive(Facet, Debug, Clone, PartialEq)]
2679pub struct FlattenMultipleEnums {
2680    pub name: String,
2681    #[facet(flatten)]
2682    pub auth: FlattenAuthMethod,
2683    #[facet(flatten)]
2684    pub transport: FlattenTransport,
2685}
2686
2687// ── Error test fixtures ──
2688
2689/// Fixture for `#[facet(deny_unknown_fields)]` test.
2690#[derive(Facet, Debug, Clone, PartialEq)]
2691#[facet(deny_unknown_fields)]
2692pub struct DenyUnknownStruct {
2693    pub foo: String,
2694    pub bar: i32,
2695}
2696
2697/// Fixture for type mismatch error (string to int).
2698#[derive(Facet, Debug, Clone, PartialEq)]
2699pub struct ExpectsInteger {
2700    pub value: i32,
2701}
2702
2703/// Fixture for structure mismatch error (object to array).
2704#[derive(Facet, Debug, Clone, PartialEq)]
2705pub struct ExpectsArray {
2706    pub items: Vec<i32>,
2707}
2708
2709/// Fixture for missing required field error.
2710#[derive(Facet, Debug, Clone, PartialEq)]
2711pub struct RequiresAllFields {
2712    pub name: String,
2713    pub age: u32,
2714    pub email: String,
2715}
2716
2717/// Fixture for `#[facet(alias = "...")]` test.
2718#[derive(Facet, Debug, Clone, PartialEq)]
2719pub struct WithAlias {
2720    #[facet(alias = "old_name")]
2721    pub new_name: String,
2722    pub count: u32,
2723}
2724
2725// ── Attribute precedence test fixtures ──
2726
2727/// Fixture for testing rename vs alias precedence (rename should win).
2728#[derive(Facet, Debug, Clone, PartialEq)]
2729pub struct RenameVsAlias {
2730    #[facet(rename = "officialName")]
2731    #[facet(alias = "oldName")]
2732    pub field: String,
2733    pub id: u32,
2734}
2735
2736/// Fixture for `#[facet(rename_all = "kebab-case")]` test.
2737#[derive(Facet, Debug, Clone, PartialEq)]
2738#[facet(rename_all = "kebab-case")]
2739pub struct RenameAllKebab {
2740    pub first_name: String,
2741    pub last_name: String,
2742    pub user_id: u32,
2743}
2744
2745/// Fixture for `#[facet(rename_all = "SCREAMING_SNAKE_CASE")]` test.
2746#[derive(Facet, Debug, Clone, PartialEq)]
2747#[facet(rename_all = "SCREAMING_SNAKE_CASE")]
2748pub struct RenameAllScreaming {
2749    pub api_key: String,
2750    pub max_retry_count: u32,
2751}
2752
2753/// Struct with unicode (emoji) field name via rename.
2754#[derive(Facet, Clone, Debug, PartialEq)]
2755pub struct RenameUnicode {
2756    #[facet(rename = "🎉")]
2757    pub celebration: String,
2758}
2759
2760/// Struct with special characters in field name via rename.
2761#[derive(Facet, Clone, Debug, PartialEq)]
2762pub struct RenameSpecialChars {
2763    #[facet(rename = "@type")]
2764    pub type_field: String,
2765}
2766
2767// ── Proxy test fixtures ──
2768
2769/// Proxy type that wraps a string for serialization.
2770#[derive(Facet, Clone, Debug)]
2771#[facet(transparent)]
2772pub struct IntAsString(pub String);
2773
2774/// Target type that uses the proxy for serialization.
2775#[derive(Facet, Debug, Clone, PartialEq)]
2776#[facet(proxy = IntAsString)]
2777pub struct ProxyInt {
2778    pub value: i32,
2779}
2780
2781/// Convert from proxy (deserialization): string -> ProxyInt
2782impl TryFrom<IntAsString> for ProxyInt {
2783    type Error = std::num::ParseIntError;
2784    fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2785        Ok(ProxyInt {
2786            value: proxy.0.parse()?,
2787        })
2788    }
2789}
2790
2791/// Convert to proxy (serialization): ProxyInt -> string
2792impl From<&ProxyInt> for IntAsString {
2793    fn from(v: &ProxyInt) -> Self {
2794        IntAsString(v.value.to_string())
2795    }
2796}
2797
2798/// Struct with field-level proxy (tests field-level vs container-level).
2799#[derive(Facet, Debug, Clone, PartialEq)]
2800pub struct ProxyFieldLevel {
2801    pub name: String,
2802    #[facet(proxy = IntAsString)]
2803    pub count: i32,
2804}
2805
2806/// Convert from proxy for field-level proxy.
2807impl TryFrom<IntAsString> for i32 {
2808    type Error = std::num::ParseIntError;
2809    fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2810        proxy.0.parse()
2811    }
2812}
2813
2814/// Convert to proxy for field-level proxy.
2815impl From<&i32> for IntAsString {
2816    fn from(value: &i32) -> Self {
2817        IntAsString(value.to_string())
2818    }
2819}
2820
2821/// Struct with proxy wrapping `Option<T>`.
2822#[derive(Facet, Debug, Clone, PartialEq)]
2823pub struct ProxyWithOption {
2824    pub name: String,
2825    #[facet(proxy = IntAsString)]
2826    pub count: Option<i32>,
2827}
2828
2829/// Convert from proxy for `Option<i32>`.
2830impl TryFrom<IntAsString> for Option<i32> {
2831    type Error = std::num::ParseIntError;
2832    fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2833        if proxy.0.is_empty() {
2834            Ok(None)
2835        } else {
2836            Ok(Some(proxy.0.parse()?))
2837        }
2838    }
2839}
2840
2841/// Convert to proxy for `Option<i32>`.
2842impl From<&Option<i32>> for IntAsString {
2843    fn from(value: &Option<i32>) -> Self {
2844        match value {
2845            Some(v) => IntAsString(v.to_string()),
2846            None => IntAsString(String::new()),
2847        }
2848    }
2849}
2850
2851/// Enum with proxy on a newtype variant.
2852#[derive(Facet, Debug, Clone, PartialEq)]
2853#[repr(u8)]
2854pub enum ProxyEnum {
2855    None,
2856    #[facet(proxy = IntAsString)]
2857    Value(i32),
2858}
2859
2860/// Transparent wrapper with proxy.
2861#[derive(Facet, Debug, Clone, PartialEq)]
2862#[facet(transparent, proxy = IntAsString)]
2863pub struct TransparentProxy(pub i32);
2864
2865/// Convert from proxy for TransparentProxy.
2866impl TryFrom<IntAsString> for TransparentProxy {
2867    type Error = std::num::ParseIntError;
2868    fn try_from(proxy: IntAsString) -> Result<Self, Self::Error> {
2869        Ok(TransparentProxy(proxy.0.parse()?))
2870    }
2871}
2872
2873/// Convert to proxy for TransparentProxy.
2874impl From<&TransparentProxy> for IntAsString {
2875    fn from(value: &TransparentProxy) -> Self {
2876        IntAsString(value.0.to_string())
2877    }
2878}
2879
2880// ── Opaque proxy test fixtures ──
2881
2882/// An opaque type that does NOT implement Facet.
2883/// This tests the `#[facet(opaque, proxy = ...)]` pattern.
2884#[derive(Debug, Clone, PartialEq)]
2885pub struct OpaqueType {
2886    pub inner: u64,
2887}
2888
2889/// Proxy type for OpaqueType that implements Facet.
2890#[derive(Facet, Clone, Debug)]
2891pub struct OpaqueTypeProxy {
2892    pub inner: u64,
2893}
2894
2895/// Convert from proxy (deserialization): OpaqueTypeProxy -> OpaqueType
2896impl From<OpaqueTypeProxy> for OpaqueType {
2897    fn from(proxy: OpaqueTypeProxy) -> Self {
2898        OpaqueType { inner: proxy.inner }
2899    }
2900}
2901
2902/// Convert to proxy (serialization): &OpaqueType -> OpaqueTypeProxy
2903impl From<&OpaqueType> for OpaqueTypeProxy {
2904    fn from(v: &OpaqueType) -> Self {
2905        OpaqueTypeProxy { inner: v.inner }
2906    }
2907}
2908
2909/// Wrapper struct with an opaque field using proxy.
2910#[derive(Facet, Debug, Clone, PartialEq)]
2911pub struct OpaqueProxyWrapper {
2912    #[facet(opaque, proxy = OpaqueTypeProxy)]
2913    pub value: OpaqueType,
2914}
2915
2916/// Convert from proxy for `Option<OpaqueType>`
2917impl From<OpaqueTypeProxy> for Option<OpaqueType> {
2918    fn from(proxy: OpaqueTypeProxy) -> Self {
2919        Some(OpaqueType { inner: proxy.inner })
2920    }
2921}
2922
2923/// Convert to proxy for `Option<OpaqueType>`
2924impl From<&Option<OpaqueType>> for OpaqueTypeProxy {
2925    fn from(v: &Option<OpaqueType>) -> Self {
2926        match v {
2927            Some(ot) => OpaqueTypeProxy { inner: ot.inner },
2928            None => OpaqueTypeProxy { inner: 0 },
2929        }
2930    }
2931}
2932
2933/// Wrapper struct with an optional opaque field using proxy.
2934#[derive(Facet, Debug, Clone, PartialEq)]
2935pub struct OpaqueProxyOptionWrapper {
2936    #[facet(opaque, proxy = OpaqueTypeProxy)]
2937    pub value: Option<OpaqueType>,
2938}
2939
2940// ── Transparent test fixtures ──
2941
2942/// Inner transparent wrapper.
2943#[derive(Facet, Debug, Clone, PartialEq)]
2944#[facet(transparent)]
2945pub struct InnerTransparent(pub i32);
2946
2947/// Outer transparent wrapper wrapping another transparent type.
2948#[derive(Facet, Debug, Clone, PartialEq)]
2949#[facet(transparent)]
2950pub struct OuterTransparent(pub InnerTransparent);
2951
2952/// Transparent wrapper around `Option<T>`.
2953#[derive(Facet, Debug, Clone, PartialEq)]
2954#[facet(transparent)]
2955pub struct TransparentOption(pub Option<i32>);
2956
2957/// Transparent wrapper around NonZero type.
2958#[derive(Facet, Debug, Clone, PartialEq)]
2959#[facet(transparent)]
2960pub struct TransparentNonZero(pub std::num::NonZeroU32);
2961
2962// ── Scalar test fixtures ──
2963
2964/// Fixture for boolean scalar test.
2965#[derive(Facet, Debug, Clone, PartialEq)]
2966#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2967pub struct BoolWrapper {
2968    pub yes: bool,
2969    pub no: bool,
2970}
2971
2972/// Fixture for integer scalar test.
2973#[derive(Facet, Debug, Clone, PartialEq)]
2974#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2975pub struct IntegerTypes {
2976    pub signed_8: i8,
2977    pub unsigned_8: u8,
2978    pub signed_32: i32,
2979    pub unsigned_32: u32,
2980    pub signed_64: i64,
2981    pub unsigned_64: u64,
2982}
2983
2984/// Fixture for float scalar test.
2985#[derive(Facet, Debug, Clone, PartialEq)]
2986#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2987pub struct FloatTypes {
2988    pub float_32: f32,
2989    pub float_64: f64,
2990}
2991
2992/// Fixture for scientific notation float test.
2993#[derive(Facet, Debug, Clone, PartialEq)]
2994#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
2995pub struct FloatTypesScientific {
2996    pub large: f64,
2997    pub small: f64,
2998    pub positive_exp: f64,
2999}
3000
3001// ── Network type test fixtures ──
3002
3003/// Fixture for IpAddr (IPv4) test.
3004#[cfg(feature = "net")]
3005#[derive(Facet, Debug, Clone, PartialEq)]
3006#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3007pub struct IpAddrV4Wrapper {
3008    pub addr: std::net::IpAddr,
3009}
3010
3011/// Fixture for IpAddr (IPv6) test.
3012#[cfg(feature = "net")]
3013#[derive(Facet, Debug, Clone, PartialEq)]
3014#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3015pub struct IpAddrV6Wrapper {
3016    pub addr: std::net::IpAddr,
3017}
3018
3019/// Fixture for Ipv4Addr test.
3020#[cfg(feature = "net")]
3021#[derive(Facet, Debug, Clone, PartialEq)]
3022#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3023pub struct Ipv4AddrWrapper {
3024    pub addr: std::net::Ipv4Addr,
3025}
3026
3027/// Fixture for Ipv6Addr test.
3028#[cfg(feature = "net")]
3029#[derive(Facet, Debug, Clone, PartialEq)]
3030#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3031pub struct Ipv6AddrWrapper {
3032    pub addr: std::net::Ipv6Addr,
3033}
3034
3035/// Fixture for SocketAddr (IPv4) test.
3036#[cfg(feature = "net")]
3037#[derive(Facet, Debug, Clone, PartialEq)]
3038#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3039pub struct SocketAddrV4Wrapper {
3040    pub addr: std::net::SocketAddr,
3041}
3042
3043/// Fixture for SocketAddr (IPv6) test.
3044#[cfg(feature = "net")]
3045#[derive(Facet, Debug, Clone, PartialEq)]
3046#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3047pub struct SocketAddrV6Wrapper {
3048    pub addr: std::net::SocketAddr,
3049}
3050
3051/// Fixture for SocketAddrV4 (explicit type) test.
3052#[cfg(feature = "net")]
3053#[derive(Facet, Debug, Clone, PartialEq)]
3054#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3055pub struct SocketAddrV4ExplicitWrapper {
3056    pub addr: std::net::SocketAddrV4,
3057}
3058
3059/// Fixture for SocketAddrV6 (explicit type) test.
3060#[cfg(feature = "net")]
3061#[derive(Facet, Debug, Clone, PartialEq)]
3062#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3063pub struct SocketAddrV6ExplicitWrapper {
3064    pub addr: std::net::SocketAddrV6,
3065}
3066
3067// ── Collection test fixtures ──
3068
3069/// Fixture for BTreeMap test.
3070#[derive(Facet, Debug, Clone, PartialEq)]
3071#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3072pub struct MapWrapper {
3073    pub data: std::collections::BTreeMap<String, i32>,
3074}
3075
3076/// Fixture for tuple test.
3077#[derive(Facet, Debug, Clone, PartialEq)]
3078#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3079pub struct TupleWrapper {
3080    pub triple: (String, i32, bool),
3081}
3082
3083/// Fixture for nested tuple test.
3084#[derive(Facet, Debug, Clone, PartialEq)]
3085#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3086pub struct NestedTupleWrapper {
3087    pub outer: ((i32, i32), (String, bool)),
3088}
3089
3090/// Fixture for empty tuple test.
3091#[derive(Facet, Debug, Clone, PartialEq)]
3092#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3093pub struct EmptyTupleWrapper {
3094    pub name: String,
3095    pub empty: (),
3096}
3097
3098/// Fixture for single-element tuple test.
3099#[derive(Facet, Debug, Clone, PartialEq)]
3100#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3101pub struct SingleElementTupleWrapper {
3102    pub name: String,
3103    pub single: (i32,),
3104}
3105
3106// ── Enum variant test fixtures ──
3107
3108/// Unit variant enum.
3109#[derive(Facet, Debug, Clone, PartialEq)]
3110#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3111#[repr(u8)]
3112pub enum UnitVariantEnum {
3113    Active,
3114    Inactive,
3115    Pending,
3116}
3117
3118/// Enum with renamed variants.
3119#[derive(Facet, Debug, Clone, PartialEq)]
3120#[repr(u8)]
3121pub enum EnumVariantRename {
3122    #[facet(rename = "enabled")]
3123    Active,
3124    #[facet(rename = "disabled")]
3125    Inactive,
3126}
3127
3128/// Numeric enum with a u8 discriminant.
3129#[derive(Facet, Debug, Clone, PartialEq)]
3130#[facet(is_numeric)]
3131#[repr(u8)]
3132pub enum NumericEnum {
3133    A,
3134    B,
3135}
3136
3137/// Numeric enum with a u8 discriminant.
3138#[derive(Facet, Debug, Clone, PartialEq)]
3139#[facet(is_numeric)]
3140#[repr(i16)]
3141pub enum SignedNumericEnum {
3142    Z = -1,
3143    A,
3144}
3145
3146/// Untagged enum that matches by structure.
3147#[derive(Facet, Debug, Clone, PartialEq)]
3148#[facet(untagged)]
3149#[repr(u8)]
3150pub enum UntaggedEnum {
3151    Point { x: i32, y: i32 },
3152    Value(i64),
3153}
3154
3155/// Untagged enum with unit variant that matches null.
3156#[derive(Facet, Debug, Clone, PartialEq)]
3157#[facet(untagged)]
3158#[repr(u8)]
3159pub enum UntaggedWithNull {
3160    None,
3161    Some(i32),
3162}
3163
3164/// Untagged enum with newtype variants for discrimination testing.
3165#[derive(Facet, Debug, Clone, PartialEq)]
3166#[facet(untagged)]
3167#[repr(u8)]
3168pub enum UntaggedNewtype {
3169    String(String),
3170    Number(u64),
3171    Bool(bool),
3172}
3173
3174/// Struct containing an untagged enum field (nesting test).
3175#[derive(Facet, Debug, Clone, PartialEq)]
3176pub struct UntaggedAsField {
3177    pub name: String,
3178    pub value: UntaggedNewtype,
3179}
3180
3181/// Untagged enum with only unit variants (dataless enum).
3182/// Tests issue #1228: deserializing untagged dataless enums from strings.
3183#[derive(Facet, Debug, Clone, PartialEq)]
3184#[facet(untagged)]
3185#[repr(u8)]
3186pub enum UntaggedUnitOnly {
3187    Alpha,
3188    Beta,
3189    Gamma,
3190}
3191
3192/// Enum with tuple variant (multiple fields).
3193#[derive(Facet, Debug, Clone, PartialEq)]
3194#[repr(u8)]
3195pub enum TupleVariantEnum {
3196    Empty,
3197    Pair(String, i32),
3198    Triple(bool, f64, String),
3199}
3200
3201/// Enum with newtype variant (single field).
3202#[derive(Facet, Debug, Clone, PartialEq)]
3203#[repr(u8)]
3204pub enum NewtypeVariantEnum {
3205    None,
3206    Some(i32),
3207}
3208
3209// ── Smart pointer test fixtures ──
3210
3211/// Fixture for `Box<T>` test.
3212#[derive(Facet, Debug, Clone, PartialEq)]
3213#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3214pub struct BoxWrapper {
3215    pub inner: Box<i32>,
3216}
3217
3218/// Fixture for `Arc<T>` test.
3219#[derive(Facet, Debug, Clone, PartialEq)]
3220#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3221pub struct ArcWrapper {
3222    pub inner: std::sync::Arc<i32>,
3223}
3224
3225/// Fixture for `Rc<T>` test.
3226#[derive(Facet, Debug, Clone, PartialEq)]
3227#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3228pub struct RcWrapper {
3229    pub inner: std::rc::Rc<i32>,
3230}
3231
3232/// Fixture for `Box<str>` test.
3233#[derive(Facet, Debug, Clone, PartialEq)]
3234pub struct BoxStrWrapper {
3235    pub inner: Box<str>,
3236}
3237
3238/// Fixture for `Arc<str>` test.
3239#[derive(Facet, Debug, Clone, PartialEq)]
3240pub struct ArcStrWrapper {
3241    pub inner: std::sync::Arc<str>,
3242}
3243
3244/// Fixture for `Rc<str>` test.
3245#[derive(Facet, Debug, Clone, PartialEq)]
3246pub struct RcStrWrapper {
3247    pub inner: std::rc::Rc<str>,
3248}
3249
3250/// Fixture for `Arc<[T]>` test.
3251#[derive(Facet, Debug, Clone, PartialEq)]
3252pub struct ArcSliceWrapper {
3253    pub inner: std::sync::Arc<[i32]>,
3254}
3255
3256// ── Yoke test fixtures ──
3257
3258/// Fixture for `Yoke<Cow<'static, str>, Arc<str>>` test.
3259#[cfg(feature = "yoke")]
3260#[derive(Facet, Debug)]
3261pub struct YokeWrapper {
3262    pub value: yoke::Yoke<std::borrow::Cow<'static, str>, std::sync::Arc<str>>,
3263}
3264
3265#[cfg(feature = "yoke")]
3266impl PartialEq for YokeWrapper {
3267    fn eq(&self, other: &Self) -> bool {
3268        self.value.get() == other.value.get()
3269    }
3270}
3271
3272/// Fixture for `YokingWrapper` test.
3273#[cfg(feature = "yoke")]
3274#[derive(Facet, Debug)]
3275pub struct YokingWrapper {
3276    pub value: yoke::Yoke<SplittingYoker<'static>, std::sync::Arc<str>>,
3277}
3278
3279#[cfg(feature = "yoke")]
3280impl PartialEq for YokingWrapper {
3281    fn eq(&self, other: &Self) -> bool {
3282        self.value.get() == other.value.get()
3283    }
3284}
3285
3286#[cfg(feature = "yoke")]
3287#[derive(Facet, Debug, PartialEq, yoke::Yokeable)]
3288#[facet(try_from_ref = SplittingYoker::try_from_ref)]
3289pub struct SplittingYoker<'a> {
3290    left: &'a str,
3291    right: &'a str,
3292}
3293
3294#[cfg(feature = "yoke")]
3295impl<'a> SplittingYoker<'a> {
3296    pub fn try_from_ref(value: &'a str) -> Result<Self, &'static str> {
3297        if let Some((left, right)) = value.split_once('|') {
3298            Ok(SplittingYoker { left, right })
3299        } else {
3300            Err("Invalid format")
3301        }
3302    }
3303}
3304
3305// ── Set test fixtures ──
3306
3307/// Fixture for BTreeSet test.
3308#[derive(Facet, Debug, Clone, PartialEq)]
3309#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3310pub struct SetWrapper {
3311    pub items: std::collections::BTreeSet<String>,
3312}
3313
3314// ── Extended numeric test fixtures ──
3315
3316/// Fixture for 16-bit integer test.
3317#[derive(Facet, Debug, Clone, PartialEq)]
3318#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3319pub struct IntegerTypes16 {
3320    pub signed_16: i16,
3321    pub unsigned_16: u16,
3322}
3323
3324/// Fixture for 128-bit integer test.
3325#[derive(Facet, Debug, Clone, PartialEq)]
3326#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3327pub struct IntegerTypes128 {
3328    pub signed_128: i128,
3329    pub unsigned_128: u128,
3330}
3331
3332/// Fixture for pointer-sized integer test.
3333#[derive(Facet, Debug, Clone, PartialEq)]
3334#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3335pub struct IntegerTypesSize {
3336    pub signed_size: isize,
3337    pub unsigned_size: usize,
3338}
3339
3340// ── NonZero test fixtures ──
3341
3342/// Fixture for NonZero integer test.
3343#[derive(Facet, Debug, Clone, PartialEq)]
3344#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3345pub struct NonZeroTypes {
3346    pub nz_u32: std::num::NonZeroU32,
3347    pub nz_i64: std::num::NonZeroI64,
3348}
3349
3350/// Fixture for extended NonZero integer test.
3351#[derive(Facet, Debug, Clone, PartialEq)]
3352#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3353pub struct NonZeroTypesExtended {
3354    pub nz_u8: std::num::NonZeroU8,
3355    pub nz_i8: std::num::NonZeroI8,
3356    pub nz_u16: std::num::NonZeroU16,
3357    pub nz_i16: std::num::NonZeroI16,
3358    pub nz_u128: std::num::NonZeroU128,
3359    pub nz_i128: std::num::NonZeroI128,
3360    pub nz_usize: std::num::NonZeroUsize,
3361    pub nz_isize: std::num::NonZeroIsize,
3362}
3363
3364// ── Borrowed string test fixtures ──
3365
3366/// Fixture for Cow<'static, str> test.
3367#[derive(Facet, Debug, Clone, PartialEq)]
3368#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3369pub struct CowStrWrapper {
3370    pub owned: std::borrow::Cow<'static, str>,
3371    pub message: std::borrow::Cow<'static, str>,
3372}
3373
3374// ── Newtype test fixtures ──
3375
3376/// Newtype wrapper around u64.
3377#[derive(Facet, Debug, Clone, PartialEq)]
3378#[facet(transparent)]
3379pub struct NewtypeU64(pub u64);
3380
3381/// Fixture containing newtype u64.
3382#[derive(Facet, Debug, Clone, PartialEq)]
3383pub struct NewtypeU64Wrapper {
3384    pub value: NewtypeU64,
3385}
3386
3387/// Newtype wrapper around String.
3388#[derive(Facet, Debug, Clone, PartialEq)]
3389#[facet(transparent)]
3390pub struct NewtypeString(pub String);
3391
3392/// Fixture containing newtype String.
3393#[derive(Facet, Debug, Clone, PartialEq)]
3394pub struct NewtypeStringWrapper {
3395    pub value: NewtypeString,
3396}
3397
3398// ── Char test fixtures ──
3399
3400/// Fixture for char scalar test.
3401#[derive(Facet, Debug, Clone, PartialEq)]
3402#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3403pub struct CharWrapper {
3404    pub letter: char,
3405    pub emoji: char,
3406}
3407
3408// ── HashSet test fixtures ──
3409
3410/// Fixture for HashSet test.
3411#[derive(Facet, Debug, Clone, PartialEq)]
3412#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3413pub struct HashSetWrapper {
3414    pub items: std::collections::HashSet<String>,
3415}
3416
3417// ── Nested collection test fixtures ──
3418
3419/// Fixture for nested Vec test.
3420#[derive(Facet, Debug, Clone, PartialEq)]
3421#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3422pub struct NestedVecWrapper {
3423    pub matrix: Vec<Vec<i32>>,
3424}
3425
3426// ── Bytes/binary data test fixtures ──
3427
3428/// Fixture for `Vec<u8>` binary data test.
3429#[derive(Facet, Debug, Clone, PartialEq)]
3430#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3431pub struct BytesWrapper {
3432    pub data: Vec<u8>,
3433}
3434
3435// ── Fixed-size array test fixtures ──
3436
3437/// Fixture for fixed-size array test.
3438#[derive(Facet, Debug, Clone, PartialEq)]
3439#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3440pub struct ArrayWrapper {
3441    pub values: [u64; 3],
3442}
3443
3444// ── Unknown field handling test fixtures ──
3445
3446/// Fixture for skip_unknown_fields test (no deny_unknown_fields attribute).
3447#[derive(Facet, Debug, Clone, PartialEq)]
3448#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3449pub struct SkipUnknownStruct {
3450    pub known: String,
3451}
3452
3453// ── String escape test fixtures ──
3454
3455/// Fixture for string escape test.
3456#[derive(Facet, Debug, Clone, PartialEq)]
3457pub struct StringEscapes {
3458    pub text: String,
3459}
3460
3461/// Fixture for extended string escape test.
3462#[derive(Facet, Debug, Clone, PartialEq)]
3463pub struct StringEscapesExtended {
3464    pub backspace: String,
3465    pub formfeed: String,
3466    pub carriage_return: String,
3467    pub control_char: String,
3468}
3469
3470// ── Unit type test fixtures ──
3471
3472/// Fixture for unit struct test (zero-sized type).
3473#[derive(Facet, Debug, Clone, PartialEq)]
3474#[cfg_attr(feature = "msgpack", derive(serde::Serialize, serde::Deserialize))]
3475pub struct UnitStruct;
3476
3477// ── Third-party type case descriptors ──
3478
3479#[cfg(feature = "uuid")]
3480const CASE_UUID: CaseDescriptor<UuidWrapper> = CaseDescriptor {
3481    id: "third_party::uuid",
3482    description: "uuid::Uuid type",
3483    expected: || UuidWrapper {
3484        id: uuid::Uuid::from_bytes([
3485            0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
3486            0x00, 0x00,
3487        ]),
3488    },
3489};
3490
3491#[cfg(feature = "ulid")]
3492const CASE_ULID: CaseDescriptor<UlidWrapper> = CaseDescriptor {
3493    id: "third_party::ulid",
3494    description: "ulid::Ulid type",
3495    expected: || UlidWrapper {
3496        id: ulid::Ulid::from_string("01ARZ3NDEKTSV4RRFFQ69G5FAV").unwrap(),
3497    },
3498};
3499
3500#[cfg(feature = "camino")]
3501const CASE_CAMINO_PATH: CaseDescriptor<CaminoWrapper> = CaseDescriptor {
3502    id: "third_party::camino",
3503    description: "camino::Utf8PathBuf type",
3504    expected: || CaminoWrapper {
3505        path: camino::Utf8PathBuf::from("/home/user/documents"),
3506    },
3507};
3508
3509#[cfg(feature = "ordered-float")]
3510const CASE_ORDERED_FLOAT: CaseDescriptor<OrderedFloatWrapper> = CaseDescriptor {
3511    id: "third_party::ordered_float",
3512    description: "ordered_float::OrderedFloat type",
3513    expected: || OrderedFloatWrapper {
3514        value: ordered_float::OrderedFloat(1.23456),
3515    },
3516};
3517
3518#[cfg(feature = "rust_decimal")]
3519const CASE_RUST_DECIMAL: CaseDescriptor<RustDecimalWrapper> = CaseDescriptor {
3520    id: "third_party::rust_decimal",
3521    description: "rust_decimal::Decimal type",
3522    expected: || RustDecimalWrapper {
3523        amount: rust_decimal::Decimal::new(2499, 2), // 24.99
3524    },
3525};
3526
3527#[cfg(feature = "time")]
3528const CASE_TIME_OFFSET_DATETIME: CaseDescriptor<TimeOffsetDateTimeWrapper> = CaseDescriptor {
3529    id: "third_party::time_offset_datetime",
3530    description: "time::OffsetDateTime type",
3531    expected: || TimeOffsetDateTimeWrapper {
3532        created_at: time::macros::datetime!(2023-01-15 12:34:56 UTC),
3533    },
3534};
3535
3536#[cfg(feature = "jiff02")]
3537const CASE_JIFF_TIMESTAMP: CaseDescriptor<JiffTimestampWrapper> = CaseDescriptor {
3538    id: "third_party::jiff_timestamp",
3539    description: "jiff::Timestamp type",
3540    expected: || JiffTimestampWrapper {
3541        created_at: "2023-12-31T11:30:00Z".parse().unwrap(),
3542    },
3543};
3544
3545#[cfg(feature = "jiff02")]
3546const CASE_JIFF_CIVIL_DATETIME: CaseDescriptor<JiffCivilDateTimeWrapper> = CaseDescriptor {
3547    id: "third_party::jiff_civil_datetime",
3548    description: "jiff::civil::DateTime type",
3549    expected: || JiffCivilDateTimeWrapper {
3550        created_at: "2024-06-19T15:22:45".parse().unwrap(),
3551    },
3552};
3553
3554#[cfg(feature = "chrono")]
3555const CASE_CHRONO_DATETIME_UTC: CaseDescriptor<ChronoDateTimeUtcWrapper> = CaseDescriptor {
3556    id: "third_party::chrono_datetime_utc",
3557    description: "chrono::DateTime<Utc> type",
3558    expected: || ChronoDateTimeUtcWrapper {
3559        created_at: chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2023, 1, 15, 12, 34, 56)
3560            .unwrap(),
3561    },
3562};
3563
3564#[cfg(feature = "chrono")]
3565const CASE_CHRONO_NAIVE_DATETIME: CaseDescriptor<ChronoNaiveDateTimeWrapper> = CaseDescriptor {
3566    id: "third_party::chrono_naive_datetime",
3567    description: "chrono::NaiveDateTime type",
3568    expected: || ChronoNaiveDateTimeWrapper {
3569        created_at: chrono::NaiveDate::from_ymd_opt(2023, 1, 15)
3570            .unwrap()
3571            .and_hms_opt(12, 34, 56)
3572            .unwrap(),
3573    },
3574};
3575
3576#[cfg(feature = "chrono")]
3577const CASE_CHRONO_NAIVE_DATE: CaseDescriptor<ChronoNaiveDateWrapper> = CaseDescriptor {
3578    id: "third_party::chrono_naive_date",
3579    description: "chrono::NaiveDate type",
3580    expected: || ChronoNaiveDateWrapper {
3581        birth_date: chrono::NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(),
3582    },
3583};
3584
3585#[cfg(feature = "chrono")]
3586const CASE_CHRONO_NAIVE_TIME: CaseDescriptor<ChronoNaiveTimeWrapper> = CaseDescriptor {
3587    id: "third_party::chrono_naive_time",
3588    description: "chrono::NaiveTime type",
3589    expected: || ChronoNaiveTimeWrapper {
3590        alarm_time: chrono::NaiveTime::from_hms_opt(12, 34, 56).unwrap(),
3591    },
3592};
3593
3594#[cfg(feature = "chrono")]
3595const CASE_CHRONO_IN_VEC: CaseDescriptor<ChronoInVecWrapper> = CaseDescriptor {
3596    id: "third_party::chrono_in_vec",
3597    description: "Vec<chrono::DateTime<Utc>> - chrono in collections",
3598    expected: || ChronoInVecWrapper {
3599        timestamps: vec![
3600            chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2023, 1, 1, 0, 0, 0).unwrap(),
3601            chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2023, 6, 15, 12, 30, 0).unwrap(),
3602        ],
3603    },
3604};
3605
3606#[cfg(feature = "chrono")]
3607const CASE_CHRONO_DURATION: CaseDescriptor<ChronoDurationWrapper> = CaseDescriptor {
3608    id: "third_party::chrono_duration",
3609    description: "chrono::Duration type - serializes as (secs, nanos) tuple",
3610    expected: || ChronoDurationWrapper {
3611        duration: chrono::Duration::seconds(3600) + chrono::Duration::nanoseconds(500_000_000),
3612    },
3613};
3614
3615#[cfg(feature = "chrono")]
3616const CASE_CHRONO_DURATION_NEGATIVE: CaseDescriptor<ChronoDurationNegativeWrapper> =
3617    CaseDescriptor {
3618        id: "third_party::chrono_duration_negative",
3619        description: "chrono::Duration negative value - tests signed (secs, nanos) handling",
3620        expected: || ChronoDurationNegativeWrapper {
3621            duration: chrono::Duration::seconds(-90) + chrono::Duration::nanoseconds(-250_000_000),
3622        },
3623    };
3624
3625// ── Standard library time case descriptors ──
3626
3627const CASE_STD_DURATION: CaseDescriptor<StdDurationWrapper> = CaseDescriptor {
3628    id: "std::std_duration",
3629    description: "core::time::Duration type - serializes as (secs, nanos) tuple",
3630    expected: || StdDurationWrapper {
3631        duration: core::time::Duration::new(3600, 500_000_000),
3632    },
3633};
3634
3635// ── Bytes crate case descriptors ──
3636
3637#[cfg(feature = "bytes")]
3638const CASE_BYTES_BYTES: CaseDescriptor<BytesBytesWrapper> = CaseDescriptor {
3639    id: "third_party::bytes_bytes",
3640    description: "bytes::Bytes type",
3641    expected: || BytesBytesWrapper {
3642        data: bytes::Bytes::from_static(&[1, 2, 3, 4, 255]),
3643    },
3644};
3645
3646#[cfg(feature = "bytes")]
3647const CASE_BYTES_BYTES_MUT: CaseDescriptor<BytesBytesMutWrapper> = CaseDescriptor {
3648    id: "third_party::bytes_bytes_mut",
3649    description: "bytes::BytesMut type",
3650    expected: || BytesBytesMutWrapper {
3651        data: bytes::BytesMut::from(&[1, 2, 3, 4, 255][..]),
3652    },
3653};
3654
3655// ── String optimization crate case descriptors ──
3656
3657#[cfg(feature = "bytestring")]
3658const CASE_BYTESTRING: CaseDescriptor<ByteStringWrapper> = CaseDescriptor {
3659    id: "third_party::bytestring",
3660    description: "bytestring::ByteString type",
3661    expected: || ByteStringWrapper {
3662        value: bytestring::ByteString::from("hello world"),
3663    },
3664};
3665
3666#[cfg(feature = "compact_str")]
3667const CASE_COMPACT_STRING: CaseDescriptor<CompactStringWrapper> = CaseDescriptor {
3668    id: "third_party::compact_string",
3669    description: "compact_str::CompactString type",
3670    expected: || CompactStringWrapper {
3671        value: compact_str::CompactString::from("hello world"),
3672    },
3673};
3674
3675#[cfg(feature = "smartstring")]
3676const CASE_SMARTSTRING: CaseDescriptor<SmartStringWrapper> = CaseDescriptor {
3677    id: "third_party::smartstring",
3678    description: "smartstring::SmartString type",
3679    expected: || SmartStringWrapper {
3680        value: smartstring::SmartString::from("hello world"),
3681    },
3682};
3683
3684#[cfg(feature = "smol_str")]
3685const CASE_SMOL_STR: CaseDescriptor<SmolStrWrapper> = CaseDescriptor {
3686    id: "third_party::smol_str",
3687    description: "smol_str::SmolStr type",
3688    expected: || SmolStrWrapper {
3689        value: smol_str::SmolStr::from("hello world"),
3690    },
3691};
3692
3693#[cfg(feature = "iddqd")]
3694const CASE_IDDQD_ID_HASH_MAP: CaseDescriptor<IddqdIdHashMapWrapper> = CaseDescriptor {
3695    id: "third_party::iddqd_id_hash_map",
3696    description: "iddqd::IdHashMap type (set-like collection with key extraction)",
3697    expected: || {
3698        let mut map = iddqd::IdHashMap::with_hasher(std::hash::RandomState::new());
3699        map.insert_overwrite(IddqdTestItem {
3700            id: 1,
3701            name: String::from("Alice"),
3702        });
3703        IddqdIdHashMapWrapper { items: map }
3704    },
3705};
3706
3707#[cfg(feature = "iddqd")]
3708const CASE_IDDQD_ID_ORD_MAP: CaseDescriptor<IddqdIdOrdMapWrapper> = CaseDescriptor {
3709    id: "third_party::iddqd_id_ord_map",
3710    description: "iddqd::IdOrdMap type (ordered set-like collection with key extraction)",
3711    expected: || {
3712        let mut map = iddqd::IdOrdMap::new();
3713        map.insert_overwrite(IddqdOrdTestItem {
3714            id: 1,
3715            name: String::from("Alice"),
3716        });
3717        IddqdIdOrdMapWrapper { items: map }
3718    },
3719};
3720
3721#[cfg(feature = "iddqd")]
3722const CASE_IDDQD_BI_HASH_MAP: CaseDescriptor<IddqdBiHashMapWrapper> = CaseDescriptor {
3723    id: "third_party::iddqd_bi_hash_map",
3724    description: "iddqd::BiHashMap type (bijective map with two keys per value)",
3725    expected: || {
3726        let mut map = iddqd::BiHashMap::with_hasher(std::hash::RandomState::new());
3727        map.insert_overwrite(IddqdBiTestItem {
3728            id: 1,
3729            code: String::from("A001"),
3730            name: String::from("Alice"),
3731        });
3732        IddqdBiHashMapWrapper { items: map }
3733    },
3734};
3735
3736#[cfg(feature = "iddqd")]
3737const CASE_IDDQD_TRI_HASH_MAP: CaseDescriptor<IddqdTriHashMapWrapper> = CaseDescriptor {
3738    id: "third_party::iddqd_tri_hash_map",
3739    description: "iddqd::TriHashMap type (trijective map with three keys per value)",
3740    expected: || {
3741        let mut map = iddqd::TriHashMap::with_hasher(std::hash::RandomState::new());
3742        map.insert_overwrite(IddqdTriTestItem {
3743            id: 1,
3744            code: String::from("A001"),
3745            email: String::from("alice@example.com"),
3746            name: String::from("Alice"),
3747        });
3748        IddqdTriHashMapWrapper { items: map }
3749    },
3750};
3751
3752// ── Dynamic value case descriptors ──
3753
3754#[cfg(feature = "facet-value")]
3755const CASE_VALUE_NULL: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3756    id: "value::null",
3757    description: "facet_value::Value - null",
3758    expected: || facet_value::Value::NULL,
3759};
3760
3761#[cfg(feature = "facet-value")]
3762const CASE_VALUE_BOOL: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3763    id: "value::bool",
3764    description: "facet_value::Value - bool",
3765    expected: || facet_value::Value::TRUE,
3766};
3767
3768#[cfg(feature = "facet-value")]
3769const CASE_VALUE_INTEGER: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3770    id: "value::integer",
3771    description: "facet_value::Value - integer",
3772    expected: || facet_value::Value::from(42i64),
3773};
3774
3775#[cfg(feature = "facet-value")]
3776const CASE_VALUE_FLOAT: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3777    id: "value::float",
3778    description: "facet_value::Value - float",
3779    expected: || facet_value::Value::from(2.5f64),
3780};
3781
3782#[cfg(feature = "facet-value")]
3783const CASE_VALUE_STRING: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3784    id: "value::string",
3785    description: "facet_value::Value - string",
3786    expected: || facet_value::Value::from("hello world"),
3787};
3788
3789#[cfg(feature = "facet-value")]
3790const CASE_VALUE_ARRAY: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3791    id: "value::array",
3792    description: "facet_value::Value - array",
3793    expected: || {
3794        facet_value::VArray::from_iter([
3795            facet_value::Value::from(1i64),
3796            facet_value::Value::from(2i64),
3797            facet_value::Value::from(3i64),
3798        ])
3799        .into()
3800    },
3801};
3802
3803#[cfg(feature = "facet-value")]
3804const CASE_VALUE_OBJECT: CaseDescriptor<facet_value::Value> = CaseDescriptor {
3805    id: "value::object",
3806    description: "facet_value::Value - object",
3807    expected: || {
3808        let mut map = facet_value::VObject::new();
3809        map.insert("name", facet_value::Value::from("test"));
3810        map.insert("count", facet_value::Value::from(42i64));
3811        map.into()
3812    },
3813};
3814
3815// ── Third-party type test fixtures ──
3816
3817/// Fixture for uuid::Uuid test.
3818#[cfg(feature = "uuid")]
3819#[derive(Facet, Debug, Clone, PartialEq)]
3820pub struct UuidWrapper {
3821    pub id: uuid::Uuid,
3822}
3823
3824/// Fixture for ulid::Ulid test.
3825#[cfg(feature = "ulid")]
3826#[derive(Facet, Debug, Clone, PartialEq)]
3827pub struct UlidWrapper {
3828    pub id: ulid::Ulid,
3829}
3830
3831/// Fixture for camino::Utf8PathBuf test.
3832#[cfg(feature = "camino")]
3833#[derive(Facet, Debug, Clone, PartialEq)]
3834pub struct CaminoWrapper {
3835    pub path: camino::Utf8PathBuf,
3836}
3837
3838/// Fixture for ordered_float::OrderedFloat test.
3839#[cfg(feature = "ordered-float")]
3840#[derive(Facet, Debug, Clone, PartialEq)]
3841pub struct OrderedFloatWrapper {
3842    pub value: ordered_float::OrderedFloat<f64>,
3843}
3844
3845/// Fixture for rust_decimal::Decimal test.
3846#[cfg(feature = "rust_decimal")]
3847#[derive(Facet, Debug, Clone, PartialEq)]
3848pub struct RustDecimalWrapper {
3849    pub amount: rust_decimal::Decimal,
3850}
3851
3852/// Fixture for time::OffsetDateTime test.
3853#[cfg(feature = "time")]
3854#[derive(Facet, Debug, Clone, PartialEq)]
3855pub struct TimeOffsetDateTimeWrapper {
3856    pub created_at: time::OffsetDateTime,
3857}
3858
3859/// Fixture for jiff::Timestamp test.
3860#[cfg(feature = "jiff02")]
3861#[derive(Facet, Debug, Clone, PartialEq)]
3862pub struct JiffTimestampWrapper {
3863    pub created_at: jiff::Timestamp,
3864}
3865
3866/// Fixture for jiff::civil::DateTime test.
3867#[cfg(feature = "jiff02")]
3868#[derive(Facet, Debug, Clone, PartialEq)]
3869pub struct JiffCivilDateTimeWrapper {
3870    pub created_at: jiff::civil::DateTime,
3871}
3872
3873/// Fixture for `chrono::DateTime<Utc>` test.
3874#[cfg(feature = "chrono")]
3875#[derive(Facet, Debug, Clone, PartialEq)]
3876pub struct ChronoDateTimeUtcWrapper {
3877    pub created_at: chrono::DateTime<chrono::Utc>,
3878}
3879
3880/// Fixture for chrono::NaiveDateTime test.
3881#[cfg(feature = "chrono")]
3882#[derive(Facet, Debug, Clone, PartialEq)]
3883pub struct ChronoNaiveDateTimeWrapper {
3884    pub created_at: chrono::NaiveDateTime,
3885}
3886
3887/// Fixture for chrono::NaiveDate test.
3888#[cfg(feature = "chrono")]
3889#[derive(Facet, Debug, Clone, PartialEq)]
3890pub struct ChronoNaiveDateWrapper {
3891    pub birth_date: chrono::NaiveDate,
3892}
3893
3894/// Fixture for chrono::NaiveTime test.
3895#[cfg(feature = "chrono")]
3896#[derive(Facet, Debug, Clone, PartialEq)]
3897pub struct ChronoNaiveTimeWrapper {
3898    pub alarm_time: chrono::NaiveTime,
3899}
3900
3901/// Fixture for chrono in collections test.
3902#[cfg(feature = "chrono")]
3903#[derive(Facet, Debug, Clone, PartialEq)]
3904pub struct ChronoInVecWrapper {
3905    pub timestamps: Vec<chrono::DateTime<chrono::Utc>>,
3906}
3907
3908/// Fixture for `chrono::Duration` test.
3909#[cfg(feature = "chrono")]
3910#[derive(Facet, Debug, Clone, PartialEq)]
3911pub struct ChronoDurationWrapper {
3912    pub duration: chrono::Duration,
3913}
3914
3915/// Fixture for `chrono::Duration` negative test.
3916#[cfg(feature = "chrono")]
3917#[derive(Facet, Debug, Clone, PartialEq)]
3918pub struct ChronoDurationNegativeWrapper {
3919    pub duration: chrono::Duration,
3920}
3921
3922// ── Standard library time test fixtures ──
3923
3924/// Fixture for `core::time::Duration` test.
3925#[derive(Facet, Debug, Clone, PartialEq)]
3926pub struct StdDurationWrapper {
3927    pub duration: core::time::Duration,
3928}
3929
3930// ── Bytes crate test fixtures ──
3931
3932/// Fixture for `bytes::Bytes` test.
3933#[cfg(feature = "bytes")]
3934#[derive(Facet, Debug, Clone, PartialEq)]
3935pub struct BytesBytesWrapper {
3936    pub data: bytes::Bytes,
3937}
3938
3939/// Fixture for `bytes::BytesMut` test.
3940#[cfg(feature = "bytes")]
3941#[derive(Facet, Debug, Clone, PartialEq)]
3942pub struct BytesBytesMutWrapper {
3943    pub data: bytes::BytesMut,
3944}
3945
3946// ── String optimization crate test fixtures ──
3947
3948/// Fixture for `bytestring::ByteString` test.
3949#[cfg(feature = "bytestring")]
3950#[derive(Facet, Debug, Clone, PartialEq)]
3951pub struct ByteStringWrapper {
3952    pub value: bytestring::ByteString,
3953}
3954
3955/// Fixture for `compact_str::CompactString` test.
3956#[cfg(feature = "compact_str")]
3957#[derive(Facet, Debug, Clone, PartialEq)]
3958pub struct CompactStringWrapper {
3959    pub value: compact_str::CompactString,
3960}
3961
3962/// Fixture for `smartstring::SmartString` test.
3963#[cfg(feature = "smartstring")]
3964#[derive(Facet, Debug, Clone, PartialEq)]
3965pub struct SmartStringWrapper {
3966    pub value: smartstring::SmartString<smartstring::LazyCompact>,
3967}
3968
3969/// Fixture for `smol_str::SmolStr` test.
3970#[cfg(feature = "smol_str")]
3971#[derive(Facet, Debug, Clone, PartialEq)]
3972pub struct SmolStrWrapper {
3973    pub value: smol_str::SmolStr,
3974}
3975
3976// ── iddqd crate test fixtures ──
3977
3978/// Test item type for iddqd collections.
3979/// Implements `IdHashItem` with `id` as the key.
3980#[cfg(feature = "iddqd")]
3981#[derive(Facet, Debug, Clone, PartialEq)]
3982pub struct IddqdTestItem {
3983    pub id: u64,
3984    pub name: String,
3985}
3986
3987#[cfg(feature = "iddqd")]
3988impl iddqd::IdHashItem for IddqdTestItem {
3989    type Key<'a> = u64;
3990    fn key(&self) -> Self::Key<'_> {
3991        self.id
3992    }
3993    iddqd::id_upcast!();
3994}
3995
3996/// Fixture for `iddqd::IdHashMap` test.
3997/// Uses `std::hash::RandomState` as the hasher since it implements `Facet`.
3998#[cfg(feature = "iddqd")]
3999#[derive(Facet, Debug, Clone, PartialEq)]
4000pub struct IddqdIdHashMapWrapper {
4001    pub items: iddqd::IdHashMap<IddqdTestItem, std::hash::RandomState>,
4002}
4003
4004/// Test item type for iddqd IdOrdMap.
4005/// Implements `IdOrdItem` with `id` as the key.
4006#[cfg(feature = "iddqd")]
4007#[derive(Facet, Debug, Clone, PartialEq)]
4008pub struct IddqdOrdTestItem {
4009    pub id: u64,
4010    pub name: String,
4011}
4012
4013#[cfg(feature = "iddqd")]
4014impl iddqd::IdOrdItem for IddqdOrdTestItem {
4015    type Key<'a> = u64;
4016    fn key(&self) -> Self::Key<'_> {
4017        self.id
4018    }
4019    iddqd::id_upcast!();
4020}
4021
4022/// Fixture for `iddqd::IdOrdMap` test.
4023#[cfg(feature = "iddqd")]
4024#[derive(Facet, Debug, Clone, PartialEq)]
4025pub struct IddqdIdOrdMapWrapper {
4026    pub items: iddqd::IdOrdMap<IddqdOrdTestItem>,
4027}
4028
4029/// Test item type for iddqd BiHashMap.
4030/// Implements `BiHashItem` with `id` as key1 and `code` as key2.
4031#[cfg(feature = "iddqd")]
4032#[derive(Facet, Debug, Clone, PartialEq)]
4033pub struct IddqdBiTestItem {
4034    pub id: u64,
4035    pub code: String,
4036    pub name: String,
4037}
4038
4039#[cfg(feature = "iddqd")]
4040impl iddqd::BiHashItem for IddqdBiTestItem {
4041    type K1<'a> = u64;
4042    type K2<'a> = &'a str;
4043    fn key1(&self) -> Self::K1<'_> {
4044        self.id
4045    }
4046    fn key2(&self) -> Self::K2<'_> {
4047        &self.code
4048    }
4049    iddqd::bi_upcast!();
4050}
4051
4052/// Fixture for `iddqd::BiHashMap` test.
4053/// Uses `std::hash::RandomState` as the hasher since it implements `Facet`.
4054#[cfg(feature = "iddqd")]
4055#[derive(Facet, Debug, Clone, PartialEq)]
4056pub struct IddqdBiHashMapWrapper {
4057    pub items: iddqd::BiHashMap<IddqdBiTestItem, std::hash::RandomState>,
4058}
4059
4060/// Test item type for iddqd TriHashMap.
4061/// Implements `TriHashItem` with `id` as key1, `code` as key2, and `email` as key3.
4062#[cfg(feature = "iddqd")]
4063#[derive(Facet, Debug, Clone, PartialEq)]
4064pub struct IddqdTriTestItem {
4065    pub id: u64,
4066    pub code: String,
4067    pub email: String,
4068    pub name: String,
4069}
4070
4071#[cfg(feature = "iddqd")]
4072impl iddqd::TriHashItem for IddqdTriTestItem {
4073    type K1<'a> = u64;
4074    type K2<'a> = &'a str;
4075    type K3<'a> = &'a str;
4076    fn key1(&self) -> Self::K1<'_> {
4077        self.id
4078    }
4079    fn key2(&self) -> Self::K2<'_> {
4080        &self.code
4081    }
4082    fn key3(&self) -> Self::K3<'_> {
4083        &self.email
4084    }
4085    iddqd::tri_upcast!();
4086}
4087
4088/// Fixture for `iddqd::TriHashMap` test.
4089/// Uses `std::hash::RandomState` as the hasher since it implements `Facet`.
4090#[cfg(feature = "iddqd")]
4091#[derive(Facet, Debug, Clone, PartialEq)]
4092pub struct IddqdTriHashMapWrapper {
4093    pub items: iddqd::TriHashMap<IddqdTriTestItem, std::hash::RandomState>,
4094}
4095
4096fn emit_case_showcase<S, T>(
4097    desc: &'static CaseDescriptor<T>,
4098    note: Option<&'static str>,
4099    roundtrip_disabled_reason: Option<&'static str>,
4100    input: &'static [u8],
4101    highlight_language: Option<&'static str>,
4102    actual: &T,
4103) where
4104    S: FormatSuite,
4105    for<'facet> T: Facet<'facet>,
4106    T: Debug,
4107{
4108    let (input_label, input_block) = match highlight_language {
4109        Some(language) => match highlight_payload(language, input) {
4110            Some(html) => (format!("Input highlighted via arborium ({language})"), html),
4111            None => (
4112                format!("Input (UTF-8, highlighting unavailable for {language})"),
4113                String::from_utf8_lossy(input).into_owned(),
4114            ),
4115        },
4116        None => (
4117            "Input (UTF-8)".to_string(),
4118            String::from_utf8_lossy(input).into_owned(),
4119        ),
4120    };
4121
4122    let pretty_output = format!(
4123        "{}",
4124        actual.pretty_with(PrettyPrinter::new().with_indent_size(2))
4125    );
4126    let note_line = note.map(|n| format!("note: {n}\n")).unwrap_or_default();
4127    let roundtrip_line = roundtrip_disabled_reason
4128        .map(|r| format!("roundtrip: disabled ({r})\n"))
4129        .unwrap_or_default();
4130
4131    println!(
4132        "{}",
4133        formatdoc!(
4134            "
4135            ── facet-format-suite :: {format_name} :: {case_id} ──
4136            description: {description}
4137            {note_line}{roundtrip_line}{input_label}:
4138            {input_block}
4139
4140            facet-pretty output:
4141            {pretty_output}
4142            ",
4143            format_name = S::format_name(),
4144            case_id = desc.id,
4145            description = desc.description,
4146            note_line = note_line,
4147            roundtrip_line = roundtrip_line,
4148            input_label = input_label,
4149            input_block = input_block,
4150            pretty_output = pretty_output,
4151        )
4152    );
4153}
4154
4155fn emit_error_case_showcase<S: FormatSuite>(
4156    case_id: &str,
4157    description: &str,
4158    note: Option<&'static str>,
4159    input: &[u8],
4160    highlight_language: Option<&'static str>,
4161    error_contains: &str,
4162) {
4163    let (input_label, input_block) = match highlight_language {
4164        Some(language) => match highlight_payload(language, input) {
4165            Some(html) => (format!("Input highlighted via arborium ({language})"), html),
4166            None => (
4167                format!("Input (UTF-8, highlighting unavailable for {language})"),
4168                String::from_utf8_lossy(input).into_owned(),
4169            ),
4170        },
4171        None => (
4172            "Input (UTF-8)".to_string(),
4173            String::from_utf8_lossy(input).into_owned(),
4174        ),
4175    };
4176
4177    let note_line = note.map(|n| format!("note: {n}\n")).unwrap_or_default();
4178
4179    println!(
4180        "{}",
4181        formatdoc!(
4182            "
4183            ── facet-format-suite :: {format_name} :: {case_id} ──
4184            description: {description}
4185            {note_line}expects error containing: \"{error_contains}\"
4186            {input_label}:
4187            {input_block}
4188            ",
4189            format_name = S::format_name(),
4190            case_id = case_id,
4191            description = description,
4192            note_line = note_line,
4193            error_contains = error_contains,
4194            input_label = input_label,
4195            input_block = input_block,
4196        )
4197    );
4198}
4199
4200fn highlight_payload(language: &str, input: &[u8]) -> Option<String> {
4201    let source = core::str::from_utf8(input).ok()?;
4202    let mut highlighter = Highlighter::new();
4203    highlighter.highlight(language, source).ok()
4204}
4205
4206// ── MsgPack interoperability helpers ──
4207
4208/// Generate MsgPack bytes using `rmp-serde` for interoperability testing.
4209///
4210/// This module provides functions to serialize test fixtures using the
4211/// reference MsgPack implementation, allowing our parser to be tested
4212/// against known-good input.
4213#[cfg(feature = "msgpack")]
4214pub mod msgpack {
4215    use super::*;
4216
4217    /// Serialize a value to MsgPack bytes using rmp-serde.
4218    ///
4219    /// Uses `to_vec_named` to serialize structs as maps with field names,
4220    /// which is the format our parser expects.
4221    pub fn serialize<T: serde::Serialize>(value: &T) -> Vec<u8> {
4222        rmp_serde::to_vec_named(value).expect("rmp-serde serialization failed")
4223    }
4224
4225    /// Create a CaseSpec with MsgPack input from the expected value.
4226    ///
4227    /// This serializes the expected value using rmp-serde (reference impl)
4228    /// and returns a CaseSpec that will test our deserializer against it.
4229    pub fn case_from_expected<T>(expected: &T) -> CaseSpec
4230    where
4231        T: serde::Serialize,
4232    {
4233        CaseSpec::from_bytes_vec(serialize(expected))
4234    }
4235
4236    // ── Pre-built test inputs ──
4237
4238    /// MsgPack bytes for StructSingleField { name: "facet" }
4239    pub fn struct_single_field_bytes() -> Vec<u8> {
4240        serialize(&StructSingleField {
4241            name: "facet".into(),
4242        })
4243    }
4244
4245    /// MsgPack bytes for sequence [1, 2, 3]
4246    pub fn sequence_numbers_bytes() -> Vec<u8> {
4247        serialize(&vec![1u64, 2, 3])
4248    }
4249
4250    /// MsgPack bytes for NestedParent
4251    pub fn struct_nested_bytes() -> Vec<u8> {
4252        serialize(&NestedParent {
4253            id: 42,
4254            child: NestedChild {
4255                code: "alpha".into(),
4256                active: true,
4257            },
4258            tags: vec!["core".into(), "json".into()],
4259        })
4260    }
4261
4262    /// MsgPack bytes for BoolWrapper
4263    pub fn scalar_bool_bytes() -> Vec<u8> {
4264        serialize(&BoolWrapper {
4265            yes: true,
4266            no: false,
4267        })
4268    }
4269
4270    /// MsgPack bytes for IntegerTypes
4271    pub fn scalar_integers_bytes() -> Vec<u8> {
4272        serialize(&IntegerTypes {
4273            signed_8: -128,
4274            unsigned_8: 255,
4275            signed_32: -2_147_483_648,
4276            unsigned_32: 4_294_967_295,
4277            signed_64: -9_223_372_036_854_775_808,
4278            unsigned_64: 18_446_744_073_709_551_615,
4279        })
4280    }
4281
4282    /// MsgPack bytes for FloatTypes
4283    pub fn scalar_floats_bytes() -> Vec<u8> {
4284        serialize(&FloatTypes {
4285            float_32: 1.5,
4286            float_64: 2.25,
4287        })
4288    }
4289
4290    /// MsgPack bytes for MapWrapper (HashMap/BTreeMap)
4291    pub fn map_string_keys_bytes() -> Vec<u8> {
4292        let mut map = std::collections::BTreeMap::new();
4293        map.insert("alpha".to_string(), 1);
4294        map.insert("beta".to_string(), 2);
4295        serialize(&MapWrapper { data: map })
4296    }
4297
4298    /// MsgPack bytes for TupleWrapper
4299    pub fn tuple_simple_bytes() -> Vec<u8> {
4300        serialize(&TupleWrapper {
4301            triple: ("hello".to_string(), 42, true),
4302        })
4303    }
4304
4305    /// MsgPack bytes for NestedTupleWrapper
4306    pub fn tuple_nested_bytes() -> Vec<u8> {
4307        serialize(&NestedTupleWrapper {
4308            outer: ((1, 2), ("test".to_string(), true)),
4309        })
4310    }
4311
4312    /// MsgPack bytes for WithOption (None case)
4313    pub fn option_none_bytes() -> Vec<u8> {
4314        serialize(&WithOption {
4315            name: "test".to_string(),
4316            nickname: None,
4317        })
4318    }
4319
4320    /// MsgPack bytes for WithOption (Some case)
4321    pub fn option_some_bytes() -> Vec<u8> {
4322        serialize(&WithOption {
4323            name: "test".to_string(),
4324            nickname: Some("nick".to_string()),
4325        })
4326    }
4327
4328    /// MsgPack bytes for UnitVariantEnum
4329    pub fn enum_unit_variant_bytes() -> Vec<u8> {
4330        serialize(&UnitVariantEnum::Active)
4331    }
4332
4333    /// MsgPack bytes for NestedVecWrapper
4334    pub fn vec_nested_bytes() -> Vec<u8> {
4335        serialize(&NestedVecWrapper {
4336            matrix: vec![vec![1, 2], vec![3, 4, 5]],
4337        })
4338    }
4339
4340    /// MsgPack bytes for BytesWrapper
4341    pub fn bytes_vec_u8_bytes() -> Vec<u8> {
4342        serialize(&BytesWrapper {
4343            data: vec![0xDE, 0xAD, 0xBE, 0xEF],
4344        })
4345    }
4346
4347    /// MsgPack bytes for CharWrapper
4348    pub fn char_scalar_bytes() -> Vec<u8> {
4349        serialize(&CharWrapper {
4350            letter: 'A',
4351            emoji: '🦀',
4352        })
4353    }
4354
4355    /// MsgPack bytes for UnitStruct
4356    pub fn unit_struct_bytes() -> Vec<u8> {
4357        serialize(&UnitStruct)
4358    }
4359
4360    /// MsgPack bytes for HashSetWrapper
4361    pub fn hashset_bytes() -> Vec<u8> {
4362        let mut items = std::collections::HashSet::new();
4363        items.insert("alpha".to_string());
4364        items.insert("beta".to_string());
4365        items.insert("gamma".to_string());
4366        serialize(&HashSetWrapper { items })
4367    }
4368
4369    /// MsgPack bytes for EmptyTupleWrapper
4370    pub fn tuple_empty_bytes() -> Vec<u8> {
4371        serialize(&EmptyTupleWrapper {
4372            name: "test".to_string(),
4373            empty: (),
4374        })
4375    }
4376
4377    /// MsgPack bytes for SingleElementTupleWrapper
4378    pub fn tuple_single_element_bytes() -> Vec<u8> {
4379        serialize(&SingleElementTupleWrapper {
4380            name: "test".to_string(),
4381            single: (42,),
4382        })
4383    }
4384
4385    /// MsgPack bytes for ComplexEnum (Label variant)
4386    pub fn enum_complex_bytes() -> Vec<u8> {
4387        serialize(&ComplexEnum::Label {
4388            name: "facet".into(),
4389            level: 7,
4390        })
4391    }
4392
4393    /// MsgPack bytes for BoxWrapper
4394    pub fn box_wrapper_bytes() -> Vec<u8> {
4395        serialize(&BoxWrapper {
4396            inner: Box::new(42),
4397        })
4398    }
4399
4400    /// MsgPack bytes for ArcWrapper
4401    pub fn arc_wrapper_bytes() -> Vec<u8> {
4402        serialize(&ArcWrapper {
4403            inner: std::sync::Arc::new(42),
4404        })
4405    }
4406
4407    /// MsgPack bytes for RcWrapper
4408    pub fn rc_wrapper_bytes() -> Vec<u8> {
4409        serialize(&RcWrapper {
4410            inner: std::rc::Rc::new(42),
4411        })
4412    }
4413
4414    /// MsgPack bytes for SetWrapper (BTreeSet)
4415    pub fn set_btree_bytes() -> Vec<u8> {
4416        let mut items = std::collections::BTreeSet::new();
4417        items.insert("alpha".to_string());
4418        items.insert("beta".to_string());
4419        items.insert("gamma".to_string());
4420        serialize(&SetWrapper { items })
4421    }
4422
4423    /// MsgPack bytes for IntegerTypes16
4424    pub fn scalar_integers_16_bytes() -> Vec<u8> {
4425        serialize(&IntegerTypes16 {
4426            signed_16: -32768,
4427            unsigned_16: 65535,
4428        })
4429    }
4430
4431    /// MsgPack bytes for IntegerTypes128
4432    pub fn scalar_integers_128_bytes() -> Vec<u8> {
4433        serialize(&IntegerTypes128 {
4434            signed_128: -170141183460469231731687303715884105728,
4435            unsigned_128: 340282366920938463463374607431768211455,
4436        })
4437    }
4438
4439    /// MsgPack bytes for IntegerTypesSize
4440    pub fn scalar_integers_size_bytes() -> Vec<u8> {
4441        serialize(&IntegerTypesSize {
4442            signed_size: -1000,
4443            unsigned_size: 2000,
4444        })
4445    }
4446
4447    /// MsgPack bytes for NonZeroTypes
4448    pub fn nonzero_integers_bytes() -> Vec<u8> {
4449        serialize(&NonZeroTypes {
4450            nz_u32: std::num::NonZeroU32::new(42).unwrap(),
4451            nz_i64: std::num::NonZeroI64::new(-100).unwrap(),
4452        })
4453    }
4454
4455    /// MsgPack bytes for CowStrWrapper
4456    pub fn cow_str_bytes() -> Vec<u8> {
4457        serialize(&CowStrWrapper {
4458            owned: std::borrow::Cow::Owned("hello world".to_string()),
4459            message: std::borrow::Cow::Borrowed("borrowed"),
4460        })
4461    }
4462
4463    /// MsgPack bytes for ArrayWrapper
4464    pub fn array_fixed_size_bytes() -> Vec<u8> {
4465        serialize(&ArrayWrapper { values: [1, 2, 3] })
4466    }
4467
4468    /// MsgPack bytes for SkipUnknownStruct with extra fields
4469    /// Includes unknown field "extra" that should be ignored during deserialization
4470    pub fn skip_unknown_fields_bytes() -> Vec<u8> {
4471        #[derive(serde::Serialize)]
4472        struct WithExtra {
4473            known: String,
4474            extra: String,
4475        }
4476        serialize(&WithExtra {
4477            known: "value".to_string(),
4478            extra: "ignored".to_string(),
4479        })
4480    }
4481
4482    // ── Network type MsgPack bytes ──
4483    // Note: facet serializes net types as strings, not as serde's tuple/array format.
4484    // These helpers create MsgPack with string values for compatibility with facet.
4485
4486    /// Helper to create MsgPack bytes for a struct with a single string "addr" field.
4487    /// MsgPack format: fixmap(1) + fixstr("addr") + str(value)
4488    #[cfg(feature = "net")]
4489    fn net_addr_wrapper_bytes(addr_str: &str) -> Vec<u8> {
4490        use std::collections::BTreeMap;
4491        let mut map: BTreeMap<&str, &str> = BTreeMap::new();
4492        map.insert("addr", addr_str);
4493        serialize(&map)
4494    }
4495
4496    /// MsgPack bytes for IpAddrV4Wrapper
4497    #[cfg(feature = "net")]
4498    pub fn net_ip_addr_v4_bytes() -> Vec<u8> {
4499        net_addr_wrapper_bytes("192.168.1.1")
4500    }
4501
4502    /// MsgPack bytes for IpAddrV6Wrapper
4503    #[cfg(feature = "net")]
4504    pub fn net_ip_addr_v6_bytes() -> Vec<u8> {
4505        net_addr_wrapper_bytes("2001:db8::1")
4506    }
4507
4508    /// MsgPack bytes for Ipv4AddrWrapper
4509    #[cfg(feature = "net")]
4510    pub fn net_ipv4_addr_bytes() -> Vec<u8> {
4511        net_addr_wrapper_bytes("127.0.0.1")
4512    }
4513
4514    /// MsgPack bytes for Ipv6AddrWrapper
4515    #[cfg(feature = "net")]
4516    pub fn net_ipv6_addr_bytes() -> Vec<u8> {
4517        net_addr_wrapper_bytes("::1")
4518    }
4519
4520    /// MsgPack bytes for SocketAddrV4Wrapper
4521    #[cfg(feature = "net")]
4522    pub fn net_socket_addr_v4_bytes() -> Vec<u8> {
4523        net_addr_wrapper_bytes("192.168.1.1:8080")
4524    }
4525
4526    /// MsgPack bytes for SocketAddrV6Wrapper
4527    #[cfg(feature = "net")]
4528    pub fn net_socket_addr_v6_bytes() -> Vec<u8> {
4529        net_addr_wrapper_bytes("[2001:db8::1]:443")
4530    }
4531
4532    /// MsgPack bytes for SocketAddrV4ExplicitWrapper
4533    #[cfg(feature = "net")]
4534    pub fn net_socket_addr_v4_explicit_bytes() -> Vec<u8> {
4535        net_addr_wrapper_bytes("10.0.0.1:3000")
4536    }
4537
4538    /// MsgPack bytes for SocketAddrV6ExplicitWrapper
4539    #[cfg(feature = "net")]
4540    pub fn net_socket_addr_v6_explicit_bytes() -> Vec<u8> {
4541        net_addr_wrapper_bytes("[fe80::1]:9000")
4542    }
4543}
4544
4545/// Display showcase for dynamic input (binary formats).
4546fn emit_case_showcase_dynamic<S, T>(
4547    desc: &'static CaseDescriptor<T>,
4548    note: Option<&'static str>,
4549    roundtrip_disabled_reason: Option<&'static str>,
4550    input: &[u8],
4551    actual: &T,
4552) where
4553    S: FormatSuite,
4554    for<'facet> T: Facet<'facet>,
4555    T: Debug,
4556{
4557    // Format binary input as hex dump
4558    let input_block = format_hex_dump(input);
4559
4560    let pretty_output = format!(
4561        "{}",
4562        actual.pretty_with(PrettyPrinter::new().with_indent_size(2))
4563    );
4564    let note_line = note.map(|n| format!("note: {n}\n")).unwrap_or_default();
4565    let roundtrip_line = roundtrip_disabled_reason
4566        .map(|r| format!("roundtrip: disabled ({r})\n"))
4567        .unwrap_or_default();
4568
4569    println!(
4570        "{}",
4571        formatdoc!(
4572            "
4573            ── facet-format-suite :: {format_name} :: {case_id} ──
4574            description: {description}
4575            {note_line}{roundtrip_line}Input (binary, {len} bytes):
4576            {input_block}
4577
4578            facet-pretty output:
4579            {pretty_output}
4580            ",
4581            format_name = S::format_name(),
4582            case_id = desc.id,
4583            description = desc.description,
4584            note_line = note_line,
4585            roundtrip_line = roundtrip_line,
4586            len = input.len(),
4587            input_block = input_block,
4588            pretty_output = pretty_output,
4589        )
4590    );
4591}
4592
4593/// Format bytes as a hex dump.
4594fn format_hex_dump(input: &[u8]) -> String {
4595    use std::fmt::Write;
4596    let mut output = String::new();
4597    for (i, chunk) in input.chunks(16).enumerate() {
4598        // Offset
4599        write!(output, "{:08x}  ", i * 16).unwrap();
4600        // Hex bytes
4601        for (j, byte) in chunk.iter().enumerate() {
4602            if j == 8 {
4603                output.push(' ');
4604            }
4605            write!(output, "{:02x} ", byte).unwrap();
4606        }
4607        // Padding for incomplete lines
4608        for j in chunk.len()..16 {
4609            if j == 8 {
4610                output.push(' ');
4611            }
4612            output.push_str("   ");
4613        }
4614        // ASCII representation
4615        output.push_str(" |");
4616        for byte in chunk {
4617            if byte.is_ascii_graphic() || *byte == b' ' {
4618                output.push(*byte as char);
4619            } else {
4620                output.push('.');
4621            }
4622        }
4623        output.push_str("|\n");
4624    }
4625    output
4626}