use std::{ borrow::Cow, cell::RefCell, cmp::Ordering, collections::{BTreeMap, HashSet}, }; use indoc::indoc; use serde::Serialize; use serde_hooks::{ser, Path}; #[derive(Serialize)] struct UnitStruct; #[derive(Serialize)] struct Struct { struct_val: (), } #[derive(Serialize)] struct TupleStruct((), ()); #[allow(clippy::enum_variant_names)] #[derive(Serialize)] enum Enum { UnitVariant, NewtypeVariant(()), StructVariant { struct_variant_val: () }, TupleVariant((), ()), } #[derive(Serialize)] struct Newtype(()); #[derive(Serialize)] struct Payload<'s, 'b> { val_bool: bool, val_i8: i8, val_i16: i16, val_i32: i32, val_i64: i64, val_i128: i128, val_u8: u8, val_u16: u16, val_u32: u32, val_u64: u64, val_u128: u128, val_f32: f32, val_f64: f64, val_char: char, val_str: &'s str, val_str_static: &'static str, val_str_owned: String, #[serde(with = "serde_bytes")] val_bytes: &'b [u8], #[serde(with = "serde_bytes")] val_bytes_static: &'static [u8], #[serde(with = "serde_bytes")] val_bytes_owned: Vec, val_unit: (), val_none: Option<()>, val_some: Option<()>, val_struct: Struct, val_newtype: Newtype, val_unit_struct: UnitStruct, val_tuple_struct: TupleStruct, val_struct_variant: Enum, val_unit_variant: Enum, val_newtype_variant: Enum, val_tuple_variant: Enum, val_map: BTreeMap, val_seq: Vec, val_tuple: ((), (), ()), } impl<'s, 'b> Payload<'s, 'b> { fn new(val_str: &'s str, val_bytes: &'b [u8]) -> Self { Payload { val_bool: true, val_i8: -8, val_i16: -16, val_i32: -32, val_i64: -64, val_i128: -128, val_u8: 8, val_u16: 16, val_u32: 32, val_u64: 64, val_u128: 128, val_f32: 32.0, val_f64: 64.0, val_char: 'x', val_str, val_str_static: "str_static", val_str_owned: "str_owned".into(), val_bytes, val_bytes_static: &[2, 3, 4], val_bytes_owned: [3, 4, 5, 6].into(), val_unit: (), val_none: None, val_some: Some(()), val_struct: Struct { struct_val: () }, val_newtype: Newtype(()), val_unit_struct: UnitStruct, val_tuple_struct: TupleStruct((), ()), val_struct_variant: Enum::StructVariant { struct_variant_val: (), }, val_unit_variant: Enum::UnitVariant, val_newtype_variant: Enum::NewtypeVariant(()), val_tuple_variant: Enum::TupleVariant((), ()), val_map: [(1, 2), (3, 4)].into_iter().collect(), val_seq: vec![1, 2, 3], val_tuple: ((), (), ()), } } fn fields() -> HashSet { match serde_json::to_value(Self::new("", &[])).unwrap() { serde_json::Value::Object(o) => o.into_iter().map(|(k, _)| k).collect(), _ => unreachable!(), } } } #[test] fn test_values() { let val_str = "str".to_string(); let val_bytes: Vec = vec![1, 2]; struct Hooks { fields_to_expect: RefCell>, } impl ser::Hooks for Hooks { fn on_value(&self, path: &Path, value: &mut ser::ValueScope) { let path = path.borrow_str(); self.fields_to_expect.borrow_mut().remove(path.as_str()); use serde_hooks::Value; // Note, all owned values will be received here as borrowed, just // with their own lifetimes match (path.as_str(), value.value()) { ( "", Value::Struct { name: "Payload", .. }, ) | ("val_bool", Value::Bool(true)) | ("val_i8", Value::I8(-8)) | ("val_i16", Value::I16(-16)) | ("val_i32", Value::I32(-32)) | ("val_i64", Value::I64(-64)) | ("val_i128", Value::I128(-128)) | ("val_u8", Value::U8(8)) | ("val_u16", Value::U16(16)) | ("val_u32", Value::U32(32)) | ("val_u64", Value::U64(64)) | ("val_u128", Value::U128(128)) | ("val_char", Value::Char('x')) | ("val_str", Value::Str(Cow::Borrowed("str"))) | ("val_str_static", Value::Str(Cow::Borrowed("str_static"))) | ("val_str_owned", Value::Str(Cow::Borrowed("str_owned"))) | ("val_bytes", Value::Bytes(Cow::Borrowed(&[1, 2]))) | ("val_bytes_static", Value::Bytes(Cow::Borrowed(&[2, 3, 4]))) | ("val_bytes_owned", Value::Bytes(Cow::Borrowed(&[3, 4, 5, 6]))) | ("val_unit", Value::Unit) | ("val_none", Value::None) | ("val_some", Value::Some) | ( "val_struct", Value::Struct { name: "Struct", len: 1, }, ) | ("val_struct.struct_val", _) | ("val_newtype", Value::NewtypeStruct("Newtype")) | ("val_unit_struct", Value::UnitStruct("UnitStruct")) | ( "val_tuple_struct", Value::TupleStruct { name: "TupleStruct", len: 2, }, ) | ("val_tuple_struct[0]", Value::Unit) | ("val_tuple_struct[1]", Value::Unit) | ( "val_unit_variant", Value::UnitVariant { name: "Enum", variant_index: 0, variant: "UnitVariant", }, ) | ( "val_newtype_variant", Value::NewtypeVariant { name: "Enum", variant_index: 1, variant: "NewtypeVariant", }, ) | ( "val_struct_variant", Value::StructVariant { name: "Enum", variant_index: 2, variant: "StructVariant", len: 1, }, ) | ( "val_tuple_variant", Value::TupleVariant { name: "Enum", variant_index: 3, variant: "TupleVariant", len: 2, }, ) | ("val_tuple_variant[0]", Value::Unit) | ("val_tuple_variant[1]", Value::Unit) | ("val_struct_variant.struct_variant_val", _) | ("val_map", Value::Map(Some(2))) | ("val_map[1]", _) | ("val_map[3]", _) | ("val_seq", Value::Seq(Some(3))) | ("val_seq[0]", Value::U32(1)) | ("val_seq[1]", Value::U32(2)) | ("val_seq[2]", Value::U32(3)) | ("val_tuple", Value::Tuple(3)) | ("val_tuple[0]", Value::Unit) | ("val_tuple[1]", Value::Unit) | ("val_tuple[2]", Value::Unit) => {} ("val_f32", Value::F32(v)) => { assert_eq!(v.partial_cmp(&32.0f32), Some(Ordering::Equal)); } ("val_f64", Value::F64(v)) => { assert_eq!(v.partial_cmp(&64.0f64), Some(Ordering::Equal)); } (path, value) => panic!("unexpected value {:?} at path '{}'", value, path), } } } let hooks = Hooks { fields_to_expect: RefCell::new(Payload::fields()), }; assert!(serde_json::to_string(&ser::hook(&Payload::new(&val_str, &val_bytes), &hooks)).is_ok()); assert!( hooks.fields_to_expect.borrow().is_empty(), "following fields were expected, but not called back about {:?}", hooks.fields_to_expect.borrow() ); } #[test] fn test_replace_in_struct() { let val_str = "str".to_string(); let val_bytes: Vec = vec![1, 2]; struct Hooks; impl ser::Hooks for Hooks { fn on_value(&self, path: &Path, value: &mut ser::ValueScope) { if !path.is_root() { value.replace(&format!("R {}", path)); } } } let actual = serde_yaml::to_string(&ser::hook(&Payload::new(&val_str, &val_bytes), &Hooks)).unwrap(); let expected = indoc! {" val_bool: R val_bool val_i8: R val_i8 val_i16: R val_i16 val_i32: R val_i32 val_i64: R val_i64 val_i128: R val_i128 val_u8: R val_u8 val_u16: R val_u16 val_u32: R val_u32 val_u64: R val_u64 val_u128: R val_u128 val_f32: R val_f32 val_f64: R val_f64 val_char: R val_char val_str: R val_str val_str_static: R val_str_static val_str_owned: R val_str_owned val_bytes: R val_bytes val_bytes_static: R val_bytes_static val_bytes_owned: R val_bytes_owned val_unit: R val_unit val_none: R val_none val_some: R val_some val_struct: R val_struct val_newtype: R val_newtype val_unit_struct: R val_unit_struct val_tuple_struct: R val_tuple_struct val_struct_variant: R val_struct_variant val_unit_variant: R val_unit_variant val_newtype_variant: R val_newtype_variant val_tuple_variant: R val_tuple_variant val_map: R val_map val_seq: R val_seq val_tuple: R val_tuple "}; assert_eq!( actual, expected, "\n\nExpected YAML:\n\n{expected}\n\nActual YAML:\n\n{actual}\n\n" ); } #[test] fn test_replace_in_seq() { struct Hooks; impl ser::Hooks for Hooks { fn on_value(&self, path: &Path, value: &mut ser::ValueScope) { if !path.is_root() { value.replace(&format!("R {}", path)); } } } let actual = serde_yaml::to_string(&ser::hook(&vec![0i32, 1, 2, 3], &Hooks)).unwrap(); let expected = indoc! {" - R [0] - R [1] - R [2] - R [3] "}; assert_eq!( actual, expected, "\n\nExpected YAML:\n\n{expected}\n\nActual YAML:\n\n{actual}\n\n" ); } #[test] fn test_fail_serialization() { struct Hooks; impl ser::Hooks for Hooks { fn on_value(&self, _path: &Path, value: &mut ser::ValueScope) { value.fail_serialization("FAUX ERROR"); } } let err = serde_yaml::to_string(&ser::hook(&(), &Hooks)).unwrap_err(); assert!(err.to_string().contains("FAUX ERROR")) }