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
4 changes: 4 additions & 0 deletions src/main/antlr/Graphql.g4
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ IntValue |
FloatValue |
StringValue |
BooleanValue |
NullValue |
enumValue |
arrayValue |
objectValue;
Expand All @@ -76,6 +77,7 @@ IntValue |
FloatValue |
StringValue |
BooleanValue |
NullValue |
enumValue |
arrayValueWithVariable |
objectValueWithVariable;
Expand Down Expand Up @@ -176,6 +178,8 @@ directiveLocations '|' directiveLocation

BooleanValue: 'true' | 'false';

NullValue: 'null';

FRAGMENT: 'fragment';
QUERY: 'query';
MUTATION: 'mutation';
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import graphql.execution.instrumentation.parameters.FieldFetchParameters;
import graphql.execution.instrumentation.parameters.FieldParameters;
import graphql.language.Field;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.DataFetchingEnvironmentImpl;
import graphql.schema.DataFetchingFieldSelectionSet;
Expand Down Expand Up @@ -96,7 +97,8 @@ protected ExecutionResult resolveField(ExecutionContext executionContext, Execut
InstrumentationContext<Object> fetchCtx = instrumentation.beginFieldFetch(new FieldFetchParameters(executionContext, fieldDef, environment));
Object resolvedValue = null;
try {
resolvedValue = fieldDef.getDataFetcher().get(environment);
DataFetcher dataFetcher = fieldDef.getDataFetcher();
resolvedValue = dataFetcher.get(environment);

fetchCtx.onEnd(resolvedValue);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package graphql.execution;

import graphql.GraphQLException;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeUtil;

/**
* https://facebook.github.io/graphql/#sec-Input-Objects
*
* - This unordered map should not contain any entries with names not defined by a field of this input object type, otherwise an error should be thrown.
*/
public class InputMapDefinesTooManyFieldsException extends GraphQLException {

public InputMapDefinesTooManyFieldsException(GraphQLType graphQLType, String fieldName) {
super(String.format("The variables input contains a field name '%s' that is not defined for input object type '%s' ", GraphQLTypeUtil.getUnwrappedTypeName(graphQLType), fieldName));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package graphql.execution;

import graphql.GraphQLException;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeUtil;

/**
* This is thrown if a non nullable value is coerced to a null value
*/
public class NonNullableValueCoercedAsNullException extends GraphQLException {

public NonNullableValueCoercedAsNullException(GraphQLType graphQLType) {
super(String.format("Null value for NonNull type '%s", GraphQLTypeUtil.getUnwrappedTypeName(graphQLType)));
}

}
102 changes: 86 additions & 16 deletions src/main/java/graphql/execution/ValuesResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,44 @@


import graphql.GraphQLException;
import graphql.language.*;
import graphql.schema.*;
import graphql.language.Argument;
import graphql.language.ArrayValue;
import graphql.language.NullValue;
import graphql.language.ObjectField;
import graphql.language.ObjectValue;
import graphql.language.Value;
import graphql.language.VariableDefinition;
import graphql.language.VariableReference;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLInputObjectField;
import graphql.schema.GraphQLInputObjectType;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLType;

import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ValuesResolver {


public Map<String, Object> getVariableValues(GraphQLSchema schema, List<VariableDefinition> variableDefinitions, Map<String, Object> inputs) {
public Map<String, Object> getVariableValues(GraphQLSchema schema, List<VariableDefinition> variableDefinitions, Map<String, Object> args) {
Map<String, Object> result = new LinkedHashMap<>();
for (VariableDefinition variableDefinition : variableDefinitions) {
result.put(variableDefinition.getName(), getVariableValue(schema, variableDefinition, inputs.get(variableDefinition.getName())));
String varName = variableDefinition.getName();
// we transfer the variable as field arguments if its present as value
if (args.containsKey(varName)) {
Object arg = args.get(varName);
Object variableValue = getVariableValue(schema, variableDefinition, arg);
result.put(varName, variableValue);
}
}
return result;
}
Expand All @@ -23,14 +49,19 @@ public Map<String, Object> getArgumentValues(List<GraphQLArgument> argumentTypes
Map<String, Object> result = new LinkedHashMap<>();
Map<String, Argument> argumentMap = argumentMap(arguments);
for (GraphQLArgument fieldArgument : argumentTypes) {
Argument argument = argumentMap.get(fieldArgument.getName());
String argName = fieldArgument.getName();
Argument argument = argumentMap.get(argName);
Object value;
if (argument != null) {
value = coerceValueAst(fieldArgument.getType(), argument.getValue(), variables);
} else {
value = fieldArgument.getDefaultValue();
}
result.put(fieldArgument.getName(), value);
// only put an arg into the result IF they specified a variable at all or
// the default value ended up being something non null
if (argumentMap.containsKey(argName) || value != null) {
result.put(argName, value);
}
}
return result;
}
Expand All @@ -48,6 +79,7 @@ private Map<String, Argument> argumentMap(List<Argument> arguments) {
private Object getVariableValue(GraphQLSchema schema, VariableDefinition variableDefinition, Object inputValue) {
GraphQLType type = TypeFromAST.getTypeFromAST(schema, variableDefinition.getType());

//noinspection ConstantConditions
if (!isValid(type, inputValue)) {
throw new GraphQLException("Invalid value for type");
}
Expand All @@ -67,7 +99,7 @@ private Object coerceValue(GraphQLType graphQLType, Object value) {
if (graphQLType instanceof GraphQLNonNull) {
Object returnValue = coerceValue(((GraphQLNonNull) graphQLType).getWrappedType(), value);
if (returnValue == null) {
throw new GraphQLException("Null value for NonNull type " + graphQLType);
throw new NonNullableValueCoercedAsNullException(graphQLType);
}
return returnValue;
}
Expand All @@ -81,6 +113,7 @@ private Object coerceValue(GraphQLType graphQLType, Object value) {
} else if (graphQLType instanceof GraphQLList) {
return coerceValueForList((GraphQLList) graphQLType, value);
} else if (graphQLType instanceof GraphQLInputObjectType && value instanceof Map) {
//noinspection unchecked
return coerceValueForInputObjectType((GraphQLInputObjectType) graphQLType, (Map<String, Object>) value);
} else if (graphQLType instanceof GraphQLInputObjectType) {
return value;
Expand All @@ -91,7 +124,15 @@ private Object coerceValue(GraphQLType graphQLType, Object value) {

private Object coerceValueForInputObjectType(GraphQLInputObjectType inputObjectType, Map<String, Object> input) {
Map<String, Object> result = new LinkedHashMap<>();
for (GraphQLInputObjectField inputField : inputObjectType.getFields()) {
List<GraphQLInputObjectField> fields = inputObjectType.getFields();
List<String> fieldNames = fields.stream().map(GraphQLInputObjectField::getName).collect(Collectors.toList());
for (String inputFieldName : input.keySet()) {
if (!fieldNames.contains(inputFieldName)) {
throw new InputMapDefinesTooManyFieldsException(inputObjectType, inputFieldName);
}
}

for (GraphQLInputObjectField inputField : fields) {
if (input.containsKey(inputField.getName()) || alwaysHasValue(inputField)) {
Object value = coerceValue(inputField.getType(), input.get(inputField.getName()));
result.put(inputField.getName(), value == null ? inputField.getDefaultValue() : value);
Expand Down Expand Up @@ -129,6 +170,9 @@ private Object coerceValueAst(GraphQLType type, Value inputValue, Map<String, Ob
if (inputValue instanceof VariableReference) {
return variables.get(((VariableReference) inputValue).getName());
}
if (inputValue instanceof NullValue) {
return null;
}
if (type instanceof GraphQLScalarType) {
return ((GraphQLScalarType) type).getCoercing().parseLiteral(inputValue);
}
Expand Down Expand Up @@ -167,22 +211,48 @@ private Object coerceValueAstForInputObject(GraphQLInputObjectType type, ObjectV

for (GraphQLInputObjectField inputTypeField : type.getFields()) {
if (inputValueFieldsByName.containsKey(inputTypeField.getName())) {
boolean putObjectInMap = true;

ObjectField field = inputValueFieldsByName.get(inputTypeField.getName());
Object fieldValue = coerceValueAst(inputTypeField.getType(), field.getValue(), variables);
if (fieldValue == null) {
fieldValue = inputTypeField.getDefaultValue();
Value fieldInputValue = field.getValue();

Object fieldObject = null;
if (fieldInputValue instanceof VariableReference) {
String varName = ((VariableReference) fieldInputValue).getName();
if (!variables.containsKey(varName)) {
putObjectInMap = false;
} else {
fieldObject = variables.get(varName);
}
} else {
fieldObject = coerceValueAst(inputTypeField.getType(), fieldInputValue, variables);
}

if (fieldObject == null) {
if (!field.getValue().isEqualTo(NullValue.Null)) {
fieldObject = inputTypeField.getDefaultValue();
}
}
if (putObjectInMap) {
result.put(field.getName(), fieldObject);
} else {
assertNonNullInputField(inputTypeField);
}
result.put(field.getName(), fieldValue);
} else if (inputTypeField.getDefaultValue() != null) {
result.put(inputTypeField.getName(), inputTypeField.getDefaultValue());
} else if (inputTypeField.getType() instanceof GraphQLNonNull) {
// Possibly overkill; an object literal with a missing non null field shouldn't pass validation
throw new GraphQLException("Null value for NonNull type " + inputTypeField.getType());
} else {
assertNonNullInputField(inputTypeField);
}
}
return result;
}

private void assertNonNullInputField(GraphQLInputObjectField inputTypeField) {
if (inputTypeField.getType() instanceof GraphQLNonNull) {
throw new NonNullableValueCoercedAsNullException(inputTypeField.getType());
}
}

private Map<String, ObjectField> mapObjectValueFieldsByName(ObjectValue inputValue) {
Map<String, ObjectField> inputValueFieldsByName = new LinkedHashMap<>();
for (ObjectField objectField : inputValue.getObjectFields()) {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/graphql/language/AstPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class AstPrinter {
printers.put(Argument.class, argument());
printers.put(ArrayValue.class, value());
printers.put(BooleanValue.class, value());
printers.put(NullValue.class, value());
printers.put(Directive.class, directive());
printers.put(DirectiveDefinition.class, directiveDefinition());
printers.put(DirectiveLocation.class, directiveLocation());
Expand Down Expand Up @@ -399,6 +400,8 @@ static private String value(Value value) {
return valueOf(((EnumValue) value).getName());
} else if (value instanceof BooleanValue) {
return valueOf(((BooleanValue) value).isValue());
} else if (value instanceof NullValue) {
return "null";
} else if (value instanceof ArrayValue) {
return "[" + join(((ArrayValue) value).getValues(), ", ") + "]";
} else if (value instanceof ObjectValue) {
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/graphql/language/NullValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package graphql.language;


import java.util.Collections;
import java.util.List;

public class NullValue extends AbstractNode implements Value {

public static NullValue Null = new NullValue();

private NullValue() {
}

@Override
public List<Node> getChildren() {
return Collections.emptyList();
}

@Override
public boolean isEqualTo(Node o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

return true;

}

@Override
public String toString() {
return "NullValue{" +
'}';
}
}
9 changes: 9 additions & 0 deletions src/main/java/graphql/parser/GraphqlAntlrToLanguage.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@
import java.util.Deque;
import java.util.List;

import static graphql.language.NullValue.Null;

@Internal

public class GraphqlAntlrToLanguage extends GraphqlBaseVisitor<Void> {

private final CommonTokenStream tokens;
Expand Down Expand Up @@ -668,6 +671,9 @@ private Value getValue(GraphqlParser.ValueWithVariableContext ctx) {
BooleanValue booleanValue = new BooleanValue(Boolean.parseBoolean(ctx.BooleanValue().getText()));
newNode(booleanValue, ctx);
return booleanValue;
} else if (ctx.NullValue() != null) {
newNode(Null, ctx);
return Null;
} else if (ctx.StringValue() != null) {
StringValue stringValue = new StringValue(parseString(ctx.StringValue().getText()));
newNode(stringValue, ctx);
Expand Down Expand Up @@ -713,6 +719,9 @@ private Value getValue(GraphqlParser.ValueContext ctx) {
BooleanValue booleanValue = new BooleanValue(Boolean.parseBoolean(ctx.BooleanValue().getText()));
newNode(booleanValue, ctx);
return booleanValue;
} else if (ctx.NullValue() != null) {
newNode(Null, ctx);
return Null;
} else if (ctx.StringValue() != null) {
StringValue stringValue = new StringValue(parseString(ctx.StringValue().getText()));
newNode(stringValue, ctx);
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/graphql/schema/idl/SchemaGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import graphql.language.IntValue;
import graphql.language.InterfaceTypeDefinition;
import graphql.language.Node;
import graphql.language.NullValue;
import graphql.language.ObjectTypeDefinition;
import graphql.language.ObjectValue;
import graphql.language.OperationTypeDefinition;
Expand Down Expand Up @@ -475,6 +476,8 @@ private Object buildValue(Value value) {
result = arrayValue.getValues().stream().map(this::buildValue).toArray();
} else if (value instanceof ObjectValue) {
result = buildObjectValue((ObjectValue) value);
} else if (value instanceof NullValue) {
result = null;
}
return result;

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/graphql/validation/ValidationUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public boolean isValidLiteralValue(Value value, GraphQLType type) {
if (value == null) {
return !(type instanceof GraphQLNonNull);
}
if (value instanceof NullValue) {
return !(type instanceof GraphQLNonNull);
}
if (value instanceof VariableReference) {
return true;
}
Expand Down
19 changes: 19 additions & 0 deletions src/test/groovy/graphql/CapturingDataFetcher.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package graphql

import graphql.schema.DataFetcher
import graphql.schema.DataFetchingEnvironment

/**
* Help to capture data fetcher arguments passed in
*/
class CapturingDataFetcher implements DataFetcher {
Map<String, Object> args
DataFetchingEnvironment environment

@Override
Object get(DataFetchingEnvironment environment) {
this.environment = environment
this.args = environment.getArguments()
null
}
}
Loading