Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input Value Validation #3086

Closed
wants to merge 1 commit into from
Closed
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
6 changes: 3 additions & 3 deletions src/execution/__tests__/nonnull-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ describe('Execute: handles non-nullable types', () => {
errors: [
{
message:
'Argument Query.withNonNullArg(cannotBeNull:) of non-null type String! must not be null.',
'Argument Query.withNonNullArg(cannotBeNull:) has invalid value: Expected value of non-null type String! not to be null.',
locations: [{ line: 3, column: 42 }],
path: ['withNonNullArg'],
},
Expand Down Expand Up @@ -673,7 +673,7 @@ describe('Execute: handles non-nullable types', () => {
errors: [
{
message:
'Argument Query.withNonNullArg(cannotBeNull:) of required type String! was provided the variable "$testVar" which was not provided a runtime value.',
'Argument Query.withNonNullArg(cannotBeNull:) has invalid value: Expected variable "$testVar" provided to type String! to provide a runtime value.',
locations: [{ line: 3, column: 42 }],
path: ['withNonNullArg'],
},
Expand Down Expand Up @@ -701,7 +701,7 @@ describe('Execute: handles non-nullable types', () => {
errors: [
{
message:
'Argument Query.withNonNullArg(cannotBeNull:) of non-null type String! must not be null.',
'Argument Query.withNonNullArg(cannotBeNull:) has invalid value: Expected variable "$testVar" provided to non-null type String! not to be null.',
locations: [{ line: 3, column: 43 }],
path: ['withNonNullArg'],
},
Expand Down
34 changes: 17 additions & 17 deletions src/execution/__tests__/variables-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Argument TestType.fieldWithObjectInput(input:) of type TestInputObject has invalid value ["foo", "bar", "baz"].',
'Argument TestType.fieldWithObjectInput(input:) has invalid value: Expected value of type TestInputObject to be an object, found: ["foo", "bar", "baz"].',
path: ['fieldWithObjectInput'],
locations: [{ line: 3, column: 41 }],
},
Expand Down Expand Up @@ -373,7 +373,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" got invalid value null at "input.c"; Expected non-nullable type "String!" not to be null.',
'Variable "$input" has invalid value at .c: Expected value of non-null type String! not to be null.',
locations: [{ line: 2, column: 16 }],
},
],
Expand All @@ -387,7 +387,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" got invalid value "foo bar"; Expected type "TestInputObject" to be an object.',
'Variable "$input" has invalid value: Expected value of type TestInputObject to be an object, found: "foo bar".',
locations: [{ line: 2, column: 16 }],
},
],
Expand All @@ -401,7 +401,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" got invalid value { a: "foo", b: "bar" }; Field "c" of required type "String!" was not provided.',
'Variable "$input" has invalid value: Expected value of type TestInputObject to include required field "c", found: { a: "foo", b: "bar" }.',
locations: [{ line: 2, column: 16 }],
},
],
Expand All @@ -420,12 +420,12 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" got invalid value { a: "foo" } at "input.na"; Field "c" of required type "String!" was not provided.',
'Variable "$input" has invalid value at .na: Expected value of type TestInputObject to include required field "c", found: { a: "foo" }.',
locations: [{ line: 2, column: 18 }],
},
{
message:
'Variable "$input" got invalid value { na: { a: "foo" } }; Field "nb" of required type "String!" was not provided.',
'Variable "$input" has invalid value: Expected value of type TestNestedInputObject to include required field "nb", found: { na: { a: "foo" } }.',
locations: [{ line: 2, column: 18 }],
},
],
Expand All @@ -442,7 +442,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" got invalid value { a: "foo", b: "bar", c: "baz", extra: "dog" }; Field "extra" is not defined by type "TestInputObject".',
'Variable "$input" has invalid value: Expected value of type TestInputObject not to include unknown field "extra", found: { a: "foo", b: "bar", c: "baz", extra: "dog" }.',
locations: [{ line: 2, column: 16 }],
},
],
Expand Down Expand Up @@ -617,7 +617,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$value" of required type String! was not provided.',
'Variable "$value" has invalid value: Expected a value of non-null type String! to be provided.',
locations: [{ line: 2, column: 16 }],
},
],
Expand All @@ -636,7 +636,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$value" of non-null type String! must not be null.',
'Variable "$value" has invalid value: Expected value of non-null type String! not to be null.',
locations: [{ line: 2, column: 16 }],
},
],
Expand Down Expand Up @@ -702,7 +702,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$value" got invalid value [1, 2, 3]; String cannot represent a non string value: [1, 2, 3]',
'Variable "$value" has invalid value: String cannot represent a non string value: [1, 2, 3]',
locations: [{ line: 2, column: 16 }],
},
],
Expand Down Expand Up @@ -730,7 +730,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Argument TestType.fieldWithNonNullableStringInput(input:) of required type String! was provided the variable "$foo" which was not provided a runtime value.',
'Argument TestType.fieldWithNonNullableStringInput(input:) has invalid value: Expected variable "$foo" provided to type String! to provide a runtime value.',
locations: [{ line: 3, column: 50 }],
path: ['fieldWithNonNullableStringInput'],
},
Expand Down Expand Up @@ -785,7 +785,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" of non-null type [String]! must not be null.',
'Variable "$input" has invalid value: Expected value of non-null type [String]! not to be null.',
locations: [{ line: 2, column: 16 }],
},
],
Expand Down Expand Up @@ -848,7 +848,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" got invalid value null at "input[1]"; Expected non-nullable type "String!" not to be null.',
'Variable "$input" has invalid value at [1]: Expected value of non-null type String! not to be null.',
locations: [{ line: 2, column: 16 }],
},
],
Expand All @@ -867,7 +867,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" of non-null type [String!]! must not be null.',
'Variable "$input" has invalid value: Expected value of non-null type [String!]! not to be null.',
locations: [{ line: 2, column: 16 }],
},
],
Expand Down Expand Up @@ -897,7 +897,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" got invalid value null at "input[1]"; Expected non-nullable type "String!" not to be null.',
'Variable "$input" has invalid value at [1]: Expected value of non-null type String! not to be null.',
locations: [{ line: 2, column: 16 }],
},
],
Expand Down Expand Up @@ -982,7 +982,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Argument TestType.fieldWithDefaultArgumentValue(input:) of type String has invalid value WRONG_TYPE.',
'Argument TestType.fieldWithDefaultArgumentValue(input:) has invalid value: String cannot represent a non string value: WRONG_TYPE',
locations: [{ line: 3, column: 48 }],
path: ['fieldWithDefaultArgumentValue'],
},
Expand Down Expand Up @@ -1022,7 +1022,7 @@ describe('Execute: Handles inputs', () => {

function invalidValueError(value: number, index: number) {
return {
message: `Variable "$input" got invalid value ${value} at "input[${index}]"; String cannot represent a non string value: ${value}`,
message: `Variable "$input" has invalid value at [${index}]: String cannot represent a non string value: ${value}`,
locations: [{ line: 2, column: 14 }],
};
}
Expand Down
144 changes: 59 additions & 85 deletions src/execution/values.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ObjMap, ReadOnlyObjMap } from '../jsutils/ObjMap';
import type { Maybe } from '../jsutils/Maybe';
import { keyMap } from '../jsutils/keyMap';
import { inspect } from '../jsutils/inspect';
import { invariant } from '../jsutils/invariant';
import { printPathArray } from '../jsutils/printPathArray';

import { GraphQLError } from '../error/GraphQLError';
Expand All @@ -17,14 +17,22 @@ import { print } from '../language/printer';
import type { GraphQLSchema } from '../type/schema';
import type { GraphQLInputType, GraphQLField } from '../type/definition';
import type { GraphQLDirective } from '../type/directives';
import { isInputType, isNonNullType } from '../type/definition';
import {
isInputType,
isNonNullType,
isRequiredArgument,
} from '../type/definition';

import { typeFromAST } from '../utilities/typeFromAST';
import {
coerceInputValue,
coerceInputLiteral,
coerceDefaultValue,
} from '../utilities/coerceInputValue';
import {
validateInputValue,
validateInputLiteral,
} from '../utilities/validateInputValue';

export interface VariableValues {
readonly sources: ReadOnlyObjMap<VariableValueSource>;
Expand Down Expand Up @@ -109,59 +117,38 @@ function coerceVariableValues(
continue;
}

if (!hasOwnProperty(inputs, varName)) {
const defaultValue = varDefNode.defaultValue;
if (defaultValue) {
sources[varName] = {
variable: varDefNode,
type: varType,
value: undefined,
};
coerced[varName] = coerceInputLiteral(defaultValue, varType);
} else if (isNonNullType(varType)) {
onError(
new GraphQLError(
`Variable "$${varName}" of required type ${varType} was not provided.`,
varDefNode,
),
);
}
continue;
}
const value = hasOwnProperty(inputs, varName) ? inputs[varName] : undefined;
sources[varName] = { variable: varDefNode, type: varType, value };

const value = inputs[varName];
if (value === null && isNonNullType(varType)) {
onError(
new GraphQLError(
`Variable "$${varName}" of non-null type ${varType} must not be null.`,
varDefNode,
),
);
continue;
if (value === undefined) {
if (varDefNode.defaultValue) {
coerced[varName] = coerceInputLiteral(varDefNode.defaultValue, varType);
continue;
} else if (!isNonNullType(varType)) {
// Non-provided values for nullable variables are omitted.
continue;
}
}

sources[varName] = { variable: varDefNode, type: varType, value };
coerced[varName] = coerceInputValue(
value,
varType,
(path, invalidValue, error) => {
let prefix =
`Variable "$${varName}" got invalid value ` + inspect(invalidValue);
if (path.length > 0) {
prefix += ` at "${varName}${printPathArray(path)}"`;
}
const coercedValue = coerceInputValue(value, varType);
if (coercedValue !== undefined) {
coerced[varName] = coercedValue;
} else {
validateInputValue(value, varType, (error, path) => {
onError(
new GraphQLError(
prefix + '; ' + error.message,
`Variable "$${varName}" has invalid value${printPathArray(path)}: ${
error.message
}`,
varDefNode,
undefined,
undefined,
undefined,
error.originalError,
),
);
},
);
});
}
}

return { sources, coerced };
Expand Down Expand Up @@ -193,65 +180,52 @@ export function getArgumentValues(
const argType = argDef.type;
const argumentNode = argNodeMap[name];

if (!argumentNode) {
if (!argumentNode && isRequiredArgument(argDef)) {
// Note: ProvidedRequiredArgumentsRule validation should catch this before
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new GraphQLError(
`Argument ${argDef} of required type ${argType} was not provided.`,
node,
);
}

// Variables without a value are treated as if no argument was provided if
// the argument is not required.
if (
!argumentNode ||
(argumentNode.value.kind === Kind.VARIABLE &&
variableValues?.coerced[argumentNode.value.name.value] === undefined &&
!isRequiredArgument(argDef))
) {
if (argDef.defaultValue) {
coercedValues[name] = coerceDefaultValue(
argDef.defaultValue,
argDef.type,
);
} else if (isNonNullType(argType)) {
throw new GraphQLError(
`Argument ${argDef} of required type ${argType} was not provided.`,
node,
);
}
continue;
}

const valueNode = argumentNode.value;
let isNull = valueNode.kind === Kind.NULL;

if (valueNode.kind === Kind.VARIABLE) {
const variableName = valueNode.name.value;
if (
variableValues == null ||
variableValues.coerced[variableName] === undefined
) {
if (argDef.defaultValue) {
coercedValues[name] = coerceDefaultValue(
argDef.defaultValue,
argDef.type,
);
} else if (isNonNullType(argType)) {
throw new GraphQLError(
`Argument ${argDef} of required type ${argType} ` +
`was provided the variable "$${variableName}" which was not provided a runtime value.`,
valueNode,
);
}
continue;
}
isNull = variableValues.coerced[variableName] == null;
}

if (isNull && isNonNullType(argType)) {
throw new GraphQLError(
`Argument ${argDef} of non-null type ${argType} must not be null.`,
valueNode,
);
}

const coercedValue = coerceInputLiteral(valueNode, argType, variableValues);
if (coercedValue === undefined) {
// Note: ValuesOfCorrectTypeRule validation should catch this before
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new GraphQLError(
`Argument ${argDef} of type ${argType} has invalid value ${print(
valueNode,
)}.`,
validateInputLiteral(
valueNode,
argType,
variableValues,
(error, path) => {
error.message = `Argument ${argDef} has invalid value${printPathArray(
path,
)}: ${error.message}`;
throw error;
},
);
// istanbul ignore next (validateInputLiteral should throw)
invariant(false, 'Invalid argument');
}
coercedValues[name] = coercedValue;
}
Expand Down
Loading