Skip to main content

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