Initial work on supporting structs as map with unknown field collection
This commit is contained in:
parent
7ad836e6a9
commit
5a91ac5ba5
@ -63,6 +63,7 @@ impl<'a> Container<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut have_collection_field = false;
|
||||||
match data {
|
match data {
|
||||||
Data::Enum(ref mut variants) => for variant in variants {
|
Data::Enum(ref mut variants) => for variant in variants {
|
||||||
variant.attrs.rename_by_rule(attrs.rename_all());
|
variant.attrs.rename_by_rule(attrs.rename_all());
|
||||||
@ -71,10 +72,20 @@ impl<'a> Container<'a> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Data::Struct(_, ref mut fields) => for field in fields {
|
Data::Struct(_, ref mut fields) => for field in fields {
|
||||||
|
if field.ident.is_some() && field.ident.as_ref() == attrs.unknown_fields_into() {
|
||||||
|
field.attrs.mark_as_collection_field();
|
||||||
|
have_collection_field = true;
|
||||||
|
}
|
||||||
field.attrs.rename_by_rule(attrs.rename_all());
|
field.attrs.rename_by_rule(attrs.rename_all());
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if attrs.unknown_fields_into().is_some() && !have_collection_field {
|
||||||
|
cx.error(format!("#[serde(unknown_fields_into)] was defined but target \
|
||||||
|
field `{}` does not exist",
|
||||||
|
attrs.unknown_fields_into().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
let item = Container {
|
let item = Container {
|
||||||
ident: item.ident,
|
ident: item.ident,
|
||||||
attrs: attrs,
|
attrs: attrs,
|
||||||
|
@ -15,6 +15,7 @@ use syn::punctuated::Punctuated;
|
|||||||
use syn::synom::{Synom, ParseError};
|
use syn::synom::{Synom, ParseError};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::fmt;
|
||||||
use proc_macro2::{Span, TokenStream, TokenNode, TokenTree};
|
use proc_macro2::{Span, TokenStream, TokenNode, TokenTree};
|
||||||
|
|
||||||
// This module handles parsing of `#[serde(...)]` attributes. The entrypoints
|
// This module handles parsing of `#[serde(...)]` attributes. The entrypoints
|
||||||
@ -27,6 +28,7 @@ use proc_macro2::{Span, TokenStream, TokenNode, TokenTree};
|
|||||||
|
|
||||||
pub use case::RenameRule;
|
pub use case::RenameRule;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
struct Attr<'c, T> {
|
struct Attr<'c, T> {
|
||||||
cx: &'c Ctxt,
|
cx: &'c Ctxt,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
@ -66,6 +68,10 @@ impl<'c, T> Attr<'c, T> {
|
|||||||
fn get(self) -> Option<T> {
|
fn get(self) -> Option<T> {
|
||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_set(&self) -> bool {
|
||||||
|
self.value.is_some()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BoolAttr<'c>(Attr<'c, ()>);
|
struct BoolAttr<'c>(Attr<'c, ()>);
|
||||||
@ -101,6 +107,35 @@ impl Name {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum ContainerRepr {
|
||||||
|
Auto,
|
||||||
|
Struct,
|
||||||
|
Map,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ContainerRepr {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", match *self {
|
||||||
|
ContainerRepr::Auto => "auto",
|
||||||
|
ContainerRepr::Struct => "struct",
|
||||||
|
ContainerRepr::Map => "map",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ContainerRepr {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<ContainerRepr, ()> {
|
||||||
|
match s {
|
||||||
|
"auto" => Ok(ContainerRepr::Auto),
|
||||||
|
"struct" => Ok(ContainerRepr::Struct),
|
||||||
|
"map" => Ok(ContainerRepr::Map),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents container (e.g. struct) attribute information
|
/// Represents container (e.g. struct) attribute information
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
name: Name,
|
name: Name,
|
||||||
@ -114,6 +149,8 @@ pub struct Container {
|
|||||||
type_into: Option<syn::Type>,
|
type_into: Option<syn::Type>,
|
||||||
remote: Option<syn::Path>,
|
remote: Option<syn::Path>,
|
||||||
identifier: Identifier,
|
identifier: Identifier,
|
||||||
|
repr: ContainerRepr,
|
||||||
|
unknown_fields_into: Option<syn::Ident>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Styles of representing an enum.
|
/// Styles of representing an enum.
|
||||||
@ -191,6 +228,8 @@ impl Container {
|
|||||||
let mut remote = Attr::none(cx, "remote");
|
let mut remote = Attr::none(cx, "remote");
|
||||||
let mut field_identifier = BoolAttr::none(cx, "field_identifier");
|
let mut field_identifier = BoolAttr::none(cx, "field_identifier");
|
||||||
let mut variant_identifier = BoolAttr::none(cx, "variant_identifier");
|
let mut variant_identifier = BoolAttr::none(cx, "variant_identifier");
|
||||||
|
let mut repr = Attr::none(cx, "repr");
|
||||||
|
let mut unknown_fields_into = Attr::none(cx, "unknown_fields_into");
|
||||||
|
|
||||||
for meta_items in item.attrs.iter().filter_map(get_serde_meta_items) {
|
for meta_items in item.attrs.iter().filter_map(get_serde_meta_items) {
|
||||||
for meta_item in meta_items {
|
for meta_item in meta_items {
|
||||||
@ -228,6 +267,31 @@ impl Container {
|
|||||||
// Parse `#[serde(deny_unknown_fields)]`
|
// Parse `#[serde(deny_unknown_fields)]`
|
||||||
Meta(Word(word)) if word == "deny_unknown_fields" => {
|
Meta(Word(word)) if word == "deny_unknown_fields" => {
|
||||||
deny_unknown_fields.set_true();
|
deny_unknown_fields.set_true();
|
||||||
|
if unknown_fields_into.is_set() {
|
||||||
|
cx.error("#[serde(deny_unknown_fields)] cannot be combined \
|
||||||
|
with #[serde(unknown_fields_into)]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse `#[serde(unknown_fields_into = "foo")]`
|
||||||
|
Meta(NameValue(ref m)) if m.ident == "unknown_fields_into" => {
|
||||||
|
if let Ok(s) = get_lit_str(cx, m.ident.as_ref(), m.ident.as_ref(), &m.lit) {
|
||||||
|
unknown_fields_into.set(Ident::new(&s.value(), Span::call_site()).into());
|
||||||
|
if deny_unknown_fields.get() {
|
||||||
|
cx.error("#[serde(deny_unknown_fields)] cannot be combined \
|
||||||
|
with #[serde(unknown_fields_into)]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse `#[serde(repr = "foo")]`
|
||||||
|
Meta(NameValue(ref m)) if m.ident == "repr" => {
|
||||||
|
if let Ok(s) = get_lit_str(cx, m.ident.as_ref(), m.ident.as_ref(), &m.lit) {
|
||||||
|
match ContainerRepr::from_str(&s.value()) {
|
||||||
|
Ok(value) => repr.set(value),
|
||||||
|
Err(()) => cx.error(format!("unknown value for #[serde(repr = {})]", s.value()))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse `#[serde(default)]`
|
// Parse `#[serde(default)]`
|
||||||
@ -358,6 +422,10 @@ impl Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if unknown_fields_into.get().is_some() && repr.get() != Some(ContainerRepr::Map) {
|
||||||
|
cx.error("#[serde(unknown_fields_into)] requires repr=\"map\"");
|
||||||
|
}
|
||||||
|
|
||||||
Container {
|
Container {
|
||||||
name: Name {
|
name: Name {
|
||||||
serialize: ser_name.get().unwrap_or_else(|| item.ident.to_string()),
|
serialize: ser_name.get().unwrap_or_else(|| item.ident.to_string()),
|
||||||
@ -373,6 +441,8 @@ impl Container {
|
|||||||
type_into: type_into.get(),
|
type_into: type_into.get(),
|
||||||
remote: remote.get(),
|
remote: remote.get(),
|
||||||
identifier: decide_identifier(cx, item, &field_identifier, &variant_identifier),
|
identifier: decide_identifier(cx, item, &field_identifier, &variant_identifier),
|
||||||
|
repr: repr.get().unwrap_or(ContainerRepr::Auto),
|
||||||
|
unknown_fields_into: unknown_fields_into.get(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,6 +489,14 @@ impl Container {
|
|||||||
pub fn identifier(&self) -> Identifier {
|
pub fn identifier(&self) -> Identifier {
|
||||||
self.identifier
|
self.identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn repr(&self) -> ContainerRepr {
|
||||||
|
self.repr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unknown_fields_into(&self) -> Option<&syn::Ident> {
|
||||||
|
self.unknown_fields_into.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decide_tag(
|
fn decide_tag(
|
||||||
@ -699,6 +777,7 @@ pub struct Field {
|
|||||||
de_bound: Option<Vec<syn::WherePredicate>>,
|
de_bound: Option<Vec<syn::WherePredicate>>,
|
||||||
borrowed_lifetimes: BTreeSet<syn::Lifetime>,
|
borrowed_lifetimes: BTreeSet<syn::Lifetime>,
|
||||||
getter: Option<syn::ExprPath>,
|
getter: Option<syn::ExprPath>,
|
||||||
|
collection_field: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the default to use for a field when deserializing.
|
/// Represents the default to use for a field when deserializing.
|
||||||
@ -970,6 +1049,7 @@ impl Field {
|
|||||||
de_bound: de_bound.get(),
|
de_bound: de_bound.get(),
|
||||||
borrowed_lifetimes: borrowed_lifetimes,
|
borrowed_lifetimes: borrowed_lifetimes,
|
||||||
getter: getter.get(),
|
getter: getter.get(),
|
||||||
|
collection_field: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -977,6 +1057,10 @@ impl Field {
|
|||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mark_as_collection_field(&mut self) {
|
||||||
|
self.collection_field = true;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn rename_by_rule(&mut self, rule: &RenameRule) {
|
pub fn rename_by_rule(&mut self, rule: &RenameRule) {
|
||||||
if !self.ser_renamed {
|
if !self.ser_renamed {
|
||||||
self.name.serialize = rule.apply_to_field(&self.name.serialize);
|
self.name.serialize = rule.apply_to_field(&self.name.serialize);
|
||||||
@ -987,11 +1071,15 @@ impl Field {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip_serializing(&self) -> bool {
|
pub fn skip_serializing(&self) -> bool {
|
||||||
self.skip_serializing
|
self.skip_serializing || self.collection_field
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip_deserializing(&self) -> bool {
|
pub fn skip_deserializing(&self) -> bool {
|
||||||
self.skip_deserializing
|
self.skip_deserializing || self.collection_field
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collection_field(&self) -> bool {
|
||||||
|
self.collection_field
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip_serializing_if(&self) -> Option<&syn::ExprPath> {
|
pub fn skip_serializing_if(&self) -> Option<&syn::ExprPath> {
|
||||||
|
Loading…
Reference in New Issue
Block a user