From f776d696dbbbb34fdd0f0a51553b7585cd384578 Mon Sep 17 00:00:00 2001 From: Christopher Brown Date: Sun, 14 Jun 2020 03:54:20 -0400 Subject: [PATCH 1/3] fix unions with custom scalar values --- .../fail/union/enum_same_type_ugly.stderr | 2 +- .../fail/union/struct_same_type_ugly.stderr | 2 +- .../fail/union/trait_same_type_ugly.stderr | 2 +- .../juniper_tests/src/codegen/union_derive.rs | 163 ++++++++++++++++++ juniper/src/types/marker.rs | 2 +- juniper_codegen/src/graphql_union/mod.rs | 2 +- 6 files changed, 168 insertions(+), 5 deletions(-) diff --git a/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr index 236daf7f2..9afa8a9a0 100644 --- a/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr +++ b/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: +error[E0119]: conflicting implementations of trait `>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: --> $DIR/enum_same_type_ugly.rs:3:10 | 3 | #[derive(GraphQLUnion)] diff --git a/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr index e901067c5..5d8169522 100644 --- a/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr +++ b/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: +error[E0119]: conflicting implementations of trait `>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: --> $DIR/struct_same_type_ugly.rs:3:10 | 3 | #[derive(GraphQLUnion)] diff --git a/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr index 39684e76d..c56e0af0d 100644 --- a/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr +++ b/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::types::marker::GraphQLUnion>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: +error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::types::marker::GraphQLUnion>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: --> $DIR/trait_same_type_ugly.rs:3:1 | 3 | #[graphql_union] diff --git a/integration_tests/juniper_tests/src/codegen/union_derive.rs b/integration_tests/juniper_tests/src/codegen/union_derive.rs index 55d4c047a..da1928bae 100644 --- a/integration_tests/juniper_tests/src/codegen/union_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/union_derive.rs @@ -485,6 +485,169 @@ mod explicit_scalar { } } +mod custom_scalar { + use super::*; + use juniper::serde::de; + + #[derive(Debug, Clone, PartialEq, juniper::GraphQLScalarValue)] + enum MyScalarValue { + Int(i32), + Float(f64), + String(String), + Boolean(bool), + } + + impl ScalarValue for MyScalarValue { + type Visitor = MyScalarValueVisitor; + + fn as_int(&self) -> Option { + match *self { + MyScalarValue::Int(ref i) => Some(*i), + _ => None, + } + } + + fn as_string(&self) -> Option { + match *self { + MyScalarValue::String(ref s) => Some(s.clone()), + _ => None, + } + } + + fn as_str(&self) -> Option<&str> { + match *self { + MyScalarValue::String(ref s) => Some(s.as_str()), + _ => None, + } + } + + fn as_float(&self) -> Option { + match *self { + MyScalarValue::Int(ref i) => Some(*i as f64), + MyScalarValue::Float(ref f) => Some(*f), + _ => None, + } + } + + fn as_boolean(&self) -> Option { + match *self { + MyScalarValue::Boolean(ref b) => Some(*b), + _ => None, + } + } + } + + #[derive(Default, Debug)] + struct MyScalarValueVisitor; + + impl<'de> de::Visitor<'de> for MyScalarValueVisitor { + type Value = MyScalarValue; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid input value") + } + + fn visit_bool(self, value: bool) -> Result { + Ok(Self::Value::Boolean(value)) + } + + fn visit_i64(self, value: i64) -> Result { + if value >= i64::from(i32::min_value()) && value <= i64::from(i32::max_value()) { + Ok(Self::Value::Int(value as i32)) + } else { + Ok(Self::Value::Float(value as f64)) + } + } + + fn visit_u64(self, value: u64) -> Result { + if value <= i32::max_value() as u64 { + self.visit_i64(value as i64) + } else { + Ok(Self::Value::Float(value as f64)) + } + } + + fn visit_f64(self, value: f64) -> Result { + Ok(Self::Value::Float(value)) + } + + fn visit_str(self, value: &str) -> Result { + self.visit_string(value.into()) + } + + fn visit_string(self, value: String) -> Result { + Ok(Self::Value::String(value)) + } + } + + #[derive(GraphQLUnion)] + #[graphql(scalar = MyScalarValue)] + enum Character { + A(Human), + B(Droid), + } + + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn character(&self) -> Character { + match self { + Self::Human => Character::A(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Character::B(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + } + } + } + + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + #[tokio::test] + async fn resolves_human() { + let schema = schema::<_, MyScalarValue, _>(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + let schema = schema::<_, MyScalarValue, _>(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } +} + mod custom_context { use super::*; diff --git a/juniper/src/types/marker.rs b/juniper/src/types/marker.rs index 5e2fc909e..24d4b58f0 100644 --- a/juniper/src/types/marker.rs +++ b/juniper/src/types/marker.rs @@ -37,7 +37,7 @@ pub trait GraphQLObjectType: GraphQLType { /// [4]: https://spec.graphql.org/June2018/#sec-Objects /// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects /// [6]: https://spec.graphql.org/June2018/#sec-Interfaces -pub trait GraphQLUnion: GraphQLType { +pub trait GraphQLUnion: GraphQLType { /// An arbitrary function without meaning. /// /// May contain compile timed check logic which ensures that types are used correctly according diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 2d8132c28..5ccec84dd 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -618,7 +618,7 @@ impl ToTokens for UnionDefinition { let union_impl = quote! { #[automatically_derived] - impl#impl_generics #crate_path::marker::GraphQLUnion for #ty_full { + impl#impl_generics #crate_path::marker::GraphQLUnion<#default_scalar> for #ty_full { fn mark() { #all_variants_unique From 1f524ddf2f52dccf2af4b9fa59a6050fd07616fe Mon Sep 17 00:00:00 2001 From: tyranron Date: Sun, 14 Jun 2020 13:19:25 +0300 Subject: [PATCH 2/3] Make impl generic and trim redundant code --- juniper_codegen/src/graphql_union/mod.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 5ccec84dd..af06d112a 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -414,11 +414,6 @@ impl ToTokens for UnionDefinition { .as_ref() .map(|scl| quote! { #scl }) .unwrap_or_else(|| quote! { __S }); - let default_scalar = self - .scalar - .as_ref() - .map(|scl| quote! { #scl }) - .unwrap_or_else(|| quote! { #crate_path::DefaultScalarValue }); let description = self .description @@ -493,13 +488,10 @@ impl ToTokens for UnionDefinition { let (_, ty_generics, _) = self.generics.split_for_impl(); - let mut base_generics = self.generics.clone(); + let mut ext_generics = self.generics.clone(); if self.is_trait_object { - base_generics.params.push(parse_quote! { '__obj }); + ext_generics.params.push(parse_quote! { '__obj }); } - let (impl_generics, _, _) = base_generics.split_for_impl(); - - let mut ext_generics = base_generics.clone(); if self.scalar.is_none() { ext_generics.params.push(parse_quote! { #scalar }); ext_generics @@ -618,13 +610,13 @@ impl ToTokens for UnionDefinition { let union_impl = quote! { #[automatically_derived] - impl#impl_generics #crate_path::marker::GraphQLUnion<#default_scalar> for #ty_full { + impl#ext_impl_generics #crate_path::marker::GraphQLUnion<#scalar> for #ty_full + #where_clause + { fn mark() { #all_variants_unique - #( <#var_types as #crate_path::marker::GraphQLObjectType< - #default_scalar, - >>::mark(); )* + #( <#var_types as #crate_path::marker::GraphQLObjectType<#scalar>>::mark(); )* } } }; From 083850e11c62940d3e188ab16044a60c8493e4c8 Mon Sep 17 00:00:00 2001 From: tyranron Date: Sun, 14 Jun 2020 13:39:02 +0300 Subject: [PATCH 3/3] Polish tests --- .../fail/union/enum_non_object_variant.stderr | 9 -- .../fail/union/enum_same_type_ugly.stderr | 2 +- .../union/struct_non_object_variant.stderr | 9 -- .../fail/union/struct_same_type_ugly.stderr | 2 +- .../union/trait_non_object_variant.stderr | 9 -- .../fail/union/trait_same_type_ugly.stderr | 2 +- .../juniper_tests/src/codegen/union_attr.rs | 91 ++++++++++++++++++ .../juniper_tests/src/codegen/union_derive.rs | 94 +------------------ .../juniper_tests/src/custom_scalar.rs | 4 +- 9 files changed, 98 insertions(+), 124 deletions(-) diff --git a/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr b/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr index 1da87bdd8..add99e411 100644 --- a/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr +++ b/integration_tests/codegen_fail/fail/union/enum_non_object_variant.stderr @@ -1,12 +1,3 @@ -error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType` is not satisfied - --> $DIR/enum_non_object_variant.rs:9:10 - | -9 | #[derive(GraphQLUnion)] - | ^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType` is not implemented for `Test` - | - = note: required by `juniper::types::marker::GraphQLObjectType::mark` - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<__S>` is not satisfied --> $DIR/enum_non_object_variant.rs:9:10 | diff --git a/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr index 9afa8a9a0..16a967e31 100644 --- a/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr +++ b/integration_tests/codegen_fail/fail/union/enum_same_type_ugly.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: +error[E0119]: conflicting implementations of trait `>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: --> $DIR/enum_same_type_ugly.rs:3:10 | 3 | #[derive(GraphQLUnion)] diff --git a/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr b/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr index 53d7ad7e5..ca3363722 100644 --- a/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr +++ b/integration_tests/codegen_fail/fail/union/struct_non_object_variant.stderr @@ -1,12 +1,3 @@ -error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType` is not satisfied - --> $DIR/struct_non_object_variant.rs:9:10 - | -9 | #[derive(GraphQLUnion)] - | ^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType` is not implemented for `Test` - | - = note: required by `juniper::types::marker::GraphQLObjectType::mark` - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<__S>` is not satisfied --> $DIR/struct_non_object_variant.rs:9:10 | diff --git a/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr index 5d8169522..4babd863e 100644 --- a/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr +++ b/integration_tests/codegen_fail/fail/union/struct_same_type_ugly.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: +error[E0119]: conflicting implementations of trait `>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: --> $DIR/struct_same_type_ugly.rs:3:10 | 3 | #[derive(GraphQLUnion)] diff --git a/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr b/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr index 98e0193b3..a0940e829 100644 --- a/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr +++ b/integration_tests/codegen_fail/fail/union/trait_non_object_variant.stderr @@ -1,12 +1,3 @@ -error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType` is not satisfied - --> $DIR/trait_non_object_variant.rs:9:1 - | -9 | #[graphql_union] - | ^^^^^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType` is not implemented for `Test` - | - = note: required by `juniper::types::marker::GraphQLObjectType::mark` - = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<__S>` is not satisfied --> $DIR/trait_non_object_variant.rs:9:1 | diff --git a/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr b/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr index c56e0af0d..1b57de71b 100644 --- a/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr +++ b/integration_tests/codegen_fail/fail/union/trait_same_type_ugly.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::types::marker::GraphQLUnion>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: +error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::types::marker::GraphQLUnion<__S>>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`: --> $DIR/trait_same_type_ugly.rs:3:1 | 3 | #[graphql_union] diff --git a/integration_tests/juniper_tests/src/codegen/union_attr.rs b/integration_tests/juniper_tests/src/codegen/union_attr.rs index 7377dbea3..2581512cd 100644 --- a/integration_tests/juniper_tests/src/codegen/union_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/union_attr.rs @@ -556,6 +556,97 @@ mod explicit_scalar { } } +mod custom_scalar { + use crate::custom_scalar::MyScalarValue; + + use super::*; + + #[graphql_union(scalar = MyScalarValue)] + trait Character { + fn as_human(&self) -> Option<&Human> { + None + } + fn as_droid(&self) -> Option<&Droid> { + None + } + } + + impl Character for Human { + fn as_human(&self) -> Option<&Human> { + Some(&self) + } + } + + impl Character for Droid { + fn as_droid(&self) -> Option<&Droid> { + Some(&self) + } + } + + type DynCharacter<'a> = dyn Character + Send + Sync + 'a; + + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = MyScalarValue)] + impl QueryRoot { + fn character(&self) -> Box> { + let ch: Box> = match self { + Self::Human => Box::new(Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + }), + Self::Droid => Box::new(Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + }), + }; + ch + } + } + + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + #[tokio::test] + async fn resolves_human() { + let schema = schema::<_, MyScalarValue, _>(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn resolves_droid() { + let schema = schema::<_, MyScalarValue, _>(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &Variables::new(), &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } +} + mod inferred_custom_context { use super::*; diff --git a/integration_tests/juniper_tests/src/codegen/union_derive.rs b/integration_tests/juniper_tests/src/codegen/union_derive.rs index da1928bae..655ca9954 100644 --- a/integration_tests/juniper_tests/src/codegen/union_derive.rs +++ b/integration_tests/juniper_tests/src/codegen/union_derive.rs @@ -486,99 +486,9 @@ mod explicit_scalar { } mod custom_scalar { - use super::*; - use juniper::serde::de; - - #[derive(Debug, Clone, PartialEq, juniper::GraphQLScalarValue)] - enum MyScalarValue { - Int(i32), - Float(f64), - String(String), - Boolean(bool), - } - - impl ScalarValue for MyScalarValue { - type Visitor = MyScalarValueVisitor; - - fn as_int(&self) -> Option { - match *self { - MyScalarValue::Int(ref i) => Some(*i), - _ => None, - } - } - - fn as_string(&self) -> Option { - match *self { - MyScalarValue::String(ref s) => Some(s.clone()), - _ => None, - } - } - - fn as_str(&self) -> Option<&str> { - match *self { - MyScalarValue::String(ref s) => Some(s.as_str()), - _ => None, - } - } - - fn as_float(&self) -> Option { - match *self { - MyScalarValue::Int(ref i) => Some(*i as f64), - MyScalarValue::Float(ref f) => Some(*f), - _ => None, - } - } - - fn as_boolean(&self) -> Option { - match *self { - MyScalarValue::Boolean(ref b) => Some(*b), - _ => None, - } - } - } - - #[derive(Default, Debug)] - struct MyScalarValueVisitor; + use crate::custom_scalar::MyScalarValue; - impl<'de> de::Visitor<'de> for MyScalarValueVisitor { - type Value = MyScalarValue; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid input value") - } - - fn visit_bool(self, value: bool) -> Result { - Ok(Self::Value::Boolean(value)) - } - - fn visit_i64(self, value: i64) -> Result { - if value >= i64::from(i32::min_value()) && value <= i64::from(i32::max_value()) { - Ok(Self::Value::Int(value as i32)) - } else { - Ok(Self::Value::Float(value as f64)) - } - } - - fn visit_u64(self, value: u64) -> Result { - if value <= i32::max_value() as u64 { - self.visit_i64(value as i64) - } else { - Ok(Self::Value::Float(value as f64)) - } - } - - fn visit_f64(self, value: f64) -> Result { - Ok(Self::Value::Float(value)) - } - - fn visit_str(self, value: &str) -> Result { - self.visit_string(value.into()) - } - - fn visit_string(self, value: String) -> Result { - Ok(Self::Value::String(value)) - } - } + use super::*; #[derive(GraphQLUnion)] #[graphql(scalar = MyScalarValue)] diff --git a/integration_tests/juniper_tests/src/custom_scalar.rs b/integration_tests/juniper_tests/src/custom_scalar.rs index 4d14e1352..cc17169e5 100644 --- a/integration_tests/juniper_tests/src/custom_scalar.rs +++ b/integration_tests/juniper_tests/src/custom_scalar.rs @@ -10,7 +10,7 @@ use juniper::{ use std::fmt; #[derive(Debug, Clone, PartialEq, juniper::GraphQLScalarValue)] -enum MyScalarValue { +pub(crate) enum MyScalarValue { Int(i32), Long(i64), Float(f64), @@ -59,7 +59,7 @@ impl ScalarValue for MyScalarValue { } #[derive(Default, Debug)] -struct MyScalarValueVisitor; +pub(crate) struct MyScalarValueVisitor; impl<'de> de::Visitor<'de> for MyScalarValueVisitor { type Value = MyScalarValue;