| // @flow strict |
| |
| import arrayFrom from '../polyfills/arrayFrom'; |
| |
| import inspect from '../jsutils/inspect'; |
| import memoize3 from '../jsutils/memoize3'; |
| import invariant from '../jsutils/invariant'; |
| import devAssert from '../jsutils/devAssert'; |
| import isPromise from '../jsutils/isPromise'; |
| import { type ObjMap } from '../jsutils/ObjMap'; |
| import isObjectLike from '../jsutils/isObjectLike'; |
| import isCollection from '../jsutils/isCollection'; |
| import promiseReduce from '../jsutils/promiseReduce'; |
| import promiseForObject from '../jsutils/promiseForObject'; |
| import { type PromiseOrValue } from '../jsutils/PromiseOrValue'; |
| import { type Path, addPath, pathToArray } from '../jsutils/Path'; |
| |
| import { GraphQLError } from '../error/GraphQLError'; |
| import { locatedError } from '../error/locatedError'; |
| |
| import { Kind } from '../language/kinds'; |
| import { |
| type DocumentNode, |
| type OperationDefinitionNode, |
| type SelectionSetNode, |
| type FieldNode, |
| type FragmentSpreadNode, |
| type InlineFragmentNode, |
| type FragmentDefinitionNode, |
| } from '../language/ast'; |
| |
| import { assertValidSchema } from '../type/validate'; |
| import { type GraphQLSchema } from '../type/schema'; |
| import { |
| SchemaMetaFieldDef, |
| TypeMetaFieldDef, |
| TypeNameMetaFieldDef, |
| } from '../type/introspection'; |
| import { |
| GraphQLIncludeDirective, |
| GraphQLSkipDirective, |
| } from '../type/directives'; |
| import { |
| type GraphQLObjectType, |
| type GraphQLOutputType, |
| type GraphQLLeafType, |
| type GraphQLAbstractType, |
| type GraphQLField, |
| type GraphQLFieldResolver, |
| type GraphQLResolveInfo, |
| type GraphQLTypeResolver, |
| type GraphQLList, |
| isObjectType, |
| isAbstractType, |
| isLeafType, |
| isListType, |
| isNonNullType, |
| } from '../type/definition'; |
| |
| import { typeFromAST } from '../utilities/typeFromAST'; |
| import { getOperationRootType } from '../utilities/getOperationRootType'; |
| |
| import { |
| getVariableValues, |
| getArgumentValues, |
| getDirectiveValues, |
| } from './values'; |
| |
| /** |
| * Terminology |
| * |
| * "Definitions" are the generic name for top-level statements in the document. |
| * Examples of this include: |
| * 1) Operations (such as a query) |
| * 2) Fragments |
| * |
| * "Operations" are a generic name for requests in the document. |
| * Examples of this include: |
| * 1) query, |
| * 2) mutation |
| * |
| * "Selections" are the definitions that can appear legally and at |
| * single level of the query. These include: |
| * 1) field references e.g "a" |
| * 2) fragment "spreads" e.g. "...c" |
| * 3) inline fragment "spreads" e.g. "...on Type { a }" |
| */ |
| |
| /** |
| * Data that must be available at all points during query execution. |
| * |
| * Namely, schema of the type system that is currently executing, |
| * and the fragments defined in the query document |
| */ |
| export type ExecutionContext = {| |
| schema: GraphQLSchema, |
| fragments: ObjMap<FragmentDefinitionNode>, |
| rootValue: mixed, |
| contextValue: mixed, |
| operation: OperationDefinitionNode, |
| variableValues: { [variable: string]: mixed, ... }, |
| fieldResolver: GraphQLFieldResolver<any, any>, |
| typeResolver: GraphQLTypeResolver<any, any>, |
| errors: Array<GraphQLError>, |
| |}; |
| |
| /** |
| * The result of GraphQL execution. |
| * |
| * - `errors` is included when any errors occurred as a non-empty array. |
| * - `data` is the result of a successful execution of the query. |
| */ |
| export type ExecutionResult = {| |
| errors?: $ReadOnlyArray<GraphQLError>, |
| data?: ObjMap<mixed> | null, |
| |}; |
| |
| export type ExecutionArgs = {| |
| schema: GraphQLSchema, |
| document: DocumentNode, |
| rootValue?: mixed, |
| contextValue?: mixed, |
| variableValues?: ?{ +[variable: string]: mixed, ... }, |
| operationName?: ?string, |
| fieldResolver?: ?GraphQLFieldResolver<any, any>, |
| typeResolver?: ?GraphQLTypeResolver<any, any>, |
| |}; |
| |
| /** |
| * Implements the "Evaluating requests" section of the GraphQL specification. |
| * |
| * Returns either a synchronous ExecutionResult (if all encountered resolvers |
| * are synchronous), or a Promise of an ExecutionResult that will eventually be |
| * resolved and never rejected. |
| * |
| * If the arguments to this function do not result in a legal execution context, |
| * a GraphQLError will be thrown immediately explaining the invalid input. |
| * |
| * Accepts either an object with named arguments, or individual arguments. |
| */ |
| declare function execute( |
| ExecutionArgs, |
| ..._: [] |
| ): PromiseOrValue<ExecutionResult>; |
| /* eslint-disable no-redeclare */ |
| declare function execute( |
| schema: GraphQLSchema, |
| document: DocumentNode, |
| rootValue?: mixed, |
| contextValue?: mixed, |
| variableValues?: ?{ +[variable: string]: mixed, ... }, |
| operationName?: ?string, |
| fieldResolver?: ?GraphQLFieldResolver<any, any>, |
| typeResolver?: ?GraphQLTypeResolver<any, any>, |
| ): PromiseOrValue<ExecutionResult>; |
| export function execute( |
| argsOrSchema, |
| document, |
| rootValue, |
| contextValue, |
| variableValues, |
| operationName, |
| fieldResolver, |
| typeResolver, |
| ) { |
| /* eslint-enable no-redeclare */ |
| // Extract arguments from object args if provided. |
| return arguments.length === 1 |
| ? executeImpl(argsOrSchema) |
| : executeImpl({ |
| schema: argsOrSchema, |
| document, |
| rootValue, |
| contextValue, |
| variableValues, |
| operationName, |
| fieldResolver, |
| typeResolver, |
| }); |
| } |
| |
| function executeImpl(args: ExecutionArgs): PromiseOrValue<ExecutionResult> { |
| const { |
| schema, |
| document, |
| rootValue, |
| contextValue, |
| variableValues, |
| operationName, |
| fieldResolver, |
| typeResolver, |
| } = args; |
| |
| // If arguments are missing or incorrect, throw an error. |
| assertValidExecutionArguments(schema, document, variableValues); |
| |
| // If a valid execution context cannot be created due to incorrect arguments, |
| // a "Response" with only errors is returned. |
| const exeContext = buildExecutionContext( |
| schema, |
| document, |
| rootValue, |
| contextValue, |
| variableValues, |
| operationName, |
| fieldResolver, |
| typeResolver, |
| ); |
| |
| // Return early errors if execution context failed. |
| if (Array.isArray(exeContext)) { |
| return { errors: exeContext }; |
| } |
| |
| // Return a Promise that will eventually resolve to the data described by |
| // The "Response" section of the GraphQL specification. |
| // |
| // If errors are encountered while executing a GraphQL field, only that |
| // field and its descendants will be omitted, and sibling fields will still |
| // be executed. An execution which encounters errors will still result in a |
| // resolved Promise. |
| const data = executeOperation(exeContext, exeContext.operation, rootValue); |
| return buildResponse(exeContext, data); |
| } |
| |
| /** |
| * Given a completed execution context and data, build the { errors, data } |
| * response defined by the "Response" section of the GraphQL specification. |
| */ |
| function buildResponse( |
| exeContext: ExecutionContext, |
| data: PromiseOrValue<ObjMap<mixed> | null>, |
| ): PromiseOrValue<ExecutionResult> { |
| if (isPromise(data)) { |
| return data.then(resolved => buildResponse(exeContext, resolved)); |
| } |
| return exeContext.errors.length === 0 |
| ? { data } |
| : { errors: exeContext.errors, data }; |
| } |
| |
| /** |
| * Essential assertions before executing to provide developer feedback for |
| * improper use of the GraphQL library. |
| * |
| * @internal |
| */ |
| export function assertValidExecutionArguments( |
| schema: GraphQLSchema, |
| document: DocumentNode, |
| rawVariableValues: ?{ +[variable: string]: mixed, ... }, |
| ): void { |
| devAssert(document, 'Must provide document.'); |
| |
| // If the schema used for execution is invalid, throw an error. |
| assertValidSchema(schema); |
| |
| // Variables, if provided, must be an object. |
| devAssert( |
| rawVariableValues == null || isObjectLike(rawVariableValues), |
| 'Variables must be provided as an Object where each property is a variable value. Perhaps look to see if an unparsed JSON string was provided.', |
| ); |
| } |
| |
| /** |
| * Constructs a ExecutionContext object from the arguments passed to |
| * execute, which we will pass throughout the other execution methods. |
| * |
| * Throws a GraphQLError if a valid execution context cannot be created. |
| * |
| * @internal |
| */ |
| export function buildExecutionContext( |
| schema: GraphQLSchema, |
| document: DocumentNode, |
| rootValue: mixed, |
| contextValue: mixed, |
| rawVariableValues: ?{ +[variable: string]: mixed, ... }, |
| operationName: ?string, |
| fieldResolver: ?GraphQLFieldResolver<mixed, mixed>, |
| typeResolver?: ?GraphQLTypeResolver<mixed, mixed>, |
| ): $ReadOnlyArray<GraphQLError> | ExecutionContext { |
| let operation: OperationDefinitionNode | void; |
| const fragments: ObjMap<FragmentDefinitionNode> = Object.create(null); |
| for (const definition of document.definitions) { |
| switch (definition.kind) { |
| case Kind.OPERATION_DEFINITION: |
| if (operationName == null) { |
| if (operation !== undefined) { |
| return [ |
| new GraphQLError( |
| 'Must provide operation name if query contains multiple operations.', |
| ), |
| ]; |
| } |
| operation = definition; |
| } else if (definition.name?.value === operationName) { |
| operation = definition; |
| } |
| break; |
| case Kind.FRAGMENT_DEFINITION: |
| fragments[definition.name.value] = definition; |
| break; |
| } |
| } |
| |
| if (!operation) { |
| if (operationName != null) { |
| return [new GraphQLError(`Unknown operation named "${operationName}".`)]; |
| } |
| return [new GraphQLError('Must provide an operation.')]; |
| } |
| |
| /* istanbul ignore next (See https://github.com/graphql/graphql-js/issues/2203) */ |
| const variableDefinitions = operation.variableDefinitions ?? []; |
| |
| const coercedVariableValues = getVariableValues( |
| schema, |
| variableDefinitions, |
| rawVariableValues ?? {}, |
| { maxErrors: 50 }, |
| ); |
| |
| if (coercedVariableValues.errors) { |
| return coercedVariableValues.errors; |
| } |
| |
| return { |
| schema, |
| fragments, |
| rootValue, |
| contextValue, |
| operation, |
| variableValues: coercedVariableValues.coerced, |
| fieldResolver: fieldResolver ?? defaultFieldResolver, |
| typeResolver: typeResolver ?? defaultTypeResolver, |
| errors: [], |
| }; |
| } |
| |
| /** |
| * Implements the "Evaluating operations" section of the spec. |
| */ |
| function executeOperation( |
| exeContext: ExecutionContext, |
| operation: OperationDefinitionNode, |
| rootValue: mixed, |
| ): PromiseOrValue<ObjMap<mixed> | null> { |
| const type = getOperationRootType(exeContext.schema, operation); |
| const fields = collectFields( |
| exeContext, |
| type, |
| operation.selectionSet, |
| Object.create(null), |
| Object.create(null), |
| ); |
| |
| const path = undefined; |
| |
| // Errors from sub-fields of a NonNull type may propagate to the top level, |
| // at which point we still log the error and null the parent field, which |
| // in this case is the entire response. |
| // |
| // Similar to completeValueCatchingError. |
| try { |
| const result = |
| operation.operation === 'mutation' |
| ? executeFieldsSerially(exeContext, type, rootValue, path, fields) |
| : executeFields(exeContext, type, rootValue, path, fields); |
| if (isPromise(result)) { |
| return result.then(undefined, error => { |
| exeContext.errors.push(error); |
| return Promise.resolve(null); |
| }); |
| } |
| return result; |
| } catch (error) { |
| exeContext.errors.push(error); |
| return null; |
| } |
| } |
| |
| /** |
| * Implements the "Evaluating selection sets" section of the spec |
| * for "write" mode. |
| */ |
| function executeFieldsSerially( |
| exeContext: ExecutionContext, |
| parentType: GraphQLObjectType, |
| sourceValue: mixed, |
| path: Path | void, |
| fields: ObjMap<Array<FieldNode>>, |
| ): PromiseOrValue<ObjMap<mixed>> { |
| return promiseReduce( |
| Object.keys(fields), |
| (results, responseName) => { |
| const fieldNodes = fields[responseName]; |
| const fieldPath = addPath(path, responseName); |
| const result = resolveField( |
| exeContext, |
| parentType, |
| sourceValue, |
| fieldNodes, |
| fieldPath, |
| ); |
| if (result === undefined) { |
| return results; |
| } |
| if (isPromise(result)) { |
| return result.then(resolvedResult => { |
| results[responseName] = resolvedResult; |
| return results; |
| }); |
| } |
| results[responseName] = result; |
| return results; |
| }, |
| Object.create(null), |
| ); |
| } |
| |
| /** |
| * Implements the "Evaluating selection sets" section of the spec |
| * for "read" mode. |
| */ |
| function executeFields( |
| exeContext: ExecutionContext, |
| parentType: GraphQLObjectType, |
| sourceValue: mixed, |
| path: Path | void, |
| fields: ObjMap<Array<FieldNode>>, |
| ): PromiseOrValue<ObjMap<mixed>> { |
| const results = Object.create(null); |
| let containsPromise = false; |
| |
| for (const responseName of Object.keys(fields)) { |
| const fieldNodes = fields[responseName]; |
| const fieldPath = addPath(path, responseName); |
| const result = resolveField( |
| exeContext, |
| parentType, |
| sourceValue, |
| fieldNodes, |
| fieldPath, |
| ); |
| |
| if (result !== undefined) { |
| results[responseName] = result; |
| if (!containsPromise && isPromise(result)) { |
| containsPromise = true; |
| } |
| } |
| } |
| |
| // If there are no promises, we can just return the object |
| if (!containsPromise) { |
| return results; |
| } |
| |
| // Otherwise, results is a map from field name to the result of resolving that |
| // field, which is possibly a promise. Return a promise that will return this |
| // same map, but with any promises replaced with the values they resolved to. |
| return promiseForObject(results); |
| } |
| |
| /** |
| * Given a selectionSet, adds all of the fields in that selection to |
| * the passed in map of fields, and returns it at the end. |
| * |
| * CollectFields requires the "runtime type" of an object. For a field which |
| * returns an Interface or Union type, the "runtime type" will be the actual |
| * Object type returned by that field. |
| * |
| * @internal |
| */ |
| export function collectFields( |
| exeContext: ExecutionContext, |
| runtimeType: GraphQLObjectType, |
| selectionSet: SelectionSetNode, |
| fields: ObjMap<Array<FieldNode>>, |
| visitedFragmentNames: ObjMap<boolean>, |
| ): ObjMap<Array<FieldNode>> { |
| for (const selection of selectionSet.selections) { |
| switch (selection.kind) { |
| case Kind.FIELD: { |
| if (!shouldIncludeNode(exeContext, selection)) { |
| continue; |
| } |
| const name = getFieldEntryKey(selection); |
| if (!fields[name]) { |
| fields[name] = []; |
| } |
| fields[name].push(selection); |
| break; |
| } |
| case Kind.INLINE_FRAGMENT: { |
| if ( |
| !shouldIncludeNode(exeContext, selection) || |
| !doesFragmentConditionMatch(exeContext, selection, runtimeType) |
| ) { |
| continue; |
| } |
| collectFields( |
| exeContext, |
| runtimeType, |
| selection.selectionSet, |
| fields, |
| visitedFragmentNames, |
| ); |
| break; |
| } |
| case Kind.FRAGMENT_SPREAD: { |
| const fragName = selection.name.value; |
| if ( |
| visitedFragmentNames[fragName] || |
| !shouldIncludeNode(exeContext, selection) |
| ) { |
| continue; |
| } |
| visitedFragmentNames[fragName] = true; |
| const fragment = exeContext.fragments[fragName]; |
| if ( |
| !fragment || |
| !doesFragmentConditionMatch(exeContext, fragment, runtimeType) |
| ) { |
| continue; |
| } |
| collectFields( |
| exeContext, |
| runtimeType, |
| fragment.selectionSet, |
| fields, |
| visitedFragmentNames, |
| ); |
| break; |
| } |
| } |
| } |
| return fields; |
| } |
| |
| /** |
| * Determines if a field should be included based on the @include and @skip |
| * directives, where @skip has higher precedence than @include. |
| */ |
| function shouldIncludeNode( |
| exeContext: ExecutionContext, |
| node: FragmentSpreadNode | FieldNode | InlineFragmentNode, |
| ): boolean { |
| const skip = getDirectiveValues( |
| GraphQLSkipDirective, |
| node, |
| exeContext.variableValues, |
| ); |
| if (skip?.if === true) { |
| return false; |
| } |
| |
| const include = getDirectiveValues( |
| GraphQLIncludeDirective, |
| node, |
| exeContext.variableValues, |
| ); |
| if (include?.if === false) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Determines if a fragment is applicable to the given type. |
| */ |
| function doesFragmentConditionMatch( |
| exeContext: ExecutionContext, |
| fragment: FragmentDefinitionNode | InlineFragmentNode, |
| type: GraphQLObjectType, |
| ): boolean { |
| const typeConditionNode = fragment.typeCondition; |
| if (!typeConditionNode) { |
| return true; |
| } |
| const conditionalType = typeFromAST(exeContext.schema, typeConditionNode); |
| if (conditionalType === type) { |
| return true; |
| } |
| if (isAbstractType(conditionalType)) { |
| return exeContext.schema.isSubType(conditionalType, type); |
| } |
| return false; |
| } |
| |
| /** |
| * Implements the logic to compute the key of a given field's entry |
| */ |
| function getFieldEntryKey(node: FieldNode): string { |
| return node.alias ? node.alias.value : node.name.value; |
| } |
| |
| /** |
| * Resolves the field on the given source object. In particular, this |
| * figures out the value that the field returns by calling its resolve function, |
| * then calls completeValue to complete promises, serialize scalars, or execute |
| * the sub-selection-set for objects. |
| */ |
| function resolveField( |
| exeContext: ExecutionContext, |
| parentType: GraphQLObjectType, |
| source: mixed, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| path: Path, |
| ): PromiseOrValue<mixed> { |
| const fieldNode = fieldNodes[0]; |
| const fieldName = fieldNode.name.value; |
| |
| const fieldDef = getFieldDef(exeContext.schema, parentType, fieldName); |
| if (!fieldDef) { |
| return; |
| } |
| |
| const resolveFn = fieldDef.resolve ?? exeContext.fieldResolver; |
| |
| const info = buildResolveInfo( |
| exeContext, |
| fieldDef, |
| fieldNodes, |
| parentType, |
| path, |
| ); |
| |
| // Get the resolve function, regardless of if its result is normal |
| // or abrupt (error). |
| const result = resolveFieldValueOrError( |
| exeContext, |
| fieldDef, |
| fieldNodes, |
| resolveFn, |
| source, |
| info, |
| ); |
| |
| return completeValueCatchingError( |
| exeContext, |
| fieldDef.type, |
| fieldNodes, |
| info, |
| path, |
| result, |
| ); |
| } |
| |
| /** |
| * @internal |
| */ |
| export function buildResolveInfo( |
| exeContext: ExecutionContext, |
| fieldDef: GraphQLField<mixed, mixed>, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| parentType: GraphQLObjectType, |
| path: Path, |
| ): GraphQLResolveInfo { |
| // The resolve function's optional fourth argument is a collection of |
| // information about the current execution state. |
| return { |
| fieldName: fieldDef.name, |
| fieldNodes, |
| returnType: fieldDef.type, |
| parentType, |
| path, |
| schema: exeContext.schema, |
| fragments: exeContext.fragments, |
| rootValue: exeContext.rootValue, |
| operation: exeContext.operation, |
| variableValues: exeContext.variableValues, |
| }; |
| } |
| |
| /** |
| * Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField` |
| * function. Returns the result of resolveFn or the abrupt-return Error object. |
| * |
| * @internal |
| */ |
| export function resolveFieldValueOrError( |
| exeContext: ExecutionContext, |
| fieldDef: GraphQLField<mixed, mixed>, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| resolveFn: GraphQLFieldResolver<mixed, mixed>, |
| source: mixed, |
| info: GraphQLResolveInfo, |
| ): Error | mixed { |
| try { |
| // Build a JS object of arguments from the field.arguments AST, using the |
| // variables scope to fulfill any variable references. |
| // TODO: find a way to memoize, in case this field is within a List type. |
| const args = getArgumentValues( |
| fieldDef, |
| fieldNodes[0], |
| exeContext.variableValues, |
| ); |
| |
| // The resolve function's optional third argument is a context value that |
| // is provided to every resolve function within an execution. It is commonly |
| // used to represent an authenticated user, or request-specific caches. |
| const contextValue = exeContext.contextValue; |
| |
| const result = resolveFn(source, args, contextValue, info); |
| return isPromise(result) ? result.then(undefined, asErrorInstance) : result; |
| } catch (error) { |
| return asErrorInstance(error); |
| } |
| } |
| |
| // Sometimes a non-error is thrown, wrap it as an Error instance to ensure a |
| // consistent Error interface. |
| function asErrorInstance(error: mixed): Error { |
| if (error instanceof Error) { |
| return error; |
| } |
| return new Error('Unexpected error value: ' + inspect(error)); |
| } |
| |
| // This is a small wrapper around completeValue which detects and logs errors |
| // in the execution context. |
| function completeValueCatchingError( |
| exeContext: ExecutionContext, |
| returnType: GraphQLOutputType, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| info: GraphQLResolveInfo, |
| path: Path, |
| result: mixed, |
| ): PromiseOrValue<mixed> { |
| try { |
| let completed; |
| if (isPromise(result)) { |
| completed = result.then(resolved => |
| completeValue(exeContext, returnType, fieldNodes, info, path, resolved), |
| ); |
| } else { |
| completed = completeValue( |
| exeContext, |
| returnType, |
| fieldNodes, |
| info, |
| path, |
| result, |
| ); |
| } |
| |
| if (isPromise(completed)) { |
| // Note: we don't rely on a `catch` method, but we do expect "thenable" |
| // to take a second callback for the error case. |
| return completed.then(undefined, error => |
| handleFieldError(error, fieldNodes, path, returnType, exeContext), |
| ); |
| } |
| return completed; |
| } catch (error) { |
| return handleFieldError(error, fieldNodes, path, returnType, exeContext); |
| } |
| } |
| |
| function handleFieldError(rawError, fieldNodes, path, returnType, exeContext) { |
| const error = locatedError( |
| asErrorInstance(rawError), |
| fieldNodes, |
| pathToArray(path), |
| ); |
| |
| // If the field type is non-nullable, then it is resolved without any |
| // protection from errors, however it still properly locates the error. |
| if (isNonNullType(returnType)) { |
| throw error; |
| } |
| |
| // Otherwise, error protection is applied, logging the error and resolving |
| // a null value for this field if one is encountered. |
| exeContext.errors.push(error); |
| return null; |
| } |
| |
| /** |
| * Implements the instructions for completeValue as defined in the |
| * "Field entries" section of the spec. |
| * |
| * If the field type is Non-Null, then this recursively completes the value |
| * for the inner type. It throws a field error if that completion returns null, |
| * as per the "Nullability" section of the spec. |
| * |
| * If the field type is a List, then this recursively completes the value |
| * for the inner type on each item in the list. |
| * |
| * If the field type is a Scalar or Enum, ensures the completed value is a legal |
| * value of the type by calling the `serialize` method of GraphQL type |
| * definition. |
| * |
| * If the field is an abstract type, determine the runtime type of the value |
| * and then complete based on that type |
| * |
| * Otherwise, the field type expects a sub-selection set, and will complete the |
| * value by evaluating all sub-selections. |
| */ |
| function completeValue( |
| exeContext: ExecutionContext, |
| returnType: GraphQLOutputType, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| info: GraphQLResolveInfo, |
| path: Path, |
| result: mixed, |
| ): PromiseOrValue<mixed> { |
| // If result is an Error, throw a located error. |
| if (result instanceof Error) { |
| throw result; |
| } |
| |
| // If field type is NonNull, complete for inner type, and throw field error |
| // if result is null. |
| if (isNonNullType(returnType)) { |
| const completed = completeValue( |
| exeContext, |
| returnType.ofType, |
| fieldNodes, |
| info, |
| path, |
| result, |
| ); |
| if (completed === null) { |
| throw new Error( |
| `Cannot return null for non-nullable field ${info.parentType.name}.${info.fieldName}.`, |
| ); |
| } |
| return completed; |
| } |
| |
| // If result value is null or undefined then return null. |
| if (result == null) { |
| return null; |
| } |
| |
| // If field type is List, complete each item in the list with the inner type |
| if (isListType(returnType)) { |
| return completeListValue( |
| exeContext, |
| returnType, |
| fieldNodes, |
| info, |
| path, |
| result, |
| ); |
| } |
| |
| // If field type is a leaf type, Scalar or Enum, serialize to a valid value, |
| // returning null if serialization is not possible. |
| if (isLeafType(returnType)) { |
| return completeLeafValue(returnType, result); |
| } |
| |
| // If field type is an abstract type, Interface or Union, determine the |
| // runtime Object type and complete for that type. |
| if (isAbstractType(returnType)) { |
| return completeAbstractValue( |
| exeContext, |
| returnType, |
| fieldNodes, |
| info, |
| path, |
| result, |
| ); |
| } |
| |
| // If field type is Object, execute and complete all sub-selections. |
| if (isObjectType(returnType)) { |
| return completeObjectValue( |
| exeContext, |
| returnType, |
| fieldNodes, |
| info, |
| path, |
| result, |
| ); |
| } |
| |
| // Not reachable. All possible output types have been considered. |
| invariant( |
| false, |
| 'Cannot complete value of unexpected output type: ' + |
| inspect((returnType: empty)), |
| ); |
| } |
| |
| /** |
| * Complete a list value by completing each item in the list with the |
| * inner type |
| */ |
| function completeListValue( |
| exeContext: ExecutionContext, |
| returnType: GraphQLList<GraphQLOutputType>, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| info: GraphQLResolveInfo, |
| path: Path, |
| result: mixed, |
| ): PromiseOrValue<$ReadOnlyArray<mixed>> { |
| if (!isCollection(result)) { |
| throw new GraphQLError( |
| `Expected Iterable, but did not find one for field "${info.parentType.name}.${info.fieldName}".`, |
| ); |
| } |
| |
| // This is specified as a simple map, however we're optimizing the path |
| // where the list contains no Promises by avoiding creating another Promise. |
| const itemType = returnType.ofType; |
| let containsPromise = false; |
| const completedResults = arrayFrom(result, (item, index) => { |
| // No need to modify the info object containing the path, |
| // since from here on it is not ever accessed by resolver functions. |
| const fieldPath = addPath(path, index); |
| const completedItem = completeValueCatchingError( |
| exeContext, |
| itemType, |
| fieldNodes, |
| info, |
| fieldPath, |
| item, |
| ); |
| |
| if (!containsPromise && isPromise(completedItem)) { |
| containsPromise = true; |
| } |
| |
| return completedItem; |
| }); |
| |
| return containsPromise ? Promise.all(completedResults) : completedResults; |
| } |
| |
| /** |
| * Complete a Scalar or Enum by serializing to a valid value, returning |
| * null if serialization is not possible. |
| */ |
| function completeLeafValue(returnType: GraphQLLeafType, result: mixed): mixed { |
| const serializedResult = returnType.serialize(result); |
| if (serializedResult === undefined) { |
| throw new Error( |
| `Expected a value of type "${inspect(returnType)}" but ` + |
| `received: ${inspect(result)}`, |
| ); |
| } |
| return serializedResult; |
| } |
| |
| /** |
| * Complete a value of an abstract type by determining the runtime object type |
| * of that value, then complete the value for that type. |
| */ |
| function completeAbstractValue( |
| exeContext: ExecutionContext, |
| returnType: GraphQLAbstractType, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| info: GraphQLResolveInfo, |
| path: Path, |
| result: mixed, |
| ): PromiseOrValue<ObjMap<mixed>> { |
| const resolveTypeFn = returnType.resolveType ?? exeContext.typeResolver; |
| const contextValue = exeContext.contextValue; |
| const runtimeType = resolveTypeFn(result, contextValue, info, returnType); |
| |
| if (isPromise(runtimeType)) { |
| return runtimeType.then(resolvedRuntimeType => |
| completeObjectValue( |
| exeContext, |
| ensureValidRuntimeType( |
| resolvedRuntimeType, |
| exeContext, |
| returnType, |
| fieldNodes, |
| info, |
| result, |
| ), |
| fieldNodes, |
| info, |
| path, |
| result, |
| ), |
| ); |
| } |
| |
| return completeObjectValue( |
| exeContext, |
| ensureValidRuntimeType( |
| runtimeType, |
| exeContext, |
| returnType, |
| fieldNodes, |
| info, |
| result, |
| ), |
| fieldNodes, |
| info, |
| path, |
| result, |
| ); |
| } |
| |
| function ensureValidRuntimeType( |
| runtimeTypeOrName: ?GraphQLObjectType | string, |
| exeContext: ExecutionContext, |
| returnType: GraphQLAbstractType, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| info: GraphQLResolveInfo, |
| result: mixed, |
| ): GraphQLObjectType { |
| const runtimeType = |
| typeof runtimeTypeOrName === 'string' |
| ? exeContext.schema.getType(runtimeTypeOrName) |
| : runtimeTypeOrName; |
| |
| if (!isObjectType(runtimeType)) { |
| throw new GraphQLError( |
| `Abstract type "${returnType.name}" must resolve to an Object type at runtime for field "${info.parentType.name}.${info.fieldName}" with ` + |
| `value ${inspect(result)}, received "${inspect(runtimeType)}". ` + |
| `Either the "${returnType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`, |
| fieldNodes, |
| ); |
| } |
| |
| if (!exeContext.schema.isSubType(returnType, runtimeType)) { |
| throw new GraphQLError( |
| `Runtime Object type "${runtimeType.name}" is not a possible type for "${returnType.name}".`, |
| fieldNodes, |
| ); |
| } |
| |
| return runtimeType; |
| } |
| |
| /** |
| * Complete an Object value by executing all sub-selections. |
| */ |
| function completeObjectValue( |
| exeContext: ExecutionContext, |
| returnType: GraphQLObjectType, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| info: GraphQLResolveInfo, |
| path: Path, |
| result: mixed, |
| ): PromiseOrValue<ObjMap<mixed>> { |
| // If there is an isTypeOf predicate function, call it with the |
| // current result. If isTypeOf returns false, then raise an error rather |
| // than continuing execution. |
| if (returnType.isTypeOf) { |
| const isTypeOf = returnType.isTypeOf(result, exeContext.contextValue, info); |
| |
| if (isPromise(isTypeOf)) { |
| return isTypeOf.then(resolvedIsTypeOf => { |
| if (!resolvedIsTypeOf) { |
| throw invalidReturnTypeError(returnType, result, fieldNodes); |
| } |
| return collectAndExecuteSubfields( |
| exeContext, |
| returnType, |
| fieldNodes, |
| path, |
| result, |
| ); |
| }); |
| } |
| |
| if (!isTypeOf) { |
| throw invalidReturnTypeError(returnType, result, fieldNodes); |
| } |
| } |
| |
| return collectAndExecuteSubfields( |
| exeContext, |
| returnType, |
| fieldNodes, |
| path, |
| result, |
| ); |
| } |
| |
| function invalidReturnTypeError( |
| returnType: GraphQLObjectType, |
| result: mixed, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| ): GraphQLError { |
| return new GraphQLError( |
| `Expected value of type "${returnType.name}" but got: ${inspect(result)}.`, |
| fieldNodes, |
| ); |
| } |
| |
| function collectAndExecuteSubfields( |
| exeContext: ExecutionContext, |
| returnType: GraphQLObjectType, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| path: Path, |
| result: mixed, |
| ): PromiseOrValue<ObjMap<mixed>> { |
| // Collect sub-fields to execute to complete this value. |
| const subFieldNodes = collectSubfields(exeContext, returnType, fieldNodes); |
| return executeFields(exeContext, returnType, result, path, subFieldNodes); |
| } |
| |
| /** |
| * A memoized collection of relevant subfields with regard to the return |
| * type. Memoizing ensures the subfields are not repeatedly calculated, which |
| * saves overhead when resolving lists of values. |
| */ |
| const collectSubfields = memoize3(_collectSubfields); |
| function _collectSubfields( |
| exeContext: ExecutionContext, |
| returnType: GraphQLObjectType, |
| fieldNodes: $ReadOnlyArray<FieldNode>, |
| ): ObjMap<Array<FieldNode>> { |
| let subFieldNodes = Object.create(null); |
| const visitedFragmentNames = Object.create(null); |
| for (const node of fieldNodes) { |
| if (node.selectionSet) { |
| subFieldNodes = collectFields( |
| exeContext, |
| returnType, |
| node.selectionSet, |
| subFieldNodes, |
| visitedFragmentNames, |
| ); |
| } |
| } |
| return subFieldNodes; |
| } |
| |
| /** |
| * If a resolveType function is not given, then a default resolve behavior is |
| * used which attempts two strategies: |
| * |
| * First, See if the provided value has a `__typename` field defined, if so, use |
| * that value as name of the resolved type. |
| * |
| * Otherwise, test each possible type for the abstract type by calling |
| * isTypeOf for the object being coerced, returning the first type that matches. |
| */ |
| export const defaultTypeResolver: GraphQLTypeResolver<mixed, mixed> = function( |
| value, |
| contextValue, |
| info, |
| abstractType, |
| ) { |
| // First, look for `__typename`. |
| if (isObjectLike(value) && typeof value.__typename === 'string') { |
| return value.__typename; |
| } |
| |
| // Otherwise, test each possible type. |
| const possibleTypes = info.schema.getPossibleTypes(abstractType); |
| const promisedIsTypeOfResults = []; |
| |
| for (let i = 0; i < possibleTypes.length; i++) { |
| const type = possibleTypes[i]; |
| |
| if (type.isTypeOf) { |
| const isTypeOfResult = type.isTypeOf(value, contextValue, info); |
| |
| if (isPromise(isTypeOfResult)) { |
| promisedIsTypeOfResults[i] = isTypeOfResult; |
| } else if (isTypeOfResult) { |
| return type; |
| } |
| } |
| } |
| |
| if (promisedIsTypeOfResults.length) { |
| return Promise.all(promisedIsTypeOfResults).then(isTypeOfResults => { |
| for (let i = 0; i < isTypeOfResults.length; i++) { |
| if (isTypeOfResults[i]) { |
| return possibleTypes[i]; |
| } |
| } |
| }); |
| } |
| }; |
| |
| /** |
| * If a resolve function is not given, then a default resolve behavior is used |
| * which takes the property of the source object of the same name as the field |
| * and returns it as the result, or if it's a function, returns the result |
| * of calling that function while passing along args and context value. |
| */ |
| export const defaultFieldResolver: GraphQLFieldResolver< |
| mixed, |
| mixed, |
| > = function(source: any, args, contextValue, info) { |
| // ensure source is a value for which property access is acceptable. |
| if (isObjectLike(source) || typeof source === 'function') { |
| const property = source[info.fieldName]; |
| if (typeof property === 'function') { |
| return source[info.fieldName](args, contextValue, info); |
| } |
| return property; |
| } |
| }; |
| |
| /** |
| * This method looks up the field on the given type definition. |
| * It has special casing for the two introspection fields, __schema |
| * and __typename. __typename is special because it can always be |
| * queried as a field, even in situations where no other fields |
| * are allowed, like on a Union. __schema could get automatically |
| * added to the query type, but that would require mutating type |
| * definitions, which would cause issues. |
| * |
| * @internal |
| */ |
| export function getFieldDef( |
| schema: GraphQLSchema, |
| parentType: GraphQLObjectType, |
| fieldName: string, |
| ): ?GraphQLField<mixed, mixed> { |
| if ( |
| fieldName === SchemaMetaFieldDef.name && |
| schema.getQueryType() === parentType |
| ) { |
| return SchemaMetaFieldDef; |
| } else if ( |
| fieldName === TypeMetaFieldDef.name && |
| schema.getQueryType() === parentType |
| ) { |
| return TypeMetaFieldDef; |
| } else if (fieldName === TypeNameMetaFieldDef.name) { |
| return TypeNameMetaFieldDef; |
| } |
| return parentType.getFields()[fieldName]; |
| } |