juniper/juniper_codegen/src/derive_scalar_value.rs
Kai Ren ddc1488195
Codegen reimplementation for GraphQL unions (#666)
- reimplement #[derive(GraphQLUnion)] macro to support:
    - both structs and enums
    - generics in type definition
    - multiple #[graphql] attributes
    - external resolver functions
- remove From trait impls generation for enum variants

- reimplement #[graphql_union] macro to support:
    - traits
    - generics in trait definition
    - multiple attributes
    - external resolver functions
    - GraphQLType implemetation for a raw trait object
    - GraphQLTypeAsync implemetation (#549)

- add marker::GraphQLUnion trait

- rewrite "2.5 Unions" section in Book (Juniper user documentation)

- rewrite `codegen` and `codegen_fail` integration tests for GraphQL unions

Additionally:
- re-export `futures` crate in `juniper` for convenient reuse in the generated code without requiring library user to provide `futures` crate by himself (#663)
- use unit type () as default context for EmptyMutation and EmptySubscriptions
- relax Sized trait bound on some GraphQLType and GraphQLTypeAsync definitions, implementations and usages
2020-06-04 11:19:01 +03:00

322 lines
9.3 KiB
Rust

use crate::{
result::GraphQLScope,
util::{self, span_container::SpanContainer},
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{self, spanned::Spanned, Data, Fields, Ident, Variant};
#[derive(Debug, Default)]
struct TransparentAttributes {
transparent: Option<bool>,
name: Option<String>,
description: Option<String>,
}
impl syn::parse::Parse for TransparentAttributes {
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
let mut output = Self {
transparent: None,
name: None,
description: None,
};
while !input.is_empty() {
let ident: syn::Ident = input.parse()?;
match ident.to_string().as_str() {
"name" => {
input.parse::<syn::Token![=]>()?;
let val = input.parse::<syn::LitStr>()?;
output.name = Some(val.value());
}
"description" => {
input.parse::<syn::Token![=]>()?;
let val = input.parse::<syn::LitStr>()?;
output.description = Some(val.value());
}
"transparent" => {
output.transparent = Some(true);
}
_ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
}
if input.lookahead1().peek(syn::Token![,]) {
input.parse::<syn::Token![,]>()?;
}
}
Ok(output)
}
}
impl TransparentAttributes {
fn from_attrs(attrs: &[syn::Attribute]) -> syn::parse::Result<Self> {
match util::find_graphql_attr(attrs) {
Some(attr) => {
let mut parsed: TransparentAttributes = attr.parse_args()?;
if parsed.description.is_none() {
parsed.description =
util::get_doc_comment(attrs).map(SpanContainer::into_inner);
}
Ok(parsed)
}
None => Ok(Default::default()),
}
}
}
pub fn impl_scalar_value(
ast: &syn::DeriveInput,
is_internal: bool,
error: GraphQLScope,
) -> syn::Result<TokenStream> {
let ident = &ast.ident;
match ast.data {
Data::Enum(ref enum_data) => impl_scalar_enum(ident, enum_data, is_internal, error),
Data::Struct(ref struct_data) => impl_scalar_struct(ast, struct_data, is_internal, error),
Data::Union(_) => Err(error.custom_error(ast.span(), "may not be applied to unions")),
}
}
fn impl_scalar_struct(
ast: &syn::DeriveInput,
data: &syn::DataStruct,
is_internal: bool,
error: GraphQLScope,
) -> syn::Result<TokenStream> {
let field = match data.fields {
syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
fields.unnamed.first().unwrap()
}
_ => {
return Err(error.custom_error(
data.fields.span(),
"requires exact one field, e.g., Test(i32)",
))
}
};
let ident = &ast.ident;
let attrs = TransparentAttributes::from_attrs(&ast.attrs)?;
let inner_ty = &field.ty;
let name = attrs.name.unwrap_or_else(|| ident.to_string());
let crate_name = if is_internal {
quote!(crate)
} else {
quote!(juniper)
};
let description = match attrs.description {
Some(val) => quote!( .description( #val ) ),
None => quote!(),
};
let _async = quote!(
impl <__S> #crate_name::GraphQLTypeAsync<__S> for #ident
where
__S: #crate_name::ScalarValue + Send + Sync,
Self: #crate_name::GraphQLType<__S> + Send + Sync,
Self::Context: Send + Sync,
Self::TypeInfo: Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
selection_set: Option<&'a [#crate_name::Selection<__S>]>,
executor: &'a #crate_name::Executor<Self::Context, __S>,
) -> #crate_name::BoxFuture<'a, #crate_name::ExecutionResult<__S>> {
use #crate_name::GraphQLType;
use #crate_name::futures::future;
let v = self.resolve(info, selection_set, executor);
Box::pin(future::ready(v))
}
}
);
let content = quote!(
#_async
impl<S> #crate_name::GraphQLType<S> for #ident
where
S: #crate_name::ScalarValue,
{
type Context = ();
type TypeInfo = ();
fn name(_: &Self::TypeInfo) -> Option<&str> {
Some(#name)
}
fn meta<'r>(
info: &Self::TypeInfo,
registry: &mut #crate_name::Registry<'r, S>,
) -> #crate_name::meta::MetaType<'r, S>
where
S: 'r,
{
registry.build_scalar_type::<Self>(info)
#description
.into_meta()
}
fn resolve(
&self,
info: &(),
selection: Option<&[#crate_name::Selection<S>]>,
executor: &#crate_name::Executor<Self::Context, S>,
) -> #crate_name::ExecutionResult<S> {
#crate_name::GraphQLType::resolve(&self.0, info, selection, executor)
}
}
impl<S> #crate_name::ToInputValue<S> for #ident
where
S: #crate_name::ScalarValue,
{
fn to_input_value(&self) -> #crate_name::InputValue<S> {
#crate_name::ToInputValue::to_input_value(&self.0)
}
}
impl<S> #crate_name::FromInputValue<S> for #ident
where
S: #crate_name::ScalarValue,
{
fn from_input_value(v: &#crate_name::InputValue<S>) -> Option<#ident> {
let inner: #inner_ty = #crate_name::FromInputValue::from_input_value(v)?;
Some(#ident(inner))
}
}
impl<S> #crate_name::ParseScalarValue<S> for #ident
where
S: #crate_name::ScalarValue,
{
fn from_str<'a>(
value: #crate_name::parser::ScalarToken<'a>,
) -> #crate_name::ParseScalarResult<'a, S> {
<#inner_ty as #crate_name::ParseScalarValue<S>>::from_str(value)
}
}
);
Ok(content)
}
fn impl_scalar_enum(
ident: &syn::Ident,
data: &syn::DataEnum,
is_internal: bool,
error: GraphQLScope,
) -> syn::Result<TokenStream> {
let froms = data
.variants
.iter()
.map(|v| derive_from_variant(v, ident, &error))
.collect::<Result<Vec<_>, _>>()?;
let serialize = derive_serialize(data.variants.iter(), ident, is_internal);
let display = derive_display(data.variants.iter(), ident);
Ok(quote! {
#(#froms)*
#serialize
#display
})
}
fn derive_display<'a, I>(variants: I, ident: &Ident) -> TokenStream
where
I: Iterator<Item = &'a Variant>,
{
let arms = variants.map(|v| {
let variant = &v.ident;
quote!(#ident::#variant(ref v) => write!(f, "{}", v),)
});
quote! {
impl std::fmt::Display for #ident {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
#(#arms)*
}
}
}
}
}
fn derive_serialize<'a, I>(variants: I, ident: &Ident, is_internal: bool) -> TokenStream
where
I: Iterator<Item = &'a Variant>,
{
let arms = variants.map(|v| {
let variant = &v.ident;
quote!(#ident::#variant(ref v) => v.serialize(serializer),)
});
let serde_path = if is_internal {
quote!(crate::serde)
} else {
quote!(juniper::serde)
};
quote! {
impl #serde_path::Serialize for #ident {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where S: #serde_path::Serializer
{
match *self {
#(#arms)*
}
}
}
}
}
fn derive_from_variant(
variant: &Variant,
ident: &Ident,
error: &GraphQLScope,
) -> syn::Result<TokenStream> {
let ty = match variant.fields {
Fields::Unnamed(ref u) if u.unnamed.len() == 1 => &u.unnamed.first().unwrap().ty,
_ => {
return Err(error.custom_error(
variant.fields.span(),
"requires exact one field, e.g., Test(i32)",
))
}
};
let variant = &variant.ident;
Ok(quote! {
impl std::convert::From<#ty> for #ident {
fn from(t: #ty) -> Self {
#ident::#variant(t)
}
}
impl<'a> std::convert::From<&'a #ident> for std::option::Option<&'a #ty> {
fn from(t: &'a #ident) -> Self {
match *t {
#ident::#variant(ref t) => std::option::Option::Some(t),
_ => std::option::Option::None
}
}
}
impl std::convert::From<#ident> for std::option::Option<#ty> {
fn from(t: #ident) -> Self {
match t {
#ident::#variant(t) => std::option::Option::Some(t),
_ => std::option::Option::None
}
}
}
})
}