Skip to content

Commit

Permalink
jsutils: Add generic Path implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanGoncharov committed Jul 25, 2019
1 parent dae9f87 commit 5cf2769
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 66 deletions.
48 changes: 12 additions & 36 deletions src/execution/execute.js
Expand Up @@ -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';
Expand All @@ -32,7 +33,6 @@ import {
type GraphQLFieldResolver,
type GraphQLResolveInfo,
type GraphQLTypeResolver,
type ResponsePath,
type GraphQLList,
isObjectType,
isAbstractType,
Expand Down Expand Up @@ -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<string | number> {
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.
Expand Down Expand Up @@ -420,7 +396,7 @@ function executeFieldsSerially(
exeContext: ExecutionContext,
parentType: GraphQLObjectType,
sourceValue: mixed,
path: ResponsePath | void,
path: Path | void,
fields: ObjMap<Array<FieldNode>>,
): PromiseOrValue<ObjMap<mixed>> {
return promiseReduce(
Expand Down Expand Up @@ -459,7 +435,7 @@ function executeFields(
exeContext: ExecutionContext,
parentType: GraphQLObjectType,
sourceValue: mixed,
path: ResponsePath | void,
path: Path | void,
fields: ObjMap<Array<FieldNode>>,
): PromiseOrValue<ObjMap<mixed>> {
const results = Object.create(null);
Expand Down Expand Up @@ -639,7 +615,7 @@ function resolveField(
parentType: GraphQLObjectType,
source: mixed,
fieldNodes: $ReadOnlyArray<FieldNode>,
path: ResponsePath,
path: Path,
): PromiseOrValue<mixed> {
const fieldNode = fieldNodes[0];
const fieldName = fieldNode.name.value;
Expand Down Expand Up @@ -685,7 +661,7 @@ export function buildResolveInfo(
fieldDef: GraphQLField<mixed, mixed>,
fieldNodes: $ReadOnlyArray<FieldNode>,
parentType: GraphQLObjectType,
path: ResponsePath,
path: Path,
): GraphQLResolveInfo {
// The resolve function's optional fourth argument is a collection of
// information about the current execution state.
Expand Down Expand Up @@ -751,7 +727,7 @@ function completeValueCatchingError(
returnType: GraphQLOutputType,
fieldNodes: $ReadOnlyArray<FieldNode>,
info: GraphQLResolveInfo,
path: ResponsePath,
path: Path,
result: mixed,
): PromiseOrValue<mixed> {
try {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -829,7 +805,7 @@ function completeValue(
returnType: GraphQLOutputType,
fieldNodes: $ReadOnlyArray<FieldNode>,
info: GraphQLResolveInfo,
path: ResponsePath,
path: Path,
result: mixed,
): PromiseOrValue<mixed> {
// If result is an Error, throw a located error.
Expand Down Expand Up @@ -922,7 +898,7 @@ function completeListValue(
returnType: GraphQLList<GraphQLOutputType>,
fieldNodes: $ReadOnlyArray<FieldNode>,
info: GraphQLResolveInfo,
path: ResponsePath,
path: Path,
result: mixed,
): PromiseOrValue<$ReadOnlyArray<mixed>> {
invariant(
Expand Down Expand Up @@ -982,7 +958,7 @@ function completeAbstractValue(
returnType: GraphQLAbstractType,
fieldNodes: $ReadOnlyArray<FieldNode>,
info: GraphQLResolveInfo,
path: ResponsePath,
path: Path,
result: mixed,
): PromiseOrValue<ObjMap<mixed>> {
const resolveTypeFn = returnType.resolveType || exeContext.typeResolver;
Expand Down Expand Up @@ -1066,7 +1042,7 @@ function completeObjectValue(
returnType: GraphQLObjectType,
fieldNodes: $ReadOnlyArray<FieldNode>,
info: GraphQLResolveInfo,
path: ResponsePath,
path: Path,
result: mixed,
): PromiseOrValue<ObjMap<mixed>> {
// If there is an isTypeOf predicate function, call it with the
Expand Down Expand Up @@ -1119,7 +1095,7 @@ function collectAndExecuteSubfields(
exeContext: ExecutionContext,
returnType: GraphQLObjectType,
fieldNodes: $ReadOnlyArray<FieldNode>,
path: ResponsePath,
path: Path,
result: mixed,
): PromiseOrValue<ObjMap<mixed>> {
// Collect sub-fields to execute to complete this value.
Expand Down
9 changes: 3 additions & 6 deletions 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';
26 changes: 26 additions & 0 deletions src/jsutils/Path.js
@@ -0,0 +1,26 @@
// @flow strict

export type Path = {|
+prev: Path | void,
+key: string | number,
|};

/**
* Given a Path and a key, return a new Path containing the new key.
*/
export function addPath(prev: $ReadOnly<Path> | void, key: string | number) {
return { prev, key };
}

/**
* Given a Path, return an Array of the path keys.
*/
export function pathToArray(path: $ReadOnly<Path>): Array<string | number> {
const flattened = [];
let curr = path;
while (curr) {
flattened.push(curr.key);
curr = curr.prev;
}
return flattened.reverse();
}
7 changes: 2 additions & 5 deletions src/subscription/subscribe.js
Expand Up @@ -2,19 +2,18 @@

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,
collectFields,
execute,
getFieldDef,
resolveFieldValueOrError,
responsePathAsArray,
} from '../execution/execute';
import { type GraphQLSchema } from '../type/schema';
import mapAsyncIterator from './mapAsyncIterator';
Expand Down Expand Up @@ -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))],
};
}

Expand Down
8 changes: 2 additions & 6 deletions src/type/definition.js
Expand Up @@ -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 {
Expand Down Expand Up @@ -859,19 +860,14 @@ export type GraphQLResolveInfo = {|
+fieldNodes: $ReadOnlyArray<FieldNode>,
+returnType: GraphQLOutputType,
+parentType: GraphQLObjectType,
+path: ResponsePath,
+path: Path,
+schema: GraphQLSchema,
+fragments: ObjMap<FragmentDefinitionNode>,
+rootValue: mixed,
+operation: OperationDefinitionNode,
+variableValues: { [variable: string]: mixed, ... },
|};

export type ResponsePath = {|
+prev: ResponsePath | void,
+key: string | number,
|};

export type GraphQLFieldConfig<
TSource,
TContext,
Expand Down
3 changes: 2 additions & 1 deletion src/type/index.js
@@ -1,5 +1,7 @@
// @flow strict

export type { Path as ResponsePath } from '../jsutils/Path';

export {
// Predicate
isSchema,
Expand Down Expand Up @@ -150,7 +152,6 @@ export type {
GraphQLIsTypeOfFn,
GraphQLObjectTypeConfig,
GraphQLResolveInfo,
ResponsePath,
GraphQLScalarTypeConfig,
GraphQLTypeResolver,
GraphQLUnionTypeConfig,
Expand Down
19 changes: 7 additions & 12 deletions src/utilities/coerceValue.js
Expand Up @@ -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 {
Expand All @@ -22,8 +23,6 @@ type CoercedValue = {|
+value: mixed,
|};

type Path = {| +prev: Path | void, +key: string | number |};

/**
* Coerces a JavaScript value given a GraphQL Type.
*
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 : '.';
Expand Down

0 comments on commit 5cf2769

Please sign in to comment.