Derive JsonSchema_repr (#76)

This commit is contained in:
Graham Esau 2021-03-25 22:36:28 +00:00 committed by GitHub
parent 7de2b2276f
commit 11d95b79e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 225 additions and 18 deletions

View file

@ -18,6 +18,7 @@ pub struct Attrs {
pub description: Option<String>,
pub deprecated: bool,
pub examples: Vec<syn::Path>,
pub repr: Option<syn::Type>,
}
#[derive(Debug)]
@ -33,6 +34,10 @@ impl Attrs {
.populate(attrs, "serde", true, errors);
result.deprecated = attrs.iter().any(|a| a.path.is_ident("deprecated"));
result.repr = attrs
.iter()
.find(|a| a.path.is_ident("repr"))
.and_then(|a| a.parse_args().ok());
let (doc_title, doc_description) = doc::get_title_and_desc_from_doc(attrs);
result.title = result.title.or(doc_title);

View file

@ -17,27 +17,35 @@ use proc_macro2::TokenStream;
#[proc_macro_derive(JsonSchema, attributes(schemars, serde))]
pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
derive_json_schema(input).into()
derive_json_schema(input, false)
.unwrap_or_else(compile_error)
.into()
}
fn derive_json_schema(mut input: syn::DeriveInput) -> TokenStream {
#[proc_macro_derive(JsonSchema_repr, attributes(schemars, serde))]
pub fn derive_json_schema_repr_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
derive_json_schema(input, true)
.unwrap_or_else(compile_error)
.into()
}
fn derive_json_schema(
mut input: syn::DeriveInput,
repr: bool,
) -> Result<TokenStream, Vec<syn::Error>> {
add_trait_bounds(&mut input.generics);
if let Err(e) = attr::process_serde_attrs(&mut input) {
return compile_error(&e);
}
attr::process_serde_attrs(&mut input)?;
let cont = match Container::from_ast(&input) {
Ok(c) => c,
Err(e) => return compile_error(&e),
};
let cont = Container::from_ast(&input)?;
let type_name = &cont.ident;
let (impl_generics, ty_generics, where_clause) = cont.generics.split_for_impl();
if let Some(transparent_field) = cont.transparent_field() {
let (ty, type_def) = schema_exprs::type_for_schema(transparent_field, 0);
return quote! {
return Ok(quote! {
const _: () = {
#type_def
@ -70,7 +78,7 @@ fn derive_json_schema(mut input: syn::DeriveInput) -> TokenStream {
}
};
};
};
});
}
let mut schema_base_name = cont.name();
@ -106,9 +114,13 @@ fn derive_json_schema(mut input: syn::DeriveInput) -> TokenStream {
}
};
let schema_expr = schema_exprs::expr_for_container(&cont);
let schema_expr = if repr {
schema_exprs::expr_for_repr(&cont).map_err(|e| vec![e])?
} else {
schema_exprs::expr_for_container(&cont)
};
quote! {
Ok(quote! {
#[automatically_derived]
#[allow(unused_braces)]
impl #impl_generics schemars::JsonSchema for #type_name #ty_generics #where_clause {
@ -120,7 +132,7 @@ fn derive_json_schema(mut input: syn::DeriveInput) -> TokenStream {
#schema_expr
}
};
}
})
}
fn add_trait_bounds(generics: &mut syn::Generics) {
@ -131,8 +143,8 @@ fn add_trait_bounds(generics: &mut syn::Generics) {
}
}
fn compile_error<'a>(errors: impl IntoIterator<Item = &'a syn::Error>) -> TokenStream {
let compile_errors = errors.into_iter().map(syn::Error::to_compile_error);
fn compile_error<'a>(errors: Vec<syn::Error>) -> TokenStream {
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
quote! {
#(#compile_errors)*
}

View file

@ -1,5 +1,5 @@
use crate::{ast::*, attr::WithAttr, metadata::SchemaMetadata};
use proc_macro2::TokenStream;
use proc_macro2::{Span, TokenStream};
use serde_derive_internals::ast::Style;
use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, TagType};
use syn::spanned::Spanned;
@ -21,6 +21,41 @@ pub fn expr_for_container(cont: &Container) -> TokenStream {
doc_metadata.apply_to_schema(schema_expr)
}
pub fn expr_for_repr(cont: &Container) -> Result<TokenStream, syn::Error> {
let repr_type = cont.attrs.repr.as_ref().ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"JsonSchema_repr: missing #[repr(...)] attribute",
)
})?;
let variants = match &cont.data {
Data::Enum(variants) => variants,
_ => return Err(syn::Error::new(Span::call_site(), "oh no!")),
};
if let Some(non_unit_error) = variants.iter().find_map(|v| match v.style {
Style::Unit => None,
_ => Some(syn::Error::new(
v.original.span(),
"JsonSchema_repr: must be a unit variant",
)),
}) {
return Err(non_unit_error);
};
let enum_ident = &cont.ident;
let variant_idents = variants.iter().map(|v| &v.ident);
let schema_expr = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::Integer.into()),
enum_values: Some(vec![#((#enum_ident::#variant_idents as #repr_type).into()),*]),
});
let doc_metadata = SchemaMetadata::from_attrs(&cont.attrs);
Ok(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();
@ -300,7 +335,7 @@ fn expr_for_untagged_enum_variant_for_flatten(
) -> Option<TokenStream> {
if let Some(WithAttr::Type(with)) = &variant.attrs.with {
return Some(quote_spanned! {variant.original.span()=>
<#with>::json_schema(gen)
<#with as schemars::JsonSchema>::json_schema(gen)
});
}