From 3f95a661683ab58a046203e1a774e1a43bd49711 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Thu, 25 Jul 2019 16:26:58 +0300 Subject: [PATCH] jsutils: Add generic Path implementation --- src/execution/execute.js | 48 +++++++++-------------------------- src/execution/index.js | 9 +++---- src/subscription/subscribe.js | 7 ++--- src/type/definition.js | 8 ++---- src/type/index.js | 3 ++- src/utilities/coerceValue.js | 19 +++++--------- 6 files changed, 28 insertions(+), 66 deletions(-) diff --git a/src/execution/execute.js b/src/execution/execute.js index 4bbf745087d..83cbaf803ad 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -14,6 +14,7 @@ import promiseForObject from '../jsutils/promiseForObject'; import promiseReduce from '../jsutils/promiseReduce'; import { type ObjMap } from '../jsutils/ObjMap'; import { type PromiseOrValue } from '../jsutils/PromiseOrValue'; +import { type Path, addPath, pathToArray } from '../jsutils/Path'; import { getOperationRootType } from '../utilities/getOperationRootType'; import { typeFromAST } from '../utilities/typeFromAST'; @@ -32,7 +33,6 @@ import { type GraphQLFieldResolver, type GraphQLResolveInfo, type GraphQLTypeResolver, - type ResponsePath, type GraphQLList, isObjectType, isAbstractType, @@ -235,30 +235,6 @@ function buildResponse( : { errors: exeContext.errors, data }; } -/** - * Given a ResponsePath (found in the `path` entry in the information provided - * as the last argument to a field resolver), return an Array of the path keys. - */ -export function responsePathAsArray( - path: ResponsePath, -): $ReadOnlyArray { - const flattened = []; - let curr = path; - while (curr) { - flattened.push(curr.key); - curr = curr.prev; - } - return flattened.reverse(); -} - -/** - * Given a ResponsePath and a key, return a new ResponsePath containing the - * new key. - */ -export function addPath(prev: ResponsePath | void, key: string | number) { - return { prev, key }; -} - /** * Essential assertions before executing to provide developer feedback for * improper use of the GraphQL library. @@ -420,7 +396,7 @@ function executeFieldsSerially( exeContext: ExecutionContext, parentType: GraphQLObjectType, sourceValue: mixed, - path: ResponsePath | void, + path: Path | void, fields: ObjMap>, ): PromiseOrValue> { return promiseReduce( @@ -459,7 +435,7 @@ function executeFields( exeContext: ExecutionContext, parentType: GraphQLObjectType, sourceValue: mixed, - path: ResponsePath | void, + path: Path | void, fields: ObjMap>, ): PromiseOrValue> { const results = Object.create(null); @@ -639,7 +615,7 @@ function resolveField( parentType: GraphQLObjectType, source: mixed, fieldNodes: $ReadOnlyArray, - path: ResponsePath, + path: Path, ): PromiseOrValue { const fieldNode = fieldNodes[0]; const fieldName = fieldNode.name.value; @@ -685,7 +661,7 @@ export function buildResolveInfo( fieldDef: GraphQLField, fieldNodes: $ReadOnlyArray, parentType: GraphQLObjectType, - path: ResponsePath, + path: Path, ): GraphQLResolveInfo { // The resolve function's optional fourth argument is a collection of // information about the current execution state. @@ -751,7 +727,7 @@ function completeValueCatchingError( returnType: GraphQLOutputType, fieldNodes: $ReadOnlyArray, info: GraphQLResolveInfo, - path: ResponsePath, + path: Path, result: mixed, ): PromiseOrValue { try { @@ -788,7 +764,7 @@ function handleFieldError(rawError, fieldNodes, path, returnType, exeContext) { const error = locatedError( asErrorInstance(rawError), fieldNodes, - responsePathAsArray(path), + pathToArray(path), ); // If the field type is non-nullable, then it is resolved without any @@ -829,7 +805,7 @@ function completeValue( returnType: GraphQLOutputType, fieldNodes: $ReadOnlyArray, info: GraphQLResolveInfo, - path: ResponsePath, + path: Path, result: mixed, ): PromiseOrValue { // If result is an Error, throw a located error. @@ -922,7 +898,7 @@ function completeListValue( returnType: GraphQLList, fieldNodes: $ReadOnlyArray, info: GraphQLResolveInfo, - path: ResponsePath, + path: Path, result: mixed, ): PromiseOrValue<$ReadOnlyArray> { invariant( @@ -982,7 +958,7 @@ function completeAbstractValue( returnType: GraphQLAbstractType, fieldNodes: $ReadOnlyArray, info: GraphQLResolveInfo, - path: ResponsePath, + path: Path, result: mixed, ): PromiseOrValue> { const resolveTypeFn = returnType.resolveType || exeContext.typeResolver; @@ -1066,7 +1042,7 @@ function completeObjectValue( returnType: GraphQLObjectType, fieldNodes: $ReadOnlyArray, info: GraphQLResolveInfo, - path: ResponsePath, + path: Path, result: mixed, ): PromiseOrValue> { // If there is an isTypeOf predicate function, call it with the @@ -1119,7 +1095,7 @@ function collectAndExecuteSubfields( exeContext: ExecutionContext, returnType: GraphQLObjectType, fieldNodes: $ReadOnlyArray, - path: ResponsePath, + path: Path, result: mixed, ): PromiseOrValue> { // Collect sub-fields to execute to complete this value. diff --git a/src/execution/index.js b/src/execution/index.js index 3da79ecbe09..c7ffa5cdef2 100644 --- a/src/execution/index.js +++ b/src/execution/index.js @@ -1,11 +1,8 @@ // @flow strict -export { - execute, - defaultFieldResolver, - defaultTypeResolver, - responsePathAsArray, -} from './execute'; +export { pathToArray as responsePathAsArray } from '../jsutils/Path'; + +export { execute, defaultFieldResolver, defaultTypeResolver } from './execute'; export type { ExecutionArgs, ExecutionResult } from './execute'; export { getDirectiveValues } from './values'; diff --git a/src/subscription/subscribe.js b/src/subscription/subscribe.js index b2f4a23942d..82910b31e18 100644 --- a/src/subscription/subscribe.js +++ b/src/subscription/subscribe.js @@ -2,11 +2,11 @@ import { isAsyncIterable } from 'iterall'; import inspect from '../jsutils/inspect'; +import { addPath, pathToArray } from '../jsutils/Path'; import { GraphQLError } from '../error/GraphQLError'; import { locatedError } from '../error/locatedError'; import { type ExecutionResult, - addPath, assertValidExecutionArguments, buildExecutionContext, buildResolveInfo, @@ -14,7 +14,6 @@ import { execute, getFieldDef, resolveFieldValueOrError, - responsePathAsArray, } from '../execution/execute'; import { type GraphQLSchema } from '../type/schema'; import mapAsyncIterator from './mapAsyncIterator'; @@ -269,9 +268,7 @@ export function createSourceEventStream( // If eventStream is an Error, rethrow a located error. if (eventStream instanceof Error) { return { - errors: [ - locatedError(eventStream, fieldNodes, responsePathAsArray(path)), - ], + errors: [locatedError(eventStream, fieldNodes, pathToArray(path))], }; } diff --git a/src/type/definition.js b/src/type/definition.js index 2599b4ad0f1..b7c967b2435 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -15,6 +15,7 @@ import keyValMap from '../jsutils/keyValMap'; import mapValue from '../jsutils/mapValue'; import isObjectLike from '../jsutils/isObjectLike'; import { type ObjMap } from '../jsutils/ObjMap'; +import { type Path } from '../jsutils/Path'; import { Kind } from '../language/kinds'; import { valueFromASTUntyped } from '../utilities/valueFromASTUntyped'; import { @@ -859,7 +860,7 @@ export type GraphQLResolveInfo = {| +fieldNodes: $ReadOnlyArray, +returnType: GraphQLOutputType, +parentType: GraphQLObjectType, - +path: ResponsePath, + +path: Path, +schema: GraphQLSchema, +fragments: ObjMap, +rootValue: mixed, @@ -867,11 +868,6 @@ export type GraphQLResolveInfo = {| +variableValues: { [variable: string]: mixed, ... }, |}; -export type ResponsePath = {| - +prev: ResponsePath | void, - +key: string | number, -|}; - export type GraphQLFieldConfig< TSource, TContext, diff --git a/src/type/index.js b/src/type/index.js index c3b899b4237..2ec7d3dfb97 100644 --- a/src/type/index.js +++ b/src/type/index.js @@ -1,5 +1,7 @@ // @flow strict +export type { Path as ResponsePath } from '../jsutils/Path'; + export { // Predicate isSchema, @@ -150,7 +152,6 @@ export type { GraphQLIsTypeOfFn, GraphQLObjectTypeConfig, GraphQLResolveInfo, - ResponsePath, GraphQLScalarTypeConfig, GraphQLTypeResolver, GraphQLUnionTypeConfig, diff --git a/src/utilities/coerceValue.js b/src/utilities/coerceValue.js index f59c1bb688c..b8e424b1c09 100644 --- a/src/utilities/coerceValue.js +++ b/src/utilities/coerceValue.js @@ -6,6 +6,7 @@ import inspect from '../jsutils/inspect'; import didYouMean from '../jsutils/didYouMean'; import isObjectLike from '../jsutils/isObjectLike'; import suggestionList from '../jsutils/suggestionList'; +import { type Path, addPath, pathToArray } from '../jsutils/Path'; import { GraphQLError } from '../error/GraphQLError'; import { type ASTNode } from '../language/ast'; import { @@ -22,8 +23,6 @@ type CoercedValue = {| +value: mixed, |}; -type Path = {| +prev: Path | void, +key: string | number |}; - /** * Coerces a JavaScript value given a GraphQL Type. * @@ -108,12 +107,11 @@ export function coerceValue( let errors; const coercedValue = []; forEach((value: any), (itemValue, index) => { - const itemPath = { prev: path, key: index }; const coercedItem = coerceValue( itemValue, itemType, blameNode, - itemPath, + addPath(path, index), ); if (coercedItem.errors) { errors = add(errors, coercedItem.errors); @@ -144,7 +142,7 @@ export function coerceValue( // Ensure every defined field is valid. for (const field of objectValues(fields)) { - const fieldPath = { prev: path, key: field.name }; + const fieldPath = addPath(path, field.name); const fieldValue = value[field.name]; if (fieldValue === undefined) { if (field.defaultValue !== undefined) { @@ -215,14 +213,11 @@ function coercionError(message, blameNode, path, subMessage, originalError) { // Build a string describing the path into the value where the error was found if (path) { - const segmentStrings = []; - for (let currentPath = path; currentPath; currentPath = currentPath.prev) { - const { key } = currentPath; - segmentStrings.unshift( - typeof key === 'string' ? '.' + key : '[' + key.toString() + ']', - ); + fullMessage += ' at value'; + for (const key of pathToArray(path)) { + fullMessage += + typeof key === 'string' ? '.' + key : '[' + key.toString() + ']'; } - fullMessage += ' at value' + segmentStrings.join(''); } fullMessage += subMessage ? '.' + subMessage : '.';