schema_with attribute

This commit is contained in:
Graham Esau 2020-05-15 17:11:28 +01:00
parent 9d951b34ce
commit 3fd316063a
15 changed files with 538 additions and 51 deletions

View file

@ -1,6 +1,6 @@
mod from_serde;
use crate::attr::{Attrs, WithAttr};
use crate::attr::Attrs;
use from_serde::FromSerde;
use serde_derive_internals::ast as serde_ast;
use serde_derive_internals::{Ctxt, Derive};
@ -68,12 +68,4 @@ impl<'a> Field<'a> {
pub fn name(&self) -> String {
self.serde_attrs.name().deserialize_name()
}
pub fn type_for_schema(&self) -> &syn::Type {
match &self.attrs.with {
None => self.ty,
Some(WithAttr::Type(ty)) => ty,
Some(WithAttr::_Function(_)) => unimplemented!(), // TODO
}
}
}

View file

@ -22,7 +22,7 @@ pub struct Attrs {
#[derive(Debug)]
pub enum WithAttr {
Type(syn::Type),
_Function(syn::Path),
Function(syn::Path),
}
impl Attrs {
@ -74,14 +74,22 @@ impl Attrs {
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")
}
Some(WithAttr::Function(_)) => mutual_exclusive_error(m, "schema_with"),
None => self.with = Some(WithAttr::Type(ty)),
}
}
}
Meta(NameValue(m)) if m.path.is_ident("schema_with") => {
if let Ok(fun) = parse_lit_into_path(errors, attr_type, "schema_with", &m.lit) {
match self.with {
Some(WithAttr::Function(_)) => duplicate_error(m),
Some(WithAttr::Type(_)) => mutual_exclusive_error(m, "with"),
None => self.with = Some(WithAttr::Function(fun)),
}
}
}
Meta(_meta_item) => {
// TODO uncomment this for 0.8.0 (breaking change)
// https://github.com/GREsau/schemars/issues/18
@ -148,8 +156,8 @@ fn get_lit_str<'a>(
cx.error_spanned_by(
lit,
format!(
"expected {} attribute to be a string: `{} = \"...\"`",
attr_type, meta_item_name
"expected {} {} attribute to be a string: `{} = \"...\"`",
attr_type, meta_item_name, meta_item_name
),
);
Err(())
@ -167,7 +175,31 @@ fn parse_lit_into_ty(
parse_lit_str(string).map_err(|_| {
cx.error_spanned_by(
lit,
format!("failed to parse type: {} = {:?}", attr_type, string.value()),
format!(
"failed to parse type: `{} = {:?}`",
meta_item_name,
string.value()
),
)
})
}
fn parse_lit_into_path(
cx: &Ctxt,
attr_type: &'static str,
meta_item_name: &'static str,
lit: &syn::Lit,
) -> Result<syn::Path, ()> {
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 path: `{} = {:?}`",
meta_item_name,
string.value()
),
)
})
}

View file

@ -17,6 +17,58 @@ pub fn expr_for_container(cont: &Container) -> TokenStream {
doc_metadata.apply_to_schema(schema_expr)
}
fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream {
let (ty, type_def) = type_for_schema(field, 0);
let span = field.original.span();
if allow_ref {
quote_spanned! {span=>
{
#type_def
gen.subschema_for::<#ty>()
}
}
} else {
quote_spanned! {span=>
{
#type_def
<#ty as schemars::JsonSchema>::json_schema(gen)
}
}
}
}
fn type_for_schema(field: &Field, local_id: usize) -> (syn::Type, Option<TokenStream>) {
match &field.attrs.with {
None => (field.ty.to_owned(), None),
Some(WithAttr::Type(ty)) => (ty.to_owned(), None),
Some(WithAttr::Function(fun)) => {
let ty_name = format_ident!("_SchemarsSchemaWithFunction{}", local_id);
let fn_name = fun.segments.last().unwrap().ident.to_string();
let type_def = quote_spanned! {fun.span()=>
struct #ty_name;
impl schemars::JsonSchema for #ty_name {
fn is_referenceable() -> bool {
false
}
fn schema_name() -> std::string::String {
#fn_name.to_string()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
#fun(gen)
}
}
};
(parse_quote!(#ty_name), Some(type_def))
}
}
}
fn expr_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> TokenStream {
let variants = variants
.iter()
@ -208,7 +260,7 @@ fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
match variant.style {
Style::Unit => expr_for_unit_struct(),
Style::Newtype => expr_for_newtype_struct(&variant.fields[0]),
Style::Newtype => expr_for_field(&variant.fields[0], true),
Style::Tuple => expr_for_tuple_struct(&variant.fields),
Style::Struct => expr_for_struct(&variant.fields, None),
}
@ -223,13 +275,7 @@ fn expr_for_untagged_enum_variant_for_flatten(variant: &Variant) -> Option<Token
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::Newtype => expr_for_field(&variant.fields[0], false),
Style::Tuple => expr_for_tuple_struct(&variant.fields),
Style::Struct => expr_for_struct(&variant.fields, None),
})
@ -242,19 +288,21 @@ fn expr_for_unit_struct() -> TokenStream {
}
fn expr_for_newtype_struct(field: &Field) -> TokenStream {
let ty = field.type_for_schema();
quote_spanned! {field.original.span()=>
gen.subschema_for::<#ty>()
}
expr_for_field(field, true)
}
fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream {
let types = fields
let (types, type_defs): (Vec<_>, Vec<_>) = fields
.iter()
.filter(|f| !f.serde_attrs.skip_deserializing())
.map(Field::type_for_schema);
.enumerate()
.map(|(i, f)| type_for_schema(f, i))
.unzip();
quote! {
gen.subschema_for::<(#(#types),*)>()
{
#(#type_defs)*
gen.subschema_for::<(#(#types),*)>()
}
}
}
@ -270,7 +318,9 @@ fn expr_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) ->
SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)),
});
let properties = property_fields.iter().map(|field| {
let mut type_defs = Vec::new();
let properties: Vec<_> = property_fields.into_iter().map(|field| {
let name = field.name();
let default = field_default_expr(field, set_container_default.is_some());
@ -286,25 +336,34 @@ fn expr_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) ->
..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 (ty, type_def) = type_for_schema(field, type_defs.len());
if let Some(type_def) = type_def {
type_defs.push(type_def);
}
});
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_spanned! {ty.span()=>
<#ty as schemars::JsonSchema>::add_schema_as_property(gen, &mut schema_object, #name.to_owned(), #metadata, #required);
}
});
}).collect();
let flattens: Vec<_> = flattened_fields
.into_iter()
.map(|field| {
let (ty, type_def) = type_for_schema(field, type_defs.len());
if let Some(type_def) = type_def {
type_defs.push(type_def);
}
quote_spanned! {ty.span()=>
.flatten(<#ty as schemars::JsonSchema>::json_schema_for_flatten(gen))
}
})
.collect();
quote! {
{
#(#type_defs)*
#set_container_default
let mut schema_object = schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::Object.into()),