Skip to content

Commit 832dffc

Browse files
authored
Merge pull request #1 from Nosto/feature/allow-null-undefined-distinction
Adding support for nulls and undefined
2 parents 70ab0f7 + 223fd0a commit 832dffc

File tree

7 files changed

+185
-44
lines changed

7 files changed

+185
-44
lines changed

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ gradle.projectsEvaluated {
5757
dependencies {
5858
compile 'javax.validation:validation-api:1.1.0.Final'
5959
compile 'com.graphql-java:graphql-java:15.0'
60+
implementation "jakarta.xml.bind:jakarta.xml.bind-api:2.3.2"
61+
implementation "org.glassfish.jaxb:jaxb-runtime:2.3.2"
62+
6063

6164
// OSGi
6265
compileOnly 'org.osgi:org.osgi.core:6.0.0'

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ org.gradle.daemon=true
55
org.gradle.parallel=true
66
org.gradle.jvmargs=-Dfile.encoding=UTF-8
77

8-
version = 8.3
8+
version = 8.3-nosto1
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
#Mon Nov 30 23:20:00 EET 2020
2+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
13
distributionBase=GRADLE_USER_HOME
24
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip
4-
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6+
zipStoreBase=GRADLE_USER_HOME
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
*/
13+
package graphql.annotations.dataFetchers;
14+
15+
import graphql.schema.GraphQLSchemaElement;
16+
import graphql.schema.GraphQLType;
17+
import graphql.schema.GraphQLTypeVisitor;
18+
import graphql.util.TraversalControl;
19+
import graphql.util.TraverserContext;
20+
21+
public class GraphQLUndefined implements GraphQLType {
22+
@Override
23+
public TraversalControl accept(TraverserContext<GraphQLSchemaElement> context, GraphQLTypeVisitor visitor) {
24+
return null;
25+
}
26+
}

src/main/java/graphql/annotations/dataFetchers/MethodDataFetcher.java

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,38 @@
1212
*/
1313
package graphql.annotations.dataFetchers;
1414

15-
import graphql.annotations.annotationTypes.GraphQLBatched;
16-
import graphql.annotations.annotationTypes.GraphQLConstructor;
17-
import graphql.annotations.annotationTypes.GraphQLInvokeDetached;
18-
import graphql.annotations.annotationTypes.GraphQLName;
19-
import graphql.annotations.processor.ProcessingElementsContainer;
20-
import graphql.annotations.processor.typeFunctions.TypeFunction;
21-
import graphql.schema.*;
15+
import static graphql.annotations.processor.util.NamingKit.toGraphqlName;
16+
import static graphql.annotations.processor.util.PrefixesUtil.addPrefixToPropertyName;
17+
import static graphql.annotations.processor.util.PrefixesUtil.extractPrefixedName;
18+
import static graphql.annotations.processor.util.ReflectionKit.constructNewInstance;
19+
import static graphql.annotations.processor.util.ReflectionKit.newInstance;
2220

23-
import java.lang.reflect.*;
21+
import java.lang.reflect.Constructor;
22+
import java.lang.reflect.Field;
23+
import java.lang.reflect.InvocationTargetException;
24+
import java.lang.reflect.Method;
25+
import java.lang.reflect.Modifier;
26+
import java.lang.reflect.Parameter;
27+
import java.lang.reflect.ParameterizedType;
28+
import java.lang.reflect.Type;
2429
import java.util.ArrayList;
2530
import java.util.Arrays;
31+
import java.util.Collections;
2632
import java.util.List;
2733
import java.util.Map;
34+
import java.util.Optional;
2835

29-
import static graphql.annotations.processor.util.NamingKit.toGraphqlName;
30-
import static graphql.annotations.processor.util.PrefixesUtil.addPrefixToPropertyName;
31-
import static graphql.annotations.processor.util.PrefixesUtil.extractPrefixedName;
32-
import static graphql.annotations.processor.util.ReflectionKit.constructNewInstance;
33-
import static graphql.annotations.processor.util.ReflectionKit.newInstance;
36+
import graphql.annotations.annotationTypes.GraphQLBatched;
37+
import graphql.annotations.annotationTypes.GraphQLConstructor;
38+
import graphql.annotations.annotationTypes.GraphQLInvokeDetached;
39+
import graphql.annotations.annotationTypes.GraphQLName;
40+
import graphql.annotations.processor.ProcessingElementsContainer;
41+
import graphql.annotations.processor.typeFunctions.TypeFunction;
42+
import graphql.schema.DataFetcher;
43+
import graphql.schema.DataFetchingEnvironment;
44+
import graphql.schema.GraphQLInputObjectType;
45+
import graphql.schema.GraphQLList;
46+
import graphql.schema.GraphQLType;
3447

3548

3649
/**
@@ -111,48 +124,74 @@ private Object[] invocationArgs(DataFetchingEnvironment environment, ProcessingE
111124

112125
graphql.schema.GraphQLType graphQLType = typeFunction.buildType(true, paramType, p.getAnnotatedType(), container);
113126
if (envArgs.containsKey(parameterName)) {
114-
result.add(buildArg(p.getParameterizedType(), graphQLType, envArgs.get(parameterName)));
127+
result.add(buildArg(p.getParameterizedType(), graphQLType, envArgs.containsKey(parameterName) ? Optional.ofNullable(envArgs.get(parameterName)) : null));
115128
} else {
116129
result.add(null);
117130
}
118131
}
119132
return result.toArray();
120133
}
121134

122-
private Object buildArg(Type p, GraphQLType graphQLType, Object arg) {
135+
@SuppressWarnings("ConstantConditions")
136+
private Object buildArg(Type p, GraphQLType graphQLType, Optional<Object> arg) {
123137
if (arg == null) {
124138
return null;
125139
}
126140
if (graphQLType instanceof graphql.schema.GraphQLNonNull) {
127141
graphQLType = ((graphql.schema.GraphQLNonNull) graphQLType).getWrappedType();
128142
}
143+
129144
if (p instanceof Class<?> && graphQLType instanceof GraphQLInputObjectType) {
130-
Constructor<?> constructors[] = ((Class) p).getConstructors();
145+
Constructor<?>[] constructors = ((Class) p).getConstructors();
131146
Constructor<?> constructor = getBuildArgConstructor(constructors);
132147
Parameter[] parameters = constructor.getParameters();
133-
if (parameters.length == 1 && parameters[0].getType().isAssignableFrom(arg.getClass())) {
134-
return constructNewInstance(constructor, arg);
148+
149+
if (parameters.length == 1 && arg.isPresent() && parameters[0].getType().isAssignableFrom(arg.get().getClass())) {
150+
if (parameters[0].getType().isAssignableFrom(Optional.class)) {
151+
return constructNewInstance(constructor, arg);
152+
} else {
153+
return constructNewInstance(constructor, arg.orElse(null));
154+
}
135155
} else {
136156
List<Object> objects = new ArrayList<>();
137-
Map map = (Map) arg;
157+
Map map = (Map) arg.orElseGet(Collections::emptyMap);
138158
for (Parameter parameter : parameters) {
139159
String name = toGraphqlName(parameter.getAnnotation(GraphQLName.class) != null ? parameter.getAnnotation(GraphQLName.class).value() : parameter.getName());
140-
objects.add(buildArg(parameter.getParameterizedType(), ((GraphQLInputObjectType) graphQLType).getField(name).getType(), map.get(name)));
160+
if (!map.containsKey(name)) {
161+
objects.add(null);
162+
} else {
163+
objects.add(buildArg(parameter.getParameterizedType(), ((GraphQLInputObjectType) graphQLType).getField(name).getType(), Optional.ofNullable(map.get(name))));
164+
}
141165
}
142166
return constructNewInstance(constructor, objects.toArray(new Object[objects.size()]));
143167
}
144168
} else if (p instanceof ParameterizedType && graphQLType instanceof GraphQLList) {
145-
List<Object> list = new ArrayList<>();
146-
Type subType = ((ParameterizedType) p).getActualTypeArguments()[0];
147-
GraphQLType wrappedType = ((GraphQLList) graphQLType).getWrappedType();
169+
if (((ParameterizedType) p).getRawType() == Optional.class) {
170+
if (arg == null) {
171+
return null;
172+
} else {
173+
Type subType = ((ParameterizedType) p).getActualTypeArguments()[0];
174+
return Optional.ofNullable(buildArg(subType, graphQLType, arg));
175+
}
176+
} else {
177+
List<Object> list = new ArrayList<>();
178+
Type subType = ((ParameterizedType) p).getActualTypeArguments()[0];
179+
GraphQLType wrappedType = ((GraphQLList) graphQLType).getWrappedType();
148180

149-
for (Object item : ((List) arg)) {
150-
list.add(buildArg(subType, wrappedType, item));
181+
for (Object item : ((List) arg.orElseGet(Collections::emptyList))) {
182+
list.add(buildArg(subType, wrappedType, Optional.ofNullable(item)));
183+
}
184+
return list;
185+
}
186+
} else if (p instanceof ParameterizedType && ((ParameterizedType) p).getRawType() == Optional.class) {
187+
Type subType = ((ParameterizedType) p).getActualTypeArguments()[0];
188+
if (arg == null) {
189+
return null;
190+
} else {
191+
return Optional.ofNullable(buildArg(subType, new GraphQLUndefined(), arg));
151192
}
152-
153-
return list;
154193
} else {
155-
return arg;
194+
return arg.orElse(null);
156195
}
157196
}
158197

src/test/java/graphql/annotations/GraphQLInputTest.java

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@
1212
*/
1313
package graphql.annotations;
1414

15+
import static graphql.annotations.AnnotationsSchemaCreator.newAnnotationsSchema;
16+
import static graphql.schema.GraphQLSchema.newSchema;
17+
import static org.testng.Assert.assertEquals;
18+
import static org.testng.Assert.assertTrue;
19+
20+
import java.util.HashMap;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Optional;
24+
25+
import org.testng.annotations.BeforeMethod;
26+
import org.testng.annotations.Test;
27+
1528
import graphql.ExecutionResult;
1629
import graphql.GraphQL;
1730
import graphql.TypeResolutionEnvironment;
@@ -20,18 +33,11 @@
2033
import graphql.annotations.annotationTypes.GraphQLTypeResolver;
2134
import graphql.annotations.processor.GraphQLAnnotations;
2235
import graphql.annotations.processor.exceptions.GraphQLAnnotationsException;
23-
import graphql.schema.*;
24-
import org.testng.annotations.BeforeMethod;
25-
import org.testng.annotations.Test;
26-
27-
import java.util.HashMap;
28-
import java.util.List;
29-
import java.util.Map;
30-
31-
import static graphql.annotations.AnnotationsSchemaCreator.newAnnotationsSchema;
32-
import static graphql.schema.GraphQLSchema.newSchema;
33-
import static org.testng.Assert.assertEquals;
34-
import static org.testng.Assert.assertTrue;
36+
import graphql.schema.GraphQLInputObjectType;
37+
import graphql.schema.GraphQLNamedType;
38+
import graphql.schema.GraphQLObjectType;
39+
import graphql.schema.GraphQLSchema;
40+
import graphql.schema.TypeResolver;
3541

3642
@SuppressWarnings("unchecked")
3743
public class GraphQLInputTest {
@@ -123,6 +129,32 @@ public String value(@GraphQLName("input") RecursiveInputObject input) {
123129
}
124130
}
125131

132+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
133+
public static class AnotherCode {
134+
135+
public AnotherCode(@GraphQLName("firstField") Optional<String> one, @GraphQLName("secondField") String two) {
136+
this.firstField = one;
137+
this.secondField = two;
138+
}
139+
140+
@GraphQLField
141+
public Optional<String> firstField;
142+
143+
@GraphQLField
144+
private final String secondField;
145+
}
146+
147+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
148+
public static class AnotherCodeWithSingleField {
149+
150+
public AnotherCodeWithSingleField(@GraphQLName("one") Optional<String> one) {
151+
this.field = one;
152+
}
153+
154+
@GraphQLField
155+
public Optional<String> field;
156+
}
157+
126158
public static class Code {
127159
public Code(@GraphQLName("map") HashMap map) {
128160
this.firstField = (String) map.get("firstField");
@@ -135,6 +167,33 @@ public Code(@GraphQLName("map") HashMap map) {
135167
public String secondField;
136168
}
137169

170+
public static class QueryUndefinedParameter {
171+
172+
@SuppressWarnings({"unused", "OptionalAssignedToNull"})
173+
@GraphQLField
174+
public String something(@GraphQLName("code") AnotherCode code) {
175+
return (code.firstField != null ? code.firstField.orElse("") : "") + code.secondField;
176+
}
177+
178+
@SuppressWarnings({"unused", "OptionalAssignedToNull"})
179+
@GraphQLField
180+
public String somethingWithOneField(@GraphQLName("code") AnotherCodeWithSingleField code) {
181+
return code.field != null ? code.field.orElse("") : "was undefined";
182+
}
183+
184+
@SuppressWarnings({"unused", "OptionalAssignedToNull"})
185+
@GraphQLField
186+
public String otherthing(@GraphQLName("code") AnotherCode code) {
187+
return (code.firstField != null ? code.firstField.orElse("") : "") + code.secondField;
188+
}
189+
190+
@SuppressWarnings({"unused", "OptionalAssignedToNull", "OptionalUsedAsFieldOrParameterType"})
191+
@GraphQLField
192+
public String listthings(@GraphQLName("codes") Optional<List<AnotherCode>> code) {
193+
return code == null ? "was null" : (code.map(anotherCodes -> "code was " + anotherCodes.size()).orElse("was empty"));
194+
}
195+
}
196+
138197
public static class QueryMultipleDefinitions {
139198
@GraphQLField
140199
public String something(@GraphQLName("code") Code code) {
@@ -191,6 +250,19 @@ public void query() {
191250
assertEquals(((Map<String, Map<String, String>>) result.getData()).get("object").get("value"), "testa");
192251
}
193252

253+
@Test
254+
public void queryWithUndefinableParameters() {
255+
GraphQLSchema schema = newAnnotationsSchema().query(QueryUndefinedParameter.class).build();
256+
257+
GraphQL graphQL = GraphQL.newGraphQL(schema).build();
258+
ExecutionResult result = graphQL.execute("{ something(code: {firstField:\"a\",secondField:\"b\"}) otherthing(code: {secondField:\"c\"}) listthings somethingWithOneField(code: {}) }", new QueryUndefinedParameter());
259+
assertTrue(result.getErrors().isEmpty());
260+
assertEquals(((Map<String, String>) result.getData()).get("something"), "ab");
261+
assertEquals(((Map<String, String>) result.getData()).get("otherthing"), "c");
262+
assertEquals(((Map<String, String>) result.getData()).get("listthings"), "was null");
263+
assertEquals(((Map<String, String>) result.getData()).get("somethingWithOneField"), "was undefined");
264+
}
265+
194266
@Test
195267
public void queryMultipleDefinitions() {
196268
GraphQLSchema schema = newAnnotationsSchema().query(QueryMultipleDefinitions.class).build();

src/test/java/graphql/annotations/MethodDataFetcherTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,4 +501,4 @@ public void queryingFieldsFromNoApiEntityFetcher_noMatchingFieldInEntity_throwEx
501501
assertFalse(result.getErrors().isEmpty());
502502
assertTrue(((ExceptionWhileDataFetching) result.getErrors().get(0)).getException().getCause() instanceof NoSuchFieldException);
503503
}
504-
}
504+
}

0 commit comments

Comments
 (0)