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
44
schemars/tests/expected/remote_derive.json
Normal file
44
schemars/tests/expected/remote_derive.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Process",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"command_line": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"system_cpu_time": {
|
||||||
|
"$ref": "#/definitions/DurationDef"
|
||||||
|
},
|
||||||
|
"user_cpu_time": {
|
||||||
|
"$ref": "#/definitions/DurationDef"
|
||||||
|
},
|
||||||
|
"wall_time": {
|
||||||
|
"$ref": "#/definitions/DurationDef"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"command_line",
|
||||||
|
"system_cpu_time",
|
||||||
|
"user_cpu_time",
|
||||||
|
"wall_time"
|
||||||
|
],
|
||||||
|
"definitions": {
|
||||||
|
"DurationDef": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nanos": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"secs": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nanos",
|
||||||
|
"secs"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
schemars/tests/remote_derive.rs
Normal file
37
schemars/tests/remote_derive.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use other_crate::Duration;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use util::*;
|
||||||
|
|
||||||
|
mod other_crate {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Duration {
|
||||||
|
pub secs: i64,
|
||||||
|
pub nanos: i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, JsonSchema)]
|
||||||
|
#[serde(remote = "Duration")]
|
||||||
|
struct DurationDef {
|
||||||
|
secs: i64,
|
||||||
|
nanos: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, JsonSchema)]
|
||||||
|
struct Process {
|
||||||
|
command_line: String,
|
||||||
|
#[serde(with = "DurationDef")]
|
||||||
|
wall_time: Duration,
|
||||||
|
#[serde(with = "DurationDef")]
|
||||||
|
user_cpu_time: Duration,
|
||||||
|
#[serde(deserialize_with = "some_serialize_function")]
|
||||||
|
#[schemars(with = "DurationDef")]
|
||||||
|
system_cpu_time: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remote_derive_json_schema() -> TestResult {
|
||||||
|
test_default_generated_schema::<Process>("remote_derive")
|
||||||
|
}
|
|
@ -2,21 +2,20 @@
|
||||||
extern crate quote;
|
extern crate quote;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate syn;
|
extern crate syn;
|
||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
mod preprocess;
|
mod preprocess;
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::ToTokens;
|
||||||
use serde_derive_internals::ast::{Container, Data, Field, Style, Variant};
|
use serde_derive_internals::ast::{Container, Data, Field, Style, Variant};
|
||||||
use serde_derive_internals::attr::{self, Default as SerdeDefault, EnumTag};
|
use serde_derive_internals::attr::{self, Default as SerdeDefault, EnumTag};
|
||||||
use serde_derive_internals::{Ctxt, Derive};
|
use serde_derive_internals::{Ctxt, Derive};
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::DeriveInput;
|
|
||||||
|
|
||||||
#[proc_macro_derive(JsonSchema, attributes(schemars, serde))]
|
#[proc_macro_derive(JsonSchema, attributes(schemars, serde))]
|
||||||
pub fn derive_json_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
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);
|
preprocess::add_trait_bounds(&mut input.generics);
|
||||||
if let Err(e) = preprocess::process_serde_attrs(&mut input) {
|
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 {
|
fn schema_for_newtype_struct(field: &Field) -> TokenStream {
|
||||||
let ty = field.ty;
|
let ty = get_json_schema_type(field);
|
||||||
quote_spanned! {field.original.span()=>
|
quote_spanned! {field.original.span()=>
|
||||||
gen.subschema_for::<#ty>()?
|
gen.subschema_for::<#ty>()?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn schema_for_tuple_struct(fields: &[Field]) -> TokenStream {
|
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! {
|
quote! {
|
||||||
gen.subschema_for::<(#(#types),*)>()?
|
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()) {
|
if !container_has_default && !has_default(field.attrs.default()) {
|
||||||
required.push(name.clone());
|
required.push(name.clone());
|
||||||
}
|
}
|
||||||
let ty = field.ty;
|
let ty = get_json_schema_type(field);
|
||||||
quote_spanned! {field.original.span()=>
|
quote_spanned! {field.original.span()=>
|
||||||
props.insert(#name.to_owned(), gen.subschema_for::<#ty>()?);
|
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 flattens = flat.iter().map(|field| {
|
||||||
let ty = f.ty;
|
let ty = get_json_schema_type(field);
|
||||||
quote_spanned! {f.original.span()=>
|
quote_spanned! {field.original.span()=>
|
||||||
.flatten(<#ty>::json_schema(gen)?)?
|
.flatten(<#ty>::json_schema(gen)?)?
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -282,3 +281,37 @@ fn has_default(d: &SerdeDefault) -> bool {
|
||||||
_ => true,
|
_ => 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());
|
.push(Ident::new("serde", schemars_ident.span()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let schemars_meta_names: BTreeSet<Ident> = attrs
|
let mut schemars_meta_names: BTreeSet<String> = attrs
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|attr| get_meta_items(&ctxt, attr))
|
.flat_map(|attr| get_meta_items(&ctxt, attr))
|
||||||
.flatten()
|
.flatten()
|
||||||
.flat_map(|m| get_meta_ident(&ctxt, &m))
|
.flat_map(|m| get_meta_ident(&ctxt, &m))
|
||||||
|
.map(|i| i.to_string())
|
||||||
.collect();
|
.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| {
|
serde_meta.retain(|m| {
|
||||||
get_meta_ident(&ctxt, 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)
|
.unwrap_or(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -129,8 +134,8 @@ mod tests {
|
||||||
struct MyStruct {
|
struct MyStruct {
|
||||||
#[serde(field, field2)]
|
#[serde(field, field2)]
|
||||||
field1: i32,
|
field1: i32,
|
||||||
#[serde(field, field2)]
|
#[serde(field, field2, serialize_with = "se", deserialize_with = "de")]
|
||||||
#[schemars(field = "overridden")]
|
#[schemars(field = "overridden", with = "with")]
|
||||||
field2: i32,
|
field2: i32,
|
||||||
#[schemars(field)]
|
#[schemars(field)]
|
||||||
field3: i32,
|
field3: i32,
|
||||||
|
@ -142,7 +147,7 @@ mod tests {
|
||||||
struct MyStruct {
|
struct MyStruct {
|
||||||
#[serde(field, field2)]
|
#[serde(field, field2)]
|
||||||
field1: i32,
|
field1: i32,
|
||||||
#[serde(field = "overridden")]
|
#[serde(field = "overridden", with = "with")]
|
||||||
#[serde(field2)]
|
#[serde(field2)]
|
||||||
field2: i32,
|
field2: i32,
|
||||||
#[serde(field)]
|
#[serde(field)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue