Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 53 additions & 58 deletions src/main/java/graphql/execution/ValuesResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -388,30 +388,38 @@ private Object externalValueToLiteralForObject(GraphqlFieldVisibility fieldVisib
*/
private Map<String, Object> externalValueToInternalValueForVariables(GraphQLSchema schema,
List<VariableDefinition> variableDefinitions,
Map<String, Object> rawVariables
) {
Map<String, Object> rawVariables) {
GraphqlFieldVisibility fieldVisibility = schema.getCodeRegistry().getFieldVisibility();
Map<String, Object> coercedValues = new LinkedHashMap<>();
for (VariableDefinition variableDefinition : variableDefinitions) {
String variableName = variableDefinition.getName();
GraphQLType variableType = TypeFromAST.getTypeFromAST(schema, variableDefinition.getType());
assertTrue(variableType instanceof GraphQLInputType);
// can be NullValue
Value defaultValue = variableDefinition.getDefaultValue();
boolean hasValue = rawVariables.containsKey(variableName);
Object value = rawVariables.get(variableName);
if (!hasValue && defaultValue != null) {
Object coercedDefaultValue = literalToInternalValue(fieldVisibility, variableType, defaultValue, Collections.emptyMap());
coercedValues.put(variableName, coercedDefaultValue);
} else if (isNonNull(variableType) && (!hasValue || value == null)) {
throw new NonNullableValueCoercedAsNullException(variableDefinition, variableType);
} else if (hasValue) {
if (value == null) {
coercedValues.put(variableName, null);
} else {
Object coercedValue = externalValueToInternalValue(fieldVisibility, variableType, value);
coercedValues.put(variableName, coercedValue);
try {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fundamental change is to move the CoercingParseValueException catch one function earlier in the chain, to externalValueToInternalValueForVariables.

Although it appears many lines are changing, it's mostly only indentation for the try catch block.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rationale for moving the catch: the source location is available here, whereas it is not passed down to the next function externalValueToInternalValue.

String variableName = variableDefinition.getName();
GraphQLType variableType = TypeFromAST.getTypeFromAST(schema, variableDefinition.getType());
assertTrue(variableType instanceof GraphQLInputType);
// can be NullValue
Value defaultValue = variableDefinition.getDefaultValue();
boolean hasValue = rawVariables.containsKey(variableName);
Object value = rawVariables.get(variableName);
if (!hasValue && defaultValue != null) {
Object coercedDefaultValue = literalToInternalValue(fieldVisibility, variableType, defaultValue, Collections.emptyMap());
coercedValues.put(variableName, coercedDefaultValue);
} else if (isNonNull(variableType) && (!hasValue || value == null)) {
throw new NonNullableValueCoercedAsNullException(variableDefinition, variableType);
} else if (hasValue) {
if (value == null) {
coercedValues.put(variableName, null);
} else {
Object coercedValue = externalValueToInternalValue(fieldVisibility, variableType, value);
coercedValues.put(variableName, coercedValue);
}
}
} catch (CoercingParseValueException e) {
throw CoercingParseValueException.newCoercingParseValueException()
.message(String.format("Variable '%s' has an invalid value: %s", variableDefinition.getName(), e.getMessage()))
.extensions(e.getExtensions())
.cause(e.getCause())
.sourceLocation(variableDefinition.getSourceLocation())
.build();
}
}

Expand Down Expand Up @@ -485,50 +493,37 @@ private Map<String, Argument> argumentMap(List<Argument> arguments) {
private Object externalValueToInternalValue(GraphqlFieldVisibility fieldVisibility,
GraphQLType graphQLType,
Object value) throws NonNullableValueCoercedAsNullException, CoercingParseValueException {
try {
if (isNonNull(graphQLType)) {
Object returnValue =
externalValueToInternalValue(fieldVisibility, unwrapOne(graphQLType), value);
if (returnValue == null) {
throw new NonNullableValueCoercedAsNullException("", emptyList(), graphQLType);
}
return returnValue;
if (isNonNull(graphQLType)) {
Object returnValue =
externalValueToInternalValue(fieldVisibility, unwrapOne(graphQLType), value);
if (returnValue == null) {
throw new NonNullableValueCoercedAsNullException("", emptyList(), graphQLType);
}
return returnValue;
}

if (value == null) {
return null;
}
if (value == null) {
return null;
}

if (graphQLType instanceof GraphQLScalarType) {
return externalValueToInternalValueForScalar((GraphQLScalarType) graphQLType, value);
} else if (graphQLType instanceof GraphQLEnumType) {
return externalValueToInternalValueForEnum((GraphQLEnumType) graphQLType, value);
} else if (graphQLType instanceof GraphQLList) {
return externalValueToInternalValueForList(fieldVisibility, (GraphQLList) graphQLType, value);
} else if (graphQLType instanceof GraphQLInputObjectType) {
if (value instanceof Map) {
return externalValueToInternalValueForObject(fieldVisibility, (GraphQLInputObjectType) graphQLType, (Map<String, Object>) value);
} else {
throw CoercingParseValueException.newCoercingParseValueException()
.message("Expected type 'Map' but was '" + value.getClass().getSimpleName() +
"'. Variables for input objects must be an instance of type 'Map'.")
.build();
}
if (graphQLType instanceof GraphQLScalarType) {
return externalValueToInternalValueForScalar((GraphQLScalarType) graphQLType, value);
} else if (graphQLType instanceof GraphQLEnumType) {
return externalValueToInternalValueForEnum((GraphQLEnumType) graphQLType, value);
} else if (graphQLType instanceof GraphQLList) {
return externalValueToInternalValueForList(fieldVisibility, (GraphQLList) graphQLType, value);
} else if (graphQLType instanceof GraphQLInputObjectType) {
if (value instanceof Map) {
return externalValueToInternalValueForObject(fieldVisibility, (GraphQLInputObjectType) graphQLType, (Map<String, Object>) value);
} else {
return assertShouldNeverHappen("unhandled type %s", graphQLType);
throw CoercingParseValueException.newCoercingParseValueException()
.message("Expected type 'Map' but was '" + value.getClass().getSimpleName() +
"'. Variables for input objects must be an instance of type 'Map'.")
.build();
}
} catch (CoercingParseValueException e) {
if (e.getLocations() != null) {
throw e;
}
CoercingParseValueException.Builder builder = CoercingParseValueException.newCoercingParseValueException();
throw builder
.message("invalid value : " + e.getMessage())
.extensions(e.getExtensions())
.cause(e.getCause())
.build();
} else {
return assertShouldNeverHappen("unhandled type %s", graphQLType);
}

}

/**
Expand Down
11 changes: 5 additions & 6 deletions src/test/groovy/graphql/Issue739.groovy
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package graphql


import graphql.language.SourceLocation
import graphql.schema.GraphQLObjectType
import graphql.schema.idl.RuntimeWiring
import spock.lang.Specification
Expand Down Expand Up @@ -87,8 +87,8 @@ class Issue739 extends Specification {
varResult.data == null
varResult.errors.size() == 1
varResult.errors[0].errorType == ErrorType.ValidationError
varResult.errors[0].message == "invalid value : invalid value : Expected type 'Map' but was 'Integer'. Variables for input objects must be an instance of type 'Map'.";
// varResult.errors[0].locations == [new SourceLocation(1, 11)]
varResult.errors[0].message == "Variable 'input' has an invalid value: Expected type 'Map' but was 'Integer'. Variables for input objects must be an instance of type 'Map'."
varResult.errors[0].locations == [new SourceLocation(1, 11)]

when:
variables = ["input": ["baz": "hi", "boom": "hi"]]
Expand All @@ -106,8 +106,7 @@ class Issue739 extends Specification {
varResult.data == null
varResult.errors.size() == 1
varResult.errors[0].errorType == ErrorType.ValidationError
// TODO: check error message
varResult.errors[0].message == "invalid value : invalid value : invalid value : Expected type 'Int' but was 'String'."
// varResult.errors[0].locations == [new SourceLocation(1, 11)]
varResult.errors[0].message == "Variable 'input' has an invalid value: Expected type 'Int' but was 'String'."
varResult.errors[0].locations == [new SourceLocation(1, 11)]
}
}
124 changes: 123 additions & 1 deletion src/test/groovy/graphql/execution/ValuesResolverTest.groovy
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package graphql.execution

import graphql.ErrorType
import graphql.ExecutionInput
import graphql.GraphQLException
import graphql.TestUtil
import graphql.language.Argument
Expand All @@ -12,6 +14,7 @@ import graphql.language.NonNullType
import graphql.language.NullValue
import graphql.language.ObjectField
import graphql.language.ObjectValue
import graphql.language.SourceLocation
import graphql.language.StringValue
import graphql.language.TypeName
import graphql.language.Value
Expand Down Expand Up @@ -548,4 +551,123 @@ class ValuesResolverTest extends Specification {
def error = thrown(NonNullableValueCoercedAsNullException)
error.message == "Argument 'arg' has coerced Null value for NonNull type 'inputObject!'"
}
}

def "invalid enum error message is not nested and contains source location - issue 2560"() {
when:
def graphQL = TestUtil.graphQL('''
enum PositionType {
MANAGER
DEVELOPER
}

input PersonInput {
name: String
position: PositionType
}

type Query {
name: String
}

type Mutation {
updatePerson(input: PersonInput!): Boolean
}
''').build()

def mutation = '''
mutation UpdatePerson($input: PersonInput!) {
updatePerson(input: $input)
}
'''

def executionInput = ExecutionInput.newExecutionInput()
.query(mutation)
.variables([input: [name: 'Name', position: 'UNKNOWN_POSITION'] ])
.build()

def executionResult = graphQL.execute(executionInput)

then:
executionResult.data == null
executionResult.errors.size() == 1
executionResult.errors[0].errorType == ErrorType.ValidationError
executionResult.errors[0].message == 'Variable \'input\' has an invalid value: Invalid input for Enum \'PositionType\'. No value found for name \'UNKNOWN_POSITION\''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record - Groovy has 4 types of comments

' single quotes '
" double quotes " 
""" triple double quotes """
''' triple single quotes '''

if you find yourself using `'input' - just swap quotes

executionResult.errors[0].locations == [new SourceLocation(2, 35)]
}

def "invalid boolean coercing parse value error message is not nested and contains source location - issue 2560"() {
when:
def graphQL = TestUtil.graphQL('''
input PersonInput {
name: String
hilarious: Boolean
}

type Query {
name: String
}

type Mutation {
updatePerson(input: PersonInput!): Boolean
}
''').build()

def mutation = '''
mutation UpdatePerson($input: PersonInput!) {
updatePerson(input: $input)
}
'''

def executionInput = ExecutionInput.newExecutionInput()
.query(mutation)
.variables([input: [name: 'Name', hilarious: 'sometimes'] ])
.build()

def executionResult = graphQL.execute(executionInput)

then:
executionResult.data == null
executionResult.errors.size() == 1
executionResult.errors[0].errorType == ErrorType.ValidationError
executionResult.errors[0].message == 'Variable \'input\' has an invalid value: Expected type \'Boolean\' but was \'String\'.'
executionResult.errors[0].locations == [new SourceLocation(2, 35)]
}

def "invalid float coercing parse value error message is not nested and contains source location - issue 2560"() {
when:
def graphQL = TestUtil.graphQL('''
input PersonInput {
name: String
laughsPerMinute: Float
}

type Query {
name: String
}

type Mutation {
updatePerson(input: PersonInput!): Boolean
}
''').build()

def mutation = '''
mutation UpdatePerson($input: PersonInput!) {
updatePerson(input: $input)
}
'''

def executionInput = ExecutionInput.newExecutionInput()
.query(mutation)
.variables([input: [name: 'Name', laughsPerMinute: 'none'] ])
.build()

def executionResult = graphQL.execute(executionInput)

then:
executionResult.data == null
executionResult.errors.size() == 1
executionResult.errors[0].errorType == ErrorType.ValidationError
executionResult.errors[0].message == 'Variable \'input\' has an invalid value: Expected type \'Float\' but was \'String\'.'
executionResult.errors[0].locations == [new SourceLocation(2, 35)]
}
}