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 crate::attr::get_with_from_attrs;
|
||||
use crate::attr::Attrs;
|
||||
use serde_derive_internals::ast as serde_ast;
|
||||
use serde_derive_internals::Ctxt;
|
||||
|
||||
|
@ -56,7 +56,7 @@ impl<'a> FromSerde for Variant<'a> {
|
|||
style: serde.style,
|
||||
fields: Field::vec_from_serde(errors, serde.fields)?,
|
||||
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,
|
||||
ty: serde.ty,
|
||||
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;
|
||||
|
||||
use crate::attr::{Attrs, WithAttr};
|
||||
use from_serde::FromSerde;
|
||||
use serde_derive_internals::ast as serde_ast;
|
||||
use serde_derive_internals::{Ctxt, Derive};
|
||||
|
@ -23,7 +24,7 @@ pub struct Variant<'a> {
|
|||
pub style: serde_ast::Style,
|
||||
pub fields: Vec<Field<'a>>,
|
||||
pub original: &'a syn::Variant,
|
||||
pub with: Option<syn::Type>,
|
||||
pub attrs: Attrs,
|
||||
}
|
||||
|
||||
pub struct Field<'a> {
|
||||
|
@ -31,7 +32,7 @@ pub struct Field<'a> {
|
|||
pub serde_attrs: serde_derive_internals::attr::Field,
|
||||
pub ty: &'a syn::Type,
|
||||
pub original: &'a syn::Field,
|
||||
pub with: Option<syn::Type>,
|
||||
pub attrs: Attrs,
|
||||
}
|
||||
|
||||
impl<'a> Container<'a> {
|
||||
|
@ -69,6 +70,10 @@ impl<'a> Field<'a> {
|
|||
}
|
||||
|
||||
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 serde_derive_internals::Ctxt;
|
||||
use syn::parse::{self, Parse};
|
||||
use syn::Meta::{List, NameValue};
|
||||
use syn::MetaNameValue;
|
||||
use syn::NestedMeta::{Lit, Meta};
|
||||
|
||||
pub fn get_with_from_attrs(
|
||||
attrs: &[syn::Attribute],
|
||||
errors: &Ctxt,
|
||||
) -> Result<Option<syn::Type>, ()> {
|
||||
attrs
|
||||
.iter()
|
||||
.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(())
|
||||
}
|
||||
})
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Attrs {
|
||||
pub with: Option<WithAttr>,
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
// TODO pub example: Option<syn::Path>,
|
||||
}
|
||||
|
||||
fn get_with_from_attr(attr: &syn::Attribute) -> Option<syn::LitStr> {
|
||||
use syn::*;
|
||||
let nested_metas = match attr.parse_meta() {
|
||||
Ok(Meta::List(meta)) => meta.nested,
|
||||
_ => return None,
|
||||
};
|
||||
for nm in nested_metas {
|
||||
if let NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
lit: Lit::Str(with),
|
||||
..
|
||||
})) = nm
|
||||
#[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],
|
||||
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") {
|
||||
return Some(with);
|
||||
match &meta_item {
|
||||
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>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{ast::*, metadata::SchemaMetadata};
|
||||
use crate::{ast::*, attr::WithAttr, metadata::SchemaMetadata};
|
||||
use proc_macro2::TokenStream;
|
||||
use serde_derive_internals::ast::Style;
|
||||
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>>,
|
||||
) -> TokenStream {
|
||||
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_schema = schema_object(quote! {
|
||||
|
@ -147,7 +147,7 @@ fn expr_for_adjacent_tagged_enum<'a>(
|
|||
content_name: &str,
|
||||
) -> TokenStream {
|
||||
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
|
||||
} else {
|
||||
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 {
|
||||
if let Some(with) = &variant.with {
|
||||
if let Some(WithAttr::Type(with)) = &variant.attrs.with {
|
||||
return quote_spanned! {variant.original.span()=>
|
||||
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> {
|
||||
if let Some(with) = &variant.with {
|
||||
if let Some(WithAttr::Type(with)) = &variant.attrs.with {
|
||||
return Some(quote_spanned! {variant.original.span()=>
|
||||
<#with>::json_schema(gen)
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue