Enable eriving JsonSchema when fields are in remote crates
This commit is contained in:
parent
8d68e36f7c
commit
709ba7b62e
4 changed files with 133 additions and 14 deletions
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue