From 4ad8eaf2780aa9d93b27a27d0ad065b64cc221db Mon Sep 17 00:00:00 2001 From: Oliver Goodman Date: Thu, 7 Jun 2018 19:18:02 +1000 Subject: [PATCH 1/7] Adds the option to encode enums as 1-key maps. --- src/de.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- src/ser.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 175 insertions(+), 6 deletions(-) diff --git a/src/de.rs b/src/de.rs index f96e7ca2..044b39a2 100644 --- a/src/de.rs +++ b/src/de.rs @@ -383,6 +383,24 @@ where }) } + fn parse_enum_map(&mut self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.recursion_checked(|de| { + let mut len = 1; + let value = visitor.visit_enum(VariantAccessMap { + map: MapAccess { de, len: &mut len }, + })?; + + if len != 0 { + Err(de.error(ErrorCode::TrailingData)) + } else { + Ok(value) + } + }) + } + fn parse_indefinite_enum(&mut self, visitor: V) -> Result where V: de::Visitor<'de>, @@ -693,6 +711,10 @@ where _ => unreachable!(), } } + Some(0xa1) => { + self.consume(); + self.parse_enum_map(visitor) + } None => Err(self.error(ErrorCode::EofWhileParsingValue)), _ => visitor.visit_enum(UnitVariantAccess { de: self }), } @@ -822,6 +844,15 @@ where } } +impl<'de, 'a, R> MakeError for MapAccess<'a, R> +where + R: Read<'de>, +{ + fn error(&self, code: ErrorCode) -> Error { + self.de.error(code) + } +} + struct IndefiniteMapAccess<'a, R: 'a> { de: &'a mut Deserializer, } @@ -1049,3 +1080,65 @@ where } } } + +struct VariantAccessMap { + map: T, +} + +impl<'de, T> de::EnumAccess<'de> for VariantAccessMap +where + T: de::MapAccess<'de, Error = Error> + MakeError, +{ + type Error = Error; + type Variant = VariantAccessMap; + + fn variant_seed(mut self, seed: V) -> Result<(V::Value, VariantAccessMap)> + where + V: de::DeserializeSeed<'de>, + { + let variant = match self.map.next_key_seed(seed) { + Ok(Some(variant)) => variant, + Ok(None) => return Err(self.map.error(ErrorCode::ArrayTooShort)), + Err(e) => return Err(e), + }; + Ok((variant, self)) + } +} + +impl<'de, T> de::VariantAccess<'de> for VariantAccessMap +where + T: de::MapAccess<'de, Error = Error> + + MakeError, +{ + type Error = Error; + + fn unit_variant(mut self) -> Result<()> { + match self.map.next_value() { + Ok(()) => Ok(()), + Err(e) => Err(e), + } + } + + fn newtype_variant_seed(mut self, seed: S) -> Result + where + S: de::DeserializeSeed<'de>, + { + self.map.next_value_seed(seed) + } + + fn tuple_variant(mut self, _len: usize, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + let seed = StructVariantSeed { visitor }; + self.map.next_value_seed(seed) + } + + fn struct_variant(mut self, _fields: &'static [&'static str], visitor: V) -> Result + where + V: de::Visitor<'de>, + { + let seed = StructVariantSeed { visitor }; + self.map.next_value_seed(seed) + } +} diff --git a/src/lib.rs b/src/lib.rs index 837af77d..88d612b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,6 +162,6 @@ pub mod value; #[doc(inline)] pub use de::{from_slice, from_reader, Deserializer, StreamDeserializer}; #[doc(inline)] -pub use ser::{to_writer, to_vec, Serializer}; +pub use ser::{to_writer, to_vec, to_vec_with_options, Serializer, SerializerOptions}; #[doc(inline)] pub use value::{Value, ObjectKey, to_value, from_value}; diff --git a/src/ser.rs b/src/ser.rs index 3329675c..353bbd18 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -52,6 +52,27 @@ where value.serialize(&mut ser) } +/// Serializes a value without names to a writer. +pub fn to_writer_with_options(mut writer: &mut W, value: &T, options: &SerializerOptions) -> Result<()> +where + W: io::Write, + T: ser::Serialize, +{ + let mut ser = Serializer::new_with_options(&mut writer, options); + value.serialize(&mut ser) +} + +/// Serializes a value without names to a writer and adds a CBOR self-describe tag. +pub fn to_writer_with_options_sd(mut writer: &mut W, value: &T, options: &SerializerOptions) -> Result<()> +where + W: io::Write, + T: ser::Serialize, +{ + let mut ser = Serializer::new_with_options(&mut writer, options); + ser.self_describe()?; + value.serialize(&mut ser) +} + /// Serializes a value to a vector. pub fn to_vec(value: &T) -> Result> where @@ -97,10 +118,42 @@ where Ok(vec) } +/// Serializes a value to a vector. +pub fn to_vec_with_options(value: &T, options: &SerializerOptions) -> Result> +where + T: ser::Serialize, +{ + let mut vec = Vec::new(); + to_writer_with_options(&mut vec, value, options)?; + Ok(vec) +} + +/// Serializes a value to a vector and adds a CBOR self-describe tag. +pub fn to_vec_with_options_sd(value: &T, options: &SerializerOptions) -> Result> +where + T: ser::Serialize, +{ + let mut vec = Vec::new(); + to_writer_with_options_sd(&mut vec, value, options)?; + Ok(vec) +} + +/// Options for a CBOR serializer. +pub struct SerializerOptions { + /// Struct fields and enum variants are identified by their numeric indices rather than names + /// to save space. + pub packed: bool, + /// When enum_as_map is set, enums are encoded as maps rather than arrays. + /// This makes no difference when decoding to an enum value but it shows + /// up when decoding to a `Value` or decoding in other languages. + pub enum_as_map: bool, +} + /// A structure for serializing Rust values to CBOR. pub struct Serializer { writer: W, packed: bool, + enum_as_map: bool, } impl Serializer @@ -113,6 +166,7 @@ where Serializer { writer, packed: false, + enum_as_map: false, } } @@ -125,6 +179,17 @@ where Serializer { writer, packed: true, + enum_as_map: false, + } + } + + /// Creates a new CBOR serializer with the specified options. + #[inline] + pub fn new_with_options(writer: W, options: &SerializerOptions) -> Serializer { + Serializer { + writer, + packed: options.packed, + enum_as_map: options.enum_as_map, } } @@ -406,8 +471,13 @@ where where T: ?Sized + ser::Serialize, { - self.writer.write_all(&[4 << 5 | 2]).map_err(Error::io)?; - self.serialize_unit_variant(name, variant_index, variant)?; + if self.enum_as_map { + self.write_u64(5, 1u64)?; + variant.serialize(&mut *self)?; + } else { + self.writer.write_all(&[4 << 5 | 2]).map_err(Error::io)?; + self.serialize_unit_variant(name, variant_index, variant)?; + } value.serialize(self) } @@ -439,9 +509,15 @@ where variant: &'static str, len: usize, ) -> Result<&'a mut Serializer> { - self.write_u64(4, (len + 1) as u64)?; - self.serialize_unit_variant(name, variant_index, variant)?; - Ok(self) + if self.enum_as_map { + self.write_u64(5, 1u64)?; + variant.serialize(&mut *self)?; + self.serialize_tuple(len) + } else { + self.write_u64(4, (len + 1) as u64)?; + self.serialize_unit_variant(name, variant_index, variant)?; + Ok(self) + } } #[inline] From 9300cbcc5df31c116c2914dc283d430c4a5979b7 Mon Sep 17 00:00:00 2001 From: Oliver Goodman Date: Sat, 15 Dec 2018 13:30:21 +1100 Subject: [PATCH 2/7] Adds missed case of enum_as_map for struct variants. --- src/ser.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ser.rs b/src/ser.rs index 353bbd18..bf4a7622 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -568,7 +568,11 @@ where variant: &'static str, len: usize, ) -> Result> { - self.writer.write_all(&[4 << 5 | 2]).map_err(Error::io)?; + if self.enum_as_map { + self.write_u64(5, 1u64)?; + } else { + self.writer.write_all(&[4 << 5 | 2]).map_err(Error::io)?; + } self.serialize_unit_variant(name, variant_index, variant)?; self.serialize_struct(name, len) } From 9600b74bb5d4a897543b32c7be27ad0d98c2817c Mon Sep 17 00:00:00 2001 From: Oliver Goodman Date: Sat, 15 Dec 2018 13:32:25 +1100 Subject: [PATCH 3/7] Adds test case for enum_as_map. --- tests/enum.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/tests/enum.rs b/tests/enum.rs index 52eff8fa..d3e8bb95 100644 --- a/tests/enum.rs +++ b/tests/enum.rs @@ -3,7 +3,9 @@ extern crate serde_cbor; #[macro_use] extern crate serde_derive; -use serde_cbor::{from_slice, to_vec}; +use std::collections::BTreeMap; + +use serde_cbor::{from_slice, to_vec, to_vec_with_options, Value, ObjectKey}; #[derive(Debug,Serialize,Deserialize,PartialEq,Eq)] enum Enum { @@ -96,3 +98,86 @@ fn test_variable_length_array() { let value: Vec = from_slice(slice).unwrap(); assert_eq!(value, [Foo::Require]); } + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +enum Bar { + Empty, + Number(i32), + Flag(String, bool), + Point{x: i32, y: i32}, +} + +#[test] +fn test_enum_as_map() { + // unit variants serialize like bare strings + let empty_s = to_vec(&Bar::Empty).unwrap(); + let empty_str_s = to_vec(&"Empty").unwrap(); + assert_eq!(empty_s, empty_str_s); + + // tuple-variants serialize like ["", values..] + let number_s = to_vec(&Bar::Number(42)).unwrap(); + let number_vec = vec![Value::String("Number".to_string()), Value::I64(42)]; + let number_vec_s = to_vec(&number_vec).unwrap(); + assert_eq!(number_s, number_vec_s); + + let flag_s = to_vec(&Bar::Flag("foo".to_string(), true)).unwrap(); + let flag_vec = vec![Value::String("Flag".to_string()), Value::String("foo".to_string()), Value::Bool(true)]; + let flag_vec_s = to_vec(&flag_vec).unwrap(); + assert_eq!(flag_s, flag_vec_s); + + // struct-variants serialize like ["", {struct..}] + let point_s = to_vec(&Bar::Point{ x: 5, y: -5}).unwrap(); + let mut struct_map = BTreeMap::new(); + struct_map.insert(ObjectKey::String("x".to_string()), Value::I64(5)); + struct_map.insert(ObjectKey::String("y".to_string()), Value::I64(-5)); + let point_vec = vec![Value::String("Point".to_string()), Value::Object(struct_map.clone())]; + let point_vec_s = to_vec(&point_vec).unwrap(); + assert_eq!(point_s, point_vec_s); + + // enum_as_map matches serde_json's default serialization for enums. + let opts = serde_cbor::SerializerOptions{ packed: false, enum_as_map: true }; + + // unit variants still serialize like bare strings + let empty_s = to_vec_with_options(&Bar::Empty, &opts).unwrap(); + assert_eq!(empty_s, empty_str_s); + + // 1-element tuple variants serialize like {"": value} + let number_s = to_vec_with_options(&Bar::Number(42), &opts).unwrap(); + let mut number_map = BTreeMap::new(); + number_map.insert("Number", 42); + let number_map_s = to_vec(&number_map).unwrap(); + assert_eq!(number_s, number_map_s); + + // multi-element tuple variants serialize like {"": [values..]} + let flag_s = to_vec_with_options(&Bar::Flag("foo".to_string(), true), &opts).unwrap(); + let mut flag_map = BTreeMap::new(); + flag_map.insert("Flag", vec![Value::String("foo".to_string()), Value::Bool(true)]); + let flag_map_s = to_vec(&flag_map).unwrap(); + assert_eq!(flag_s, flag_map_s); + + // struct-variants serialize like {"", {struct..}} + let point_s = to_vec_with_options(&Bar::Point{ x: 5, y: -5}, &opts).unwrap(); + let mut point_map = BTreeMap::new(); + point_map.insert("Point", Value::Object(struct_map)); + let point_map_s = to_vec(&point_map).unwrap(); + assert_eq!(point_s, point_map_s); + + // deserialization of all encodings should just work + let empty_str_ds = from_slice(&empty_str_s).unwrap(); + assert_eq!(Bar::Empty, empty_str_ds); + + let number_vec_ds = from_slice(&number_vec_s).unwrap(); + assert_eq!(Bar::Number(42), number_vec_ds); + let number_map_ds = from_slice(&number_map_s).unwrap(); + assert_eq!(Bar::Number(42), number_map_ds); + + let flag_vec_ds = from_slice(&flag_vec_s).unwrap(); + assert_eq!(Bar::Flag("foo".to_string(), true), flag_vec_ds); + let flag_map_ds = from_slice(&flag_map_s).unwrap(); + assert_eq!(Bar::Flag("foo".to_string(), true), flag_map_ds); + + let point_vec_ds = from_slice(&point_vec_s).unwrap(); + assert_eq!(Bar::Point{ x: 5, y: -5}, point_vec_ds); + let point_map_ds = from_slice(&point_map_s).unwrap(); + assert_eq!(Bar::Point{ x: 5, y: -5}, point_map_ds); +} From 3cc6d5a41720908c038ac39ab4597f12b85f1bac Mon Sep 17 00:00:00 2001 From: Oliver Goodman Date: Sat, 15 Dec 2018 15:12:19 +1100 Subject: [PATCH 4/7] Adds documentation for enum_as_map. --- src/ser.rs | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/ser.rs b/src/ser.rs index bf4a7622..e0883f89 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -139,13 +139,49 @@ where } /// Options for a CBOR serializer. +/// +/// The `enum_as_map` option determines how enums are encoded. +/// +/// This makes no difference when encoding and decoding enums using +/// this crate, but it shows up when decoding to a `Value` or decoding +/// in other languages. +/// +/// With enum_as_map true, the encoding scheme matches the default encoding +/// scheme used by `serde_json`. +/// +/// # Examples +/// +/// Given the following enum +/// ``` +/// enum Enum { +/// Unit, +/// NewType(i32), +/// Tuple(String, bool), +/// Struct{ x: i32, y: i32 }, +/// } +/// ``` +/// we will give the `Value` with the same encoding for each case using +/// JSON notation. +/// +/// ## Default encodings +/// +/// * `Enum::Unit` encodes as `"Unit"` +/// * `Enum::NewType(10)` encodes as `["NewType", 10]` +/// * `Enum::Tuple("x", true)` encodes as `["Tuple", "x", true]` +/// * `Enum::Struct{ x: 5, y: -5 }` encodes as `["Struct", {"x": 5, "y": -5}]` +/// +/// ## Encodings with enum_as_map true +/// +/// * `Enum::Unit` encodes as `"Unit"` +/// * `Enum::NewType(10)` encodes as `{"NewType": 10}` +/// * `Enum::Tuple("x", true)` encodes as `{"Tuple": ["x", true]}` +/// * `Enum::Struct{ x: 5, y: -5 }` encodes as `{"Struct": {"x": 5, "y": -5}}` +#[derive(Default)] pub struct SerializerOptions { - /// Struct fields and enum variants are identified by their numeric indices rather than names + /// When set, struct fields and enum variants are identified by their numeric indices rather than names /// to save space. pub packed: bool, - /// When enum_as_map is set, enums are encoded as maps rather than arrays. - /// This makes no difference when decoding to an enum value but it shows - /// up when decoding to a `Value` or decoding in other languages. + /// When set, enums are encoded as maps rather than arrays. pub enum_as_map: bool, } From 167a6bfb9904b16642a794bb19217e6de17af433 Mon Sep 17 00:00:00 2001 From: Oliver Goodman Date: Sat, 15 Dec 2018 15:54:39 +1100 Subject: [PATCH 5/7] Drops the to_writer_with_options* functions. --- src/ser.rs | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/ser.rs b/src/ser.rs index e0883f89..35de48e9 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -52,27 +52,6 @@ where value.serialize(&mut ser) } -/// Serializes a value without names to a writer. -pub fn to_writer_with_options(mut writer: &mut W, value: &T, options: &SerializerOptions) -> Result<()> -where - W: io::Write, - T: ser::Serialize, -{ - let mut ser = Serializer::new_with_options(&mut writer, options); - value.serialize(&mut ser) -} - -/// Serializes a value without names to a writer and adds a CBOR self-describe tag. -pub fn to_writer_with_options_sd(mut writer: &mut W, value: &T, options: &SerializerOptions) -> Result<()> -where - W: io::Write, - T: ser::Serialize, -{ - let mut ser = Serializer::new_with_options(&mut writer, options); - ser.self_describe()?; - value.serialize(&mut ser) -} - /// Serializes a value to a vector. pub fn to_vec(value: &T) -> Result> where @@ -124,7 +103,10 @@ where T: ser::Serialize, { let mut vec = Vec::new(); - to_writer_with_options(&mut vec, value, options)?; + { + let mut ser = Serializer::new_with_options(&mut vec, options); + value.serialize(&mut ser)?; + } Ok(vec) } @@ -134,7 +116,11 @@ where T: ser::Serialize, { let mut vec = Vec::new(); - to_writer_with_options_sd(&mut vec, value, options)?; + { + let mut ser = Serializer::new_with_options(&mut vec, options); + ser.self_describe()?; + value.serialize(&mut ser)?; + } Ok(vec) } From 55237075e3a85c59befb59e6739768890e80afbd Mon Sep 17 00:00:00 2001 From: Oliver Goodman Date: Sat, 15 Dec 2018 16:30:07 +1100 Subject: [PATCH 6/7] Adds self_describe to SerializerOptions. Adds SerializerOptions::to_vec. Removes to_vec_with_options_sd. --- src/ser.rs | 26 ++++++++++++-------------- tests/enum.rs | 12 ++++++------ tests/ser.rs | 4 ++++ 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/ser.rs b/src/ser.rs index 35de48e9..f975b325 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -105,20 +105,9 @@ where let mut vec = Vec::new(); { let mut ser = Serializer::new_with_options(&mut vec, options); - value.serialize(&mut ser)?; - } - Ok(vec) -} - -/// Serializes a value to a vector and adds a CBOR self-describe tag. -pub fn to_vec_with_options_sd(value: &T, options: &SerializerOptions) -> Result> -where - T: ser::Serialize, -{ - let mut vec = Vec::new(); - { - let mut ser = Serializer::new_with_options(&mut vec, options); - ser.self_describe()?; + if options.self_describe { + ser.self_describe()?; + } value.serialize(&mut ser)?; } Ok(vec) @@ -169,6 +158,15 @@ pub struct SerializerOptions { pub packed: bool, /// When set, enums are encoded as maps rather than arrays. pub enum_as_map: bool, + /// When set, `to_vec` will prepend the CBOR self-describe tag. + pub self_describe: bool, +} + +impl SerializerOptions { + /// Serializes a value to a vector. + pub fn to_vec(&self, value: &T) -> Result> { + to_vec_with_options(value, self) + } } /// A structure for serializing Rust values to CBOR. diff --git a/tests/enum.rs b/tests/enum.rs index d3e8bb95..8bd6540f 100644 --- a/tests/enum.rs +++ b/tests/enum.rs @@ -5,7 +5,7 @@ extern crate serde_derive; use std::collections::BTreeMap; -use serde_cbor::{from_slice, to_vec, to_vec_with_options, Value, ObjectKey}; +use serde_cbor::{from_slice, to_vec, Value, ObjectKey}; #[derive(Debug,Serialize,Deserialize,PartialEq,Eq)] enum Enum { @@ -135,28 +135,28 @@ fn test_enum_as_map() { assert_eq!(point_s, point_vec_s); // enum_as_map matches serde_json's default serialization for enums. - let opts = serde_cbor::SerializerOptions{ packed: false, enum_as_map: true }; + let opts = serde_cbor::SerializerOptions{ enum_as_map: true, ..Default::default() }; // unit variants still serialize like bare strings - let empty_s = to_vec_with_options(&Bar::Empty, &opts).unwrap(); + let empty_s = opts.to_vec(&Bar::Empty).unwrap(); assert_eq!(empty_s, empty_str_s); // 1-element tuple variants serialize like {"": value} - let number_s = to_vec_with_options(&Bar::Number(42), &opts).unwrap(); + let number_s = opts.to_vec(&Bar::Number(42)).unwrap(); let mut number_map = BTreeMap::new(); number_map.insert("Number", 42); let number_map_s = to_vec(&number_map).unwrap(); assert_eq!(number_s, number_map_s); // multi-element tuple variants serialize like {"": [values..]} - let flag_s = to_vec_with_options(&Bar::Flag("foo".to_string(), true), &opts).unwrap(); + let flag_s = opts.to_vec(&Bar::Flag("foo".to_string(), true)).unwrap(); let mut flag_map = BTreeMap::new(); flag_map.insert("Flag", vec![Value::String("foo".to_string()), Value::Bool(true)]); let flag_map_s = to_vec(&flag_map).unwrap(); assert_eq!(flag_s, flag_map_s); // struct-variants serialize like {"", {struct..}} - let point_s = to_vec_with_options(&Bar::Point{ x: 5, y: -5}, &opts).unwrap(); + let point_s = opts.to_vec(&Bar::Point{ x: 5, y: -5}).unwrap(); let mut point_map = BTreeMap::new(); point_map.insert("Point", Value::Object(struct_map)); let point_map_s = to_vec(&point_map).unwrap(); diff --git a/tests/ser.rs b/tests/ser.rs index aba7cca7..3a23f42b 100644 --- a/tests/ser.rs +++ b/tests/ser.rs @@ -135,6 +135,10 @@ fn test_self_describing() { serializer.serialize_u64(9).unwrap(); } assert_eq!(vec, b"\xd9\xd9\xf7\x09"); + + let sd = ser::SerializerOptions{ self_describe: true, ..Default::default() }; + let vec = sd.to_vec(&9).unwrap(); + assert_eq!(vec, b"\xd9\xd9\xf7\x09"); } #[test] From 29c13b6acf1f3a093fe460b8ad7395a544121143 Mon Sep 17 00:00:00 2001 From: Oliver Goodman Date: Sat, 15 Dec 2018 17:12:45 +1100 Subject: [PATCH 7/7] Fix breakage due to rebase. --- src/ser.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ser.rs b/src/ser.rs index f975b325..57b9a952 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -218,6 +218,7 @@ where let mut s = Serializer { writer: buf, packed: self.packed, + enum_as_map: self.enum_as_map, }; v.serialize(&mut s)?; Ok(s.writer)