diff --git a/src/execution/execute.ts b/src/execution/execute.ts index b153fe2e51..57d527b071 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -5,7 +5,6 @@ import { isAsyncIterable } from '../jsutils/isAsyncIterable.js'; import { isIterableObject } from '../jsutils/isIterableObject.js'; import { isObjectLike } from '../jsutils/isObjectLike.js'; import { isPromise } from '../jsutils/isPromise.js'; -import { mapValue } from '../jsutils/mapValue.js'; import type { Maybe } from '../jsutils/Maybe.js'; import { memoize3 } from '../jsutils/memoize3.js'; import type { ObjMap } from '../jsutils/ObjMap.js'; @@ -21,6 +20,7 @@ import { locatedError } from '../error/locatedError.js'; import type { DocumentNode, FieldNode, + FragmentDefinitionNode, OperationDefinitionNode, } from '../language/ast.js'; import { OperationTypeNode } from '../language/ast.js'; @@ -139,6 +139,10 @@ const collectSubfields = memoize3( */ export interface ValidatedExecutionArgs { schema: GraphQLSchema; + // TODO: consider deprecating/removing fragmentDefinitions if/when fragment + // arguments are officially supported and/or the full fragment details are + // exposed within GraphQLResolveInfo. + fragmentDefinitions: ObjMap; fragments: ObjMap; rootValue: unknown; contextValue: unknown; @@ -478,6 +482,8 @@ export function validateExecutionArgs( assertValidSchema(schema); let operation: OperationDefinitionNode | undefined; + const fragmentDefinitions: ObjMap = + Object.create(null); const fragments: ObjMap = Object.create(null); for (const definition of document.definitions) { switch (definition.kind) { @@ -496,6 +502,7 @@ export function validateExecutionArgs( } break; case Kind.FRAGMENT_DEFINITION: { + fragmentDefinitions[definition.name.value] = definition; let variableSignatures; if (definition.variableDefinitions) { variableSignatures = Object.create(null); @@ -536,6 +543,7 @@ export function validateExecutionArgs( return { schema, + fragmentDefinitions, fragments, rootValue, contextValue, @@ -827,7 +835,7 @@ export function buildResolveInfo( parentType: GraphQLObjectType, path: Path, ): GraphQLResolveInfo { - const { schema, fragments, rootValue, operation, variableValues } = + const { schema, fragmentDefinitions, rootValue, operation, variableValues } = validatedExecutionArgs; // The resolve function's optional fourth argument is a collection of // information about the current execution state. @@ -838,10 +846,7 @@ export function buildResolveInfo( parentType, path, schema, - fragments: mapValue( - fragments, - (fragmentDetails) => fragmentDetails.definition, - ), + fragments: fragmentDefinitions, rootValue, operation, variableValues, diff --git a/src/utilities/replaceVariables.ts b/src/utilities/replaceVariables.ts index 747931fab7..307c987a01 100644 --- a/src/utilities/replaceVariables.ts +++ b/src/utilities/replaceVariables.ts @@ -1,8 +1,11 @@ import type { Maybe } from '../jsutils/Maybe.js'; -import type { ConstValueNode, ValueNode } from '../language/ast.js'; +import type { + ConstValueNode, + ObjectFieldNode, + ValueNode, +} from '../language/ast.js'; import { Kind } from '../language/kinds.js'; -import { visit } from '../language/visitor.js'; import type { VariableValues } from '../execution/values.js'; @@ -21,9 +24,9 @@ export function replaceVariables( variableValues?: Maybe, fragmentVariableValues?: Maybe, ): ConstValueNode { - return visit(valueNode, { - Variable(node) { - const varName = node.name.value; + switch (valueNode.kind) { + case Kind.VARIABLE: { + const varName = valueNode.name.value; const scopedVariableValues = fragmentVariableValues?.sources[varName] ? fragmentVariableValues : variableValues; @@ -36,23 +39,19 @@ export function replaceVariables( if (scopedVariableSource.value === undefined) { const defaultValue = scopedVariableSource.signature.defaultValue; if (defaultValue !== undefined) { - return defaultValue.literal; + return defaultValue.literal as ConstValueNode; } } return valueToLiteral( scopedVariableSource.value, scopedVariableSource.signature.type, - ); - }, - ObjectValue(node) { - return { - ...node, - // Filter out any fields with a missing variable. - fields: node.fields.filter((field) => { - if (field.value.kind !== Kind.VARIABLE) { - return true; - } + ) as ConstValueNode; + } + case Kind.OBJECT: { + const newFields: Array = []; + for (const field of valueNode.fields) { + if (field.value.kind === Kind.VARIABLE) { const scopedVariableSource = fragmentVariableValues?.sources[field.value.name.value] ?? variableValues?.sources[field.value.name.value]; @@ -61,11 +60,41 @@ export function replaceVariables( scopedVariableSource?.value === undefined && scopedVariableSource?.signature.defaultValue === undefined ) { - return false; + continue; } - return true; - }), - }; - }, - }) as ConstValueNode; + } + const newFieldNodeValue = replaceVariables( + field.value, + variableValues, + fragmentVariableValues, + ); + newFields.push({ + ...field, + value: newFieldNodeValue, + }); + } + return { + ...valueNode, + fields: newFields, + } as ConstValueNode; + } + case Kind.LIST: { + const newValues: Array = []; + for (const value of valueNode.values) { + const newItemNodeValue = replaceVariables( + value, + variableValues, + fragmentVariableValues, + ); + newValues.push(newItemNodeValue); + } + return { + ...valueNode, + values: newValues, + } as ConstValueNode; + } + default: { + return valueNode; + } + } }