Skip to content

Commit 5dddce5

Browse files
authored
Merge pull request #165 from guy120494/fix-union
Fix union
2 parents a790187 + b920029 commit 5dddce5

File tree

6 files changed

+321
-21
lines changed

6 files changed

+321
-21
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,45 @@ GraphQLInterfaceType object = GraphQLAnnotations.iface(SomeInterface.class);
5454
An instance of the type resolver will be created from the specified class. If a `getInstance` method is present on the
5555
class, it will be used instead of the default constructor.
5656

57+
## Defining Unions
58+
59+
To have a union, you must annotate an interface with `@GraphQLUnion`. In the annotation, you must declare all the
60+
possible types of the union, and a type resolver.
61+
If no type resolver is specified, `UnionTypeResovler` is used. It follows this algorithm:
62+
The resolver assumes the the DB entity's name is the same as the API entity's name.
63+
If so, it takes the result from the dataFetcher and decides to which
64+
API entity it should be mapped (according to the name).
65+
Example: If you have a `Pet` union type, and the dataFetcher returns `Dog`, the typeResolver
66+
will check for each API entity if its name is equal to `Dog`, and returns if it finds something
67+
68+
```java
69+
@GraphQLUnion(possibleTypes={Dog.class, Cat.class})
70+
public interface Pet {}
71+
```
72+
and an example with custom `TypeResovler`:
73+
```java
74+
@GraphQLUnion(possibleTypes={DogApi.class, Cat.class}, typeResolver = PetTypeResolver.class)
75+
public interface Pet {}
76+
77+
78+
public class PetTypeResolver implements TypeResolver {
79+
@Override
80+
GraphQLObjectType getType(TypeResolutionEnvironment env) {
81+
Object obj = env.getObject();
82+
if(obj instanceof DogDB) {
83+
return (GraphQLObjectType) env.getSchema().getType("DogApi");
84+
}
85+
else {
86+
return (GraphQLObjectType) env.getSchema().getType("Cat");
87+
}
88+
89+
}
90+
}
91+
```
92+
NOTE: you can have (but not mandatory) a type resolver with constructor that has `Class<?>[]` as the first parameter and
93+
`ProcessingElementsContainer` as the second. the `Class<?>[]` parameter contains the possibleTypes class
94+
and `ProcessingElementsContainer` has all sorts of utils (you can check `UnionTypeResolver` to see how we use it there)
95+
5796
## Fields
5897

5998
In addition to specifying a field over a Java class field, a field can be defined over a method:

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ dependencies {
7878
compileOnly 'biz.aQute.bnd:biz.aQute.bndlib:3.2.0'
7979

8080
testCompile 'org.testng:testng:6.9.10'
81+
testCompile 'org.hamcrest:hamcrest-all:1.3'
8182
}
8283

8384
test.useTestNG()

src/main/java/graphql/annotations/annotationTypes/GraphQLUnion.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
*/
1515
package graphql.annotations.annotationTypes;
1616

17+
import graphql.annotations.typeResolvers.UnionTypeResolver;
18+
import graphql.schema.TypeResolver;
19+
1720
import java.lang.annotation.ElementType;
1821
import java.lang.annotation.Retention;
1922
import java.lang.annotation.RetentionPolicy;
@@ -23,4 +26,6 @@
2326
@Retention(RetentionPolicy.RUNTIME)
2427
public @interface GraphQLUnion {
2528
Class<?>[] possibleTypes();
29+
30+
Class<? extends TypeResolver> typeResolver() default UnionTypeResolver.class;
2631
}

src/main/java/graphql/annotations/processor/typeBuilders/UnionBuilder.java

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@
1919
import graphql.annotations.annotationTypes.GraphQLType;
2020
import graphql.annotations.annotationTypes.GraphQLUnion;
2121
import graphql.annotations.processor.ProcessingElementsContainer;
22-
import graphql.annotations.processor.util.ReflectionKit;
2322
import graphql.annotations.processor.exceptions.GraphQLAnnotationsException;
24-
import graphql.annotations.processor.typeFunctions.TypeFunction;
2523
import graphql.annotations.processor.retrievers.GraphQLObjectInfoRetriever;
24+
import graphql.annotations.processor.typeFunctions.TypeFunction;
2625
import graphql.annotations.typeResolvers.UnionTypeResolver;
2726
import graphql.schema.GraphQLObjectType;
28-
import graphql.schema.GraphQLUnionType;
27+
import graphql.schema.GraphQLUnionType.Builder;
28+
import graphql.schema.TypeResolver;
2929

30+
import java.lang.reflect.Constructor;
3031
import java.util.Arrays;
31-
import java.util.function.Function;
32+
import java.util.Optional;
3233

34+
import static graphql.annotations.processor.util.ReflectionKit.constructNewInstance;
35+
import static graphql.annotations.processor.util.ReflectionKit.newInstance;
3336
import static graphql.schema.GraphQLUnionType.newUnionType;
3437

3538
public class UnionBuilder {
@@ -41,18 +44,18 @@ public UnionBuilder(GraphQLObjectInfoRetriever graphQLObjectInfoRetriever) {
4144
}
4245

4346
/**
44-
* This will examine the class and return a {@link GraphQLUnionType.Builder} ready for further definition
47+
* This will examine the class and return a {@link Builder} ready for further definition
4548
* @param container a class that hold several members that are required in order to build schema
4649
* @param iface interface to examine
47-
* @return a {@link GraphQLUnionType.Builder}
50+
* @return a {@link Builder}
4851
* @throws GraphQLAnnotationsException if the class cannot be examined
4952
*/
5053

51-
public GraphQLUnionType.Builder getUnionBuilder(Class<?> iface, ProcessingElementsContainer container) throws GraphQLAnnotationsException, IllegalArgumentException {
54+
public Builder getUnionBuilder(Class<?> iface, ProcessingElementsContainer container) throws GraphQLAnnotationsException, IllegalArgumentException {
5255
if (!iface.isInterface()) {
5356
throw new IllegalArgumentException(iface + " is not an interface");
5457
}
55-
GraphQLUnionType.Builder builder = newUnionType();
58+
Builder builder = newUnionType();
5659

5760
GraphQLUnion unionAnnotation = iface.getAnnotation(GraphQLUnion.class);
5861
builder.name(graphQLObjectInfoRetriever.getTypeName(iface));
@@ -65,21 +68,41 @@ public GraphQLUnionType.Builder getUnionBuilder(Class<?> iface, ProcessingElemen
6568
TypeFunction typeFunction = container.getDefaultTypeFunction();
6669

6770
if (typeAnnotation != null) {
68-
typeFunction = ReflectionKit.newInstance(typeAnnotation.value());
71+
typeFunction = newInstance(typeAnnotation.value());
6972
}
7073

7174
TypeFunction finalTypeFunction = typeFunction;
72-
Arrays.asList(unionAnnotation.possibleTypes()).stream()
73-
.map(new Function<Class<?>, graphql.schema.GraphQLType>() {
74-
@Override
75-
public graphql.schema.GraphQLType apply(Class<?> aClass) {
76-
return finalTypeFunction.buildType(aClass, null, container);
77-
}
78-
})
75+
Arrays.stream(unionAnnotation.possibleTypes())
76+
.map(aClass -> finalTypeFunction.buildType(aClass, null, container))
7977
.map(v -> (GraphQLObjectType) v)
8078
.forEach(builder::possibleType);
8179

82-
builder.typeResolver(new UnionTypeResolver(unionAnnotation.possibleTypes(), container));
80+
TypeResolver typeResolver = getTypeResolver(container, unionAnnotation);
81+
82+
builder.typeResolver(typeResolver);
8383
return builder;
8484
}
85+
86+
private TypeResolver getTypeResolver(ProcessingElementsContainer container, GraphQLUnion unionAnnotation) {
87+
Optional<Constructor<?>> typeResolverConstructorOptional = Arrays.stream(unionAnnotation.typeResolver().getConstructors())
88+
.filter(constructor -> constructor.getParameterCount() == 0 || constructorHasPossibleTypesAndContainerAsParameters(container, constructor))
89+
.findFirst();
90+
91+
return typeResolverConstructorOptional
92+
.map(constructor -> {
93+
if(constructor.getParameterCount() == 0) {
94+
return (TypeResolver) constructNewInstance(constructor);
95+
}
96+
else {
97+
return (TypeResolver) constructNewInstance(constructor, unionAnnotation.possibleTypes(), container);
98+
}
99+
})
100+
.orElseGet(() -> new UnionTypeResolver(unionAnnotation.possibleTypes(), container));
101+
}
102+
103+
private boolean constructorHasPossibleTypesAndContainerAsParameters(ProcessingElementsContainer container, Constructor<?> constructor) {
104+
return constructor.getParameterCount() == 2 &&
105+
Class[].class.isAssignableFrom(constructor.getParameterTypes()[0]) &&
106+
container.getClass().isAssignableFrom(constructor.getParameterTypes()[1]);
107+
}
85108
}

src/main/java/graphql/annotations/typeResolvers/UnionTypeResolver.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@
1616

1717
import graphql.TypeResolutionEnvironment;
1818
import graphql.annotations.processor.ProcessingElementsContainer;
19-
import graphql.schema.*;
19+
import graphql.schema.GraphQLObjectType;
2020
import graphql.schema.GraphQLType;
21+
import graphql.schema.TypeResolver;
2122

2223
import java.util.Arrays;
2324
import java.util.HashMap;
2425
import java.util.Map;
26+
import java.util.Map.Entry;
2527
import java.util.Optional;
2628

2729
public class UnionTypeResolver implements TypeResolver {
28-
private final Map<Class<?>, graphql.schema.GraphQLType> types = new HashMap<>();
30+
private final Map<Class<?>, GraphQLType> types = new HashMap<>();
2931

3032
public UnionTypeResolver(Class<?>[] classes, ProcessingElementsContainer container) {
3133
Arrays.stream(classes).
@@ -35,8 +37,10 @@ public UnionTypeResolver(Class<?>[] classes, ProcessingElementsContainer contain
3537
@Override
3638
public GraphQLObjectType getType(TypeResolutionEnvironment env) {
3739
Object object = env.getObject();
38-
Optional<Map.Entry<Class<?>, GraphQLType>> maybeType = types.entrySet().
39-
stream().filter(e -> e.getKey().isAssignableFrom(object.getClass())).findFirst();
40+
Optional<Entry<Class<?>, GraphQLType>> maybeType = types.entrySet().
41+
stream().filter(e -> object.getClass().getSimpleName()
42+
.equals(e.getKey().getSimpleName())).findFirst();
43+
4044
if (maybeType.isPresent()) {
4145
return (GraphQLObjectType) maybeType.get().getValue();
4246
} else {

0 commit comments

Comments
 (0)