diff --git a/pom.xml b/pom.xml
index 834a9f849..ba7786304 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,13 +29,13 @@
2.0.0-RC1
1.1.2
0.33.0
+
2.0.4
3.0.2
2.1.1
5.0.0
2.0.0
-
- 18.1
+ 19.0
1.9.3
4.3.3
diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/ConfigKey.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/ConfigKey.java
index a358ed73f..b97c93d13 100644
--- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/ConfigKey.java
+++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/ConfigKey.java
@@ -22,5 +22,12 @@ public interface ConfigKey extends org.eclipse.microprofile.graphql.ConfigKey {
public static final String ERROR_EXTENSION_FIELDS = "smallrye.graphql.errorExtensionFields";
public static final String FIELD_VISIBILITY = "smallrye.graphql.fieldVisibility";
public static final String UNWRAP_EXCEPTIONS = "smallrye.graphql.unwrapExceptions";
+ public static final String PARSER_CAPTURE_IGNORED_CHARS = "smallrye.graphql.parser.capture.ignoredChars";
+ public static final String PARSER_CAPTURE_LINE_COMMENTS = "smallrye.graphql.parser.capture.lineComments";
+ public static final String PARSER_CAPTURE_SOURCE_LOCATION = "smallrye.graphql.parser.capture.sourceLocation";
+ public static final String PARSER_MAX_TOKENS = "smallrye.graphql.parser.maxTokens";
+ public static final String PARSER_MAX_WHITESPACE_TOKENS = "smallrye.graphql.parser.maxWhitespaceTokens";
+ public static final String INSTRUMENTATION_QUERY_COMPLEXITY = "smallrye.graphql.instrumentation.queryComplexity";
+ public static final String INSTRUMENTATION_QUERY_DEPTH = "smallrye.graphql.instrumentation.queryDepth";
-}
+}
\ No newline at end of file
diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/MicroProfileConfig.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/MicroProfileConfig.java
index db4ce3837..ea1623d25 100644
--- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/MicroProfileConfig.java
+++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/config/MicroProfileConfig.java
@@ -34,6 +34,13 @@ public class MicroProfileConfig implements Config {
private String fieldVisibility;
private Optional> unwrapExceptions;
private Optional> errorExtensionFields;
+ private Optional parserMaxTokens;
+ private Optional parserMaxWhitespaceTokens;
+ private Optional parserCaptureSourceLocation;
+ private Optional parserCaptureLineComments;
+ private Optional parserCaptureIgnoredChars;
+ private Optional queryComplexityInstrumentation;
+ private Optional queryDepthInstrumentation;
@Override
public String getName() {
@@ -168,6 +175,55 @@ public LogPayloadOption logPayload() {
return logPayload;
}
+ @Override
+ public Optional isParserCaptureIgnoredChars() {
+ if (parserCaptureIgnoredChars == null) {
+ org.eclipse.microprofile.config.Config microProfileConfig = ConfigProvider.getConfig();
+ parserCaptureIgnoredChars = microProfileConfig.getOptionalValue(ConfigKey.PARSER_CAPTURE_IGNORED_CHARS,
+ Boolean.class);
+ }
+ return parserCaptureIgnoredChars;
+ }
+
+ @Override
+ public Optional isParserCaptureLineComments() {
+ if (parserCaptureLineComments == null) {
+ org.eclipse.microprofile.config.Config microProfileConfig = ConfigProvider.getConfig();
+ parserCaptureLineComments = microProfileConfig.getOptionalValue(ConfigKey.PARSER_CAPTURE_LINE_COMMENTS,
+ Boolean.class);
+ }
+ return parserCaptureLineComments;
+ }
+
+ @Override
+ public Optional isParserCaptureSourceLocation() {
+ if (parserCaptureSourceLocation == null) {
+ org.eclipse.microprofile.config.Config microProfileConfig = ConfigProvider.getConfig();
+ parserCaptureSourceLocation = microProfileConfig.getOptionalValue(ConfigKey.PARSER_CAPTURE_SOURCE_LOCATION,
+ Boolean.class);
+ }
+ return parserCaptureSourceLocation;
+ }
+
+ @Override
+ public Optional getParserMaxTokens() {
+ if (parserMaxTokens == null) {
+ org.eclipse.microprofile.config.Config microProfileConfig = ConfigProvider.getConfig();
+ parserMaxTokens = microProfileConfig.getOptionalValue(ConfigKey.PARSER_MAX_TOKENS, Integer.class);
+ }
+ return parserMaxTokens;
+ }
+
+ @Override
+ public Optional getParserMaxWhitespaceTokens() {
+ if (parserMaxWhitespaceTokens == null) {
+ org.eclipse.microprofile.config.Config microProfileConfig = ConfigProvider.getConfig();
+ parserMaxWhitespaceTokens = microProfileConfig.getOptionalValue(ConfigKey.PARSER_MAX_WHITESPACE_TOKENS,
+ Integer.class);
+ }
+ return parserMaxWhitespaceTokens;
+ }
+
@Override
public String getFieldVisibility() {
if (fieldVisibility == null) {
@@ -194,6 +250,26 @@ public Optional> getErrorExtensionFields() {
return errorExtensionFields;
}
+ @Override
+ public Optional getQueryComplexityInstrumentation() {
+ if (queryComplexityInstrumentation == null) {
+ org.eclipse.microprofile.config.Config microProfileConfig = ConfigProvider.getConfig();
+ queryComplexityInstrumentation = microProfileConfig.getOptionalValue(ConfigKey.INSTRUMENTATION_QUERY_COMPLEXITY,
+ Integer.class);
+ }
+ return queryComplexityInstrumentation;
+ }
+
+ @Override
+ public Optional getQueryDepthInstrumentation() {
+ if (queryDepthInstrumentation == null) {
+ org.eclipse.microprofile.config.Config microProfileConfig = ConfigProvider.getConfig();
+ queryDepthInstrumentation = microProfileConfig.getOptionalValue(ConfigKey.INSTRUMENTATION_QUERY_DEPTH,
+ Integer.class);
+ }
+ return queryDepthInstrumentation;
+ }
+
@Override
public T getConfigValue(String key, Class type, T defaultValue) {
org.eclipse.microprofile.config.Config microProfileConfig = ConfigProvider.getConfig();
@@ -252,6 +328,26 @@ public void setLogPayload(LogPayloadOption logPayload) {
this.logPayload = logPayload;
}
+ public void setParserCaptureIgnoredChars(Optional parserCaptureIgnoredChars) {
+ this.parserCaptureIgnoredChars = parserCaptureIgnoredChars;
+ }
+
+ public void setParserCaptureLineComments(Optional parserCaptureLineComments) {
+ this.parserCaptureLineComments = parserCaptureLineComments;
+ }
+
+ public void setParserCaptureSourceLocation(Optional parserCaptureSourceLocation) {
+ this.parserCaptureSourceLocation = parserCaptureSourceLocation;
+ }
+
+ public void setParserMaxTokens(Optional parserMaxTokens) {
+ this.parserMaxTokens = parserMaxTokens;
+ }
+
+ public void setParserMaxWhitespaceTokens(Optional parserMaxWhitespaceTokens) {
+ this.parserMaxWhitespaceTokens = parserMaxWhitespaceTokens;
+ }
+
public void setFieldVisibility(String fieldVisibility) {
this.fieldVisibility = fieldVisibility;
}
@@ -316,6 +412,14 @@ public void setIncludeIntrospectionTypesInSchema(Boolean includeIntrospectionTyp
this.includeIntrospectionTypesInSchema = includeIntrospectionTypesInSchema;
}
+ public void setQueryComplexityInstrumentation(Optional queryComplexityInstrumentation) {
+ this.queryComplexityInstrumentation = queryComplexityInstrumentation;
+ }
+
+ public void getQueryDepthInstrumentation(Optional queryDepthInstrumentation) {
+ this.queryDepthInstrumentation = queryDepthInstrumentation;
+ }
+
private Optional> mergeList(Optional> currentList, Optional> deprecatedList) {
List combined = new ArrayList<>();
@@ -333,10 +437,6 @@ private Optional> mergeList(Optional> currentList, Opt
}
}
- private String getStringConfigValue(String key) {
- return getStringConfigValue(key, null);
- }
-
private String getStringConfigValue(String key, String defaultValue) {
return getConfigValue(key, String.class, defaultValue);
}
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java
index 2a472139d..a270f4fec 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java
@@ -2,6 +2,7 @@
import static io.smallrye.graphql.SmallRyeGraphQLServerLogging.log;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -20,9 +21,14 @@
import graphql.ExecutionInput.Builder;
import graphql.ExecutionResult;
import graphql.GraphQL;
+import graphql.analysis.MaxQueryComplexityInstrumentation;
+import graphql.analysis.MaxQueryDepthInstrumentation;
import graphql.execution.ExecutionId;
import graphql.execution.ExecutionStrategy;
import graphql.execution.SubscriptionExecutionStrategy;
+import graphql.execution.instrumentation.ChainedInstrumentation;
+import graphql.execution.instrumentation.Instrumentation;
+import graphql.parser.ParserOptions;
import graphql.schema.GraphQLSchema;
import io.smallrye.graphql.bootstrap.DataFetcherFactory;
import io.smallrye.graphql.execution.context.SmallRyeContext;
@@ -245,9 +251,24 @@ private DataLoaderRegistry getDataLoaderRegistry(List operatio
private GraphQL getGraphQL() {
if (this.graphQL == null) {
if (graphQLSchema != null) {
+ Config config = Config.get();
+ setParserOptions(config);
+
GraphQL.Builder graphqlBuilder = GraphQL.newGraphQL(graphQLSchema);
graphqlBuilder = graphqlBuilder.defaultDataFetcherExceptionHandler(new ExceptionHandler());
- graphqlBuilder = graphqlBuilder.instrumentation(queryCache);
+
+ List chainedList = new ArrayList<>();
+
+ if (config.getQueryComplexityInstrumentation().isPresent()) {
+ chainedList.add(new MaxQueryComplexityInstrumentation(config.getQueryComplexityInstrumentation().get()));
+ }
+ if (config.getQueryDepthInstrumentation().isPresent()) {
+ chainedList.add(new MaxQueryDepthInstrumentation(config.getQueryDepthInstrumentation().get()));
+ }
+ chainedList.add(queryCache);
+ // TODO: Allow users to add custome instumentations
+ graphqlBuilder = graphqlBuilder.instrumentation(new ChainedInstrumentation(chainedList));
+
graphqlBuilder = graphqlBuilder.preparsedDocumentProvider(queryCache);
if (queryExecutionStrategy != null) {
@@ -273,4 +294,30 @@ private GraphQL getGraphQL() {
return this.graphQL;
}
+
+ private void setParserOptions(Config config) {
+ if (config.hasParserOptions()) {
+ ParserOptions.Builder parserOptionsBuilder = ParserOptions.newParserOptions();
+ if (config.isParserCaptureIgnoredChars().isPresent()) {
+ parserOptionsBuilder = parserOptionsBuilder
+ .captureIgnoredChars(config.isParserCaptureIgnoredChars().get());
+ }
+ if (config.isParserCaptureLineComments().isPresent()) {
+ parserOptionsBuilder = parserOptionsBuilder
+ .captureLineComments(config.isParserCaptureLineComments().get());
+ }
+ if (config.isParserCaptureSourceLocation().isPresent()) {
+ parserOptionsBuilder = parserOptionsBuilder
+ .captureSourceLocation(config.isParserCaptureSourceLocation().get());
+ }
+ if (config.getParserMaxTokens().isPresent()) {
+ parserOptionsBuilder = parserOptionsBuilder.maxTokens(config.getParserMaxTokens().get());
+ }
+ if (config.getParserMaxWhitespaceTokens().isPresent()) {
+ parserOptionsBuilder = parserOptionsBuilder
+ .maxWhitespaceTokens(config.getParserMaxWhitespaceTokens().get());
+ }
+ ParserOptions.setDefaultParserOptions(parserOptionsBuilder.build());
+ }
+ }
}
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/spi/config/Config.java b/server/implementation/src/main/java/io/smallrye/graphql/spi/config/Config.java
index 6d4fa4b44..c52d4e391 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/spi/config/Config.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/spi/config/Config.java
@@ -163,6 +163,42 @@ default LogPayloadOption logPayload() {
return LogPayloadOption.off;
}
+ default Optional isParserCaptureIgnoredChars() {
+ return Optional.empty();
+ }
+
+ default Optional isParserCaptureLineComments() {
+ return Optional.empty();
+ }
+
+ default Optional isParserCaptureSourceLocation() {
+ return Optional.empty();
+ }
+
+ default Optional getParserMaxTokens() {
+ return Optional.empty();
+ }
+
+ default Optional getParserMaxWhitespaceTokens() {
+ return Optional.empty();
+ }
+
+ default boolean hasParserOptions() {
+ return isParserCaptureIgnoredChars().isPresent()
+ || isParserCaptureLineComments().isPresent()
+ || isParserCaptureSourceLocation().isPresent()
+ || getParserMaxTokens().isPresent()
+ || getParserMaxWhitespaceTokens().isPresent();
+ }
+
+ default Optional getQueryComplexityInstrumentation() {
+ return Optional.empty();
+ }
+
+ default Optional getQueryDepthInstrumentation() {
+ return Optional.empty();
+ }
+
default String getFieldVisibility() {
return FIELD_VISIBILITY_DEFAULT;
}
diff --git a/server/tck/src/test/resources/META-INF/microprofile-config.properties b/server/tck/src/test/resources/META-INF/microprofile-config.properties
index e8ffdb645..ad7b91b3f 100644
--- a/server/tck/src/test/resources/META-INF/microprofile-config.properties
+++ b/server/tck/src/test/resources/META-INF/microprofile-config.properties
@@ -5,4 +5,5 @@ smallrye.graphql.logPayload=true
mp.graphql.showErrorMessage=java.security.AccessControlException,io.smallrye.graphql.test.apps.exceptionlist.*
mp.graphql.hideErrorMessage=java.io.IOException,io.smallrye.graphql.test.apps.exceptionlist.*
-smallrye.graphql.errorExtensionFields=exception,classification,code,description,validationErrorType,queryPath
\ No newline at end of file
+smallrye.graphql.errorExtensionFields=exception,classification,code,description,validationErrorType,queryPath
+smallrye.graphql.parser.capture.sourceLocation=false
\ No newline at end of file
diff --git a/server/tck/src/test/resources/overrides/createNewNullNamedHero/output2.json b/server/tck/src/test/resources/overrides/createNewNullNamedHero/output2.json
new file mode 100644
index 000000000..068ed46a7
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/createNewNullNamedHero/output2.json
@@ -0,0 +1,14 @@
+{
+ "errors": [
+ {
+ "message": "Validation error (WrongType@[createNewHero]) : argument 'hero.name' with value 'NullValue{}' must not be null",
+ "locations": [
+ {
+ "line": 2,
+ "column": 19
+ }
+ ]
+ }
+ ],
+ "data": null
+}
\ No newline at end of file
diff --git a/server/tck/src/test/resources/overrides/createNewUnnamedHero/output2.json b/server/tck/src/test/resources/overrides/createNewUnnamedHero/output2.json
new file mode 100644
index 000000000..ea8331aa7
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/createNewUnnamedHero/output2.json
@@ -0,0 +1,14 @@
+{
+ "errors": [
+ {
+ "message": "Validation error (WrongType@[createNewHero]) : argument 'hero' with value 'ObjectValue{objectFields=[ObjectField{name='realName', value=StringValue{value='John Smith'}}]}' is missing required fields '[name]'",
+ "locations": [
+ {
+ "line": 2,
+ "column": 19
+ }
+ ]
+ }
+ ],
+ "data": null
+}
\ No newline at end of file
diff --git a/server/tck/src/test/resources/overrides/invalidDataTypeValue/output3.json b/server/tck/src/test/resources/overrides/invalidDataTypeValue/output3.json
new file mode 100644
index 000000000..5f9b23eab
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/invalidDataTypeValue/output3.json
@@ -0,0 +1,16 @@
+{
+ "errors": [
+ {
+ "message": "argument 'powerLevel' with value 'StringValue{value='Unlimited'}' is not a valid 'Int'",
+ "locations": [
+ {
+ "line": 2,
+ "column": 37
+ }
+ ]
+ }
+ ],
+ "data": {
+ "updateItemPowerLevel": null
+ }
+}
diff --git a/server/tck/src/test/resources/overrides/invalidEnumValue/output2.json b/server/tck/src/test/resources/overrides/invalidEnumValue/output2.json
new file mode 100644
index 000000000..a667fb5d6
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/invalidEnumValue/output2.json
@@ -0,0 +1,14 @@
+{
+ "errors": [
+ {
+ "message": "Validation error (WrongType@[createNewHero]) : argument 'hero.tshirtSize' with value 'EnumValue{name='XLTall'}' is not a valid 'ShirtSize' - Expected enum literal value not in allowable values - 'EnumValue{name='XLTall'}'.",
+ "locations": [
+ {
+ "line": 3,
+ "column": 19
+ }
+ ]
+ }
+ ],
+ "data": null
+}
\ No newline at end of file
diff --git a/server/tck/src/test/resources/overrides/invalidLocalDateFormattedValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalDateFormattedValue/output3.json
new file mode 100644
index 000000000..4fcff2375
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/invalidLocalDateFormattedValue/output3.json
@@ -0,0 +1,16 @@
+{
+ "errors": [
+ {
+ "message": "argument 'date' with value 'StringValue{value='Today'}' is not a valid 'Date'",
+ "locations": [
+ {
+ "line": 2,
+ "column": 49
+ }
+ ]
+ }
+ ],
+ "data": {
+ "checkInWithCorrectDateFormat": null
+ }
+}
diff --git a/server/tck/src/test/resources/overrides/invalidLocalDateTimeFormattedValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalDateTimeFormattedValue/output3.json
new file mode 100644
index 000000000..34be441fa
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/invalidLocalDateTimeFormattedValue/output3.json
@@ -0,0 +1,16 @@
+{
+ "errors": [
+ {
+ "message": "argument 'dateTime' with value 'StringValue{value='Today'}' is not a valid 'DateTime'",
+ "locations": [
+ {
+ "line": 2,
+ "column": 48
+ }
+ ]
+ }
+ ],
+ "data": {
+ "battleWithCorrectDateFormat": null
+ }
+}
\ No newline at end of file
diff --git a/server/tck/src/test/resources/overrides/invalidLocalDateTimeValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalDateTimeValue/output3.json
new file mode 100644
index 000000000..af2c0dc4e
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/invalidLocalDateTimeValue/output3.json
@@ -0,0 +1,16 @@
+{
+ "errors": [
+ {
+ "message": "argument 'dateTime' with value 'StringValue{value='Today'}' is not a valid 'DateTime'",
+ "locations": [
+ {
+ "line": 2,
+ "column": 27
+ }
+ ]
+ }
+ ],
+ "data": {
+ "battle": null
+ }
+}
\ No newline at end of file
diff --git a/server/tck/src/test/resources/overrides/invalidLocalDateValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalDateValue/output3.json
new file mode 100644
index 000000000..49fac88d8
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/invalidLocalDateValue/output3.json
@@ -0,0 +1,16 @@
+{
+ "errors": [
+ {
+ "message": "argument 'date' with value 'StringValue{value='Today'}' is not a valid 'Date'",
+ "locations": [
+ {
+ "line": 2,
+ "column": 28
+ }
+ ]
+ }
+ ],
+ "data": {
+ "checkIn": null
+ }
+}
diff --git a/server/tck/src/test/resources/overrides/invalidLocalTimeFormattedValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalTimeFormattedValue/output3.json
new file mode 100644
index 000000000..dbd808830
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/invalidLocalTimeFormattedValue/output3.json
@@ -0,0 +1,16 @@
+{
+ "errors": [
+ {
+ "message": "argument 'time' with value 'StringValue{value='Today'}' is not a valid 'Time'",
+ "locations": [
+ {
+ "line": 2,
+ "column": 57
+ }
+ ]
+ }
+ ],
+ "data": {
+ "startPatrollingWithCorrectDateFormat": null
+ }
+}
diff --git a/server/tck/src/test/resources/overrides/invalidLocalTimeValue/output3.json b/server/tck/src/test/resources/overrides/invalidLocalTimeValue/output3.json
new file mode 100644
index 000000000..78d6bfd37
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/invalidLocalTimeValue/output3.json
@@ -0,0 +1,16 @@
+{
+ "errors": [
+ {
+ "message": "argument 'time' with value 'StringValue{value='Today'}' is not a valid 'Time'",
+ "locations": [
+ {
+ "line": 2,
+ "column": 36
+ }
+ ]
+ }
+ ],
+ "data": {
+ "startPatrolling": null
+ }
+}
\ No newline at end of file
diff --git a/server/tck/src/test/resources/overrides/unknownField/output2.json b/server/tck/src/test/resources/overrides/unknownField/output2.json
new file mode 100644
index 000000000..6715517fc
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/unknownField/output2.json
@@ -0,0 +1,14 @@
+{
+ "errors": [
+ {
+ "message": "Validation error (FieldUndefined@[allHeroes/weaknesses]) : Field 'weaknesses' in type 'SuperHero' is undefined",
+ "locations": [
+ {
+ "line": 4,
+ "column": 5
+ }
+ ]
+ }
+ ],
+ "data": null
+}
\ No newline at end of file
diff --git a/server/tck/src/test/resources/overrides/unknownMutation/output2.json b/server/tck/src/test/resources/overrides/unknownMutation/output2.json
new file mode 100644
index 000000000..c63695ca0
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/unknownMutation/output2.json
@@ -0,0 +1,14 @@
+{
+ "errors": [
+ {
+ "message": "Validation error (FieldUndefined@[createNewHeroCat]) : Field 'createNewHeroCat' in type 'Mutation' is undefined",
+ "locations": [
+ {
+ "line": 2,
+ "column": 5
+ }
+ ]
+ }
+ ],
+ "data": null
+}
\ No newline at end of file
diff --git a/server/tck/src/test/resources/overrides/unknownQuery/output2.json b/server/tck/src/test/resources/overrides/unknownQuery/output2.json
new file mode 100644
index 000000000..474c2b5d5
--- /dev/null
+++ b/server/tck/src/test/resources/overrides/unknownQuery/output2.json
@@ -0,0 +1,14 @@
+{
+ "errors": [
+ {
+ "message": "Validation error (FieldUndefined@[allHeroesWhoLikeIceCream]) : Field 'allHeroesWhoLikeIceCream' in type 'Query' is undefined",
+ "locations": [
+ {
+ "line": 2,
+ "column": 3
+ }
+ ]
+ }
+ ],
+ "data": null
+}