diff --git a/serde/src/de/value.rs b/serde/src/de/value.rs index 3bc0c71c5..a8be0944f 100644 --- a/serde/src/de/value.rs +++ b/serde/src/de/value.rs @@ -1051,6 +1051,12 @@ where //////////////////////////////////////////////////////////////////////////////// /// A deserializer holding a `SeqAccess`. +/// +/// This deserializer will call [`Visitor::visit_seq`] for all requests except +/// for [`Deserializer::deserialize_enum`]. In the latest case the enum will be +/// deserialized from the first two elements of a sequence. The first element +/// would be interpreted as a variant name and the second as a content of +/// a variant. In case of unit variant the second element is optional. #[derive(Clone, Debug)] pub struct SeqAccessDeserializer { seq: A, @@ -1076,10 +1082,43 @@ where visitor.visit_seq(self.seq) } + fn deserialize_enum( + self, + _name: &str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_enum(self) + } + forward_to_deserialize_any! { bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum identifier ignored_any + tuple_struct map struct identifier ignored_any + } +} + +impl<'de, A> de::EnumAccess<'de> for SeqAccessDeserializer +where + A: de::SeqAccess<'de>, +{ + type Error = A::Error; + type Variant = private::SeqAsEnum; + + fn variant_seed(mut self, seed: T) -> Result<(T::Value, Self::Variant), Self::Error> + where + T: de::DeserializeSeed<'de>, + { + match tri!(self.seq.next_element_seed(seed)) { + Some(key) => Ok((key, private::seq_as_enum(self.seq))), + None => Err(de::Error::invalid_length( + self.seq.size_hint().unwrap_or(0), + &"enum tag", + )), + } } } @@ -1454,6 +1493,12 @@ where //////////////////////////////////////////////////////////////////////////////// /// A deserializer holding a `MapAccess`. +/// +/// This deserializer will call [`Visitor::visit_map`] for all requests except +/// for [`Deserializer::deserialize_enum`]. In the latest case the enum will be +/// deserialized from the first entry of a map. The map key would be interpreted +/// as a variant name and the map value would be interpreted as a content of +/// a variant. #[derive(Clone, Debug)] pub struct MapAccessDeserializer { map: A, @@ -1511,7 +1556,10 @@ where { match tri!(self.map.next_key_seed(seed)) { Some(key) => Ok((key, private::map_as_enum(self.map))), - None => Err(de::Error::invalid_type(de::Unexpected::Map, &"enum")), + None => Err(de::Error::invalid_length( + self.map.size_hint().unwrap_or(0), + &"enum", + )), } } } @@ -1557,7 +1605,8 @@ mod private { use crate::lib::*; use crate::de::{ - self, DeserializeSeed, Deserializer, MapAccess, Unexpected, VariantAccess, Visitor, + self, DeserializeSeed, Deserializer, MapAccess, SeqAccess, Unexpected, VariantAccess, + Visitor, }; pub struct UnitOnly { @@ -1662,6 +1711,77 @@ mod private { } } + pub struct SeqAsEnum { + seq: A, + } + + pub fn seq_as_enum(seq: A) -> SeqAsEnum { + SeqAsEnum { seq } + } + + impl<'de, A> VariantAccess<'de> for SeqAsEnum + where + A: SeqAccess<'de>, + { + type Error = A::Error; + + fn unit_variant(mut self) -> Result<(), Self::Error> { + // Even when content is missing, it is also acceptable + // So both ["Unit"] and ["Unit", ()] is acceptable + tri!(self.seq.next_element::<()>()); + Ok(()) + } + + fn newtype_variant_seed(mut self, seed: T) -> Result + where + T: DeserializeSeed<'de>, + { + match tri!(self.seq.next_element_seed(seed)) { + Some(value) => Ok(value), + None => Err(de::Error::invalid_length( + self.seq.size_hint().unwrap_or(0) + 1, + &"content of newtype variant", + )), + } + } + + fn tuple_variant(mut self, len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + match tri!(self + .seq + .next_element_seed(SeedTupleVariant { len, visitor })) + { + Some(value) => Ok(value), + None => Err(de::Error::invalid_length( + self.seq.size_hint().unwrap_or(0) + 1, + &"content of tuple variant", + )), + } + } + + fn struct_variant( + mut self, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + match tri!(self + .seq + .next_element_seed(SeedStructVariant { visitor: visitor })) + { + Some(value) => Ok(value), + None => Err(de::Error::invalid_length( + self.seq.size_hint().unwrap_or(0) + 1, + &"content of struct variant", + )), + } + } + } + struct SeedTupleVariant { len: usize, visitor: V, diff --git a/test_suite/tests/test_value.rs b/test_suite/tests/test_value.rs index 3a1922c0f..b12e3d72b 100644 --- a/test_suite/tests/test_value.rs +++ b/test_suite/tests/test_value.rs @@ -1,9 +1,9 @@ #![allow(clippy::derive_partial_eq_without_eq, clippy::similar_names)] -use serde::de::value::{self, MapAccessDeserializer}; -use serde::de::{Deserialize, Deserializer, IntoDeserializer, MapAccess, Visitor}; +use serde::de::value; +use serde::de::{Deserialize, Deserializer, IntoDeserializer, Visitor}; use serde_derive::Deserialize; -use serde_test::{assert_de_tokens, Token}; +use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; use std::fmt; #[test] @@ -37,14 +37,20 @@ fn test_integer128() { assert_eq!(1i128, i128::deserialize(de_i128).unwrap()); } -#[test] -fn test_map_access_to_enum() { +mod access_to_enum { + use super::*; + use serde::de::value::{MapAccessDeserializer, SeqAccessDeserializer}; + use serde::de::{MapAccess, SeqAccess}; + #[derive(PartialEq, Debug)] - struct Potential(PotentialKind); + struct UseAccess(Enum); #[derive(PartialEq, Debug, Deserialize)] - enum PotentialKind { - Airebo(Airebo), + enum Enum { + Unit, + Newtype(Airebo), + Tuple(String, f64), + Struct { lj_sigma: f64 }, } #[derive(PartialEq, Debug, Deserialize)] @@ -52,44 +58,257 @@ fn test_map_access_to_enum() { lj_sigma: f64, } - impl<'de> Deserialize<'de> for Potential { + impl<'de> Deserialize<'de> for UseAccess { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - struct PotentialVisitor; + struct UseAccessVisitor; - impl<'de> Visitor<'de> for PotentialVisitor { - type Value = Potential; + impl<'de> Visitor<'de> for UseAccessVisitor { + type Value = UseAccess; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a map") + formatter.write_str("a seq or a map") + } + + fn visit_seq(self, seq: A) -> Result + where + A: SeqAccess<'de>, + { + Deserialize::deserialize(SeqAccessDeserializer::new(seq)).map(UseAccess) } fn visit_map(self, map: A) -> Result where A: MapAccess<'de>, { - Deserialize::deserialize(MapAccessDeserializer::new(map)).map(Potential) + Deserialize::deserialize(MapAccessDeserializer::new(map)).map(UseAccess) } } - deserializer.deserialize_any(PotentialVisitor) + deserializer.deserialize_any(UseAccessVisitor) } } - let expected = Potential(PotentialKind::Airebo(Airebo { lj_sigma: 14.0 })); - - assert_de_tokens( - &expected, - &[ - Token::Map { len: Some(1) }, - Token::Str("Airebo"), - Token::Map { len: Some(1) }, - Token::Str("lj_sigma"), - Token::F64(14.0), - Token::MapEnd, - Token::MapEnd, - ], - ); + /// Because [`serde_test::de::Deserializer`] handles all tokens [`Token::Seq`], + /// [`Token::Tuple`] and [`Token::TupleStruct`] the same, we test only + /// `Token::Seq` tokens here. + mod seq { + use super::*; + + #[test] + fn unit() { + assert_de_tokens( + &UseAccess(Enum::Unit), + &[ + Token::Seq { len: Some(2) }, + Token::Str("Unit"), // tag + Token::Unit, // content + Token::SeqEnd, + ], + ); + + assert_de_tokens( + &UseAccess(Enum::Unit), + &[ + Token::Seq { len: Some(1) }, + Token::Str("Unit"), // tag + Token::SeqEnd, + ], + ); + } + + #[test] + fn newtype() { + assert_de_tokens( + &UseAccess(Enum::Newtype(Airebo { lj_sigma: 14.0 })), + &[ + Token::Seq { len: Some(2) }, + Token::Str("Newtype"), // tag + Token::Map { len: Some(1) }, // content + Token::Str("lj_sigma"), + Token::F64(14.0), + Token::MapEnd, + Token::SeqEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::Seq { len: Some(1) }, + Token::Str("Newtype"), // tag + Token::SeqEnd, + ], + "invalid length 1, expected content of newtype variant", + ); + } + + #[test] + fn tuple() { + assert_de_tokens( + &UseAccess(Enum::Tuple("lj_sigma".to_string(), 14.0)), + &[ + Token::Seq { len: Some(2) }, + Token::Str("Tuple"), // tag + Token::Seq { len: Some(2) }, // content + Token::Str("lj_sigma"), + Token::F64(14.0), + Token::SeqEnd, + Token::SeqEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::Seq { len: Some(1) }, + Token::Str("Tuple"), // tag + Token::SeqEnd, + ], + "invalid length 1, expected content of tuple variant", + ); + } + + #[test] + fn struct_() { + assert_de_tokens( + &UseAccess(Enum::Struct { lj_sigma: 14.0 }), + &[ + Token::Seq { len: Some(2) }, + Token::Str("Struct"), // tag + Token::Map { len: Some(1) }, // content + Token::Str("lj_sigma"), + Token::F64(14.0), + Token::MapEnd, + Token::SeqEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::Seq { len: Some(1) }, + Token::Str("Struct"), // tag + Token::SeqEnd, + ], + "invalid length 1, expected content of struct variant", + ); + } + + #[test] + fn wrong_tag() { + assert_de_tokens_error::( + &[ + Token::Seq { len: Some(2) }, + Token::Str("AnotherTag"), // tag + Token::Map { len: Some(1) }, // content + // Tokens that could follow, but assert_de_tokens_error do not want them + // Token::Str("lj_sigma"), + // Token::F64(14.0), + // Token::MapEnd, + // Token::SeqEnd, + ], + "unknown variant `AnotherTag`, expected one of `Unit`, `Newtype`, `Tuple`, `Struct`", + ); + } + + #[test] + fn empty_seq() { + assert_de_tokens_error::( + &[Token::Seq { len: Some(0) }, Token::SeqEnd], + "invalid length 0, expected enum tag", + ); + } + } + + /// Because [`serde_test::de::Deserializer`] handles both tokens [`Token::Map`] + /// and [`Token::Struct`] the same, we test only `Token::Map` tokens here. + mod map { + use super::*; + + #[test] + fn unit() { + assert_de_tokens( + &UseAccess(Enum::Unit), + &[ + Token::Map { len: Some(1) }, + Token::Str("Unit"), + Token::Unit, + Token::MapEnd, + ], + ); + } + + #[test] + fn newtype() { + assert_de_tokens( + &UseAccess(Enum::Newtype(Airebo { lj_sigma: 14.0 })), + &[ + Token::Map { len: Some(1) }, + Token::Str("Newtype"), + Token::Map { len: Some(1) }, + Token::Str("lj_sigma"), + Token::F64(14.0), + Token::MapEnd, + Token::MapEnd, + ], + ); + } + + #[test] + fn tuple() { + assert_de_tokens( + &UseAccess(Enum::Tuple("lj_sigma".to_string(), 14.0)), + &[ + Token::Map { len: Some(1) }, + Token::Str("Tuple"), + Token::Seq { len: Some(2) }, + Token::Str("lj_sigma"), + Token::F64(14.0), + Token::SeqEnd, + Token::MapEnd, + ], + ); + } + + #[test] + fn struct_() { + assert_de_tokens( + &UseAccess(Enum::Struct { lj_sigma: 14.0 }), + &[ + Token::Map { len: Some(1) }, + Token::Str("Struct"), + Token::Map { len: Some(1) }, + Token::Str("lj_sigma"), + Token::F64(14.0), + Token::MapEnd, + Token::MapEnd, + ], + ); + } + + #[test] + fn wrong_tag() { + assert_de_tokens_error::( + &[ + Token::Map { len: Some(1) }, + Token::Str("AnotherTag"), + Token::Map { len: Some(1) }, + // Tokens that could follow, but assert_de_tokens_error do not want them + // Token::Str("lj_sigma"), + // Token::F64(14.0), + // Token::MapEnd, + // Token::MapEnd, + ], + "unknown variant `AnotherTag`, expected one of `Unit`, `Newtype`, `Tuple`, `Struct`", + ); + } + + #[test] + fn empty_map() { + assert_de_tokens_error::( + &[Token::Map { len: Some(0) }, Token::MapEnd], + "invalid length 0, expected enum", + ); + } + } }