diff --git a/serde/src/de/identifier.rs b/serde/src/de/identifier.rs index 732b55497..63ded6795 100644 --- a/serde/src/de/identifier.rs +++ b/serde/src/de/identifier.rs @@ -486,3 +486,202 @@ impl<'de, 'a, 'b> DeserializeSeed<'de> for &'b mut FieldFlattenSeed<'a> { deserializer.deserialize_identifier(self) } } + +//////////////////////////////////////////////////////////////////////////////// + +/// Creates a field deserialization seed that wrapped array with all possible +/// field names and their aliases. +/// +/// # Example +/// +/// ``` +/// # use serde::__private::de::VariantSeed; +/// let seed = VariantSeed::new( +/// &[ +/// // First field with two aliases +/// &["a", "alias 1", "alias 2"], +/// // Second field with one alias +/// &["b", "alias 3"], +/// // Third field without aliases +/// &["c"], +/// ], +/// &[ +/// // First field with two aliases +/// "a", "alias 1", "alias 2", +/// // Second field with one alias +/// "b", "alias 3", +/// // Third field without aliases +/// "c", +/// ], +/// ); +/// ``` +#[derive(Debug)] +pub struct VariantSeed<'a> { + aliases: &'a [&'a [&'a str]], + variants: &'static [&'static str], +} + +impl<'a> VariantSeed<'a> { + #[allow(missing_docs)] + pub const fn new(aliases: &'a [&'a [&'a str]], variants: &'static [&'static str]) -> Self { + Self { aliases, variants } + } + + fn matches(&self, value: &[u8]) -> Option { + self.aliases + .iter() + .position(|variant| variant.iter().any(|v| v.as_bytes() == value)) + } +} + +impl<'de, 'a> Visitor<'de> for VariantSeed<'a> { + type Value = usize; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("variant identifier") + } + + fn visit_u64(self, value: u64) -> Result + where + E: Error, + { + if value < self.aliases.len() as u64 { + Ok(value as usize) + } else { + // length of string "variant index 0 <= i < " and u64::MAX.to_string() + let mut buf = [0u8; 23 + 20]; + let mut writer = format::Buf::new(&mut buf); + fmt::Write::write_fmt( + &mut writer, + format_args!("variant index 0 <= i < {}", value), + ) + .unwrap(); + Err(Error::invalid_value( + Unexpected::Unsigned(value), + &writer.as_str(), + )) + } + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + match self.matches(value.as_bytes()) { + Some(index) => Ok(index), + None => Err(Error::unknown_variant(value, self.variants)), + } + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: Error, + { + match self.matches(value) { + Some(index) => Ok(index), + None => Err(Error::unknown_variant( + &from_utf8_lossy(value), + self.variants, + )), + } + } +} + +impl<'de, 'a> DeserializeSeed<'de> for VariantSeed<'a> { + type Value = usize; + + #[inline] + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_identifier(self) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +/// Creates a field deserialization seed that wrapped array with all possible +/// field names and their aliases. +/// +/// # Example +/// +/// ``` +/// # use serde::__private::de::VariantOtherSeed; +/// let seed = VariantOtherSeed::new( +/// &[ +/// // First field with two aliases +/// &["a", "alias 1", "alias 2"], +/// // Second field with one alias +/// &["b", "alias 3"], +/// // Third field without aliases +/// &["c"], +/// ], +/// 2, +/// ); +/// ``` +#[derive(Debug)] +pub struct VariantOtherSeed<'a> { + aliases: &'a [&'a [&'a str]], + other: usize, +} + +impl<'a> VariantOtherSeed<'a> { + #[allow(missing_docs)] + pub const fn new(aliases: &'a [&'a [&'a str]], other: usize) -> Self { + assert!(other < aliases.len()); + Self { aliases, other } + } + + fn matches(&self, value: &[u8]) -> usize { + self.aliases + .iter() + .position(|variant| variant.iter().any(|v| v.as_bytes() == value)) + .unwrap_or(self.other) + } +} + +impl<'de, 'a> Visitor<'de> for VariantOtherSeed<'a> { + type Value = usize; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("variant identifier") + } + + fn visit_u64(self, value: u64) -> Result + where + E: Error, + { + if value < self.aliases.len() as u64 { + Ok(value as usize) + } else { + Ok(self.other) + } + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + Ok(self.matches(value.as_bytes())) + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: Error, + { + Ok(self.matches(value)) + } +} + +impl<'de, 'a> DeserializeSeed<'de> for VariantOtherSeed<'a> { + type Value = usize; + + #[inline] + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_identifier(self) + } +} diff --git a/serde/src/private/de.rs b/serde/src/private/de.rs index f60577f1e..b8236b611 100644 --- a/serde/src/private/de.rs +++ b/serde/src/private/de.rs @@ -16,7 +16,9 @@ pub use self::content::{ TagOrContentField, TagOrContentFieldVisitor, TaggedContentVisitor, UntaggedUnitVisitor, }; -pub use crate::de::identifier::{Field, FieldSeed, FieldStrong, FieldStrongSeed}; +pub use crate::de::identifier::{ + Field, FieldSeed, FieldStrong, FieldStrongSeed, VariantOtherSeed, VariantSeed, +}; #[cfg(any(feature = "std", feature = "alloc"))] pub use crate::de::identifier::{FieldFlatten, FieldFlattenSeed}; pub use crate::seed::InPlaceSeed; diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 421996251..7da67b0b3 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -1263,50 +1263,50 @@ fn deserialize_externally_tagged_enum( let expecting = format!("enum {}", params.type_name()); let expecting = cattrs.expecting().unwrap_or(&expecting); - let (variants_stmt, variant_visitor) = prepare_enum_variant_enum(variants, cattrs); - // Match arms to extract a variant from a string let variant_arms = variants .iter() + .filter(|variant| !variant.attrs.skip_deserializing()) .enumerate() - .filter(|&(_, variant)| !variant.attrs.skip_deserializing()) .map(|(i, variant)| { - let variant_name = field_i(i); - let block = Match(deserialize_externally_tagged_variant( params, variant, cattrs, )); quote! { - (__Field::#variant_name, __variant) => #block + (#i, __variant) => #block } }); - let all_skipped = variants + let seed = match variants .iter() - .all(|variant| variant.attrs.skip_deserializing()); - let match_variant = if all_skipped { - // This is an empty enum like `enum Impossible {}` or an enum in which - // all variants have `#[serde(skip_deserializing)]`. - quote! { - // FIXME: Once feature(exhaustive_patterns) is stable: - // let _serde::__private::Err(__err) = _serde::de::EnumAccess::variant::<__Field>(__data); - // _serde::__private::Err(__err) - _serde::__private::Result::map( - _serde::de::EnumAccess::variant::<__Field>(__data), - |(__impossible, _)| match __impossible {}) - } - } else { - quote! { - match _serde::de::EnumAccess::variant(__data)? { - #(#variant_arms)* - } + .filter(|variant| !variant.attrs.skip_deserializing()) + .position(|variant| variant.attrs.other()) + { + Some(other) => { + quote!(_serde::__private::de::VariantOtherSeed::new(VARIANT_ALIASES, #other)) } + None => quote!(_serde::__private::de::VariantSeed::new( + VARIANT_ALIASES, + VARIANTS + )), }; - quote_block! { - #variant_visitor + let variant_names = variants + .iter() + .filter(|variant| !variant.attrs.skip_deserializing()) + .map(|variant| variant.attrs.name().deserialize_name()); + let aliases = variants.iter().filter_map(|variant| { + if variant.attrs.skip_deserializing() { + None + } else { + let aliases = variant.attrs.aliases(); + Some(quote!(&[ #(#aliases),* ])) + } + }); + + quote_block! { #[doc(hidden)] struct __Visitor #de_impl_generics #where_clause { marker: _serde::__private::PhantomData<#this_type #ty_generics>, @@ -1324,11 +1324,17 @@ fn deserialize_externally_tagged_enum( where __A: _serde::de::EnumAccess<#delife>, { - #match_variant + match _serde::de::EnumAccess::variant_seed(__data, #seed)? { + #(#variant_arms)* + _ => unreachable!(), + } } } - #variants_stmt + #[doc(hidden)] + const VARIANTS: &'static [&'static str] = &[ #(#variant_names),* ]; + #[doc(hidden)] + const VARIANT_ALIASES: &[&[&str]] = &[ #(#aliases),* ]; _serde::Deserializer::deserialize_enum( __deserializer,