schema_with
attribute
This commit is contained in:
parent
9d951b34ce
commit
3fd316063a
15 changed files with 538 additions and 51 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue