use std::{cell::RefCell, collections::HashSet}; use indoc::indoc; use serde::Serialize; use serde_hooks::{ser, Path}; #[derive(Serialize)] struct UnitStruct; #[allow(clippy::enum_variant_names)] #[derive(Serialize)] enum Enum { UnitVariant, NewtypeVariant(()), StructVariant { struct_variant_val: () }, TupleVariant((), ()), } #[derive(Serialize)] struct Payload { unit_variant: Enum, newtype_variant: Enum, struct_variant: Enum, tuple_variant: Enum, } impl Payload { fn new() -> Self { Self { unit_variant: Enum::UnitVariant, newtype_variant: Enum::NewtypeVariant(()), struct_variant: Enum::StructVariant { struct_variant_val: (), }, tuple_variant: Enum::TupleVariant((), ()), } } } #[test] fn test_variant_traversing() { struct Hooks { variants_to_expect: RefCell>, structs_to_expect: RefCell>, structs_variants_to_expect: RefCell>, tuples_to_expect: RefCell>, tuple_variants_to_expect: RefCell>, } impl ser::Hooks for Hooks { fn on_enum_variant(&self, path: &Path, ev: &mut ser::EnumVariantScope) { let path = path.borrow_str(); assert!(self.variants_to_expect.borrow_mut().remove(path.as_str())); assert_eq!(ev.enum_name(), "Enum"); match path.as_str() { "unit_variant" => { assert_eq!(ev.variant_name(), "UnitVariant"); assert_eq!(ev.variant_index(), 0); } "newtype_variant" => { assert_eq!(ev.variant_name(), "NewtypeVariant"); assert_eq!(ev.variant_index(), 1); } "struct_variant" => { assert_eq!(ev.variant_name(), "StructVariant"); assert_eq!(ev.variant_index(), 2); } "tuple_variant" => { assert_eq!(ev.variant_name(), "TupleVariant"); assert_eq!(ev.variant_index(), 3); } _ => unreachable!("{path}"), } } fn on_struct(&self, path: &Path, st: &mut ser::StructScope) { let path = path.borrow_str(); self.structs_to_expect.borrow_mut().remove(path.as_str()); match path.as_str() { "" => {} "struct_variant" => { assert_eq!(st.struct_name(), "StructVariant"); assert_eq!(st.struct_len(), 1); } _ => unreachable!("{path}"), } } fn on_struct_variant( &self, path: &Path, ev: &mut ser::EnumVariantScope, _st: &mut ser::StructScope, ) { let path = path.borrow_str(); self.structs_variants_to_expect .borrow_mut() .remove(path.as_str()); match path.as_str() { "struct_variant" => { assert_eq!(ev.enum_name(), "Enum"); assert_eq!(ev.variant_name(), "StructVariant"); assert_eq!(ev.variant_index(), 2); } _ => unreachable!("{path}"), } } fn on_tuple(&self, path: &Path, tpl: &mut ser::TupleScope, seq: &mut ser::SeqScope) { let path = path.borrow_str(); self.tuples_to_expect.borrow_mut().remove(path.as_str()); assert_eq!(Some(tpl.tuple_len()), seq.seq_len()); match path.as_str() { "tuple_variant" => { assert_eq!(tpl.tuple_len(), 2); } _ => unreachable!("{path}"), } } fn on_tuple_variant( &self, path: &Path, ev: &mut ser::EnumVariantScope, _tpl: &mut ser::TupleScope, _seq: &mut ser::SeqScope, ) { let path = path.borrow_str(); self.tuple_variants_to_expect .borrow_mut() .remove(path.as_str()); match path.as_str() { "tuple_variant" => { assert_eq!(ev.enum_name(), "Enum"); assert_eq!(ev.variant_name(), "TupleVariant"); assert_eq!(ev.variant_index(), 3); } _ => unreachable!("{path}"), } } } let hooks = Hooks { variants_to_expect: RefCell::new( [ "unit_variant", "newtype_variant", "struct_variant", "tuple_variant", ] .into(), ), structs_to_expect: RefCell::new(["", "struct_variant"].into()), structs_variants_to_expect: RefCell::new(["struct_variant"].into()), tuples_to_expect: RefCell::new(["tuple_variant"].into()), tuple_variants_to_expect: RefCell::new(["tuple_variant"].into()), }; serde_json::to_string(&ser::hook(&Payload::new(), &hooks)).unwrap(); assert!( hooks.variants_to_expect.borrow().is_empty(), "following variants were expected, but not called back about {:?}", hooks.variants_to_expect.borrow() ); assert!( hooks.structs_to_expect.borrow().is_empty(), "following structs were expected, but not called back about {:?}", hooks.structs_to_expect.borrow() ); assert!( hooks.structs_variants_to_expect.borrow().is_empty(), "following struct variants were expected, but not called back about {:?}", hooks.structs_variants_to_expect.borrow() ); assert!( hooks.tuples_to_expect.borrow().is_empty(), "following tuples variants were expected, but not called back about {:?}", hooks.tuples_to_expect.borrow() ); assert!( hooks.tuple_variants_to_expect.borrow().is_empty(), "following tuple variants were expected, but not called back about {:?}", hooks.tuple_variants_to_expect.borrow() ); } #[test] fn test_enum_rename() { struct Hooks; impl ser::Hooks for Hooks { fn on_enum_variant(&self, path: &Path, ev: &mut ser::EnumVariantScope) { let path = path.borrow_str(); match path.as_str() { "unit_variant" => { ev.rename_enum("new_enum_name"); } "newtype_variant" => { ev.rename_enum(format!("NEW_{path}")); } "struct_variant" => { ev.rename_enum_case(serde_hooks::Case::Upper); } "tuple_variant" => {} _ => unreachable!(), } } } use serde_reflection::{Samples, Tracer, TracerConfig}; let mut tracer = Tracer::new(TracerConfig::default()); let mut samples = Samples::new(); tracer .trace_value(&mut samples, &ser::hook(&Payload::new(), &Hooks)) .unwrap(); let registry = tracer.registry().unwrap(); let actual = serde_yaml::to_string(®istry).unwrap(); let expected = indoc! {" ENUM: !ENUM 2: StructVariant: !STRUCT - struct_variant_val: UNIT Enum: !ENUM 3: TupleVariant: !TUPLE - UNIT - UNIT NEW_newtype_variant: !ENUM 1: NewtypeVariant: !NEWTYPE UNIT Payload: !STRUCT - unit_variant: !TYPENAME new_enum_name - newtype_variant: !TYPENAME NEW_newtype_variant - struct_variant: !TYPENAME ENUM - tuple_variant: !TYPENAME Enum new_enum_name: !ENUM 0: UnitVariant: UNIT "}; assert_eq!( actual, expected, "\n\nExpected YAML:\n\n{expected}\n\nActual YAML:\n\n{actual}\n\n" ); } #[test] fn test_variant_index_change() { struct Hooks; impl ser::Hooks for Hooks { fn on_enum_variant(&self, path: &Path, ev: &mut ser::EnumVariantScope) { let path = path.borrow_str(); if *path == "unit_variant" { ev.change_variant_index(10); } } } use serde_reflection::{Samples, Tracer, TracerConfig}; let mut tracer = Tracer::new(TracerConfig::default()); let mut samples = Samples::new(); tracer .trace_value(&mut samples, &ser::hook(&Payload::new(), &Hooks)) .unwrap(); let registry = tracer.registry().unwrap(); let actual = serde_yaml::to_string(®istry.get("Enum").unwrap()).unwrap(); let expected = indoc! {" !ENUM 1: NewtypeVariant: !NEWTYPE UNIT 2: StructVariant: !STRUCT - struct_variant_val: UNIT 3: TupleVariant: !TUPLE - UNIT - UNIT 10: UnitVariant: UNIT "}; assert_eq!( actual, expected, "\n\nExpected YAML:\n\n{expected}\n\nActual YAML:\n\n{actual}\n\n" ); } #[test] fn test_variant_rename() { struct Hooks; impl ser::Hooks for Hooks { fn on_enum_variant(&self, path: &Path, ev: &mut ser::EnumVariantScope) { let path = path.borrow_str(); match path.as_str() { "unit_variant" => { ev.rename_variant("new_variant_name"); } "newtype_variant" => { ev.rename_variant(format!("NEW_{path}")); } "struct_variant" => { ev.rename_variant_case(serde_hooks::Case::ScreamingKebab); } "tuple_variant" => {} _ => unreachable!(), } } } let json = serde_json::to_string(&ser::hook(&Payload::new(), &Hooks)).unwrap(); assert_eq!( json, "{\"unit_variant\":\"new_variant_name\",\"newtype_variant\":{\"NEW_newtype_variant\":null},\"struct_variant\":{\"STRUCT-VARIANT\":{\"struct_variant_val\":null}},\"tuple_variant\":{\"TupleVariant\":[null,null]}}" ); }