Enable eriving JsonSchema when fields are in remote crates

This commit is contained in:
Graham Esau 2019-09-12 18:02:37 +01:00
parent 8d68e36f7c
commit 709ba7b62e
4 changed files with 133 additions and 14 deletions

View file

@ -2,21 +2,20 @@
extern crate quote;
#[macro_use]
extern crate syn;
extern crate proc_macro;
mod preprocess;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use serde_derive_internals::ast::{Container, Data, Field, Style, Variant};
use serde_derive_internals::attr::{self, Default as SerdeDefault, EnumTag};
use serde_derive_internals::{Ctxt, Derive};
use syn::spanned::Spanned;
use syn::DeriveInput;
#[proc_macro_derive(JsonSchema, attributes(schemars, serde))]
pub fn derive_json_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut input = parse_macro_input!(input as DeriveInput);
let mut input = parse_macro_input!(input as syn::DeriveInput);
preprocess::add_trait_bounds(&mut input.generics);
if let Err(e) = preprocess::process_serde_attrs(&mut input) {
@ -222,14 +221,14 @@ fn schema_for_unit_struct() -> TokenStream {
}
fn schema_for_newtype_struct(field: &Field) -> TokenStream {
let ty = field.ty;
let ty = get_json_schema_type(field);
quote_spanned! {field.original.span()=>
gen.subschema_for::<#ty>()?
}
}
fn schema_for_tuple_struct(fields: &[Field]) -> TokenStream {
let types = fields.iter().map(|f| f.ty);
let types = fields.iter().map(get_json_schema_type);
quote! {
gen.subschema_for::<(#(#types),*)>()?
}
@ -244,7 +243,7 @@ fn schema_for_struct(fields: &[Field], cattrs: &attr::Container) -> TokenStream
if !container_has_default && !has_default(field.attrs.default()) {
required.push(name.clone());
}
let ty = field.ty;
let ty = get_json_schema_type(field);
quote_spanned! {field.original.span()=>
props.insert(#name.to_owned(), gen.subschema_for::<#ty>()?);
}
@ -264,9 +263,9 @@ fn schema_for_struct(fields: &[Field], cattrs: &attr::Container) -> TokenStream
},
});
let flattens = flat.iter().map(|f| {
let ty = f.ty;
quote_spanned! {f.original.span()=>
let flattens = flat.iter().map(|field| {
let ty = get_json_schema_type(field);
quote_spanned! {field.original.span()=>
.flatten(<#ty>::json_schema(gen)?)?
}
});
@ -282,3 +281,37 @@ fn has_default(d: &SerdeDefault) -> bool {
_ => true,
}
}
fn get_json_schema_type(field: &Field) -> Box<dyn ToTokens> {
// TODO it would probably be simpler to parse attributes manually here, instead of
// using the serde-parsed attributes
let de_with_segments = without_last_element(field.attrs.deserialize_with(), "deserialize");
let se_with_segments = without_last_element(field.attrs.serialize_with(), "serialize");
if de_with_segments == se_with_segments {
if let Some(expr_path) = de_with_segments {
return Box::from(expr_path);
}
}
Box::from(field.ty.clone())
}
fn without_last_element(path: Option<&syn::ExprPath>, last: &str) -> Option<syn::ExprPath> {
match path {
Some(expr_path)
if expr_path
.path
.segments
.last()
.map(|p| p.value().ident == last)
.unwrap_or(false) =>
{
let mut expr_path = expr_path.clone();
expr_path.path.segments.pop();
if let Some(segment) = expr_path.path.segments.pop() {
expr_path.path.segments.push(segment.into_value())
}
Some(expr_path)
}
_ => None,
}
}

View file

@ -58,16 +58,21 @@ fn process_attrs(ctxt: &Ctxt, attrs: &mut Vec<Attribute>) {
.push(Ident::new("serde", schemars_ident.span()).into());
}
let schemars_meta_names: BTreeSet<Ident> = attrs
let mut schemars_meta_names: BTreeSet<String> = attrs
.iter()
.flat_map(|attr| get_meta_items(&ctxt, attr))
.flatten()
.flat_map(|m| get_meta_ident(&ctxt, &m))
.map(|i| i.to_string())
.collect();
if schemars_meta_names.contains("with") {
schemars_meta_names.insert("serialize_with".to_string());
schemars_meta_names.insert("deserialize_with".to_string());
}
serde_meta.retain(|m| {
get_meta_ident(&ctxt, m)
.map(|i| !schemars_meta_names.contains(&i))
.map(|i| !schemars_meta_names.contains(&i.to_string()))
.unwrap_or(false)
});
@ -129,8 +134,8 @@ mod tests {
struct MyStruct {
#[serde(field, field2)]
field1: i32,
#[serde(field, field2)]
#[schemars(field = "overridden")]
#[serde(field, field2, serialize_with = "se", deserialize_with = "de")]
#[schemars(field = "overridden", with = "with")]
field2: i32,
#[schemars(field)]
field3: i32,
@ -142,7 +147,7 @@ mod tests {
struct MyStruct {
#[serde(field, field2)]
field1: i32,
#[serde(field = "overridden")]
#[serde(field = "overridden", with = "with")]
#[serde(field2)]
field2: i32,
#[serde(field)]