Refactor attribute parsing to make it more extensible

This commit is contained in:
Graham Esau 2020-05-12 21:17:34 +01:00
parent b1ded882b7
commit 780c7286a6
4 changed files with 172 additions and 46 deletions

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use crate::attr::get_with_from_attrs; use crate::attr::Attrs;
use serde_derive_internals::ast as serde_ast; use serde_derive_internals::ast as serde_ast;
use serde_derive_internals::Ctxt; use serde_derive_internals::Ctxt;
@ -56,7 +56,7 @@ impl<'a> FromSerde for Variant<'a> {
style: serde.style, style: serde.style,
fields: Field::vec_from_serde(errors, serde.fields)?, fields: Field::vec_from_serde(errors, serde.fields)?,
original: serde.original, original: serde.original,
with: get_with_from_attrs(&serde.original.attrs, errors)?, attrs: Attrs::new(&serde.original.attrs, errors),
}) })
} }
} }
@ -70,7 +70,7 @@ impl<'a> FromSerde for Field<'a> {
serde_attrs: serde.attrs, serde_attrs: serde.attrs,
ty: serde.ty, ty: serde.ty,
original: serde.original, original: serde.original,
with: get_with_from_attrs(&serde.original.attrs, errors)?, attrs: Attrs::new(&serde.original.attrs, errors),
}) })
} }
} }

View file

@ -1,5 +1,6 @@
mod from_serde; mod from_serde;
use crate::attr::{Attrs, WithAttr};
use from_serde::FromSerde; use from_serde::FromSerde;
use serde_derive_internals::ast as serde_ast; use serde_derive_internals::ast as serde_ast;
use serde_derive_internals::{Ctxt, Derive}; use serde_derive_internals::{Ctxt, Derive};
@ -23,7 +24,7 @@ pub struct Variant<'a> {
pub style: serde_ast::Style, pub style: serde_ast::Style,
pub fields: Vec<Field<'a>>, pub fields: Vec<Field<'a>>,
pub original: &'a syn::Variant, pub original: &'a syn::Variant,
pub with: Option<syn::Type>, pub attrs: Attrs,
} }
pub struct Field<'a> { pub struct Field<'a> {
@ -31,7 +32,7 @@ pub struct Field<'a> {
pub serde_attrs: serde_derive_internals::attr::Field, pub serde_attrs: serde_derive_internals::attr::Field,
pub ty: &'a syn::Type, pub ty: &'a syn::Type,
pub original: &'a syn::Field, pub original: &'a syn::Field,
pub with: Option<syn::Type>, pub attrs: Attrs,
} }
impl<'a> Container<'a> { impl<'a> Container<'a> {
@ -69,6 +70,10 @@ impl<'a> Field<'a> {
} }
pub fn type_for_schema(&self) -> &syn::Type { pub fn type_for_schema(&self) -> &syn::Type {
self.with.as_ref().unwrap_or(self.ty) match &self.attrs.with {
None => self.ty,
Some(WithAttr::Type(ty)) => ty,
Some(WithAttr::_Function(_)) => todo!(),
}
} }
} }

View file

@ -7,48 +7,169 @@ pub use schemars_to_serde::process_serde_attrs;
use proc_macro2::{Group, Span, TokenStream, TokenTree}; use proc_macro2::{Group, Span, TokenStream, TokenTree};
use serde_derive_internals::Ctxt; use serde_derive_internals::Ctxt;
use syn::parse::{self, Parse}; use syn::parse::{self, Parse};
use syn::Meta::{List, NameValue};
use syn::MetaNameValue;
use syn::NestedMeta::{Lit, Meta};
pub fn get_with_from_attrs( #[derive(Debug, Default)]
attrs: &[syn::Attribute], pub struct Attrs {
errors: &Ctxt, pub with: Option<WithAttr>,
) -> Result<Option<syn::Type>, ()> { pub title: Option<String>,
attrs pub description: Option<String>,
.iter() // TODO pub example: Option<syn::Path>,
.filter(|at| match at.path.get_ident() {
// FIXME this is relying on order of attributes (schemars before serde) from schemars_to_serde.rs
Some(i) => i == "schemars" || i == "serde",
None => false,
})
.filter_map(get_with_from_attr)
.next()
.map_or(Ok(None), |lit| match parse_lit_str::<syn::Type>(&lit) {
Ok(t) => Ok(Some(t)),
Err(e) => {
errors.error_spanned_by(lit, e);
Err(())
}
})
} }
fn get_with_from_attr(attr: &syn::Attribute) -> Option<syn::LitStr> { #[derive(Debug)]
use syn::*; pub enum WithAttr {
let nested_metas = match attr.parse_meta() { Type(syn::Type),
Ok(Meta::List(meta)) => meta.nested, _Function(syn::Path),
_ => return None, }
};
for nm in nested_metas { impl Attrs {
if let NestedMeta::Meta(Meta::NameValue(MetaNameValue { pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self {
path, let (title, description) = doc::get_title_and_desc_from_doc(attrs);
lit: Lit::Str(with), Attrs {
.. title,
})) = nm description,
..Attrs::default()
}
.populate(attrs, "schemars", false, errors)
.populate(attrs, "serde", true, errors)
}
fn populate(
mut self,
attrs: &[syn::Attribute],
attr_type: &'static str,
ignore_errors: bool,
errors: &Ctxt,
) -> Self {
let duplicate_error = |meta: &MetaNameValue| {
if !ignore_errors {
let msg = format!(
"duplicate schemars attribute `{}`",
meta.path.get_ident().unwrap()
);
errors.error_spanned_by(meta, msg)
}
};
let mutual_exclusive_error = |meta: &MetaNameValue, other: &str| {
if !ignore_errors {
let msg = format!(
"schemars attribute cannot contain both `{}` and `{}`",
meta.path.get_ident().unwrap(),
other,
);
errors.error_spanned_by(meta, msg)
}
};
for meta_item in attrs
.iter()
.flat_map(|attr| get_meta_items(attr, attr_type, errors))
.flatten()
{ {
if path.is_ident("with") { match &meta_item {
return Some(with); Meta(NameValue(m)) if m.path.is_ident("with") => {
if let Ok(ty) = parse_lit_into_ty(errors, attr_type, "with", &m.lit) {
match self.with {
Some(WithAttr::Type(_)) => duplicate_error(m),
Some(WithAttr::_Function(_)) => {
mutual_exclusive_error(m, "schema_with")
}
None => self.with = Some(WithAttr::Type(ty)),
}
}
}
Meta(_meta_item) => {
// TODO uncomment this for 0.8.0 (breaking change)
// https://github.com/GREsau/schemars/issues/18
// if !ignore_errors {
// let path = meta_item
// .path()
// .into_token_stream()
// .to_string()
// .replace(' ', "");
// errors.error_spanned_by(
// meta_item.path(),
// format!("unknown schemars container attribute `{}`", path),
// );
// }
}
Lit(_lit) => {
// TODO uncomment this for 0.8.0 (breaking change)
// https://github.com/GREsau/schemars/issues/18
// if !ignore_errors {
// errors.error_spanned_by(
// lit,
// "unexpected literal in schemars container attribute",
// );
// }
}
} }
} }
self
} }
None }
fn get_meta_items(
attr: &syn::Attribute,
attr_type: &'static str,
errors: &Ctxt,
) -> Result<Vec<syn::NestedMeta>, ()> {
if !attr.path.is_ident(attr_type) {
return Ok(Vec::new());
}
match attr.parse_meta() {
Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
Ok(other) => {
errors.error_spanned_by(other, format!("expected #[{}(...)]", attr_type));
Err(())
}
Err(err) => {
errors.error_spanned_by(attr, err);
Err(())
}
}
}
fn get_lit_str<'a>(
cx: &Ctxt,
attr_type: &'static str,
meta_item_name: &'static str,
lit: &'a syn::Lit,
) -> Result<&'a syn::LitStr, ()> {
if let syn::Lit::Str(lit) = lit {
Ok(lit)
} else {
cx.error_spanned_by(
lit,
format!(
"expected {} attribute to be a string: `{} = \"...\"`",
attr_type, meta_item_name
),
);
Err(())
}
}
fn parse_lit_into_ty(
cx: &Ctxt,
attr_type: &'static str,
meta_item_name: &'static str,
lit: &syn::Lit,
) -> Result<syn::Type, ()> {
let string = get_lit_str(cx, attr_type, meta_item_name, lit)?;
parse_lit_str(string).map_err(|_| {
cx.error_spanned_by(
lit,
format!("failed to parse type: {} = {:?}", attr_type, string.value()),
)
})
} }
fn parse_lit_str<T>(s: &syn::LitStr) -> parse::Result<T> fn parse_lit_str<T>(s: &syn::LitStr) -> parse::Result<T>

View file

@ -1,4 +1,4 @@
use crate::{ast::*, metadata::SchemaMetadata}; use crate::{ast::*, attr::WithAttr, metadata::SchemaMetadata};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use serde_derive_internals::ast::Style; use serde_derive_internals::ast::Style;
use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, TagType}; use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, TagType};
@ -33,7 +33,7 @@ fn expr_for_external_tagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>, variants: impl Iterator<Item = &'a Variant<'a>>,
) -> TokenStream { ) -> TokenStream {
let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = let (unit_variants, complex_variants): (Vec<_>, Vec<_>) =
variants.partition(|v| v.is_unit() && v.with.is_none()); variants.partition(|v| v.is_unit() && v.attrs.with.is_none());
let unit_names = unit_variants.iter().map(|v| v.name()); let unit_names = unit_variants.iter().map(|v| v.name());
let unit_schema = schema_object(quote! { let unit_schema = schema_object(quote! {
@ -147,7 +147,7 @@ fn expr_for_adjacent_tagged_enum<'a>(
content_name: &str, content_name: &str,
) -> TokenStream { ) -> TokenStream {
let schemas = variants.map(|variant| { let schemas = variants.map(|variant| {
let content_schema = if variant.is_unit() && variant.with.is_none() { let content_schema = if variant.is_unit() && variant.attrs.with.is_none() {
None None
} else { } else {
Some(expr_for_untagged_enum_variant(variant)) Some(expr_for_untagged_enum_variant(variant))
@ -200,7 +200,7 @@ fn expr_for_adjacent_tagged_enum<'a>(
} }
fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream { fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
if let Some(with) = &variant.with { if let Some(WithAttr::Type(with)) = &variant.attrs.with {
return quote_spanned! {variant.original.span()=> return quote_spanned! {variant.original.span()=>
gen.subschema_for::<#with>() gen.subschema_for::<#with>()
}; };
@ -215,7 +215,7 @@ fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
} }
fn expr_for_untagged_enum_variant_for_flatten(variant: &Variant) -> Option<TokenStream> { fn expr_for_untagged_enum_variant_for_flatten(variant: &Variant) -> Option<TokenStream> {
if let Some(with) = &variant.with { if let Some(WithAttr::Type(with)) = &variant.attrs.with {
return Some(quote_spanned! {variant.original.span()=> return Some(quote_spanned! {variant.original.span()=>
<#with>::json_schema(gen) <#with>::json_schema(gen)
}); });