diff --git a/src/execution/getVariableSignature.ts b/src/execution/getVariableSignature.ts index 984b816c9b..d23c310eb5 100644 --- a/src/execution/getVariableSignature.ts +++ b/src/execution/getVariableSignature.ts @@ -6,8 +6,8 @@ import { print } from '../language/printer.js'; import { isInputType } from '../type/definition.js'; import type { GraphQLInputType, GraphQLSchema } from '../type/index.js'; +import { coerceInputLiteral } from '../utilities/coerceInputValue.js'; import { typeFromAST } from '../utilities/typeFromAST.js'; -import { valueFromAST } from '../utilities/valueFromAST.js'; /** * A GraphQLVariableSignature is required to coerce a variable value. @@ -38,9 +38,13 @@ export function getVariableSignature( ); } + const defaultValue = varDefNode.defaultValue; + return { name: varName, type: varType, - defaultValue: valueFromAST(varDefNode.defaultValue, varType), + defaultValue: defaultValue + ? coerceInputLiteral(varDefNode.defaultValue, varType) + : undefined, }; } diff --git a/src/execution/values.ts b/src/execution/values.ts index 23863fd107..c153a616a5 100644 --- a/src/execution/values.ts +++ b/src/execution/values.ts @@ -19,8 +19,10 @@ import { isNonNullType } from '../type/definition.js'; import type { GraphQLDirective } from '../type/directives.js'; import type { GraphQLSchema } from '../type/schema.js'; -import { coerceInputValue } from '../utilities/coerceInputValue.js'; -import { valueFromAST } from '../utilities/valueFromAST.js'; +import { + coerceInputLiteral, + coerceInputValue, +} from '../utilities/coerceInputValue.js'; import type { FragmentVariables } from './collectFields.js'; import type { GraphQLVariableSignature } from './getVariableSignature.js'; @@ -217,11 +219,11 @@ export function experimentalGetArgumentValues( ); } - const coercedValue = valueFromAST( + const coercedValue = coerceInputLiteral( valueNode, argType, variableValues, - fragmentVariables?.values, + fragmentVariables, ); if (coercedValue === undefined) { // Note: ValuesOfCorrectTypeRule validation should catch this before diff --git a/src/index.ts b/src/index.ts index 3d82bffdda..cffd892db5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -440,6 +440,7 @@ export { // Create a GraphQLType from a GraphQL language AST. typeFromAST, // Create a JavaScript value from a GraphQL language AST with a Type. + /** @deprecated use `coerceInputLiteral()` instead - will be removed in v18 */ valueFromAST, // Create a JavaScript value from a GraphQL language AST without a Type. valueFromASTUntyped, @@ -450,6 +451,8 @@ export { visitWithTypeInfo, // Coerces a JavaScript value to a GraphQL type, or produces errors. coerceInputValue, + // Coerces a GraphQL literal (AST) to a GraphQL type, or returns undefined. + coerceInputLiteral, // Concatenates multiple AST together. concatAST, // Separates an AST into an AST per Operation. diff --git a/src/language/parser.ts b/src/language/parser.ts index 0bda2b0c8f..30102d2fb6 100644 --- a/src/language/parser.ts +++ b/src/language/parser.ts @@ -156,8 +156,6 @@ export function parse( * * This is useful within tools that operate upon GraphQL Values directly and * in isolation of complete GraphQL documents. - * - * Consider providing the results to the utility function: valueFromAST(). */ export function parseValue( source: string | Source, diff --git a/src/utilities/__tests__/coerceInputValue-test.ts b/src/utilities/__tests__/coerceInputValue-test.ts index b0b8306fa0..15467e8456 100644 --- a/src/utilities/__tests__/coerceInputValue-test.ts +++ b/src/utilities/__tests__/coerceInputValue-test.ts @@ -1,6 +1,13 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; +import { identityFunc } from '../../jsutils/identityFunc.js'; +import { invariant } from '../../jsutils/invariant.js'; +import type { ObjMap } from '../../jsutils/ObjMap.js'; + +import { parseValue } from '../../language/parser.js'; +import { print } from '../../language/printer.js'; + import type { GraphQLInputType } from '../../type/definition.js'; import { GraphQLEnumType, @@ -9,9 +16,15 @@ import { GraphQLNonNull, GraphQLScalarType, } from '../../type/definition.js'; -import { GraphQLInt } from '../../type/scalars.js'; +import { + GraphQLBoolean, + GraphQLFloat, + GraphQLID, + GraphQLInt, + GraphQLString, +} from '../../type/scalars.js'; -import { coerceInputValue } from '../coerceInputValue.js'; +import { coerceInputLiteral, coerceInputValue } from '../coerceInputValue.js'; interface CoerceResult { value: unknown; @@ -533,3 +546,264 @@ describe('coerceInputValue', () => { }); }); }); + +describe('coerceInputLiteral', () => { + function test( + valueText: string, + type: GraphQLInputType, + expected: unknown, + variables?: ObjMap, + ) { + const ast = parseValue(valueText); + const value = coerceInputLiteral(ast, type, variables); + expect(value).to.deep.equal(expected); + } + + function testWithVariables( + variables: ObjMap, + valueText: string, + type: GraphQLInputType, + expected: unknown, + ) { + test(valueText, type, expected, variables); + } + + it('converts according to input coercion rules', () => { + test('true', GraphQLBoolean, true); + test('false', GraphQLBoolean, false); + test('123', GraphQLInt, 123); + test('123', GraphQLFloat, 123); + test('123.456', GraphQLFloat, 123.456); + test('"abc123"', GraphQLString, 'abc123'); + test('123456', GraphQLID, '123456'); + test('"123456"', GraphQLID, '123456'); + }); + + it('does not convert when input coercion rules reject a value', () => { + test('123', GraphQLBoolean, undefined); + test('123.456', GraphQLInt, undefined); + test('true', GraphQLInt, undefined); + test('"123"', GraphQLInt, undefined); + test('"123"', GraphQLFloat, undefined); + test('123', GraphQLString, undefined); + test('true', GraphQLString, undefined); + test('123.456', GraphQLString, undefined); + test('123.456', GraphQLID, undefined); + }); + + it('convert using parseLiteral from a custom scalar type', () => { + const passthroughScalar = new GraphQLScalarType({ + name: 'PassthroughScalar', + parseLiteral(node) { + invariant(node.kind === 'StringValue'); + return node.value; + }, + parseValue: identityFunc, + }); + + test('"value"', passthroughScalar, 'value'); + + const printScalar = new GraphQLScalarType({ + name: 'PrintScalar', + parseLiteral(node) { + return `~~~${print(node)}~~~`; + }, + parseValue: identityFunc, + }); + + test('"value"', printScalar, '~~~"value"~~~'); + + const throwScalar = new GraphQLScalarType({ + name: 'ThrowScalar', + parseLiteral() { + throw new Error('Test'); + }, + parseValue: identityFunc, + }); + + test('value', throwScalar, undefined); + + const returnUndefinedScalar = new GraphQLScalarType({ + name: 'ReturnUndefinedScalar', + parseLiteral() { + return undefined; + }, + parseValue: identityFunc, + }); + + test('value', returnUndefinedScalar, undefined); + }); + + it('converts enum values according to input coercion rules', () => { + const testEnum = new GraphQLEnumType({ + name: 'TestColor', + values: { + RED: { value: 1 }, + GREEN: { value: 2 }, + BLUE: { value: 3 }, + NULL: { value: null }, + NAN: { value: NaN }, + NO_CUSTOM_VALUE: { value: undefined }, + }, + }); + + test('RED', testEnum, 1); + test('BLUE', testEnum, 3); + test('3', testEnum, undefined); + test('"BLUE"', testEnum, undefined); + test('null', testEnum, null); + test('NULL', testEnum, null); + test('NULL', new GraphQLNonNull(testEnum), null); + test('NAN', testEnum, NaN); + test('NO_CUSTOM_VALUE', testEnum, 'NO_CUSTOM_VALUE'); + }); + + // Boolean! + const nonNullBool = new GraphQLNonNull(GraphQLBoolean); + // [Boolean] + const listOfBool = new GraphQLList(GraphQLBoolean); + // [Boolean!] + const listOfNonNullBool = new GraphQLList(nonNullBool); + // [Boolean]! + const nonNullListOfBool = new GraphQLNonNull(listOfBool); + // [Boolean!]! + const nonNullListOfNonNullBool = new GraphQLNonNull(listOfNonNullBool); + + it('coerces to null unless non-null', () => { + test('null', GraphQLBoolean, null); + test('null', nonNullBool, undefined); + }); + + it('coerces lists of values', () => { + test('true', listOfBool, [true]); + test('123', listOfBool, undefined); + test('null', listOfBool, null); + test('[true, false]', listOfBool, [true, false]); + test('[true, 123]', listOfBool, undefined); + test('[true, null]', listOfBool, [true, null]); + test('{ true: true }', listOfBool, undefined); + }); + + it('coerces non-null lists of values', () => { + test('true', nonNullListOfBool, [true]); + test('123', nonNullListOfBool, undefined); + test('null', nonNullListOfBool, undefined); + test('[true, false]', nonNullListOfBool, [true, false]); + test('[true, 123]', nonNullListOfBool, undefined); + test('[true, null]', nonNullListOfBool, [true, null]); + }); + + it('coerces lists of non-null values', () => { + test('true', listOfNonNullBool, [true]); + test('123', listOfNonNullBool, undefined); + test('null', listOfNonNullBool, null); + test('[true, false]', listOfNonNullBool, [true, false]); + test('[true, 123]', listOfNonNullBool, undefined); + test('[true, null]', listOfNonNullBool, undefined); + }); + + it('coerces non-null lists of non-null values', () => { + test('true', nonNullListOfNonNullBool, [true]); + test('123', nonNullListOfNonNullBool, undefined); + test('null', nonNullListOfNonNullBool, undefined); + test('[true, false]', nonNullListOfNonNullBool, [true, false]); + test('[true, 123]', nonNullListOfNonNullBool, undefined); + test('[true, null]', nonNullListOfNonNullBool, undefined); + }); + + it('uses default values for unprovided fields', () => { + const type = new GraphQLInputObjectType({ + name: 'TestInput', + fields: { + int: { type: GraphQLInt, defaultValue: 42 }, + }, + }); + + test('{}', type, { int: 42 }); + }); + + const testInputObj = new GraphQLInputObjectType({ + name: 'TestInput', + fields: { + int: { type: GraphQLInt, defaultValue: 42 }, + bool: { type: GraphQLBoolean }, + requiredBool: { type: nonNullBool }, + }, + }); + const testOneOfInputObj = new GraphQLInputObjectType({ + name: 'TestOneOfInput', + fields: { + a: { type: GraphQLString }, + b: { type: GraphQLString }, + }, + isOneOf: true, + }); + + it('coerces input objects according to input coercion rules', () => { + test('null', testInputObj, null); + test('123', testInputObj, undefined); + test('[]', testInputObj, undefined); + test('{ requiredBool: true }', testInputObj, { + int: 42, + requiredBool: true, + }); + test('{ int: null, requiredBool: true }', testInputObj, { + int: null, + requiredBool: true, + }); + test('{ int: 123, requiredBool: false }', testInputObj, { + int: 123, + requiredBool: false, + }); + test('{ bool: true, requiredBool: false }', testInputObj, { + int: 42, + bool: true, + requiredBool: false, + }); + test('{ int: true, requiredBool: true }', testInputObj, undefined); + test('{ requiredBool: null }', testInputObj, undefined); + test('{ bool: true }', testInputObj, undefined); + test('{ requiredBool: true, unknown: 123 }', testInputObj, undefined); + test('{ a: "abc" }', testOneOfInputObj, { + a: 'abc', + }); + test('{ b: "def" }', testOneOfInputObj, { + b: 'def', + }); + test('{ a: "abc", b: null }', testOneOfInputObj, undefined); + test('{ a: null }', testOneOfInputObj, undefined); + test('{ a: 1 }', testOneOfInputObj, undefined); + test('{ a: "abc", b: "def" }', testOneOfInputObj, undefined); + test('{}', testOneOfInputObj, undefined); + test('{ c: "abc" }', testOneOfInputObj, undefined); + }); + + it('accepts variable values assuming already coerced', () => { + test('$var', GraphQLBoolean, undefined); + testWithVariables({ var: true }, '$var', GraphQLBoolean, true); + testWithVariables({ var: null }, '$var', GraphQLBoolean, null); + testWithVariables({ var: null }, '$var', nonNullBool, undefined); + }); + + it('asserts variables are provided as items in lists', () => { + test('[ $foo ]', listOfBool, [null]); + test('[ $foo ]', listOfNonNullBool, undefined); + testWithVariables({ foo: true }, '[ $foo ]', listOfNonNullBool, [true]); + // Note: variables are expected to have already been coerced, so we + // do not expect the singleton wrapping behavior for variables. + testWithVariables({ foo: true }, '$foo', listOfNonNullBool, true); + testWithVariables({ foo: [true] }, '$foo', listOfNonNullBool, [true]); + }); + + it('omits input object fields for unprovided variables', () => { + test('{ int: $foo, bool: $foo, requiredBool: true }', testInputObj, { + int: 42, + requiredBool: true, + }); + test('{ requiredBool: $foo }', testInputObj, undefined); + testWithVariables({ foo: true }, '{ requiredBool: $foo }', testInputObj, { + int: 42, + requiredBool: true, + }); + }); +}); diff --git a/src/utilities/__tests__/valueFromAST-test.ts b/src/utilities/__tests__/valueFromAST-test.ts index e287d91691..2bed756925 100644 --- a/src/utilities/__tests__/valueFromAST-test.ts +++ b/src/utilities/__tests__/valueFromAST-test.ts @@ -24,6 +24,7 @@ import { import { valueFromAST } from '../valueFromAST.js'; +/** @deprecated use `coerceInputLiteral()` instead - will be removed in v18 */ describe('valueFromAST', () => { function expectValueFrom( valueText: string, diff --git a/src/utilities/buildClientSchema.ts b/src/utilities/buildClientSchema.ts index 9e2005de93..c109b18fa7 100644 --- a/src/utilities/buildClientSchema.ts +++ b/src/utilities/buildClientSchema.ts @@ -3,7 +3,7 @@ import { inspect } from '../jsutils/inspect.js'; import { isObjectLike } from '../jsutils/isObjectLike.js'; import { keyValMap } from '../jsutils/keyValMap.js'; -import { parseValue } from '../language/parser.js'; +import { parseConstValue } from '../language/parser.js'; import type { GraphQLFieldConfig, @@ -32,6 +32,7 @@ import { specifiedScalarTypes } from '../type/scalars.js'; import type { GraphQLSchemaValidationOptions } from '../type/schema.js'; import { GraphQLSchema } from '../type/schema.js'; +import { coerceInputLiteral } from './coerceInputValue.js'; import type { IntrospectionDirective, IntrospectionEnumType, @@ -47,7 +48,6 @@ import type { IntrospectionTypeRef, IntrospectionUnionType, } from './getIntrospectionQuery.js'; -import { valueFromAST } from './valueFromAST.js'; /** * Build a GraphQLSchema for use by client tools. @@ -376,7 +376,10 @@ export function buildClientSchema( const defaultValue = inputValueIntrospection.defaultValue != null - ? valueFromAST(parseValue(inputValueIntrospection.defaultValue), type) + ? coerceInputLiteral( + parseConstValue(inputValueIntrospection.defaultValue), + type, + ) : undefined; return { description: inputValueIntrospection.description, diff --git a/src/utilities/coerceInputValue.ts b/src/utilities/coerceInputValue.ts index 7b3a61926c..88c97c8405 100644 --- a/src/utilities/coerceInputValue.ts +++ b/src/utilities/coerceInputValue.ts @@ -3,6 +3,8 @@ import { inspect } from '../jsutils/inspect.js'; import { invariant } from '../jsutils/invariant.js'; import { isIterableObject } from '../jsutils/isIterableObject.js'; import { isObjectLike } from '../jsutils/isObjectLike.js'; +import type { Maybe } from '../jsutils/Maybe.js'; +import type { ObjMap } from '../jsutils/ObjMap.js'; import type { Path } from '../jsutils/Path.js'; import { addPath, pathToArray } from '../jsutils/Path.js'; import { printPathArray } from '../jsutils/printPathArray.js'; @@ -10,14 +12,21 @@ import { suggestionList } from '../jsutils/suggestionList.js'; import { GraphQLError } from '../error/GraphQLError.js'; +import type { ValueNode, VariableNode } from '../language/ast.js'; +import { Kind } from '../language/kinds.js'; + import type { GraphQLInputType } from '../type/definition.js'; import { + assertLeafType, isInputObjectType, isLeafType, isListType, isNonNullType, + isRequiredInputField, } from '../type/definition.js'; +import type { FragmentVariables } from '../execution/collectFields.js'; + type OnErrorCB = ( path: ReadonlyArray, invalidValue: unknown, @@ -204,3 +213,169 @@ function coerceInputValueImpl( // Not reachable, all possible types have been considered. invariant(false, 'Unexpected input type: ' + inspect(type)); } + +/** + * Produces a coerced "internal" JavaScript value given a GraphQL Value AST. + * + * Returns `undefined` when the value could not be validly coerced according to + * the provided type. + */ +export function coerceInputLiteral( + valueNode: ValueNode, + type: GraphQLInputType, + variableValues?: Maybe>, + fragmentVariableValues?: Maybe, +): unknown { + if (valueNode.kind === Kind.VARIABLE) { + const variableValue = getVariableValue( + valueNode, + variableValues, + fragmentVariableValues, + ); + if (variableValue == null && isNonNullType(type)) { + return; // Invalid: intentionally return no value. + } + // Note: This does no further checking that this variable is correct. + // This assumes validated has checked this variable is of the correct type. + return variableValue; + } + + if (isNonNullType(type)) { + if (valueNode.kind === Kind.NULL) { + return; // Invalid: intentionally return no value. + } + return coerceInputLiteral( + valueNode, + type.ofType, + variableValues, + fragmentVariableValues, + ); + } + + if (valueNode.kind === Kind.NULL) { + return null; // Explicitly return the value null. + } + + if (isListType(type)) { + if (valueNode.kind !== Kind.LIST) { + // Lists accept a non-list value as a list of one. + const itemValue = coerceInputLiteral( + valueNode, + type.ofType, + variableValues, + fragmentVariableValues, + ); + if (itemValue === undefined) { + return; // Invalid: intentionally return no value. + } + return [itemValue]; + } + const coercedValue: Array = []; + for (const itemNode of valueNode.values) { + let itemValue = coerceInputLiteral( + itemNode, + type.ofType, + variableValues, + fragmentVariableValues, + ); + if (itemValue === undefined) { + if ( + itemNode.kind === Kind.VARIABLE && + getVariableValue(itemNode, variableValues, fragmentVariableValues) == + null && + !isNonNullType(type.ofType) + ) { + // A missing variable within a list is coerced to null. + itemValue = null; + } else { + return; // Invalid: intentionally return no value. + } + } + coercedValue.push(itemValue); + } + return coercedValue; + } + + if (isInputObjectType(type)) { + if (valueNode.kind !== Kind.OBJECT) { + return; // Invalid: intentionally return no value. + } + + const coercedValue: { [field: string]: unknown } = {}; + const fieldDefs = type.getFields(); + const hasUndefinedField = valueNode.fields.some( + (field) => !Object.hasOwn(fieldDefs, field.name.value), + ); + if (hasUndefinedField) { + return; // Invalid: intentionally return no value. + } + const fieldNodes = new Map( + valueNode.fields.map((field) => [field.name.value, field]), + ); + for (const field of Object.values(fieldDefs)) { + const fieldNode = fieldNodes.get(field.name); + if ( + !fieldNode || + (fieldNode.value.kind === Kind.VARIABLE && + getVariableValue( + fieldNode.value, + variableValues, + fragmentVariableValues, + ) == null) + ) { + if (isRequiredInputField(field)) { + return; // Invalid: intentionally return no value. + } + if (field.defaultValue !== undefined) { + coercedValue[field.name] = field.defaultValue; + } + } else { + const fieldValue = coerceInputLiteral( + fieldNode.value, + field.type, + variableValues, + fragmentVariableValues, + ); + if (fieldValue === undefined) { + return; // Invalid: intentionally return no value. + } + coercedValue[field.name] = fieldValue; + } + } + + if (type.isOneOf) { + const keys = Object.keys(coercedValue); + if (keys.length !== 1) { + return; // Invalid: not exactly one key, intentionally return no value. + } + + if (coercedValue[keys[0]] === null) { + return; // Invalid: value not non-null, intentionally return no value. + } + } + + return coercedValue; + } + + const leafType = assertLeafType(type); + + try { + return leafType.parseLiteral(valueNode, variableValues); + } catch (_error) { + // Invalid: ignore error and intentionally return no value. + } +} + +// Retrieves the variable value for the given variable node. +function getVariableValue( + variableNode: VariableNode, + variableValues: Maybe>, + fragmentVariableValues: Maybe | undefined, +): unknown { + const varName = variableNode.name.value; + if (fragmentVariableValues?.signatures[varName]) { + return fragmentVariableValues.values[varName]; + } + + return variableValues?.[varName]; +} diff --git a/src/utilities/extendSchema.ts b/src/utilities/extendSchema.ts index 0733aad14e..d18b53d028 100644 --- a/src/utilities/extendSchema.ts +++ b/src/utilities/extendSchema.ts @@ -83,7 +83,7 @@ import { assertValidSDLExtension } from '../validation/validate.js'; import { getDirectiveValues } from '../execution/values.js'; -import { valueFromAST } from './valueFromAST.js'; +import { coerceInputLiteral } from './coerceInputValue.js'; interface Options extends GraphQLSchemaValidationOptions { /** @@ -535,7 +535,9 @@ export function extendSchemaImpl( argConfigMap[arg.name.value] = { type, description: arg.description?.value, - defaultValue: valueFromAST(arg.defaultValue, type), + defaultValue: arg.defaultValue + ? coerceInputLiteral(arg.defaultValue, type) + : undefined, deprecationReason: getDeprecationReason(arg), astNode: arg, }; @@ -562,7 +564,9 @@ export function extendSchemaImpl( inputFieldMap[field.name.value] = { type, description: field.description?.value, - defaultValue: valueFromAST(field.defaultValue, type), + defaultValue: field.defaultValue + ? coerceInputLiteral(field.defaultValue, type) + : undefined, deprecationReason: getDeprecationReason(field), astNode: field, }; diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 6968dca4d3..dc678adf95 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -57,7 +57,10 @@ export { export { typeFromAST } from './typeFromAST.js'; // Create a JavaScript value from a GraphQL language AST with a type. -export { valueFromAST } from './valueFromAST.js'; +export { + /** @deprecated use `coerceInputLiteral()` instead - will be removed in v18 */ + valueFromAST, +} from './valueFromAST.js'; // Create a JavaScript value from a GraphQL language AST without a type. export { valueFromASTUntyped } from './valueFromASTUntyped.js'; @@ -68,8 +71,12 @@ export { astFromValue } from './astFromValue.js'; // A helper to use within recursive-descent visitors which need to be aware of the GraphQL type system. export { TypeInfo, visitWithTypeInfo } from './TypeInfo.js'; -// Coerces a JavaScript value to a GraphQL type, or produces errors. -export { coerceInputValue } from './coerceInputValue.js'; +export { + // Coerces a JavaScript value to a GraphQL type, or produces errors. + coerceInputValue, + // Coerces a GraphQL literal (AST) to a GraphQL type, or returns undefined. + coerceInputLiteral, +} from './coerceInputValue.js'; // Concatenates multiple AST together. export { concatAST } from './concatAST.js'; diff --git a/src/utilities/valueFromAST.ts b/src/utilities/valueFromAST.ts index 3aec3f272f..add9153680 100644 --- a/src/utilities/valueFromAST.ts +++ b/src/utilities/valueFromAST.ts @@ -33,12 +33,12 @@ import { * | Enum Value | Unknown | * | NullValue | null | * + * @deprecated use `coerceInputLiteral()` instead - will be removed in v18 */ export function valueFromAST( valueNode: Maybe, type: GraphQLInputType, variables?: Maybe>, - fragmentVariables?: Maybe>, ): unknown { if (!valueNode) { // When there is no node, then there is also no value. @@ -48,8 +48,7 @@ export function valueFromAST( if (valueNode.kind === Kind.VARIABLE) { const variableName = valueNode.name.value; - const variableValue = - fragmentVariables?.[variableName] ?? variables?.[variableName]; + const variableValue = variables?.[variableName]; if (variableValue === undefined) { // No valid return value. return; @@ -67,7 +66,7 @@ export function valueFromAST( if (valueNode.kind === Kind.NULL) { return; // Invalid: intentionally return no value. } - return valueFromAST(valueNode, type.ofType, variables, fragmentVariables); + return valueFromAST(valueNode, type.ofType, variables); } if (valueNode.kind === Kind.NULL) { @@ -80,7 +79,7 @@ export function valueFromAST( if (valueNode.kind === Kind.LIST) { const coercedValues = []; for (const itemNode of valueNode.values) { - if (isMissingVariable(itemNode, variables, fragmentVariables)) { + if (isMissingVariable(itemNode, variables)) { // If an array contains a missing variable, it is either coerced to // null or if the item type is non-null, it considered invalid. if (isNonNullType(itemType)) { @@ -88,12 +87,7 @@ export function valueFromAST( } coercedValues.push(null); } else { - const itemValue = valueFromAST( - itemNode, - itemType, - variables, - fragmentVariables, - ); + const itemValue = valueFromAST(itemNode, itemType, variables); if (itemValue === undefined) { return; // Invalid: intentionally return no value. } @@ -102,12 +96,7 @@ export function valueFromAST( } return coercedValues; } - const coercedValue = valueFromAST( - valueNode, - itemType, - variables, - fragmentVariables, - ); + const coercedValue = valueFromAST(valueNode, itemType, variables); if (coercedValue === undefined) { return; // Invalid: intentionally return no value. } @@ -124,10 +113,7 @@ export function valueFromAST( ); for (const field of Object.values(type.getFields())) { const fieldNode = fieldNodes.get(field.name); - if ( - fieldNode == null || - isMissingVariable(fieldNode.value, variables, fragmentVariables) - ) { + if (fieldNode == null || isMissingVariable(fieldNode.value, variables)) { if (field.defaultValue !== undefined) { coercedObj[field.name] = field.defaultValue; } else if (isNonNullType(field.type)) { @@ -135,12 +121,7 @@ export function valueFromAST( } continue; } - const fieldValue = valueFromAST( - fieldNode.value, - field.type, - variables, - fragmentVariables, - ); + const fieldValue = valueFromAST(fieldNode.value, field.type, variables); if (fieldValue === undefined) { return; // Invalid: intentionally return no value. } @@ -186,12 +167,9 @@ export function valueFromAST( function isMissingVariable( valueNode: ValueNode, variables: Maybe>, - fragmentVariables: Maybe>, ): boolean { return ( valueNode.kind === Kind.VARIABLE && - (fragmentVariables == null || - fragmentVariables[valueNode.name.value] === undefined) && (variables == null || variables[valueNode.name.value] === undefined) ); } diff --git a/src/utilities/valueFromASTUntyped.ts b/src/utilities/valueFromASTUntyped.ts index 87af11a9a3..4c8e197821 100644 --- a/src/utilities/valueFromASTUntyped.ts +++ b/src/utilities/valueFromASTUntyped.ts @@ -8,8 +8,8 @@ import { Kind } from '../language/kinds.js'; /** * Produces a JavaScript value given a GraphQL Value AST. * - * Unlike `valueFromAST()`, no type is provided. The resulting JavaScript value - * will reflect the provided GraphQL value AST. + * No type is provided. The resulting JavaScript value will reflect the + * provided GraphQL value AST. * * | GraphQL Value | JavaScript Value | * | -------------------- | ---------------- |