use serde::ast::{Container, Style};
use serde::attr::{Default as SerdeDefault, RenameRule};
use serde::{Ctxt, Derive};
use serde_derive_internals as serde;
use syn::{parenthesized, parse_quote, token, Attribute, DeriveInput, Error, Expr, Ident, Member, Result, Token, Type};
pub struct ContainerAttrs {
#[allow(dead_code)]
pub rename_all: Option<RenameRule>,
pub has_default: bool,
}
pub struct FieldAttrs {
pub serialized_name: String,
pub aliases: Vec<String>,
pub skip_deserializing: bool,
pub has_field_default: bool,
pub default_expr: Option<Expr>,
}
pub struct ParsedStruct {
pub container_attrs: ContainerAttrs,
pub fields: Vec<ParsedField>,
}
pub struct ParsedField {
pub ident: Ident,
pub ty: Type,
pub attrs: FieldAttrs,
}
const UNSUPPORTED_ATTRS: &[&str] = &[
"flatten",
"with",
"deserialize_with",
"bound",
"from",
"try_from",
"tag",
"untagged",
];
pub fn parse_struct_attrs(input: &DeriveInput) -> Result<ParsedStruct> {
check_unsupported_attrs(input)?;
let cx = Ctxt::new();
let container = match Container::from_ast(&cx, input, Derive::Deserialize) {
Some(c) => c,
None => {
return Err(cx.check().unwrap_err());
}
};
cx.check()?;
let rename_all_rules = container.attrs.rename_all_rules();
let rename_rule = rename_all_rules.deserialize;
let rename_all = if rename_rule == RenameRule::None {
None
} else {
Some(rename_rule)
};
let has_default = !container.attrs.default().is_none();
let container_attrs = ContainerAttrs {
rename_all,
has_default,
};
let fields = match &container.data {
serde::ast::Data::Struct(Style::Struct, fields) => fields,
_ => {
return Err(Error::new_spanned(
input,
"config::Deserialize can only be derived for structs with named fields",
));
}
};
let parsed_fields: Vec<ParsedField> = fields
.iter()
.map(|field| {
let ident = match &field.member {
Member::Named(ident) => ident.clone(),
Member::Unnamed(_) => unreachable!("named struct fields have idents"),
};
let serialized_name = field.attrs.name().deserialize_name().to_owned();
let aliases: Vec<String> = field
.attrs
.aliases()
.iter()
.filter(|a| *a != &serialized_name)
.cloned()
.collect();
let skip_deserializing = field.attrs.skip_deserializing();
let (has_field_default, default_expr) = match field.attrs.default() {
SerdeDefault::None => (false, None),
SerdeDefault::Default => (true, None),
SerdeDefault::Path(path) => {
let expr: Expr = parse_quote!(#path());
(true, Some(expr))
}
};
ParsedField {
ident,
ty: field.ty.clone(),
attrs: FieldAttrs {
serialized_name,
aliases,
skip_deserializing,
has_field_default,
default_expr,
},
}
})
.collect();
Ok(ParsedStruct {
container_attrs,
fields: parsed_fields,
})
}
fn check_unsupported_attrs(input: &DeriveInput) -> Result<()> {
let mut errors: Vec<Error> = Vec::new();
for attr in &input.attrs {
if !attr.path().is_ident("serde") {
continue;
}
check_meta_list_for_unsupported(attr, &mut errors);
}
if let syn::Data::Struct(data) = &input.data {
for field in &data.fields {
for attr in &field.attrs {
if !attr.path().is_ident("serde") {
continue;
}
check_meta_list_for_unsupported(attr, &mut errors);
}
}
}
if errors.is_empty() {
Ok(())
} else {
let mut combined = errors.remove(0);
for err in errors {
combined.combine(err);
}
Err(combined)
}
}
fn check_meta_list_for_unsupported(attr: &Attribute, errors: &mut Vec<Error>) {
let _ = attr.parse_nested_meta(|meta| {
for &unsupported in UNSUPPORTED_ATTRS {
if meta.path.is_ident(unsupported) {
errors.push(Error::new_spanned(
&meta.path,
format!("config::Deserialize does not support #[serde({})]", unsupported),
));
if meta.input.peek(Token![=]) {
let _: Expr = meta.value()?.parse()?;
} else if meta.input.peek(token::Paren) {
let _content;
parenthesized!(_content in meta.input);
}
return Ok(());
}
}
if meta.input.peek(Token![=]) {
let _: Expr = meta.value()?.parse()?;
} else if meta.input.peek(token::Paren) {
let _content;
parenthesized!(_content in meta.input);
}
Ok(())
});
}