Skip to content

Commit

Permalink
Fix custom ScalarValue usage for unions (#681)
Browse files Browse the repository at this point in the history
- make GraphQLUnion trait generic over ScalarValue
- generate generic over ScalarValue impls of GraphQLUnion trait
- add codegen tests with a custom ScalarValue for union macros
  • Loading branch information
ccbrown authored Jun 14, 2020
1 parent 5b9c611 commit 6dd6abb
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not satisfied
--> $DIR/enum_non_object_variant.rs:9:10
|
9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` 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
|
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error[E0119]: conflicting implementations of trait `<Character as juniper::types::marker::GraphQLUnion>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
error[E0119]: conflicting implementations of trait `<Character as juniper::types::marker::GraphQLUnion<__S>>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
--> $DIR/enum_same_type_ugly.rs:3:10
|
3 | #[derive(GraphQLUnion)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not satisfied
--> $DIR/struct_non_object_variant.rs:9:10
|
9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` 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
|
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error[E0119]: conflicting implementations of trait `<Character as juniper::types::marker::GraphQLUnion>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
error[E0119]: conflicting implementations of trait `<Character as juniper::types::marker::GraphQLUnion<__S>>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
--> $DIR/struct_same_type_ugly.rs:3:10
|
3 | #[derive(GraphQLUnion)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
error[E0277]: the trait bound `Test: juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` is not satisfied
--> $DIR/trait_non_object_variant.rs:9:1
|
9 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ the trait `juniper::types::marker::GraphQLObjectType<juniper::value::scalar::DefaultScalarValue>` 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
|
Expand Down
Original file line number Diff line number Diff line change
@@ -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]
Expand Down
91 changes: 91 additions & 0 deletions integration_tests/juniper_tests/src/codegen/union_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DynCharacter<'_>> {
let ch: Box<DynCharacter<'_>> = 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::*;

Expand Down
73 changes: 73 additions & 0 deletions integration_tests/juniper_tests/src/codegen/union_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,79 @@ mod explicit_scalar {
}
}

mod custom_scalar {
use crate::custom_scalar::MyScalarValue;

use super::*;

#[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::*;

Expand Down
4 changes: 2 additions & 2 deletions integration_tests/juniper_tests/src/custom_scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion juniper/src/types/marker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub trait GraphQLObjectType<S: ScalarValue>: GraphQLType<S> {
/// [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<S: ScalarValue>: GraphQLType<S> {
/// An arbitrary function without meaning.
///
/// May contain compile timed check logic which ensures that types are used correctly according
Expand Down
20 changes: 6 additions & 14 deletions juniper_codegen/src/graphql_union/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -618,13 +610,13 @@ impl ToTokens for UnionDefinition {

let union_impl = quote! {
#[automatically_derived]
impl#impl_generics #crate_path::marker::GraphQLUnion 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(); )*
}
}
};
Expand Down

0 comments on commit 6dd6abb

Please sign in to comment.