Skip to content

Commit 3f95a66

Browse files
committed
jsutils: Add generic Path implementation
1 parent dae9f87 commit 3f95a66

File tree

6 files changed

+28
-66
lines changed

6 files changed

+28
-66
lines changed

src/execution/execute.js

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import promiseForObject from '../jsutils/promiseForObject';
1414
import promiseReduce from '../jsutils/promiseReduce';
1515
import { type ObjMap } from '../jsutils/ObjMap';
1616
import { type PromiseOrValue } from '../jsutils/PromiseOrValue';
17+
import { type Path, addPath, pathToArray } from '../jsutils/Path';
1718

1819
import { getOperationRootType } from '../utilities/getOperationRootType';
1920
import { typeFromAST } from '../utilities/typeFromAST';
@@ -32,7 +33,6 @@ import {
3233
type GraphQLFieldResolver,
3334
type GraphQLResolveInfo,
3435
type GraphQLTypeResolver,
35-
type ResponsePath,
3636
type GraphQLList,
3737
isObjectType,
3838
isAbstractType,
@@ -235,30 +235,6 @@ function buildResponse(
235235
: { errors: exeContext.errors, data };
236236
}
237237

238-
/**
239-
* Given a ResponsePath (found in the `path` entry in the information provided
240-
* as the last argument to a field resolver), return an Array of the path keys.
241-
*/
242-
export function responsePathAsArray(
243-
path: ResponsePath,
244-
): $ReadOnlyArray<string | number> {
245-
const flattened = [];
246-
let curr = path;
247-
while (curr) {
248-
flattened.push(curr.key);
249-
curr = curr.prev;
250-
}
251-
return flattened.reverse();
252-
}
253-
254-
/**
255-
* Given a ResponsePath and a key, return a new ResponsePath containing the
256-
* new key.
257-
*/
258-
export function addPath(prev: ResponsePath | void, key: string | number) {
259-
return { prev, key };
260-
}
261-
262238
/**
263239
* Essential assertions before executing to provide developer feedback for
264240
* improper use of the GraphQL library.
@@ -420,7 +396,7 @@ function executeFieldsSerially(
420396
exeContext: ExecutionContext,
421397
parentType: GraphQLObjectType,
422398
sourceValue: mixed,
423-
path: ResponsePath | void,
399+
path: Path | void,
424400
fields: ObjMap<Array<FieldNode>>,
425401
): PromiseOrValue<ObjMap<mixed>> {
426402
return promiseReduce(
@@ -459,7 +435,7 @@ function executeFields(
459435
exeContext: ExecutionContext,
460436
parentType: GraphQLObjectType,
461437
sourceValue: mixed,
462-
path: ResponsePath | void,
438+
path: Path | void,
463439
fields: ObjMap<Array<FieldNode>>,
464440
): PromiseOrValue<ObjMap<mixed>> {
465441
const results = Object.create(null);
@@ -639,7 +615,7 @@ function resolveField(
639615
parentType: GraphQLObjectType,
640616
source: mixed,
641617
fieldNodes: $ReadOnlyArray<FieldNode>,
642-
path: ResponsePath,
618+
path: Path,
643619
): PromiseOrValue<mixed> {
644620
const fieldNode = fieldNodes[0];
645621
const fieldName = fieldNode.name.value;
@@ -685,7 +661,7 @@ export function buildResolveInfo(
685661
fieldDef: GraphQLField<mixed, mixed>,
686662
fieldNodes: $ReadOnlyArray<FieldNode>,
687663
parentType: GraphQLObjectType,
688-
path: ResponsePath,
664+
path: Path,
689665
): GraphQLResolveInfo {
690666
// The resolve function's optional fourth argument is a collection of
691667
// information about the current execution state.
@@ -751,7 +727,7 @@ function completeValueCatchingError(
751727
returnType: GraphQLOutputType,
752728
fieldNodes: $ReadOnlyArray<FieldNode>,
753729
info: GraphQLResolveInfo,
754-
path: ResponsePath,
730+
path: Path,
755731
result: mixed,
756732
): PromiseOrValue<mixed> {
757733
try {
@@ -788,7 +764,7 @@ function handleFieldError(rawError, fieldNodes, path, returnType, exeContext) {
788764
const error = locatedError(
789765
asErrorInstance(rawError),
790766
fieldNodes,
791-
responsePathAsArray(path),
767+
pathToArray(path),
792768
);
793769

794770
// If the field type is non-nullable, then it is resolved without any
@@ -829,7 +805,7 @@ function completeValue(
829805
returnType: GraphQLOutputType,
830806
fieldNodes: $ReadOnlyArray<FieldNode>,
831807
info: GraphQLResolveInfo,
832-
path: ResponsePath,
808+
path: Path,
833809
result: mixed,
834810
): PromiseOrValue<mixed> {
835811
// If result is an Error, throw a located error.
@@ -922,7 +898,7 @@ function completeListValue(
922898
returnType: GraphQLList<GraphQLOutputType>,
923899
fieldNodes: $ReadOnlyArray<FieldNode>,
924900
info: GraphQLResolveInfo,
925-
path: ResponsePath,
901+
path: Path,
926902
result: mixed,
927903
): PromiseOrValue<$ReadOnlyArray<mixed>> {
928904
invariant(
@@ -982,7 +958,7 @@ function completeAbstractValue(
982958
returnType: GraphQLAbstractType,
983959
fieldNodes: $ReadOnlyArray<FieldNode>,
984960
info: GraphQLResolveInfo,
985-
path: ResponsePath,
961+
path: Path,
986962
result: mixed,
987963
): PromiseOrValue<ObjMap<mixed>> {
988964
const resolveTypeFn = returnType.resolveType || exeContext.typeResolver;
@@ -1066,7 +1042,7 @@ function completeObjectValue(
10661042
returnType: GraphQLObjectType,
10671043
fieldNodes: $ReadOnlyArray<FieldNode>,
10681044
info: GraphQLResolveInfo,
1069-
path: ResponsePath,
1045+
path: Path,
10701046
result: mixed,
10711047
): PromiseOrValue<ObjMap<mixed>> {
10721048
// If there is an isTypeOf predicate function, call it with the
@@ -1119,7 +1095,7 @@ function collectAndExecuteSubfields(
11191095
exeContext: ExecutionContext,
11201096
returnType: GraphQLObjectType,
11211097
fieldNodes: $ReadOnlyArray<FieldNode>,
1122-
path: ResponsePath,
1098+
path: Path,
11231099
result: mixed,
11241100
): PromiseOrValue<ObjMap<mixed>> {
11251101
// Collect sub-fields to execute to complete this value.

src/execution/index.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
// @flow strict
22

3-
export {
4-
execute,
5-
defaultFieldResolver,
6-
defaultTypeResolver,
7-
responsePathAsArray,
8-
} from './execute';
3+
export { pathToArray as responsePathAsArray } from '../jsutils/Path';
4+
5+
export { execute, defaultFieldResolver, defaultTypeResolver } from './execute';
96
export type { ExecutionArgs, ExecutionResult } from './execute';
107

118
export { getDirectiveValues } from './values';

src/subscription/subscribe.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22

33
import { isAsyncIterable } from 'iterall';
44
import inspect from '../jsutils/inspect';
5+
import { addPath, pathToArray } from '../jsutils/Path';
56
import { GraphQLError } from '../error/GraphQLError';
67
import { locatedError } from '../error/locatedError';
78
import {
89
type ExecutionResult,
9-
addPath,
1010
assertValidExecutionArguments,
1111
buildExecutionContext,
1212
buildResolveInfo,
1313
collectFields,
1414
execute,
1515
getFieldDef,
1616
resolveFieldValueOrError,
17-
responsePathAsArray,
1817
} from '../execution/execute';
1918
import { type GraphQLSchema } from '../type/schema';
2019
import mapAsyncIterator from './mapAsyncIterator';
@@ -269,9 +268,7 @@ export function createSourceEventStream(
269268
// If eventStream is an Error, rethrow a located error.
270269
if (eventStream instanceof Error) {
271270
return {
272-
errors: [
273-
locatedError(eventStream, fieldNodes, responsePathAsArray(path)),
274-
],
271+
errors: [locatedError(eventStream, fieldNodes, pathToArray(path))],
275272
};
276273
}
277274

src/type/definition.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import keyValMap from '../jsutils/keyValMap';
1515
import mapValue from '../jsutils/mapValue';
1616
import isObjectLike from '../jsutils/isObjectLike';
1717
import { type ObjMap } from '../jsutils/ObjMap';
18+
import { type Path } from '../jsutils/Path';
1819
import { Kind } from '../language/kinds';
1920
import { valueFromASTUntyped } from '../utilities/valueFromASTUntyped';
2021
import {
@@ -859,19 +860,14 @@ export type GraphQLResolveInfo = {|
859860
+fieldNodes: $ReadOnlyArray<FieldNode>,
860861
+returnType: GraphQLOutputType,
861862
+parentType: GraphQLObjectType,
862-
+path: ResponsePath,
863+
+path: Path,
863864
+schema: GraphQLSchema,
864865
+fragments: ObjMap<FragmentDefinitionNode>,
865866
+rootValue: mixed,
866867
+operation: OperationDefinitionNode,
867868
+variableValues: { [variable: string]: mixed, ... },
868869
|};
869870

870-
export type ResponsePath = {|
871-
+prev: ResponsePath | void,
872-
+key: string | number,
873-
|};
874-
875871
export type GraphQLFieldConfig<
876872
TSource,
877873
TContext,

src/type/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// @flow strict
22

3+
export type { Path as ResponsePath } from '../jsutils/Path';
4+
35
export {
46
// Predicate
57
isSchema,
@@ -150,7 +152,6 @@ export type {
150152
GraphQLIsTypeOfFn,
151153
GraphQLObjectTypeConfig,
152154
GraphQLResolveInfo,
153-
ResponsePath,
154155
GraphQLScalarTypeConfig,
155156
GraphQLTypeResolver,
156157
GraphQLUnionTypeConfig,

src/utilities/coerceValue.js

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import inspect from '../jsutils/inspect';
66
import didYouMean from '../jsutils/didYouMean';
77
import isObjectLike from '../jsutils/isObjectLike';
88
import suggestionList from '../jsutils/suggestionList';
9+
import { type Path, addPath, pathToArray } from '../jsutils/Path';
910
import { GraphQLError } from '../error/GraphQLError';
1011
import { type ASTNode } from '../language/ast';
1112
import {
@@ -22,8 +23,6 @@ type CoercedValue = {|
2223
+value: mixed,
2324
|};
2425

25-
type Path = {| +prev: Path | void, +key: string | number |};
26-
2726
/**
2827
* Coerces a JavaScript value given a GraphQL Type.
2928
*
@@ -108,12 +107,11 @@ export function coerceValue(
108107
let errors;
109108
const coercedValue = [];
110109
forEach((value: any), (itemValue, index) => {
111-
const itemPath = { prev: path, key: index };
112110
const coercedItem = coerceValue(
113111
itemValue,
114112
itemType,
115113
blameNode,
116-
itemPath,
114+
addPath(path, index),
117115
);
118116
if (coercedItem.errors) {
119117
errors = add(errors, coercedItem.errors);
@@ -144,7 +142,7 @@ export function coerceValue(
144142

145143
// Ensure every defined field is valid.
146144
for (const field of objectValues(fields)) {
147-
const fieldPath = { prev: path, key: field.name };
145+
const fieldPath = addPath(path, field.name);
148146
const fieldValue = value[field.name];
149147
if (fieldValue === undefined) {
150148
if (field.defaultValue !== undefined) {
@@ -215,14 +213,11 @@ function coercionError(message, blameNode, path, subMessage, originalError) {
215213

216214
// Build a string describing the path into the value where the error was found
217215
if (path) {
218-
const segmentStrings = [];
219-
for (let currentPath = path; currentPath; currentPath = currentPath.prev) {
220-
const { key } = currentPath;
221-
segmentStrings.unshift(
222-
typeof key === 'string' ? '.' + key : '[' + key.toString() + ']',
223-
);
216+
fullMessage += ' at value';
217+
for (const key of pathToArray(path)) {
218+
fullMessage +=
219+
typeof key === 'string' ? '.' + key : '[' + key.toString() + ']';
224220
}
225-
fullMessage += ' at value' + segmentStrings.join('');
226221
}
227222

228223
fullMessage += subMessage ? '.' + subMessage : '.';

0 commit comments

Comments
 (0)