More refactoring of proc macro...
This commit is contained in:
parent
631120ead8
commit
b1ded882b7
5 changed files with 421 additions and 419 deletions
|
@ -94,5 +94,5 @@ pub enum Adjacent {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn enum_adjacent_tagged() -> TestResult {
|
fn enum_adjacent_tagged() -> TestResult {
|
||||||
test_default_generated_schema::<Adjacent>("enum_adjacent_tagged-untagged")
|
test_default_generated_schema::<Adjacent>("enum-adjacent-tagged")
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"c": {
|
"c": {
|
||||||
"type": "null"
|
"$ref": "#/definitions/UnitStruct"
|
||||||
},
|
},
|
||||||
"t": {
|
"t": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -63,20 +63,7 @@
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"c": {
|
"c": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/Struct"
|
||||||
"required": [
|
|
||||||
"bar",
|
|
||||||
"foo"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"bar": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"foo": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"t": {
|
"t": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -179,5 +166,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"definitions": {
|
||||||
|
"Struct": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"bar",
|
||||||
|
"foo"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"bar": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"foo": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UnitStruct": {
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -54,10 +54,21 @@ impl<'a> Variant<'a> {
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> String {
|
||||||
self.serde_attrs.name().deserialize_name()
|
self.serde_attrs.name().deserialize_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_unit(&self) -> bool {
|
||||||
|
match self.style {
|
||||||
|
serde_ast::Style::Unit => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Field<'a> {
|
impl<'a> Field<'a> {
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> String {
|
||||||
self.serde_attrs.name().deserialize_name()
|
self.serde_attrs.name().deserialize_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn type_for_schema(&self) -> &syn::Type {
|
||||||
|
self.with.as_ref().unwrap_or(self.ty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,10 @@ extern crate proc_macro;
|
||||||
mod ast;
|
mod ast;
|
||||||
mod attr;
|
mod attr;
|
||||||
mod metadata;
|
mod metadata;
|
||||||
|
mod schema_exprs;
|
||||||
|
|
||||||
use ast::*;
|
use ast::*;
|
||||||
use metadata::*;
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::ToTokens;
|
|
||||||
use serde_derive_internals::ast::Style;
|
|
||||||
use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, TagType};
|
|
||||||
use syn::spanned::Spanned;
|
|
||||||
|
|
||||||
#[proc_macro_derive(JsonSchema, attributes(schemars, serde))]
|
#[proc_macro_derive(JsonSchema, attributes(schemars, serde))]
|
||||||
pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
@ -34,15 +30,7 @@ fn derive_json_schema(mut input: syn::DeriveInput) -> TokenStream {
|
||||||
Err(e) => return compile_error(&e),
|
Err(e) => return compile_error(&e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let schema_expr = match &cont.data {
|
let schema_expr = schema_exprs::expr_for_container(&cont);
|
||||||
Data::Struct(Style::Unit, _) => schema_for_unit_struct(),
|
|
||||||
Data::Struct(Style::Newtype, fields) => schema_for_newtype_struct(&fields[0]),
|
|
||||||
Data::Struct(Style::Tuple, fields) => schema_for_tuple_struct(fields),
|
|
||||||
Data::Struct(Style::Struct, fields) => schema_for_struct(fields, Some(&cont.serde_attrs)),
|
|
||||||
Data::Enum(variants) => schema_for_enum(variants, &cont.serde_attrs),
|
|
||||||
};
|
|
||||||
let doc_metadata = SchemaMetadata::from_doc_attrs(&cont.original.attrs);
|
|
||||||
let schema_expr = doc_metadata.apply_to_schema(schema_expr);
|
|
||||||
|
|
||||||
let type_name = &cont.ident;
|
let type_name = &cont.ident;
|
||||||
let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect();
|
let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect();
|
||||||
|
@ -103,397 +91,9 @@ fn add_trait_bounds(generics: &mut syn::Generics) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_schema_fields(schema_contents: TokenStream) -> TokenStream {
|
|
||||||
quote! {
|
|
||||||
schemars::schema::Schema::Object(
|
|
||||||
schemars::schema::SchemaObject {
|
|
||||||
#schema_contents
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile_error<'a>(errors: impl IntoIterator<Item = &'a syn::Error>) -> TokenStream {
|
fn compile_error<'a>(errors: impl IntoIterator<Item = &'a syn::Error>) -> TokenStream {
|
||||||
let compile_errors = errors.into_iter().map(syn::Error::to_compile_error);
|
let compile_errors = errors.into_iter().map(syn::Error::to_compile_error);
|
||||||
quote! {
|
quote! {
|
||||||
#(#compile_errors)*
|
#(#compile_errors)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_unit_variant(v: &Variant) -> bool {
|
|
||||||
match v.style {
|
|
||||||
Style::Unit => v.with.is_none(),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schema_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> TokenStream {
|
|
||||||
let variants = variants
|
|
||||||
.iter()
|
|
||||||
.filter(|v| !v.serde_attrs.skip_deserializing());
|
|
||||||
match cattrs.tag() {
|
|
||||||
TagType::External => schema_for_external_tagged_enum(variants),
|
|
||||||
TagType::None => schema_for_untagged_enum(variants),
|
|
||||||
TagType::Internal { tag } => schema_for_internal_tagged_enum(variants, tag),
|
|
||||||
TagType::Adjacent { tag, content } => {
|
|
||||||
schema_for_adjacent_tagged_enum(variants, tag, content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schema_for_external_tagged_enum<'a>(
|
|
||||||
variants: impl Iterator<Item = &'a Variant<'a>>,
|
|
||||||
) -> TokenStream {
|
|
||||||
let (unit_variants, complex_variants): (Vec<_>, Vec<_>) =
|
|
||||||
variants.partition(|v| is_unit_variant(v));
|
|
||||||
let unit_count = unit_variants.len();
|
|
||||||
|
|
||||||
let unit_names = unit_variants.into_iter().map(|v| v.name());
|
|
||||||
let unit_schema = wrap_schema_fields(quote! {
|
|
||||||
enum_values: Some(vec![#(#unit_names.into()),*]),
|
|
||||||
});
|
|
||||||
|
|
||||||
if complex_variants.is_empty() {
|
|
||||||
return unit_schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut schemas = Vec::new();
|
|
||||||
if unit_count > 0 {
|
|
||||||
schemas.push(unit_schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
schemas.extend(complex_variants.into_iter().map(|variant| {
|
|
||||||
let name = variant.name();
|
|
||||||
let sub_schema = schema_for_untagged_enum_variant(variant);
|
|
||||||
let schema_expr = wrap_schema_fields(quote! {
|
|
||||||
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
|
||||||
object: Some(Box::new(schemars::schema::ObjectValidation {
|
|
||||||
properties: {
|
|
||||||
let mut props = schemars::Map::new();
|
|
||||||
props.insert(#name.to_owned(), #sub_schema);
|
|
||||||
props
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
let mut required = schemars::Set::new();
|
|
||||||
required.insert(#name.to_owned());
|
|
||||||
required
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
|
||||||
doc_metadata.apply_to_schema(schema_expr)
|
|
||||||
}));
|
|
||||||
|
|
||||||
wrap_schema_fields(quote! {
|
|
||||||
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
|
||||||
any_of: Some(vec![#(#schemas),*]),
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schema_for_internal_tagged_enum<'a>(
|
|
||||||
variants: impl Iterator<Item = &'a Variant<'a>>,
|
|
||||||
tag_name: &str,
|
|
||||||
) -> TokenStream {
|
|
||||||
let schemas = variants.map(|variant| {
|
|
||||||
let name = variant.name();
|
|
||||||
let type_schema = wrap_schema_fields(quote! {
|
|
||||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
|
||||||
enum_values: Some(vec![#name.into()]),
|
|
||||||
});
|
|
||||||
|
|
||||||
let tag_schema = wrap_schema_fields(quote! {
|
|
||||||
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
|
||||||
object: Some(Box::new(schemars::schema::ObjectValidation {
|
|
||||||
properties: {
|
|
||||||
let mut props = schemars::Map::new();
|
|
||||||
props.insert(#tag_name.to_owned(), #type_schema);
|
|
||||||
props
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
let mut required = schemars::Set::new();
|
|
||||||
required.insert(#tag_name.to_owned());
|
|
||||||
required
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
|
||||||
let tag_schema = doc_metadata.apply_to_schema(tag_schema);
|
|
||||||
|
|
||||||
// FIXME this match block is duplicated in schema_for_adjacent_tagged_enum,
|
|
||||||
// and is extremely similar to schema_for_untagged_enum_variant
|
|
||||||
let variant_schema = match (&variant.with, &variant.style) {
|
|
||||||
(Some(with), _) => {
|
|
||||||
quote_spanned! {variant.original.span()=>
|
|
||||||
<#with>::json_schema(gen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(None, Style::Unit) => return tag_schema,
|
|
||||||
(None, Style::Newtype) => {
|
|
||||||
let field = &variant.fields[0];
|
|
||||||
let ty = get_json_schema_type(field);
|
|
||||||
quote_spanned! {field.original.span()=>
|
|
||||||
<#ty>::json_schema(gen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(None, Style::Struct) => schema_for_struct(&variant.fields, None),
|
|
||||||
(None, Style::Tuple) => schema_for_tuple_struct(&variant.fields),
|
|
||||||
};
|
|
||||||
quote! {
|
|
||||||
#tag_schema.flatten(#variant_schema)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
wrap_schema_fields(quote! {
|
|
||||||
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
|
||||||
any_of: Some(vec![#(#schemas),*]),
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schema_for_untagged_enum<'a>(variants: impl Iterator<Item = &'a Variant<'a>>) -> TokenStream {
|
|
||||||
let schemas = variants.map(|variant| {
|
|
||||||
let schema_expr = schema_for_untagged_enum_variant(variant);
|
|
||||||
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
|
||||||
doc_metadata.apply_to_schema(schema_expr)
|
|
||||||
});
|
|
||||||
|
|
||||||
wrap_schema_fields(quote! {
|
|
||||||
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
|
||||||
any_of: Some(vec![#(#schemas),*]),
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schema_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
|
|
||||||
if let Some(with) = &variant.with {
|
|
||||||
return quote_spanned! {variant.original.span()=>
|
|
||||||
gen.subschema_for::<#with>()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
match variant.style {
|
|
||||||
Style::Unit => schema_for_unit_struct(),
|
|
||||||
Style::Newtype => schema_for_newtype_struct(&variant.fields[0]),
|
|
||||||
Style::Tuple => schema_for_tuple_struct(&variant.fields),
|
|
||||||
Style::Struct => schema_for_struct(&variant.fields, None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schema_for_adjacent_tagged_enum<'a>(
|
|
||||||
variants: impl Iterator<Item = &'a Variant<'a>>,
|
|
||||||
tag_name: &str,
|
|
||||||
content_name: &str,
|
|
||||||
) -> TokenStream {
|
|
||||||
let schemas = variants.map(|variant| {
|
|
||||||
let content_schema = match (&variant.with, &variant.style) {
|
|
||||||
(Some(with), _) => Some(quote_spanned! {variant.original.span()=>
|
|
||||||
<#with>::json_schema(gen)
|
|
||||||
}),
|
|
||||||
(None, Style::Unit) => None,
|
|
||||||
(None, Style::Newtype) => {
|
|
||||||
let field = &variant.fields[0];
|
|
||||||
let ty = get_json_schema_type(field);
|
|
||||||
Some(quote_spanned! {field.original.span()=>
|
|
||||||
<#ty>::json_schema(gen)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
(None, Style::Struct) => Some(schema_for_struct(&variant.fields, None)),
|
|
||||||
(None, Style::Tuple) => Some(schema_for_tuple_struct(&variant.fields)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (add_content_property, add_content_required) = content_schema
|
|
||||||
.map(|content_schema| {
|
|
||||||
(
|
|
||||||
quote!(props.insert(#content_name.to_owned(), #content_schema);),
|
|
||||||
quote!(required.insert(#content_name.to_owned());),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let name = variant.name();
|
|
||||||
let tag_schema = wrap_schema_fields(quote! {
|
|
||||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
|
||||||
enum_values: Some(vec![#name.into()]),
|
|
||||||
});
|
|
||||||
|
|
||||||
let outer_schema = wrap_schema_fields(quote! {
|
|
||||||
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
|
||||||
object: Some(Box::new(schemars::schema::ObjectValidation {
|
|
||||||
properties: {
|
|
||||||
let mut props = schemars::Map::new();
|
|
||||||
props.insert(#tag_name.to_owned(), #tag_schema);
|
|
||||||
#add_content_property
|
|
||||||
props
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
let mut required = schemars::Set::new();
|
|
||||||
required.insert(#tag_name.to_owned());
|
|
||||||
#add_content_required
|
|
||||||
required
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
|
||||||
doc_metadata.apply_to_schema(outer_schema)
|
|
||||||
});
|
|
||||||
|
|
||||||
wrap_schema_fields(quote! {
|
|
||||||
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
|
||||||
any_of: Some(vec![#(#schemas),*]),
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schema_for_unit_struct() -> TokenStream {
|
|
||||||
quote! {
|
|
||||||
gen.subschema_for::<()>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schema_for_newtype_struct(field: &Field) -> TokenStream {
|
|
||||||
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()
|
|
||||||
.filter(|f| !f.serde_attrs.skip_deserializing())
|
|
||||||
.map(get_json_schema_type);
|
|
||||||
quote! {
|
|
||||||
gen.subschema_for::<(#(#types),*)>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schema_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) -> TokenStream {
|
|
||||||
let (flattened_fields, property_fields): (Vec<_>, Vec<_>) = fields
|
|
||||||
.iter()
|
|
||||||
.filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing())
|
|
||||||
.partition(|f| f.serde_attrs.flatten());
|
|
||||||
|
|
||||||
let set_container_default = match cattrs.map_or(&SerdeDefault::None, |c| c.default()) {
|
|
||||||
SerdeDefault::None => None,
|
|
||||||
SerdeDefault::Default => Some(quote!(let container_default = Self::default();)),
|
|
||||||
SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let properties = property_fields.iter().map(|field| {
|
|
||||||
let name = field.name();
|
|
||||||
let default = field_default_expr(field, set_container_default.is_some());
|
|
||||||
|
|
||||||
let required = match default {
|
|
||||||
Some(_) => quote!(false),
|
|
||||||
None => quote!(true),
|
|
||||||
};
|
|
||||||
|
|
||||||
let metadata = &SchemaMetadata {
|
|
||||||
read_only: field.serde_attrs.skip_deserializing(),
|
|
||||||
write_only: field.serde_attrs.skip_serializing(),
|
|
||||||
default,
|
|
||||||
..SchemaMetadata::from_doc_attrs(&field.original.attrs)
|
|
||||||
};
|
|
||||||
|
|
||||||
let ty = get_json_schema_type(field);
|
|
||||||
let span = field.original.span();
|
|
||||||
|
|
||||||
quote_spanned! {span=>
|
|
||||||
<#ty>::add_schema_as_property(gen, &mut schema_object, #name.to_owned(), #metadata, #required);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let flattens = flattened_fields.iter().map(|field| {
|
|
||||||
let ty = get_json_schema_type(field);
|
|
||||||
let span = field.original.span();
|
|
||||||
|
|
||||||
quote_spanned! {span=>
|
|
||||||
.flatten(<#ty>::json_schema_for_flatten(gen))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
#set_container_default
|
|
||||||
let mut schema_object = schemars::schema::SchemaObject {
|
|
||||||
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
#(#properties)*
|
|
||||||
schemars::schema::Schema::Object(schema_object)
|
|
||||||
#(#flattens)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_default_expr(field: &Field, container_has_default: bool) -> Option<TokenStream> {
|
|
||||||
let field_default = field.serde_attrs.default();
|
|
||||||
if field.serde_attrs.skip_serializing() || (field_default.is_none() && !container_has_default) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ty = field.ty;
|
|
||||||
let default_expr = match field_default {
|
|
||||||
SerdeDefault::None => {
|
|
||||||
let member = &field.member;
|
|
||||||
quote!(container_default.#member)
|
|
||||||
}
|
|
||||||
SerdeDefault::Default => quote!(<#ty>::default()),
|
|
||||||
SerdeDefault::Path(path) => quote!(#path()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let default_expr = match field.serde_attrs.skip_serializing_if() {
|
|
||||||
Some(skip_if) => {
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
let default = #default_expr;
|
|
||||||
if #skip_if(&default) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => quote!(Some(#default_expr)),
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(if let Some(ser_with) = field.serde_attrs.serialize_with() {
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
struct _SchemarsDefaultSerialize<T>(T);
|
|
||||||
|
|
||||||
impl serde::Serialize for _SchemarsDefaultSerialize<#ty>
|
|
||||||
{
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer
|
|
||||||
{
|
|
||||||
#ser_with(&self.0, serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#default_expr.map(|d| _SchemarsDefaultSerialize(d))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
default_expr
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_json_schema_type(field: &Field) -> TokenStream {
|
|
||||||
// TODO support [schemars(schema_with= "...")] or equivalent
|
|
||||||
field
|
|
||||||
.with
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| field.ty.to_token_stream(), |w| w.to_token_stream())
|
|
||||||
}
|
|
||||||
|
|
383
schemars_derive/src/schema_exprs.rs
Normal file
383
schemars_derive/src/schema_exprs.rs
Normal file
|
@ -0,0 +1,383 @@
|
||||||
|
use crate::{ast::*, 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};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
pub fn expr_for_container(cont: &Container) -> TokenStream {
|
||||||
|
let schema_expr = match &cont.data {
|
||||||
|
Data::Struct(Style::Unit, _) => expr_for_unit_struct(),
|
||||||
|
Data::Struct(Style::Newtype, fields) => expr_for_newtype_struct(&fields[0]),
|
||||||
|
Data::Struct(Style::Tuple, fields) => expr_for_tuple_struct(fields),
|
||||||
|
Data::Struct(Style::Struct, fields) => expr_for_struct(fields, Some(&cont.serde_attrs)),
|
||||||
|
Data::Enum(variants) => expr_for_enum(variants, &cont.serde_attrs),
|
||||||
|
};
|
||||||
|
|
||||||
|
let doc_metadata = SchemaMetadata::from_doc_attrs(&cont.original.attrs);
|
||||||
|
doc_metadata.apply_to_schema(schema_expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> TokenStream {
|
||||||
|
let variants = variants
|
||||||
|
.iter()
|
||||||
|
.filter(|v| !v.serde_attrs.skip_deserializing());
|
||||||
|
match cattrs.tag() {
|
||||||
|
TagType::External => expr_for_external_tagged_enum(variants),
|
||||||
|
TagType::None => expr_for_untagged_enum(variants),
|
||||||
|
TagType::Internal { tag } => expr_for_internal_tagged_enum(variants, tag),
|
||||||
|
TagType::Adjacent { tag, content } => expr_for_adjacent_tagged_enum(variants, tag, content),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
let unit_names = unit_variants.iter().map(|v| v.name());
|
||||||
|
let unit_schema = schema_object(quote! {
|
||||||
|
enum_values: Some(vec![#(#unit_names.into()),*]),
|
||||||
|
});
|
||||||
|
|
||||||
|
if complex_variants.is_empty() {
|
||||||
|
return unit_schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut schemas = Vec::new();
|
||||||
|
if unit_variants.len() > 0 {
|
||||||
|
schemas.push(unit_schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
schemas.extend(complex_variants.into_iter().map(|variant| {
|
||||||
|
let name = variant.name();
|
||||||
|
let sub_schema = expr_for_untagged_enum_variant(variant);
|
||||||
|
let schema_expr = schema_object(quote! {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||||
|
object: Some(Box::new(schemars::schema::ObjectValidation {
|
||||||
|
properties: {
|
||||||
|
let mut props = schemars::Map::new();
|
||||||
|
props.insert(#name.to_owned(), #sub_schema);
|
||||||
|
props
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
let mut required = schemars::Set::new();
|
||||||
|
required.insert(#name.to_owned());
|
||||||
|
required
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
||||||
|
doc_metadata.apply_to_schema(schema_expr)
|
||||||
|
}));
|
||||||
|
|
||||||
|
schema_object(quote! {
|
||||||
|
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
||||||
|
any_of: Some(vec![#(#schemas),*]),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_for_internal_tagged_enum<'a>(
|
||||||
|
variants: impl Iterator<Item = &'a Variant<'a>>,
|
||||||
|
tag_name: &str,
|
||||||
|
) -> TokenStream {
|
||||||
|
let variant_schemas = variants.map(|variant| {
|
||||||
|
let name = variant.name();
|
||||||
|
let type_schema = schema_object(quote! {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||||
|
enum_values: Some(vec![#name.into()]),
|
||||||
|
});
|
||||||
|
|
||||||
|
let tag_schema = schema_object(quote! {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||||
|
object: Some(Box::new(schemars::schema::ObjectValidation {
|
||||||
|
properties: {
|
||||||
|
let mut props = schemars::Map::new();
|
||||||
|
props.insert(#tag_name.to_owned(), #type_schema);
|
||||||
|
props
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
let mut required = schemars::Set::new();
|
||||||
|
required.insert(#tag_name.to_owned());
|
||||||
|
required
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
||||||
|
let tag_schema = doc_metadata.apply_to_schema(tag_schema);
|
||||||
|
|
||||||
|
match expr_for_untagged_enum_variant_for_flatten(&variant) {
|
||||||
|
Some(variant_schema) => quote! {
|
||||||
|
#tag_schema.flatten(#variant_schema)
|
||||||
|
},
|
||||||
|
None => tag_schema,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
schema_object(quote! {
|
||||||
|
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
||||||
|
any_of: Some(vec![#(#variant_schemas),*]),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_for_untagged_enum<'a>(variants: impl Iterator<Item = &'a Variant<'a>>) -> TokenStream {
|
||||||
|
let schemas = variants.map(|variant| {
|
||||||
|
let schema_expr = expr_for_untagged_enum_variant(variant);
|
||||||
|
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
||||||
|
doc_metadata.apply_to_schema(schema_expr)
|
||||||
|
});
|
||||||
|
|
||||||
|
schema_object(quote! {
|
||||||
|
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
||||||
|
any_of: Some(vec![#(#schemas),*]),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_for_adjacent_tagged_enum<'a>(
|
||||||
|
variants: impl Iterator<Item = &'a Variant<'a>>,
|
||||||
|
tag_name: &str,
|
||||||
|
content_name: &str,
|
||||||
|
) -> TokenStream {
|
||||||
|
let schemas = variants.map(|variant| {
|
||||||
|
let content_schema = if variant.is_unit() && variant.with.is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(expr_for_untagged_enum_variant(variant))
|
||||||
|
};
|
||||||
|
|
||||||
|
let (add_content_to_props, add_content_to_required) = content_schema
|
||||||
|
.map(|content_schema| {
|
||||||
|
(
|
||||||
|
quote!(props.insert(#content_name.to_owned(), #content_schema);),
|
||||||
|
quote!(required.insert(#content_name.to_owned());),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let name = variant.name();
|
||||||
|
let tag_schema = schema_object(quote! {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||||
|
enum_values: Some(vec![#name.into()]),
|
||||||
|
});
|
||||||
|
|
||||||
|
let outer_schema = schema_object(quote! {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||||
|
object: Some(Box::new(schemars::schema::ObjectValidation {
|
||||||
|
properties: {
|
||||||
|
let mut props = schemars::Map::new();
|
||||||
|
props.insert(#tag_name.to_owned(), #tag_schema);
|
||||||
|
#add_content_to_props
|
||||||
|
props
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
let mut required = schemars::Set::new();
|
||||||
|
required.insert(#tag_name.to_owned());
|
||||||
|
#add_content_to_required
|
||||||
|
required
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
||||||
|
doc_metadata.apply_to_schema(outer_schema)
|
||||||
|
});
|
||||||
|
|
||||||
|
schema_object(quote! {
|
||||||
|
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
||||||
|
any_of: Some(vec![#(#schemas),*]),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
|
||||||
|
if let Some(with) = &variant.with {
|
||||||
|
return quote_spanned! {variant.original.span()=>
|
||||||
|
gen.subschema_for::<#with>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match variant.style {
|
||||||
|
Style::Unit => expr_for_unit_struct(),
|
||||||
|
Style::Newtype => expr_for_newtype_struct(&variant.fields[0]),
|
||||||
|
Style::Tuple => expr_for_tuple_struct(&variant.fields),
|
||||||
|
Style::Struct => expr_for_struct(&variant.fields, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_for_untagged_enum_variant_for_flatten(variant: &Variant) -> Option<TokenStream> {
|
||||||
|
if let Some(with) = &variant.with {
|
||||||
|
return Some(quote_spanned! {variant.original.span()=>
|
||||||
|
<#with>::json_schema(gen)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(match variant.style {
|
||||||
|
Style::Unit => return None,
|
||||||
|
Style::Newtype => {
|
||||||
|
let field = &variant.fields[0];
|
||||||
|
let ty = field.type_for_schema();
|
||||||
|
quote_spanned! {field.original.span()=>
|
||||||
|
<#ty>::json_schema(gen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Style::Tuple => expr_for_tuple_struct(&variant.fields),
|
||||||
|
Style::Struct => expr_for_struct(&variant.fields, None),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_for_unit_struct() -> TokenStream {
|
||||||
|
quote! {
|
||||||
|
gen.subschema_for::<()>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_for_newtype_struct(field: &Field) -> TokenStream {
|
||||||
|
let ty = field.type_for_schema();
|
||||||
|
quote_spanned! {field.original.span()=>
|
||||||
|
gen.subschema_for::<#ty>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream {
|
||||||
|
let types = fields
|
||||||
|
.iter()
|
||||||
|
.filter(|f| !f.serde_attrs.skip_deserializing())
|
||||||
|
.map(Field::type_for_schema);
|
||||||
|
quote! {
|
||||||
|
gen.subschema_for::<(#(#types),*)>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) -> TokenStream {
|
||||||
|
let (flattened_fields, property_fields): (Vec<_>, Vec<_>) = fields
|
||||||
|
.iter()
|
||||||
|
.filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing())
|
||||||
|
.partition(|f| f.serde_attrs.flatten());
|
||||||
|
|
||||||
|
let set_container_default = cattrs.and_then(|c| match c.default() {
|
||||||
|
SerdeDefault::None => None,
|
||||||
|
SerdeDefault::Default => Some(quote!(let container_default = Self::default();)),
|
||||||
|
SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)),
|
||||||
|
});
|
||||||
|
|
||||||
|
let properties = property_fields.iter().map(|field| {
|
||||||
|
let name = field.name();
|
||||||
|
let default = field_default_expr(field, set_container_default.is_some());
|
||||||
|
|
||||||
|
let required = match default {
|
||||||
|
Some(_) => quote!(false),
|
||||||
|
None => quote!(true),
|
||||||
|
};
|
||||||
|
|
||||||
|
let metadata = &SchemaMetadata {
|
||||||
|
read_only: field.serde_attrs.skip_deserializing(),
|
||||||
|
write_only: field.serde_attrs.skip_serializing(),
|
||||||
|
default,
|
||||||
|
..SchemaMetadata::from_doc_attrs(&field.original.attrs)
|
||||||
|
};
|
||||||
|
|
||||||
|
let ty = field.type_for_schema();
|
||||||
|
let span = field.original.span();
|
||||||
|
|
||||||
|
quote_spanned! {span=>
|
||||||
|
<#ty>::add_schema_as_property(gen, &mut schema_object, #name.to_owned(), #metadata, #required);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let flattens = flattened_fields.iter().map(|field| {
|
||||||
|
let ty = field.type_for_schema();
|
||||||
|
let span = field.original.span();
|
||||||
|
|
||||||
|
quote_spanned! {span=>
|
||||||
|
.flatten(<#ty>::json_schema_for_flatten(gen))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
#set_container_default
|
||||||
|
let mut schema_object = schemars::schema::SchemaObject {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
#(#properties)*
|
||||||
|
schemars::schema::Schema::Object(schema_object)
|
||||||
|
#(#flattens)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_default_expr(field: &Field, container_has_default: bool) -> Option<TokenStream> {
|
||||||
|
let field_default = field.serde_attrs.default();
|
||||||
|
if field.serde_attrs.skip_serializing() || (field_default.is_none() && !container_has_default) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ty = field.ty;
|
||||||
|
let default_expr = match field_default {
|
||||||
|
SerdeDefault::None => {
|
||||||
|
let member = &field.member;
|
||||||
|
quote!(container_default.#member)
|
||||||
|
}
|
||||||
|
SerdeDefault::Default => quote!(<#ty>::default()),
|
||||||
|
SerdeDefault::Path(path) => quote!(#path()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let default_expr = match field.serde_attrs.skip_serializing_if() {
|
||||||
|
Some(skip_if) => {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
let default = #default_expr;
|
||||||
|
if #skip_if(&default) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => quote!(Some(#default_expr)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(if let Some(ser_with) = field.serde_attrs.serialize_with() {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
struct _SchemarsDefaultSerialize<T>(T);
|
||||||
|
|
||||||
|
impl serde::Serialize for _SchemarsDefaultSerialize<#ty>
|
||||||
|
{
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer
|
||||||
|
{
|
||||||
|
#ser_with(&self.0, serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#default_expr.map(|d| _SchemarsDefaultSerialize(d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
default_expr
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema_object(properties: TokenStream) -> TokenStream {
|
||||||
|
quote! {
|
||||||
|
schemars::schema::Schema::Object(
|
||||||
|
schemars::schema::SchemaObject {
|
||||||
|
#properties
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue