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<HashSet<&'static str>>,
structs_to_expect: RefCell<HashSet<&'static str>>,
structs_variants_to_expect: RefCell<HashSet<&'static str>>,
tuples_to_expect: RefCell<HashSet<&'static str>>,
tuple_variants_to_expect: RefCell<HashSet<&'static str>>,
}
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]}}"
);
}