From 8d0d93576023adb691781264eb1ec57efbd82706 Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Tue, 14 Apr 2020 14:31:35 +0200 Subject: [PATCH 01/14] Updated implementation of derive enum - allows context specification - allows scalar specification - shares code with derive object --- juniper_codegen/src/derive_enum.rs | 334 ++++++----------------------- juniper_codegen/src/lib.rs | 4 +- juniper_codegen/src/util/mod.rs | 196 +++++++++++++++++ 3 files changed, 264 insertions(+), 270 deletions(-) diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs index e404e79e4..205f68540 100644 --- a/juniper_codegen/src/derive_enum.rs +++ b/juniper_codegen/src/derive_enum.rs @@ -1,288 +1,86 @@ use proc_macro2::TokenStream; +use crate::util; use quote::quote; -use syn::{self, Data, DeriveInput, Fields, Variant}; - -use crate::util::*; - -#[derive(Default, Debug)] -struct EnumAttrs { - name: Option, - description: Option, -} - -impl EnumAttrs { - fn from_input(input: &DeriveInput) -> EnumAttrs { - let mut res = EnumAttrs { - name: None, - description: None, - }; - - // Check doc comments for description. - res.description = get_doc_comment(&input.attrs); - - // Check attributes for name and description. - if let Some(items) = get_graphql_attr(&input.attrs) { - for item in items { - if let Some(AttributeValue::String(val)) = - keyed_item_value(&item, "name", AttributeValidation::String) - { - if is_valid_name(&*val) { - res.name = Some(val); - continue; - } else { - panic!( - "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", - &*val - ); - } - } - if let Some(AttributeValue::String(val)) = - keyed_item_value(&item, "description", AttributeValidation::String) - { - res.description = Some(val); - continue; - } - panic!(format!( - "Unknown enum attribute for #[derive(GraphQLEnum)]: {:?}", - item - )); - } - } - res - } -} - -#[derive(Default)] -struct EnumVariantAttrs { - name: Option, - description: Option, - deprecation: Option, -} - -impl EnumVariantAttrs { - fn from_input(variant: &Variant) -> EnumVariantAttrs { - let mut res = EnumVariantAttrs::default(); - - // Check doc comments for description. - res.description = get_doc_comment(&variant.attrs); - - // Check builtin deprecated attribute for deprecation. - res.deprecation = get_deprecated(&variant.attrs); - - // Check attributes for name and description. - if let Some(items) = get_graphql_attr(&variant.attrs) { - for item in items { - if let Some(AttributeValue::String(val)) = - keyed_item_value(&item, "name", AttributeValidation::String) - { - if is_valid_name(&*val) { - res.name = Some(val); - continue; - } else { - panic!( - "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", - &*val - ); - } - } - if let Some(AttributeValue::String(val)) = - keyed_item_value(&item, "description", AttributeValidation::String) - { - res.description = Some(val); - continue; - } - if let Some(AttributeValue::String(val)) = - keyed_item_value(&item, "deprecation", AttributeValidation::String) - { - res.deprecation = Some(DeprecationAttr { reason: Some(val) }); - continue; - } - match keyed_item_value(&item, "deprecated", AttributeValidation::String) { - Some(AttributeValue::String(val)) => { - res.deprecation = Some(DeprecationAttr { reason: Some(val) }); - continue; - } - Some(AttributeValue::Bare) => { - res.deprecation = Some(DeprecationAttr { reason: None }); - continue; - } - None => {} - } - panic!(format!( - "Unknown variant attribute for #[derive(GraphQLEnum)]: {:?}", - item - )); - } - } - res - } -} - -pub fn impl_enum(ast: &syn::DeriveInput, is_internal: bool) -> TokenStream { - let juniper_path = if is_internal { - quote!(crate) - } else { - quote!(juniper) - }; +use syn::{self, Data, Fields}; +pub fn impl_enum(ast: syn::DeriveInput, is_internal: bool) -> TokenStream { let variants = match ast.data { - Data::Enum(ref enum_data) => enum_data.variants.iter().collect::>(), + Data::Enum(enum_data) => enum_data.variants, _ => { panic!("#[derive(GraphlQLEnum)] may only be applied to enums, not to structs"); } }; // Parse attributes. - let ident = &ast.ident; - let attrs = EnumAttrs::from_input(ast); - let name = attrs.name.unwrap_or_else(|| ast.ident.to_string()); - - let meta_description = match attrs.description { - Some(descr) => quote! { let meta = meta.description(#descr); }, - None => quote! { let meta = meta; }, + let attrs = match util::ObjectAttributes::from_attrs(&ast.attrs) { + Ok(a) => a, + Err(e) => { + panic!("Invalid #[graphql(...)] attribute: {}", e); + } }; - - let mut values = TokenStream::new(); - let mut resolves = TokenStream::new(); - let mut from_inputs = TokenStream::new(); - let mut to_inputs = TokenStream::new(); - - for variant in variants { - match variant.fields { - Fields::Unit => {} - _ => { - panic!(format!( - "Invalid enum variant {}.\nGraphQL enums may only contain unit variants.", - variant.ident - )); - } - }; - - let var_attrs = EnumVariantAttrs::from_input(variant); - let var_ident = &variant.ident; - - // Build value. - let name = var_attrs - .name - .unwrap_or_else(|| crate::util::to_upper_snake_case(&variant.ident.to_string())); - let descr = match var_attrs.description { - Some(s) => quote! { Some(#s.to_string()) }, - None => quote! { None }, - }; - let depr = match var_attrs.deprecation { - Some(DeprecationAttr { reason: Some(s) }) => quote! { - #juniper_path::meta::DeprecationStatus::Deprecated(Some(#s.to_string())) - }, - Some(DeprecationAttr { reason: None }) => quote! { - #juniper_path::meta::DeprecationStatus::Deprecated(None) - }, - None => quote! { - #juniper_path::meta::DeprecationStatus::Current - }, - }; - values.extend(quote! { - #juniper_path::meta::EnumValue{ - name: #name.to_string(), - description: #descr, - deprecation_status: #depr, - }, - }); - - // Build resolve match clause. - resolves.extend(quote! { - &#ident::#var_ident => #juniper_path::Value::scalar(String::from(#name)), - }); - - // Build from_input clause. - from_inputs.extend(quote! { - Some(#name) => Some(#ident::#var_ident), - }); - - // Build to_input clause. - to_inputs.extend(quote! { - &#ident::#var_ident => - #juniper_path::InputValue::scalar(#name.to_string()), - }); + if !attrs.interfaces.is_empty() { + panic!("Invalid #[graphql(...)] attribute 'interfaces': #[derive(GraphQLEnum) does not support 'interfaces'"); } - let _async = quote!( - impl<__S> #juniper_path::GraphQLTypeAsync<__S> for #ident - where - __S: #juniper_path::ScalarValue + Send + Sync, - { - fn resolve_async<'a>( - &'a self, - info: &'a Self::TypeInfo, - selection_set: Option<&'a [#juniper_path::Selection<__S>]>, - executor: &'a #juniper_path::Executor, - ) -> #juniper_path::BoxFuture<'a, #juniper_path::ExecutionResult<__S>> { - use #juniper_path::GraphQLType; - use futures::future; - let v = self.resolve(info, selection_set, executor); - future::FutureExt::boxed(future::ready(v)) - } - } - ); - - let body = quote! { - impl<__S> #juniper_path::GraphQLType<__S> for #ident - where __S: - #juniper_path::ScalarValue, - { - type Context = (); - type TypeInfo = (); - - fn name(_: &()) -> Option<&'static str> { - Some(#name) - } - - fn meta<'r>(_: &(), registry: &mut #juniper_path::Registry<'r, __S>) - -> #juniper_path::meta::MetaType<'r, __S> - where __S: 'r, - { - let meta = registry.build_enum_type::<#ident>(&(), &[ - #values - ]); - #meta_description - meta.into_meta() - } - - fn resolve( - &self, - _: &(), - _: Option<&[#juniper_path::Selection<__S>]>, - _: &#juniper_path::Executor - ) -> #juniper_path::ExecutionResult<__S> { - let v = match self { - #resolves + // Parse attributes. + let ident = &ast.ident; + let name = attrs.name.unwrap_or_else(|| ident.to_string()); + + let fields = variants + .into_iter() + .filter_map(|field| { + let field_attrs = match util::FieldAttributes::from_attrs( + field.attrs, + util::FieldAttributeParseMode::Object, + ) { + Ok(attrs) => attrs, + Err(e) => panic!("Invalid #[graphql] attribute for field: \n{}", e), + }; + + if field_attrs.skip { + panic!("#[derive(GraphQLEnum)] does not support #[graphql(skip)] on fields"); + } else { + let field_name = field.ident; + let name = field_attrs + .name + .clone() + .unwrap_or_else(|| util::to_upper_snake_case(&field_name.to_string())); + let resolver_code = quote!( #ident::#field_name ); + + let _type = match field.fields { + Fields::Unit => syn::parse_str(&field_name.to_string()).unwrap(), + _ => panic!("#[derive(GraphQLEnum)] all fields of the enum must be unnamed"), }; - Ok(v) - } - } - - impl<__S: #juniper_path::ScalarValue> #juniper_path::FromInputValue<__S> for #ident { - fn from_input_value(v: &#juniper_path::InputValue<__S>) -> Option<#ident> - { - match v.as_enum_value().or_else(|| { - v.as_string_value() - }) { - #from_inputs - _ => None, - } - } - } - impl<__S: #juniper_path::ScalarValue> #juniper_path::ToInputValue<__S> for #ident { - fn to_input_value(&self) -> #juniper_path::InputValue<__S> { - match self { - #to_inputs - } + Some(util::GraphQLTypeDefinitionField { + name, + _type, + args: Vec::new(), + description: field_attrs.description, + deprecation: field_attrs.deprecation, + resolver_code, + is_type_inferred: true, + is_async: false, + }) } - } - - #_async + }) + .collect(); + + let definition = util::GraphQLTypeDefiniton { + name, + _type: syn::parse_str(&ast.ident.to_string()).unwrap(), + context: attrs.context, + scalar: attrs.scalar, + description: attrs.description, + fields, + generics: ast.generics, + interfaces: None, + include_type_generics: true, + generic_scalar: true, + no_async: attrs.no_async, }; - body + let juniper_crate_name = if is_internal { "crate" } else { "juniper" }; + definition.into_enum_tokens(juniper_crate_name) } diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 923519a45..9a21c4578 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -24,7 +24,7 @@ use proc_macro::TokenStream; #[proc_macro_derive(GraphQLEnum, attributes(graphql))] pub fn derive_enum(input: TokenStream) -> TokenStream { let ast = syn::parse::(input).unwrap(); - let gen = derive_enum::impl_enum(&ast, false); + let gen = derive_enum::impl_enum(ast, false); gen.into() } @@ -32,7 +32,7 @@ pub fn derive_enum(input: TokenStream) -> TokenStream { #[doc(hidden)] pub fn derive_enum_internal(input: TokenStream) -> TokenStream { let ast = syn::parse::(input).unwrap(); - let gen = derive_enum::impl_enum(&ast, true); + let gen = derive_enum::impl_enum(ast, true); gen.into() } diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index b55a3ceae..168263388 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -1460,6 +1460,202 @@ impl GraphQLTypeDefiniton { type_impl } + + + pub fn into_enum_tokens(self, juniper_crate_name: &str) -> proc_macro2::TokenStream { + let juniper_crate_name = syn::parse_str::(juniper_crate_name).unwrap(); + + let name = &self.name; + let ty = &self._type; + let context = self + .context + .as_ref() + .map(|ctx| quote!( #ctx )) + .unwrap_or_else(|| quote!(())); + + let scalar = self + .scalar + .as_ref() + .map(|s| quote!( #s )) + .unwrap_or_else(|| { + if self.generic_scalar { + // If generic_scalar is true, we always insert a generic scalar. + // See more comments below. + quote!(__S) + } else { + quote!(#juniper_crate_name::DefaultScalarValue) + } + }); + + let description = self + .description + .as_ref() + .map(|description| quote!( .description(#description) )); + + let values = self.fields.iter().map(|variant| { + let variant_name = &variant.name; + + let descr = variant + .description + .as_ref() + .map(|description| quote!(Some(#description.to_string()))) + .unwrap_or_else(|| quote!(None)); + + let depr = variant + .deprecation + .as_ref() + .map(|deprecation| match deprecation.reason.as_ref() { + Some(reason) => quote!( #juniper_crate_name::meta::DeprecationStatus::Deprecated(Some(#reason.to_string())) ), + None => quote!( #juniper_crate_name::meta::DeprecationStatus::Deprecated(None) ), + }) + .unwrap_or_else(|| quote!(#juniper_crate_name::meta::DeprecationStatus::Current)); + + quote!( + #juniper_crate_name::meta::EnumValue { + name: #variant_name.to_string(), + description: #descr, + deprecation_status: #depr, + }, + ) + }); + + let resolves = self.fields.iter().map(|variant| { + let variant_name = &variant.name; + let resolver_code = &variant.resolver_code; + + quote!( + &#resolver_code => #juniper_crate_name::Value::scalar(String::from(#variant_name)), + ) + }); + + let from_inputs = self.fields.iter().map(|variant| { + let variant_name = &variant.name; + let resolver_code = &variant.resolver_code; + + quote!( + Some(#variant_name) => Some(#resolver_code), + ) + }); + + let to_inputs = self.fields.iter().map(|variant| { + let variant_name = &variant.name; + let resolver_code = &variant.resolver_code; + + quote!( + &#resolver_code => + #juniper_crate_name::InputValue::scalar(#variant_name.to_string()), + ) + }); + + let mut generics = self.generics.clone(); + + if self.scalar.is_none() && self.generic_scalar { + // No custom scalar specified, but always generic specified. + // Therefore we inject the generic scalar. + + generics.params.push(parse_quote!(__S)); + + let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); + // Insert ScalarValue constraint. + where_clause + .predicates + .push(parse_quote!(__S: #juniper_crate_name::ScalarValue)); + } + + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + let mut where_async = where_clause.cloned().unwrap_or_else(|| parse_quote!(where)); + where_async + .predicates + .push(parse_quote!( #scalar: Send + Sync )); + where_async.predicates.push(parse_quote!(Self: Send + Sync)); + + let _async = quote!( + impl#impl_generics #juniper_crate_name::GraphQLTypeAsync<#scalar> for #ty + #where_async + { + fn resolve_async<'a>( + &'a self, + info: &'a Self::TypeInfo, + selection_set: Option<&'a [#juniper_crate_name::Selection<#scalar>]>, + executor: &'a #juniper_crate_name::Executor, + ) -> #juniper_crate_name::BoxFuture<'a, #juniper_crate_name::ExecutionResult<#scalar>> { + use #juniper_crate_name::GraphQLType; + use futures::future; + let v = self.resolve(info, selection_set, executor); + future::FutureExt::boxed(future::ready(v)) + } + } + ); + + let mut body = quote!( + impl#impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty + #where_clause + { + type Context = #context; + type TypeInfo = (); + + fn name(_: &()) -> Option<&'static str> { + Some(#name) + } + + fn meta<'r>( + _: &(), + registry: &mut #juniper_crate_name::Registry<'r, #scalar> + ) -> #juniper_crate_name::meta::MetaType<'r, #scalar> + where #scalar: 'r, + { + registry.build_enum_type::<#ty>(&(), &[ + #( #values )* + ]) + #description + .into_meta() + } + + fn resolve( + &self, + _: &(), + _: Option<&[#juniper_crate_name::Selection<#scalar>]>, + _: &#juniper_crate_name::Executor + ) -> #juniper_crate_name::ExecutionResult<#scalar> { + let v = match self { + #( #resolves )* + }; + Ok(v) + } + } + + impl#impl_generics #juniper_crate_name::FromInputValue<#scalar> for #ty + #where_clause + { + fn from_input_value(v: &#juniper_crate_name::InputValue<#scalar>) -> Option<#ty> + { + match v.as_enum_value().or_else(|| { + v.as_string_value() + }) { + #( #from_inputs )* + _ => None, + } + } + } + + impl#impl_generics #juniper_crate_name::ToInputValue<#scalar> for #ty + #where_clause + { + fn to_input_value(&self) -> #juniper_crate_name::InputValue<#scalar> { + match self { + #( #to_inputs )* + } + } + } + ); + + if !self.no_async { + body.extend(_async) + } + + body + } } #[cfg(test)] From 900eaf21643897120dce0d66cb9a70261b3bbdc7 Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Tue, 14 Apr 2020 14:55:41 +0200 Subject: [PATCH 02/14] Added this feature to CHANGELOG --- juniper/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index c4da0799d..7866a74ae 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -27,6 +27,8 @@ See [#569](https://github.com/graphql-rust/juniper/pull/569). See [#618](https://github.com/graphql-rust/juniper/pull/618). +- Derive macro `GraphQLEnum` supports custom context and scalar (see [#621](https://github.com/graphql-rust/juniper/pull/621)) + ## Breaking Changes - `juniper::graphiql` has moved to `juniper::http::graphiql` From f5116f539533e032bf125ab1e2bdc08277a39182 Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Thu, 16 Apr 2020 15:05:33 +0200 Subject: [PATCH 03/14] Added matrix with supported macro attributes for enums --- docs/book/content/types/enums.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/book/content/types/enums.md b/docs/book/content/types/enums.md index 14122b219..736b70d4c 100644 --- a/docs/book/content/types/enums.md +++ b/docs/book/content/types/enums.md @@ -54,3 +54,17 @@ enum StarWarsEpisode { # fn main() {} ``` + +## Supported Macro Attributes (Derive) + +| Name of Attribute | Container Support | Field Support | +|-------------------|:-----------------:|:----------------:| +| context | ✔ | ? | +| deprecated | ✔ | ✔ | +| description | ✔ | ✔ | +| interfaces | ? | ✘ | +| name | ✔ | ✔ | +| noasync | ✔ | ? | +| scalar | ✔ | ? | +| skip | ? | ✔ | +| ✔: supported | ✘: not supported | ?: not available | From ad3ca4dd2f1c43de7e35a186a9cf5d3b429857a9 Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Thu, 16 Apr 2020 15:06:00 +0200 Subject: [PATCH 04/14] Added case which checks for custom context --- .../juniper_tests/src/codegen/derive_enum.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/integration_tests/juniper_tests/src/codegen/derive_enum.rs index 5dc1848ef..e55883e3c 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_enum.rs @@ -4,6 +4,10 @@ use fnv::FnvHashMap; #[cfg(test)] use juniper::{self, DefaultScalarValue, FromInputValue, GraphQLType, InputValue, ToInputValue}; +pub struct CustomContext {} + +impl juniper::Context for CustomContext {} + #[derive(juniper::GraphQLEnum, Debug, PartialEq)] #[graphql(name = "Some", description = "enum descr")] enum SomeEnum { @@ -98,3 +102,16 @@ fn test_doc_comment_override() { let meta = OverrideDocEnum::meta(&(), &mut registry); assert_eq!(meta.description(), Some(&"enum override".to_string())); } + +fn test_context(_t: T) +where + T: GraphQLType, +{ + // empty +} + +#[test] +fn test_doc_custom_context() { + test_context(ContextEnum::A); + // test_context(OverrideDocEnum::Foo); does not work +} From 7b35f43eacedfac151ff47b68ad77ffea279b63e Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Thu, 16 Apr 2020 18:36:01 +0200 Subject: [PATCH 05/14] GraphQLUnion now can use a different context per variant --- .../juniper_tests/src/codegen/derive_enum.rs | 20 ++++++++++++ juniper_codegen/src/util/mod.rs | 31 +++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/integration_tests/juniper_tests/src/codegen/derive_enum.rs index e55883e3c..7702146cc 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_enum.rs @@ -43,6 +43,26 @@ enum OverrideDocEnum { Foo, } +#[derive(juniper::GraphQLEnum)] +#[graphql(context = CustomContext)] +enum ContextEnum { + A, +} + +/// This is not used as the description. +#[derive(juniper::GraphQLObject)] +#[graphql(context = CustomContext)] +struct MyObject { + custom: String, +} + +#[derive(juniper::GraphQLUnion)] +#[graphql(context = CustomContext)] +enum Item { + Object(MyObject), + Enum(DocEnum), +} + #[test] fn test_derived_enum() { // Ensure that rename works. diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 168263388..1dbc8cf43 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -1329,7 +1329,21 @@ impl GraphQLTypeDefiniton { quote! { if type_name == (<#var_ty as #juniper_crate_name::GraphQLType<#scalar>>::name(&())).unwrap() { - return executor.resolve(&(), &{ #expr }); + let inner_res = #juniper_crate_name::IntoResolvable::into( + { #expr }, + executor.context() + ); + + let res = match inner_res { + Ok(Some((ctx, r))) => { + let subexec = executor.replaced_context(ctx); + subexec.resolve_with_ctx(&(), &r) + }, + Ok(None) => Ok(#juniper_crate_name::Value::null()), + Err(e) => Err(e), + }; + + return res; } } }); @@ -1339,8 +1353,20 @@ impl GraphQLTypeDefiniton { quote! { if type_name == (<#var_ty as #juniper_crate_name::GraphQLType<#scalar>>::name(&())).unwrap() { + let inner_res = #juniper_crate_name::IntoResolvable::into( + { #expr }, + executor.context() + ); + let f = async move { - executor.resolve_async(&(), &{ #expr }).await + match inner_res { + Ok(Some((ctx, r))) => { + let subexec = executor.replaced_context(ctx); + subexec.resolve_with_ctx_async(&(), &r).await + }, + Ok(None) => Ok(#juniper_crate_name::Value::null()), + Err(e) => Err(e), + } }; use futures::future; return future::FutureExt::boxed(f); @@ -1461,7 +1487,6 @@ impl GraphQLTypeDefiniton { type_impl } - pub fn into_enum_tokens(self, juniper_crate_name: &str) -> proc_macro2::TokenStream { let juniper_crate_name = syn::parse_str::(juniper_crate_name).unwrap(); From a098593593284ba6def7b3938061581ecf93434e Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Thu, 16 Apr 2020 18:58:30 +0200 Subject: [PATCH 06/14] Moved context switch test for union into right folder --- .../juniper_tests/src/codegen/derive_enum.rs | 14 -------------- .../juniper_tests/src/codegen/derive_union.rs | 7 +++++++ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/integration_tests/juniper_tests/src/codegen/derive_enum.rs index 7702146cc..67174ea72 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_enum.rs @@ -49,20 +49,6 @@ enum ContextEnum { A, } -/// This is not used as the description. -#[derive(juniper::GraphQLObject)] -#[graphql(context = CustomContext)] -struct MyObject { - custom: String, -} - -#[derive(juniper::GraphQLUnion)] -#[graphql(context = CustomContext)] -enum Item { - Object(MyObject), - Enum(DocEnum), -} - #[test] fn test_derived_enum() { // Ensure that rename works. diff --git a/integration_tests/juniper_tests/src/codegen/derive_union.rs b/integration_tests/juniper_tests/src/codegen/derive_union.rs index 153857bf2..fcec2e74c 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_union.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_union.rs @@ -91,6 +91,13 @@ impl DroidCompat { } } +#[derive(juniper::GraphQLUnion)] +#[graphql(Context = CustomContext)] +pub enum DifferentContext { + A(DroidContext), + B(Droid), +} + // NOTICE: this can not compile due to generic implementation of GraphQLType<__S> // #[derive(juniper::GraphQLUnion)] // pub enum CharacterCompatFail { From d4db840551c1b88b196d36d5079f8e2c9f5b1db7 Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Fri, 17 Apr 2020 14:09:07 +0200 Subject: [PATCH 07/14] Sync resolve expression has the same form as the other impls --- juniper_codegen/src/util/mod.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 1dbc8cf43..eac3e6559 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -1329,21 +1329,16 @@ impl GraphQLTypeDefiniton { quote! { if type_name == (<#var_ty as #juniper_crate_name::GraphQLType<#scalar>>::name(&())).unwrap() { - let inner_res = #juniper_crate_name::IntoResolvable::into( + return #juniper_crate_name::IntoResolvable::into( { #expr }, executor.context() - ); - - let res = match inner_res { - Ok(Some((ctx, r))) => { - let subexec = executor.replaced_context(ctx); - subexec.resolve_with_ctx(&(), &r) - }, - Ok(None) => Ok(#juniper_crate_name::Value::null()), - Err(e) => Err(e), - }; - - return res; + ) + .and_then(|res| { + match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r), + None => Ok(#juniper_crate_name::Value::null()), + } + }); } } }); From dc038745a22a93a439bc4d1b921348af1bd2abcb Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Fri, 17 Apr 2020 16:12:51 +0200 Subject: [PATCH 08/14] Disabled custom scalar on GraphQLEnum --- juniper_codegen/src/derive_enum.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs index 205f68540..e8ababc4d 100644 --- a/juniper_codegen/src/derive_enum.rs +++ b/juniper_codegen/src/derive_enum.rs @@ -22,6 +22,9 @@ pub fn impl_enum(ast: syn::DeriveInput, is_internal: bool) -> TokenStream { if !attrs.interfaces.is_empty() { panic!("Invalid #[graphql(...)] attribute 'interfaces': #[derive(GraphQLEnum) does not support 'interfaces'"); } + if attrs.scalar.is_some() { + panic!("Invalid #[graphql(...)] attribute 'scalar': #[derive(GraphQLEnum) does not support explicit scalars"); + } // Parse attributes. let ident = &ast.ident; @@ -71,7 +74,7 @@ pub fn impl_enum(ast: syn::DeriveInput, is_internal: bool) -> TokenStream { name, _type: syn::parse_str(&ast.ident.to_string()).unwrap(), context: attrs.context, - scalar: attrs.scalar, + scalar: None, description: attrs.description, fields, generics: ast.generics, From 972777a8fb2faed0d509208a3d07b75fbebd1ad5 Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Fri, 17 Apr 2020 16:39:39 +0200 Subject: [PATCH 09/14] Fixed CHANGELOG --- juniper/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 7866a74ae..427c71a63 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -27,7 +27,7 @@ See [#569](https://github.com/graphql-rust/juniper/pull/569). See [#618](https://github.com/graphql-rust/juniper/pull/618). -- Derive macro `GraphQLEnum` supports custom context and scalar (see [#621](https://github.com/graphql-rust/juniper/pull/621)) +- Derive macro `GraphQLEnum` supports custom context (see [#621](https://github.com/graphql-rust/juniper/pull/621)) ## Breaking Changes From 44317f08e6a4192203d60d18aeaf01c730b14a8b Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Fri, 17 Apr 2020 17:22:20 +0200 Subject: [PATCH 10/14] Fixed support matrix of GraphQLEnum in the book - scalar not supported! - skip not supported! --- docs/book/content/types/enums.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/content/types/enums.md b/docs/book/content/types/enums.md index 736b70d4c..5e23341b3 100644 --- a/docs/book/content/types/enums.md +++ b/docs/book/content/types/enums.md @@ -65,6 +65,6 @@ enum StarWarsEpisode { | interfaces | ? | ✘ | | name | ✔ | ✔ | | noasync | ✔ | ? | -| scalar | ✔ | ? | -| skip | ? | ✔ | +| scalar | ✘ | ? | +| skip | ? | ✘ | | ✔: supported | ✘: not supported | ?: not available | From fae923fd828312791312dfee92b685afd7fbd08b Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Fri, 17 Apr 2020 17:24:45 +0200 Subject: [PATCH 11/14] Added test case for "noasync" derive attribute --- integration_tests/juniper_tests/src/codegen/derive_enum.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/integration_tests/juniper_tests/src/codegen/derive_enum.rs index 67174ea72..88a54a08b 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_enum.rs @@ -44,7 +44,7 @@ enum OverrideDocEnum { } #[derive(juniper::GraphQLEnum)] -#[graphql(context = CustomContext)] +#[graphql(context = CustomContext, noasync)] enum ContextEnum { A, } From 28157a44dbf219371308d8015a3bd0b223cef589 Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Fri, 17 Apr 2020 17:58:33 +0200 Subject: [PATCH 12/14] Disallowed generics and lifetimes on GraphQLEnum --- juniper_codegen/src/derive_enum.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs index e8ababc4d..dc4b320b7 100644 --- a/juniper_codegen/src/derive_enum.rs +++ b/juniper_codegen/src/derive_enum.rs @@ -5,6 +5,10 @@ use quote::quote; use syn::{self, Data, Fields}; pub fn impl_enum(ast: syn::DeriveInput, is_internal: bool) -> TokenStream { + if !ast.generics.params.is_empty() { + panic!("#[derive(GraphQLEnum) does not support generics or lifetimes"); + } + let variants = match ast.data { Data::Enum(enum_data) => enum_data.variants, _ => { @@ -77,7 +81,8 @@ pub fn impl_enum(ast: syn::DeriveInput, is_internal: bool) -> TokenStream { scalar: None, description: attrs.description, fields, - generics: ast.generics, + // NOTICE: only unit variants allow -> no generics possible + generics: syn::Generics::default(), interfaces: None, include_type_generics: true, generic_scalar: true, From bd10d05e24a95361b53786dd4d4cdd214ac10afb Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Fri, 17 Apr 2020 18:38:19 +0200 Subject: [PATCH 13/14] Added error message for duplicate naming --- juniper_codegen/src/derive_enum.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs index dc4b320b7..d793e2980 100644 --- a/juniper_codegen/src/derive_enum.rs +++ b/juniper_codegen/src/derive_enum.rs @@ -34,6 +34,8 @@ pub fn impl_enum(ast: syn::DeriveInput, is_internal: bool) -> TokenStream { let ident = &ast.ident; let name = attrs.name.unwrap_or_else(|| ident.to_string()); + let mut mapping = std::collections::HashMap::new(); + let fields = variants .into_iter() .filter_map(|field| { @@ -53,6 +55,15 @@ pub fn impl_enum(ast: syn::DeriveInput, is_internal: bool) -> TokenStream { .name .clone() .unwrap_or_else(|| util::to_upper_snake_case(&field_name.to_string())); + + match mapping.get(&name) { + Some(other_field_name) => + panic!(format!("#[derive(GraphQLEnum)] all variants needs to be unique. Another field name `{}` has the same identifier `{}`, thus `{}` can not be named `{}`. One of the fields is manually renamed!", other_field_name, name, field_name, name)), + None => { + mapping.insert(name.clone(), field_name.clone()); + } + } + let resolver_code = quote!( #ident::#field_name ); let _type = match field.fields { From 2abfd1eecf4fe501f22d5ab0f74eb248d2637d77 Mon Sep 17 00:00:00 2001 From: Jonas Meurer Date: Fri, 17 Apr 2020 19:06:06 +0200 Subject: [PATCH 14/14] Added error message for empty variant --- juniper_codegen/src/derive_enum.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs index d793e2980..d98e60c03 100644 --- a/juniper_codegen/src/derive_enum.rs +++ b/juniper_codegen/src/derive_enum.rs @@ -83,7 +83,11 @@ pub fn impl_enum(ast: syn::DeriveInput, is_internal: bool) -> TokenStream { }) } }) - .collect(); + .collect::>(); + + if fields.len() == 0 { + panic!("#[derive(GraphQLEnum)] requires at least one variants"); + } let definition = util::GraphQLTypeDefiniton { name,