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

RFC: Default value validation & coercion #3814

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions cspell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ ignoreRegExpList:

words:
- graphiql
- sublinks
- instanceof
- uncoerce
- uncoerced

# Different names used inside tests
- Skywalker
Expand Down
8 changes: 4 additions & 4 deletions src/execution/__tests__/nonnull-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ describe('Execute: handles non-nullable types', () => {
errors: [
{
message:
'Argument "cannotBeNull" of required type "String!" was not provided.',
'Argument Query.withNonNullArg(cannotBeNull:) of required type String! was not provided.',
locations: [{ line: 3, column: 13 }],
path: ['withNonNullArg'],
},
Expand All @@ -647,7 +647,7 @@ describe('Execute: handles non-nullable types', () => {
errors: [
{
message:
'Argument "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 @@ -677,7 +677,7 @@ describe('Execute: handles non-nullable types', () => {
errors: [
{
message:
'Argument "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 @@ -705,7 +705,7 @@ describe('Execute: handles non-nullable types', () => {
errors: [
{
message:
'Argument "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
2 changes: 1 addition & 1 deletion src/execution/__tests__/subscribe-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ describe('Subscription Initialization Phase', () => {
errors: [
{
message:
'Variable "$arg" got invalid value "meow"; Int cannot represent non-integer value: "meow"',
'Variable "$arg" has invalid value: Int cannot represent non-integer value: "meow"',
locations: [{ line: 2, column: 21 }],
},
],
Expand Down
47 changes: 24 additions & 23 deletions src/execution/__tests__/variables-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ const TestType = new GraphQLObjectType({
}),
fieldWithNestedInputObject: fieldWithInputArg({
type: TestNestedInputObject,
defaultValue: 'Hello World',
}),
list: fieldWithInputArg({ type: new GraphQLList(GraphQLString) }),
nnList: fieldWithInputArg({
Expand Down Expand Up @@ -226,7 +225,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Argument "input" 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 @@ -262,9 +261,10 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Argument "input" has invalid value { c: "foo", e: "bar" }.',
'Argument TestType.fieldWithObjectInput(input:) has invalid value at .e: FaultyScalarErrorMessage',
path: ['fieldWithObjectInput'],
locations: [{ line: 3, column: 41 }],
extensions: { code: 'FaultyScalarErrorExtensionCode' },
},
],
});
Expand Down Expand Up @@ -418,7 +418,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" got invalid value "SerializedValue" at "input.e"; FaultyScalarErrorMessage',
'Variable "$input" has invalid value at .e: FaultyScalarErrorMessage',
locations: [{ line: 2, column: 16 }],
extensions: { code: 'FaultyScalarErrorExtensionCode' },
},
Expand All @@ -434,7 +434,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 @@ -448,7 +448,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 @@ -462,7 +462,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 @@ -481,12 +481,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 @@ -503,7 +503,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 @@ -678,7 +678,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 @@ -697,7 +697,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 @@ -743,7 +743,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Argument "input" of required type "String!" was not provided.',
'Argument TestType.fieldWithNonNullableStringInput(input:) of required type String! was not provided.',
locations: [{ line: 1, column: 3 }],
path: ['fieldWithNonNullableStringInput'],
},
Expand All @@ -763,7 +763,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 @@ -791,7 +791,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Argument "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 @@ -846,7 +846,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 @@ -909,7 +909,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 @@ -928,7 +928,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 @@ -958,7 +958,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 @@ -977,7 +977,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" expected value of type "TestType!" which cannot be used as an input type.',
'Variable "$input" expected value of type TestType! which cannot be used as an input type.',
locations: [{ line: 2, column: 24 }],
},
],
Expand All @@ -996,7 +996,7 @@ describe('Execute: Handles inputs', () => {
errors: [
{
message:
'Variable "$input" expected value of type "UnknownType!" which cannot be used as an input type.',
'Variable "$input" expected value of type UnknownType! which cannot be used as an input type.',
locations: [{ line: 2, column: 24 }],
},
],
Expand Down Expand Up @@ -1042,7 +1042,8 @@ describe('Execute: Handles inputs', () => {
},
errors: [
{
message: 'Argument "input" has invalid value WRONG_TYPE.',
message:
'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 @@ -1082,7 +1083,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
11 changes: 6 additions & 5 deletions src/execution/collectFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { GraphQLSchema } from '../type/schema.js';

import { typeFromAST } from '../utilities/typeFromAST.js';

import type { VariableValues } from './values.js';
import { getDirectiveValues } from './values.js';

export type FieldGroup = ReadonlyArray<FieldNode>;
Expand Down Expand Up @@ -52,7 +53,7 @@ export interface FieldsAndPatches {
export function collectFields(
schema: GraphQLSchema,
fragments: ObjMap<FragmentDefinitionNode>,
variableValues: { [variable: string]: unknown },
variableValues: VariableValues,
runtimeType: GraphQLObjectType,
operation: OperationDefinitionNode,
): FieldsAndPatches {
Expand Down Expand Up @@ -86,7 +87,7 @@ export function collectFields(
export function collectSubfields(
schema: GraphQLSchema,
fragments: ObjMap<FragmentDefinitionNode>,
variableValues: { [variable: string]: unknown },
variableValues: VariableValues,
operation: OperationDefinitionNode,
returnType: GraphQLObjectType,
fieldGroup: FieldGroup,
Expand Down Expand Up @@ -122,7 +123,7 @@ export function collectSubfields(
function collectFieldsImpl(
schema: GraphQLSchema,
fragments: ObjMap<FragmentDefinitionNode>,
variableValues: { [variable: string]: unknown },
variableValues: VariableValues,
operation: OperationDefinitionNode,
runtimeType: GraphQLObjectType,
selectionSet: SelectionSetNode,
Expand Down Expand Up @@ -248,7 +249,7 @@ function collectFieldsImpl(
*/
function getDeferValues(
operation: OperationDefinitionNode,
variableValues: { [variable: string]: unknown },
variableValues: VariableValues,
node: FragmentSpreadNode | InlineFragmentNode,
): undefined | { label: string | undefined } {
const defer = getDirectiveValues(GraphQLDeferDirective, node, variableValues);
Expand Down Expand Up @@ -276,7 +277,7 @@ function getDeferValues(
* directives, where `@skip` has higher precedence than `@include`.
*/
function shouldIncludeNode(
variableValues: { [variable: string]: unknown },
variableValues: VariableValues,
node: FragmentSpreadNode | FieldNode | InlineFragmentNode,
): boolean {
const skip = getDirectiveValues(GraphQLSkipDirective, node, variableValues);
Expand Down
13 changes: 7 additions & 6 deletions src/execution/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
collectSubfields as _collectSubfields,
} from './collectFields.js';
import { mapAsyncIterable } from './mapAsyncIterable.js';
import type { VariableValues } from './values.js';
import {
getArgumentValues,
getDirectiveValues,
Expand Down Expand Up @@ -116,7 +117,7 @@ export interface ExecutionContext {
rootValue: unknown;
contextValue: unknown;
operation: OperationDefinitionNode;
variableValues: { [variable: string]: unknown };
variableValues: VariableValues;
fieldResolver: GraphQLFieldResolver<any, any>;
typeResolver: GraphQLTypeResolver<any, any>;
subscribeFieldResolver: GraphQLFieldResolver<any, any>;
Expand Down Expand Up @@ -482,15 +483,15 @@ export function buildExecutionContext(
/* c8 ignore next */
const variableDefinitions = operation.variableDefinitions ?? [];

const coercedVariableValues = getVariableValues(
const variableValuesOrErrors = getVariableValues(
schema,
variableDefinitions,
rawVariableValues ?? {},
{ maxErrors: 50 },
);

if (coercedVariableValues.errors) {
return coercedVariableValues.errors;
if (variableValuesOrErrors.errors) {
return variableValuesOrErrors.errors;
}

return {
Expand All @@ -499,7 +500,7 @@ export function buildExecutionContext(
rootValue,
contextValue,
operation,
variableValues: coercedVariableValues.coerced,
variableValues: variableValuesOrErrors.variableValues,
fieldResolver: fieldResolver ?? defaultFieldResolver,
typeResolver: typeResolver ?? defaultTypeResolver,
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
Expand Down Expand Up @@ -810,7 +811,7 @@ export function buildResolveInfo(
fragments: exeContext.fragments,
rootValue: exeContext.rootValue,
operation: exeContext.operation,
variableValues: exeContext.variableValues,
variableValues: exeContext.variableValues.coerced,
};
}

Expand Down