Refactor attribute parsing to make it more extensible
This commit is contained in:
parent
b1ded882b7
commit
780c7286a6
4 changed files with 172 additions and 46 deletions
|
@ -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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
pub struct Attrs {
|
||||||
|
pub with: Option<WithAttr>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
// TODO pub example: Option<syn::Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum WithAttr {
|
||||||
|
Type(syn::Type),
|
||||||
|
_Function(syn::Path),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Attrs {
|
||||||
|
pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self {
|
||||||
|
let (title, description) = doc::get_title_and_desc_from_doc(attrs);
|
||||||
|
Attrs {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
..Attrs::default()
|
||||||
|
}
|
||||||
|
.populate(attrs, "schemars", false, errors)
|
||||||
|
.populate(attrs, "serde", true, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn populate(
|
||||||
|
mut self,
|
||||||
attrs: &[syn::Attribute],
|
attrs: &[syn::Attribute],
|
||||||
|
attr_type: &'static str,
|
||||||
|
ignore_errors: bool,
|
||||||
errors: &Ctxt,
|
errors: &Ctxt,
|
||||||
) -> Result<Option<syn::Type>, ()> {
|
) -> Self {
|
||||||
attrs
|
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()
|
.iter()
|
||||||
.filter(|at| match at.path.get_ident() {
|
.flat_map(|attr| get_meta_items(attr, attr_type, errors))
|
||||||
// FIXME this is relying on order of attributes (schemars before serde) from schemars_to_serde.rs
|
.flatten()
|
||||||
Some(i) => i == "schemars" || i == "serde",
|
{
|
||||||
None => false,
|
match &meta_item {
|
||||||
})
|
Meta(NameValue(m)) if m.path.is_ident("with") => {
|
||||||
.filter_map(get_with_from_attr)
|
if let Ok(ty) = parse_lit_into_ty(errors, attr_type, "with", &m.lit) {
|
||||||
.next()
|
match self.with {
|
||||||
.map_or(Ok(None), |lit| match parse_lit_str::<syn::Type>(&lit) {
|
Some(WithAttr::Type(_)) => duplicate_error(m),
|
||||||
Ok(t) => Ok(Some(t)),
|
Some(WithAttr::_Function(_)) => {
|
||||||
Err(e) => {
|
mutual_exclusive_error(m, "schema_with")
|
||||||
errors.error_spanned_by(lit, e);
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(err) => {
|
||||||
|
errors.error_spanned_by(attr, err);
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_with_from_attr(attr: &syn::Attribute) -> Option<syn::LitStr> {
|
fn get_lit_str<'a>(
|
||||||
use syn::*;
|
cx: &Ctxt,
|
||||||
let nested_metas = match attr.parse_meta() {
|
attr_type: &'static str,
|
||||||
Ok(Meta::List(meta)) => meta.nested,
|
meta_item_name: &'static str,
|
||||||
_ => return None,
|
lit: &'a syn::Lit,
|
||||||
};
|
) -> Result<&'a syn::LitStr, ()> {
|
||||||
for nm in nested_metas {
|
if let syn::Lit::Str(lit) = lit {
|
||||||
if let NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
Ok(lit)
|
||||||
path,
|
} else {
|
||||||
lit: Lit::Str(with),
|
cx.error_spanned_by(
|
||||||
..
|
lit,
|
||||||
})) = nm
|
format!(
|
||||||
{
|
"expected {} attribute to be a string: `{} = \"...\"`",
|
||||||
if path.is_ident("with") {
|
attr_type, meta_item_name
|
||||||
return Some(with);
|
),
|
||||||
|
);
|
||||||
|
Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None
|
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>
|
||||||
|
|
|
@ -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)
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue