facet_format_suite/
lib.rs

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