graphql.execution.ValuesResolver Maven / Gradle / Ivy
package graphql.execution;
import com.google.common.collect.ImmutableList;
import graphql.AssertException;
import graphql.Internal;
import graphql.Scalars;
import graphql.VisibleForTesting;
import graphql.language.Argument;
import graphql.language.ArrayValue;
import graphql.language.BooleanValue;
import graphql.language.EnumValue;
import graphql.language.FloatValue;
import graphql.language.IntValue;
import graphql.language.NullValue;
import graphql.language.ObjectField;
import graphql.language.ObjectValue;
import graphql.language.StringValue;
import graphql.language.Value;
import graphql.language.VariableDefinition;
import graphql.language.VariableReference;
import graphql.normalized.NormalizedInputValue;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLCodeRegistry;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLInputObjectField;
import graphql.schema.GraphQLInputObjectType;
import graphql.schema.GraphQLInputType;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeUtil;
import graphql.schema.InputValueWithState;
import graphql.schema.PropertyDataFetcherHelper;
import graphql.schema.visibility.DefaultGraphqlFieldVisibility;
import graphql.schema.visibility.GraphqlFieldVisibility;
import graphql.util.FpKit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static graphql.Assert.assertShouldNeverHappen;
import static graphql.Assert.assertTrue;
import static graphql.collect.ImmutableKit.emptyList;
import static graphql.collect.ImmutableKit.emptyMap;
import static graphql.collect.ImmutableKit.map;
import static graphql.execution.ValuesResolver.ValueMode.NORMALIZED;
import static graphql.language.NullValue.newNullValue;
import static graphql.language.ObjectField.newObjectField;
import static graphql.schema.GraphQLTypeUtil.isList;
import static graphql.schema.GraphQLTypeUtil.isNonNull;
import static graphql.schema.GraphQLTypeUtil.simplePrint;
import static graphql.schema.GraphQLTypeUtil.unwrapNonNull;
import static graphql.schema.GraphQLTypeUtil.unwrapOne;
import static graphql.schema.visibility.DefaultGraphqlFieldVisibility.DEFAULT_FIELD_VISIBILITY;
@SuppressWarnings("rawtypes")
@Internal
public class ValuesResolver {
public enum ValueMode {
LITERAL,
NORMALIZED
}
/**
* This method coerces the "raw" variables values provided to the engine. The coerced values will be used to
* provide arguments to {@link graphql.schema.DataFetchingEnvironment}
*
* This method is called once per execution and also performs validation.
*
* @param schema the schema
* @param variableDefinitions the variable definitions
* @param rawVariables the supplied variables
*
* @return coerced variable values as a map
*/
public Map coerceVariableValues(GraphQLSchema schema,
List variableDefinitions,
Map rawVariables) throws CoercingParseValueException, NonNullableValueCoercedAsNullException {
return externalValueToInternalValueForVariables(schema, variableDefinitions, rawVariables);
}
/**
* Normalized variables values are Literals with type information. No validation here!
*
* @param schema the schema to use
* @param variableDefinitions the list of variable definitions
* @param rawVariables the raw variables
*
* @return a map of the normalised values
*/
public Map getNormalizedVariableValues(GraphQLSchema schema,
List variableDefinitions,
Map rawVariables) {
GraphqlFieldVisibility fieldVisibility = schema.getCodeRegistry().getFieldVisibility();
Map result = 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) {
result.put(variableName, new NormalizedInputValue(simplePrint(variableType), defaultValue));
} else if (isNonNull(variableType) && (!hasValue || value == null)) {
return assertShouldNeverHappen("variable values are expected to be valid");
} else if (hasValue) {
if (value == null) {
result.put(variableName, new NormalizedInputValue(simplePrint(variableType), null));
} else {
Object literal = externalValueToLiteral(fieldVisibility, value, (GraphQLInputType) variableType, NORMALIZED);
result.put(variableName, new NormalizedInputValue(simplePrint(variableType), literal));
}
} else {
// hasValue = false && no defaultValue for a nullable type
// meaning no value was provided for variableName
}
}
return result;
}
/**
* This is not used for validation: the argument literals are all validated and the variables are validated (when coerced)
*
* @param argumentTypes the list of argument types
* @param arguments the AST arguments
* @param coercedVariables the coerced variables
*
* @return a map of named argument values
*/
public Map getArgumentValues(List argumentTypes,
List arguments,
Map coercedVariables) {
GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry().fieldVisibility(DEFAULT_FIELD_VISIBILITY).build();
return getArgumentValuesImpl(codeRegistry, argumentTypes, arguments, coercedVariables);
}
/**
* No validation as the arguments are assumed valid
*
* @param argumentTypes the list of argument types
* @param arguments the AST arguments
* @param normalizedVariables the nomalised variables
*
* @return a map of named normalised values
*/
public Map getNormalizedArgumentValues(List argumentTypes,
List arguments,
Map normalizedVariables) {
GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry().fieldVisibility(DEFAULT_FIELD_VISIBILITY).build();
if (argumentTypes.isEmpty()) {
return Collections.emptyMap();
}
Map result = new LinkedHashMap<>();
Map argumentMap = argumentMap(arguments);
for (GraphQLArgument argumentDefinition : argumentTypes) {
String argumentName = argumentDefinition.getName();
Argument argument = argumentMap.get(argumentName);
if (argument == null) {
continue;
}
// If a variable doesn't exist then we can't put it into the result Map
if (isVariableAbsent(argument.getValue(), normalizedVariables)) {
continue;
}
GraphQLInputType argumentType = argumentDefinition.getType();
Object value = literalToNormalizedValue(codeRegistry.getFieldVisibility(), argumentType, argument.getValue(), normalizedVariables);
result.put(argumentName, new NormalizedInputValue(simplePrint(argumentType), value));
}
return result;
}
public Map getArgumentValues(GraphQLCodeRegistry codeRegistry,
List argumentTypes,
List arguments,
Map coercedVariables) {
return getArgumentValuesImpl(codeRegistry, argumentTypes, arguments, coercedVariables);
}
public static Value> valueToLiteral(InputValueWithState inputValueWithState, GraphQLType type) {
return valueToLiteral(DEFAULT_FIELD_VISIBILITY, inputValueWithState, type);
}
/**
* Takes a value which can be in different states (internal, literal, external value) and converts into Literal
*
* This assumes the value is valid!
*
* @param fieldVisibility the field visibility to use
* @param inputValueWithState the input value
* @param type the type of input value
*
* @return a value converted to a literal
*/
public static Value> valueToLiteral(@NotNull GraphqlFieldVisibility fieldVisibility, @NotNull InputValueWithState inputValueWithState, @NotNull GraphQLType type) {
return (Value>) valueToLiteral(fieldVisibility, inputValueWithState, type, ValueMode.LITERAL);
}
private static Object valueToLiteral(GraphqlFieldVisibility fieldVisibility, InputValueWithState inputValueWithState, GraphQLType type, ValueMode valueMode) {
if (inputValueWithState.isInternal()) {
if (valueMode == NORMALIZED) {
return assertShouldNeverHappen("can't infer normalized structure");
}
return valueToLiteralLegacy(inputValueWithState.getValue(), type);
}
if (inputValueWithState.isLiteral()) {
return inputValueWithState.getValue();
}
if (inputValueWithState.isExternal()) {
return new ValuesResolver().externalValueToLiteral(fieldVisibility, inputValueWithState.getValue(), (GraphQLInputType) type, valueMode);
}
return assertShouldNeverHappen("unexpected value state " + inputValueWithState);
}
/**
* Converts an external value to an internal value
*
* @param fieldVisibility the field visibility to use
* @param externalValue the input external value
* @param type the type of input value
*
* @return a value converted to an internal value
*/
public static Object externalValueToInternalValue(GraphqlFieldVisibility fieldVisibility, Object externalValue, GraphQLInputType type) {
return new ValuesResolver().externalValueToInternalValue(fieldVisibility, type, externalValue);
}
public static Object valueToInternalValue(InputValueWithState inputValueWithState, GraphQLType type) throws CoercingParseValueException, CoercingParseLiteralException {
DefaultGraphqlFieldVisibility fieldVisibility = DEFAULT_FIELD_VISIBILITY;
if (inputValueWithState.isInternal()) {
return inputValueWithState.getValue();
}
if (inputValueWithState.isLiteral()) {
return new ValuesResolver().literalToInternalValue(fieldVisibility, type, (Value>) inputValueWithState.getValue(), emptyMap());
}
if (inputValueWithState.isExternal()) {
return new ValuesResolver().externalValueToInternalValue(fieldVisibility, (GraphQLInputType) type, inputValueWithState.getValue());
}
return assertShouldNeverHappen("unexpected value state " + inputValueWithState);
}
@Nullable
@SuppressWarnings("unchecked")
public static T getInputValueImpl(GraphQLInputType inputType, InputValueWithState inputValue) {
if (inputValue.isNotSet()) {
return null;
}
return (T) valueToInternalValue(inputValue, inputType);
}
/**
* No validation: the external value is assumed to be valid.
*/
private Object externalValueToLiteral(GraphqlFieldVisibility fieldVisibility,
@Nullable Object value,
GraphQLInputType type,
ValueMode valueMode
) {
if (value == null) {
return newNullValue().build();
}
if (GraphQLTypeUtil.isNonNull(type)) {
return externalValueToLiteral(fieldVisibility, value, (GraphQLInputType) unwrapNonNull(type), valueMode);
}
if (type instanceof GraphQLScalarType) {
return externalValueToLiteralForScalar((GraphQLScalarType) type, value);
} else if (type instanceof GraphQLEnumType) {
return externalValueToLiteralForEnum((GraphQLEnumType) type, value);
} else if (type instanceof GraphQLList) {
return externalValueToLiteralForList(fieldVisibility, (GraphQLList) type, value, valueMode);
} else if (type instanceof GraphQLInputObjectType) {
return externalValueToLiteralForObject(fieldVisibility, (GraphQLInputObjectType) type, value, valueMode);
} else {
return assertShouldNeverHappen("unexpected type %s", type);
}
}
/**
* No validation
*/
private Value externalValueToLiteralForScalar(GraphQLScalarType scalarType, Object value) {
return scalarType.getCoercing().valueToLiteral(value);
}
/**
* No validation
*/
private Value externalValueToLiteralForEnum(GraphQLEnumType enumType, Object value) {
return enumType.valueToLiteral(value);
}
/**
* No validation
*/
private Object externalValueToLiteralForList(GraphqlFieldVisibility fieldVisibility, GraphQLList listType, Object value, ValueMode valueMode) {
if (value instanceof Iterable) {
List result = new ArrayList<>();
for (Object val : (Iterable) value) {
result.add(externalValueToLiteral(fieldVisibility, val, (GraphQLInputType) listType.getWrappedType(), valueMode));
}
if (valueMode == NORMALIZED) {
return result;
} else {
return ArrayValue.newArrayValue().values(result).build();
}
} else {
List result = Collections.singletonList(externalValueToLiteral(fieldVisibility, value, (GraphQLInputType) listType.getWrappedType(), valueMode));
if (valueMode == NORMALIZED) {
return result;
} else {
return ArrayValue.newArrayValue().values(result).build();
}
}
}
/**
* No validation
*/
private Object externalValueToLiteralForObject(GraphqlFieldVisibility fieldVisibility,
GraphQLInputObjectType inputObjectType,
Object inputValue,
ValueMode valueMode) {
assertTrue(inputValue instanceof Map, () -> "Expect Map as input");
Map inputMap = (Map) inputValue;
List fieldDefinitions = fieldVisibility.getFieldDefinitions(inputObjectType);
Map normalizedResult = new LinkedHashMap<>();
ImmutableList.Builder objectFields = ImmutableList.builder();
for (GraphQLInputObjectField inputFieldDefinition : fieldDefinitions) {
GraphQLInputType fieldType = inputFieldDefinition.getType();
String fieldName = inputFieldDefinition.getName();
boolean hasValue = inputMap.containsKey(fieldName);
Object fieldValue = inputMap.getOrDefault(fieldName, null);
if (!hasValue && inputFieldDefinition.hasSetDefaultValue()) {
//TODO: consider valueMode
Object defaultValueLiteral = valueToLiteral(fieldVisibility, inputFieldDefinition.getInputFieldDefaultValue(), fieldType);
if (valueMode == ValueMode.LITERAL) {
normalizedResult.put(fieldName, new NormalizedInputValue(simplePrint(fieldType), defaultValueLiteral));
} else {
objectFields.add(newObjectField().value((Value) defaultValueLiteral).build());
}
} else if (hasValue) {
if (fieldValue == null) {
if (valueMode == NORMALIZED) {
normalizedResult.put(fieldName, new NormalizedInputValue(simplePrint(fieldType), null));
} else {
objectFields.add(newObjectField().value(newNullValue().build()).build());
}
} else {
Object literal = externalValueToLiteral(fieldVisibility,
fieldValue,
fieldType,
valueMode);
if (valueMode == NORMALIZED) {
normalizedResult.put(fieldName, new NormalizedInputValue(simplePrint(fieldType), literal));
} else {
objectFields.add(newObjectField().value((Value) literal).build());
}
}
} else {
// nullable type && hasValue == false && hasDefaultValue == false
// meaning no value was provided for this field
}
}
if (valueMode == NORMALIZED) {
return normalizedResult;
}
return ObjectValue.newObjectValue().objectFields(objectFields.build()).build();
}
/**
* performs validation too
*/
private Map externalValueToInternalValueForVariables(GraphQLSchema schema,
List variableDefinitions,
Map rawVariables
) {
GraphqlFieldVisibility fieldVisibility = schema.getCodeRegistry().getFieldVisibility();
Map 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);
}
} else {
// hasValue = false && no defaultValue for a nullable type
// meaning no value was provided for variableName
}
}
return coercedValues;
}
private Map getArgumentValuesImpl(GraphQLCodeRegistry codeRegistry,
List argumentTypes,
List arguments,
Map coercedVariables
) {
if (argumentTypes.isEmpty()) {
return Collections.emptyMap();
}
Map coercedValues = new LinkedHashMap<>();
Map argumentMap = argumentMap(arguments);
for (GraphQLArgument argumentDefinition : argumentTypes) {
GraphQLInputType argumentType = argumentDefinition.getType();
String argumentName = argumentDefinition.getName();
Argument argument = argumentMap.get(argumentName);
InputValueWithState defaultValue = argumentDefinition.getArgumentDefaultValue();
boolean hasValue = argument != null;
Object value;
Value argumentValue = argument != null ? argument.getValue() : null;
if (argumentValue instanceof VariableReference) {
String variableName = ((VariableReference) argumentValue).getName();
hasValue = coercedVariables.containsKey(variableName);
value = coercedVariables.get(variableName);
} else {
value = argumentValue;
}
if (!hasValue && argumentDefinition.hasSetDefaultValue()) {
Object coercedDefaultValue = defaultValueToInternalValue(
codeRegistry.getFieldVisibility(),
defaultValue,
argumentType);
coercedValues.put(argumentName, coercedDefaultValue);
} else if (isNonNull(argumentType) && (!hasValue || isNullValue(value))) {
throw new RuntimeException();
} else if (hasValue) {
if (isNullValue(value)) {
coercedValues.put(argumentName, value);
} else if (argumentValue instanceof VariableReference) {
coercedValues.put(argumentName, value);
} else {
value = literalToInternalValue(codeRegistry.getFieldVisibility(), argumentType, argument.getValue(), coercedVariables);
coercedValues.put(argumentName, value);
}
} else {
// nullable type && hasValue == false && hasDefaultValue == false
// meaning no value was provided for argumentName
}
}
return coercedValues;
}
private Map argumentMap(List arguments) {
Map result = new LinkedHashMap<>(arguments.size());
for (Argument argument : arguments) {
result.put(argument.getName(), argument);
}
return result;
}
/**
* Performs validation too
*/
@SuppressWarnings("unchecked")
private Object externalValueToInternalValue(GraphqlFieldVisibility fieldVisibility,
GraphQLType graphQLType,
Object value) throws NonNullableValueCoercedAsNullException, CoercingParseValueException {
// nameStack.addLast(inputName);
try {
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 (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) 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'.")
// .path(Arrays.asList(nameStack.toArray()))
.build();
}
} else {
return assertShouldNeverHappen("unhandled type %s", graphQLType);
}
} 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())
// .path(Arrays.asList(nameStack.toArray()))
.build();
} finally {
// nameStack.removeLast();
}
}
/**
* performs validation
*/
private Object externalValueToInternalValueForObject(GraphqlFieldVisibility fieldVisibility,
GraphQLInputObjectType inputObjectType,
Map inputMap
) throws NonNullableValueCoercedAsNullException, CoercingParseValueException {
List fieldDefinitions = fieldVisibility.getFieldDefinitions(inputObjectType);
List fieldNames = map(fieldDefinitions, GraphQLInputObjectField::getName);
for (String providedFieldName : inputMap.keySet()) {
if (!fieldNames.contains(providedFieldName)) {
throw new InputMapDefinesTooManyFieldsException(inputObjectType, providedFieldName);
}
}
Map coercedValues = new LinkedHashMap<>();
for (GraphQLInputObjectField inputFieldDefinition : fieldDefinitions) {
GraphQLInputType fieldType = inputFieldDefinition.getType();
String fieldName = inputFieldDefinition.getName();
InputValueWithState defaultValue = inputFieldDefinition.getInputFieldDefaultValue();
boolean hasValue = inputMap.containsKey(fieldName);
Object value;
Object fieldValue = inputMap.getOrDefault(fieldName, null);
value = fieldValue;
if (!hasValue && inputFieldDefinition.hasSetDefaultValue()) {
Object coercedDefaultValue = defaultValueToInternalValue(fieldVisibility,
defaultValue,
fieldType);
coercedValues.put(fieldName, coercedDefaultValue);
} else if (isNonNull(fieldType) && (!hasValue || value == null)) {
throw new NonNullableValueCoercedAsNullException(fieldName, emptyList(), fieldType);
} else if (hasValue) {
if (value == null) {
coercedValues.put(fieldName, null);
} else {
value = externalValueToInternalValue(fieldVisibility,
fieldType, value);
coercedValues.put(fieldName, value);
}
} else {
// nullable type && hasValue == false && hasDefaultValue == false
// meaning no value was provided for this field
}
}
return coercedValues;
}
/**
* including validation
*/
private Object externalValueToInternalValueForScalar(GraphQLScalarType graphQLScalarType, Object value) throws CoercingParseValueException {
return graphQLScalarType.getCoercing().parseValue(value);
}
/**
* including validation
*/
private Object externalValueToInternalValueForEnum(GraphQLEnumType graphQLEnumType, Object value) throws CoercingParseValueException {
return graphQLEnumType.parseValue(value);
}
/**
* including validation
*/
private List externalValueToInternalValueForList(GraphqlFieldVisibility fieldVisibility,
GraphQLList graphQLList,
Object value
) throws CoercingParseValueException, NonNullableValueCoercedAsNullException {
if (value instanceof Iterable) {
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy