Skip to content

Commit 6b1c6ef

Browse files
author
Mridang Agarwalla
committed
Initial support for adding support for nulls and undefined
1 parent 70ab0f7 commit 6b1c6ef

File tree

6 files changed

+157
-35
lines changed

6 files changed

+157
-35
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'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Copyright 2016 Yurii Rashkovskii
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
*/
15+
package graphql.annotations.annotationTypes;
16+
17+
import java.lang.annotation.ElementType;
18+
import java.lang.annotation.Retention;
19+
import java.lang.annotation.RetentionPolicy;
20+
import java.lang.annotation.Target;
21+
22+
@Target({ElementType.PARAMETER})
23+
@Retention(RetentionPolicy.RUNTIME)
24+
public @interface GraphQLUndefinable {
25+
}
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: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,37 @@
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;
2631
import java.util.List;
2732
import java.util.Map;
33+
import java.util.Optional;
2834

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;
35+
import graphql.annotations.annotationTypes.GraphQLBatched;
36+
import graphql.annotations.annotationTypes.GraphQLConstructor;
37+
import graphql.annotations.annotationTypes.GraphQLInvokeDetached;
38+
import graphql.annotations.annotationTypes.GraphQLName;
39+
import graphql.annotations.processor.ProcessingElementsContainer;
40+
import graphql.annotations.processor.typeFunctions.TypeFunction;
41+
import graphql.schema.DataFetcher;
42+
import graphql.schema.DataFetchingEnvironment;
43+
import graphql.schema.GraphQLInputObjectType;
44+
import graphql.schema.GraphQLList;
45+
import graphql.schema.GraphQLType;
3446

3547

3648
/**
@@ -111,33 +123,35 @@ private Object[] invocationArgs(DataFetchingEnvironment environment, ProcessingE
111123

112124
graphql.schema.GraphQLType graphQLType = typeFunction.buildType(true, paramType, p.getAnnotatedType(), container);
113125
if (envArgs.containsKey(parameterName)) {
114-
result.add(buildArg(p.getParameterizedType(), graphQLType, envArgs.get(parameterName)));
126+
result.add(buildArg(p.getParameterizedType(), graphQLType, envArgs.containsKey(parameterName) ? Optional.ofNullable(envArgs.get(parameterName)) : null));
115127
} else {
116128
result.add(null);
117129
}
118130
}
119131
return result.toArray();
120132
}
121133

122-
private Object buildArg(Type p, GraphQLType graphQLType, Object arg) {
134+
private Object buildArg(Type p, GraphQLType graphQLType, Optional<Object> arg) {
123135
if (arg == null) {
124136
return null;
125137
}
126138
if (graphQLType instanceof graphql.schema.GraphQLNonNull) {
127139
graphQLType = ((graphql.schema.GraphQLNonNull) graphQLType).getWrappedType();
128140
}
141+
129142
if (p instanceof Class<?> && graphQLType instanceof GraphQLInputObjectType) {
130143
Constructor<?> constructors[] = ((Class) p).getConstructors();
131144
Constructor<?> constructor = getBuildArgConstructor(constructors);
132145
Parameter[] parameters = constructor.getParameters();
133-
if (parameters.length == 1 && parameters[0].getType().isAssignableFrom(arg.getClass())) {
134-
return constructNewInstance(constructor, arg);
146+
147+
if (parameters.length == 1 && parameters[0].getType().isAssignableFrom(arg.get().getClass())) {
148+
return constructNewInstance(constructor, arg.get());
135149
} else {
136150
List<Object> objects = new ArrayList<>();
137-
Map map = (Map) arg;
151+
Map map = (Map) arg.get();
138152
for (Parameter parameter : parameters) {
139153
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)));
154+
objects.add(buildArg(parameter.getParameterizedType(), ((GraphQLInputObjectType) graphQLType).getField(name).getType(), map.containsKey(name) ? Optional.ofNullable(map.get(name)) : null));
141155
}
142156
return constructNewInstance(constructor, objects.toArray(new Object[objects.size()]));
143157
}
@@ -146,13 +160,20 @@ private Object buildArg(Type p, GraphQLType graphQLType, Object arg) {
146160
Type subType = ((ParameterizedType) p).getActualTypeArguments()[0];
147161
GraphQLType wrappedType = ((GraphQLList) graphQLType).getWrappedType();
148162

149-
for (Object item : ((List) arg)) {
150-
list.add(buildArg(subType, wrappedType, item));
163+
for (Object item : ((List) arg.get())) {
164+
list.add(buildArg(subType, wrappedType, Optional.ofNullable(item)));
151165
}
152166

153167
return list;
168+
} else if (p instanceof ParameterizedType && ((ParameterizedType) p).getRawType() == Optional.class) {
169+
Type subType = ((ParameterizedType) p).getActualTypeArguments()[0];
170+
if (arg == null) {
171+
return null;
172+
} else {
173+
return Optional.ofNullable(buildArg(subType, new GraphQLUndefined(), arg));
174+
}
154175
} else {
155-
return arg;
176+
return arg.get();
156177
}
157178
}
158179

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

Lines changed: 59 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,21 @@ 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+
126147
public static class Code {
127148
public Code(@GraphQLName("map") HashMap map) {
128149
this.firstField = (String) map.get("firstField");
@@ -135,6 +156,21 @@ public Code(@GraphQLName("map") HashMap map) {
135156
public String secondField;
136157
}
137158

159+
public static class QueryUndefinedParameter {
160+
161+
@SuppressWarnings({"unused", "OptionalAssignedToNull"})
162+
@GraphQLField
163+
public String something(@GraphQLName("code") AnotherCode code) {
164+
return (code.firstField != null ? code.firstField.orElse("") : "") + code.secondField;
165+
}
166+
167+
@SuppressWarnings({"unused", "OptionalAssignedToNull"})
168+
@GraphQLField
169+
public String otherthing(@GraphQLName("code") AnotherCode code) {
170+
return (code.firstField != null ? code.firstField.orElse("") : "") + code.secondField;
171+
}
172+
}
173+
138174
public static class QueryMultipleDefinitions {
139175
@GraphQLField
140176
public String something(@GraphQLName("code") Code code) {
@@ -191,6 +227,17 @@ public void query() {
191227
assertEquals(((Map<String, Map<String, String>>) result.getData()).get("object").get("value"), "testa");
192228
}
193229

230+
@Test
231+
public void queryWithUndefinableParameters() {
232+
GraphQLSchema schema = newAnnotationsSchema().query(QueryUndefinedParameter.class).build();
233+
234+
GraphQL graphQL = GraphQL.newGraphQL(schema).build();
235+
ExecutionResult result = graphQL.execute("{ something(code: {firstField:\"a\",secondField:\"b\"}) otherthing(code: {secondField:\"c\"}) }", new QueryUndefinedParameter());
236+
assertTrue(result.getErrors().isEmpty());
237+
assertEquals(((Map<String, String>) result.getData()).get("something"), "ab");
238+
assertEquals(((Map<String, String>) result.getData()).get("otherthing"), "c");
239+
}
240+
194241
@Test
195242
public void queryMultipleDefinitions() {
196243
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)