Skip to content

Commit 1c26aa7

Browse files
committed
inital changes for re-attempt at #1047
1 parent 4cadaf7 commit 1c26aa7

File tree

8 files changed

+114
-85
lines changed

8 files changed

+114
-85
lines changed

packages/delegate/src/proxiedResult.ts

+18-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
import { GraphQLError } from 'graphql';
2-
3-
import { mergeDeep, ERROR_SYMBOL, relocatedError, setErrors, getErrors } from '@graphql-tools/utils';
1+
import {
2+
mergeDeep,
3+
ERROR_SYMBOL,
4+
relocatedError,
5+
setErrors,
6+
getErrors,
7+
RelativeGraphQLError,
8+
} from '@graphql-tools/utils';
49

510
import { handleNull } from './results/handleNull';
611

@@ -25,10 +30,7 @@ export function unwrapResult(parent: any, path: Array<string>): any {
2530
return handleNull(errors);
2631
}
2732

28-
setErrors(
29-
object,
30-
errors.map(error => relocatedError(error, error.path != null ? error.path.slice(1) : undefined))
31-
);
33+
setErrors(object, errors);
3234
setObjectSubschema(object, subschema);
3335

3436
newParent = object;
@@ -51,15 +53,15 @@ export function dehoistResult(parent: any, delimeter = '__gqltf__'): any {
5153
obj[fieldName] = parent[alias];
5254
});
5355

54-
result[ERROR_SYMBOL] = parent[ERROR_SYMBOL].map((error: GraphQLError) => {
55-
if (error.path != null) {
56-
const path = error.path.slice();
57-
const pathSegment = path.shift();
58-
const expandedPathSegment: Array<string | number> = (pathSegment as string).split(delimeter);
59-
return relocatedError(error, expandedPathSegment.concat(path));
60-
}
61-
62-
return error;
56+
result[ERROR_SYMBOL] = parent[ERROR_SYMBOL].map((error: RelativeGraphQLError) => {
57+
const path = error.relativePath.slice();
58+
const pathSegment = path.pop();
59+
const expandedPathSegment: Array<string | number> = (pathSegment as string).split(delimeter);
60+
return {
61+
relativePath: path.concat(expandedPathSegment),
62+
// setting path to null will cause issues for errors that bubble up from non nullable fields
63+
graphQLError: relocatedError(error.graphQLError, null),
64+
};
6365
});
6466

6567
result[OBJECT_SUBSCHEMA_SYMBOL] = parent[OBJECT_SUBSCHEMA_SYMBOL];

packages/delegate/src/results/handleList.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
GraphQLList,
33
GraphQLSchema,
4-
GraphQLError,
54
GraphQLResolveInfo,
65
getNullableType,
76
GraphQLType,
@@ -10,7 +9,7 @@ import {
109
isListType,
1110
} from 'graphql';
1211

13-
import { getErrorsByPathSegment } from '@graphql-tools/utils';
12+
import { getErrorsByPathSegment, RelativeGraphQLError } from '@graphql-tools/utils';
1413

1514
import { handleNull } from './handleNull';
1615
import { handleObject } from './handleObject';
@@ -19,7 +18,7 @@ import { SubschemaConfig } from '../types';
1918
export function handleList(
2019
type: GraphQLList<any>,
2120
list: Array<any>,
22-
errors: ReadonlyArray<GraphQLError>,
21+
errors: Array<RelativeGraphQLError>,
2322
subschema: GraphQLSchema | SubschemaConfig,
2423
context: Record<string, any>,
2524
info: GraphQLResolveInfo,
@@ -43,7 +42,7 @@ export function handleList(
4342
function handleListMember(
4443
type: GraphQLType,
4544
listMember: any,
46-
errors: ReadonlyArray<GraphQLError>,
45+
errors: Array<RelativeGraphQLError>,
4746
subschema: GraphQLSchema | SubschemaConfig,
4847
context: Record<string, any>,
4948
info: GraphQLResolveInfo,

packages/delegate/src/results/handleNull.ts

+5-28
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,13 @@
1-
import { GraphQLError } from 'graphql';
2-
31
import AggregateError from 'aggregate-error';
4-
import { getErrorsByPathSegment, relocatedError } from '@graphql-tools/utils';
2+
import { RelativeGraphQLError } from '@graphql-tools/utils';
53

6-
export function handleNull(errors: ReadonlyArray<GraphQLError>) {
4+
export function handleNull(errors: Array<RelativeGraphQLError>) {
75
if (errors.length) {
8-
if (errors.some(error => !error.path || error.path.length < 2)) {
9-
if (errors.length > 1) {
10-
const combinedError = new AggregateError(errors);
11-
return combinedError;
12-
}
13-
const error = errors[0];
14-
return error.originalError || relocatedError(error, null);
15-
} else if (errors.some(error => typeof error.path[1] === 'string')) {
16-
const childErrors = getErrorsByPathSegment(errors);
17-
18-
const result = {};
19-
Object.keys(childErrors).forEach(pathSegment => {
20-
result[pathSegment] = handleNull(childErrors[pathSegment]);
21-
});
22-
23-
return result;
6+
if (errors.length > 1) {
7+
return new AggregateError(errors.map(error => error.graphQLError));
248
}
259

26-
const childErrors = getErrorsByPathSegment(errors);
27-
28-
const result: Array<any> = [];
29-
Object.keys(childErrors).forEach(pathSegment => {
30-
result.push(handleNull(childErrors[pathSegment]));
31-
});
32-
33-
return result;
10+
return errors[0].graphQLError;
3411
}
3512

3613
return null;

packages/delegate/src/results/handleObject.ts

+3-8
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,29 @@
11
import {
22
GraphQLCompositeType,
3-
GraphQLError,
43
GraphQLSchema,
54
isAbstractType,
65
FieldNode,
76
GraphQLObjectType,
87
GraphQLResolveInfo,
98
} from 'graphql';
109

11-
import { collectFields, GraphQLExecutionContext, setErrors, slicedError } from '@graphql-tools/utils';
10+
import { collectFields, GraphQLExecutionContext, setErrors, RelativeGraphQLError } from '@graphql-tools/utils';
1211
import { setObjectSubschema, isSubschemaConfig } from '../Subschema';
1312
import { mergeFields } from '../mergeFields';
1413
import { MergedTypeInfo, SubschemaConfig } from '../types';
1514

1615
export function handleObject(
1716
type: GraphQLCompositeType,
1817
object: any,
19-
errors: ReadonlyArray<GraphQLError>,
18+
errors: Array<RelativeGraphQLError>,
2019
subschema: GraphQLSchema | SubschemaConfig,
2120
context: Record<string, any>,
2221
info: GraphQLResolveInfo,
2322
skipTypeMerging?: boolean
2423
) {
2524
const stitchingInfo = info?.schema.extensions?.stitchingInfo;
2625

27-
setErrors(
28-
object,
29-
errors.map(error => slicedError(error))
30-
);
31-
26+
setErrors(object, errors);
3227
setObjectSubschema(object, subschema);
3328

3429
if (skipTypeMerging || !stitchingInfo) {

packages/delegate/src/results/handleResult.ts

+4-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
import {
2-
GraphQLResolveInfo,
3-
getNullableType,
4-
isCompositeType,
5-
isLeafType,
6-
isListType,
7-
GraphQLError,
8-
GraphQLSchema,
9-
} from 'graphql';
1+
import { GraphQLResolveInfo, getNullableType, isCompositeType, isLeafType, isListType, GraphQLSchema } from 'graphql';
2+
3+
import { RelativeGraphQLError } from '@graphql-tools/utils';
104

115
import { SubschemaConfig } from '../types';
126

@@ -16,7 +10,7 @@ import { handleList } from './handleList';
1610

1711
export function handleResult(
1812
result: any,
19-
errors: ReadonlyArray<GraphQLError>,
13+
errors: Array<RelativeGraphQLError>,
2014
subschema: GraphQLSchema | SubschemaConfig,
2115
context: Record<string, any>,
2216
info: GraphQLResolveInfo,

packages/delegate/src/transforms/CheckResultAndHandleErrors.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { GraphQLResolveInfo, ExecutionResult, GraphQLOutputType, GraphQLSchema } from 'graphql';
22

3-
import { Transform, getResponseKeyFromInfo } from '@graphql-tools/utils';
3+
import { Transform, getResponseKeyFromInfo, toRelativeErrors } from '@graphql-tools/utils';
44
import { handleResult } from '../results/handleResult';
55
import { SubschemaConfig } from '../types';
66

@@ -50,7 +50,7 @@ export function checkResultAndHandleErrors(
5050
returnType: GraphQLOutputType = info.returnType,
5151
skipTypeMerging?: boolean
5252
): any {
53-
const errors = result.errors != null ? result.errors : [];
53+
const errors = result.errors != null ? toRelativeErrors(result.errors, info) : [];
5454
const data = result.data != null ? result.data[responseKey] : undefined;
5555

5656
return handleResult(data, errors, subschema, context, info, returnType, skipTypeMerging);

packages/stitch/tests/errors.test.ts

+35
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ describe('passes along errors for missing fields on list', () => {
3838
const originalResult = await graphql(schema, query);
3939
const stitchedResult = await graphql(stitchedSchema, query);
4040
expect(stitchedResult).toEqual(originalResult);
41+
expect(stitchedResult.errors[0].path).toEqual(originalResult.errors[0].path);
4142
});
4243

4344
test('even if nullable', async () => {
@@ -72,6 +73,7 @@ describe('passes along errors for missing fields on list', () => {
7273
const originalResult = await graphql(schema, query);
7374
const stitchedResult = await graphql(stitchedSchema, query);
7475
expect(stitchedResult).toEqual(originalResult);
76+
expect(stitchedResult.errors[0].path).toEqual(originalResult.errors[0].path);
7577
});
7678
});
7779

@@ -108,6 +110,7 @@ describe('passes along errors when list field errors', () => {
108110
const originalResult = await graphql(schema, query);
109111
const stitchedResult = await graphql(stitchedSchema, query);
110112
expect(stitchedResult).toEqual(originalResult);
113+
expect(stitchedResult.errors[0].path).toEqual(originalResult.errors[0].path);
111114
});
112115

113116
test('even if nullable', async () => {
@@ -142,6 +145,38 @@ describe('passes along errors when list field errors', () => {
142145
const originalResult = await graphql(schema, query);
143146
const stitchedResult = await graphql(stitchedSchema, query);
144147
expect(stitchedResult).toEqual(originalResult);
148+
expect(stitchedResult.errors[0].path).toEqual(originalResult.errors[0].path);
149+
});
150+
151+
describe('passes along correct error when there are two non-null fields', () => {
152+
test('should work', async () => {
153+
const schema = makeExecutableSchema({
154+
typeDefs: `
155+
type Query {
156+
getBoth: Both
157+
}
158+
type Both {
159+
mandatoryField1: String!
160+
mandatoryField2: String!
161+
}
162+
`,
163+
resolvers: {
164+
Query: {
165+
getBoth: () => ({ mandatoryField1: 'test' }),
166+
},
167+
},
168+
});
169+
170+
const stitchedSchema = stitchSchemas({
171+
subschemas: [schema],
172+
});
173+
174+
const query = '{ getBoth { mandatoryField1 mandatoryField2 } }';
175+
const originalResult = await graphql(schema, query);
176+
const stitchedResult = await graphql(stitchedSchema, query);
177+
expect(stitchedResult).toEqual(originalResult);
178+
expect(stitchedResult.errors[0].path).toEqual(originalResult.errors[0].path);
179+
});
145180
});
146181
});
147182

packages/utils/src/errors.ts

+44-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { GraphQLError } from 'graphql';
1+
import { GraphQLError, responsePathAsArray, GraphQLResolveInfo } from 'graphql';
22

33
export const ERROR_SYMBOL = Symbol('subschemaErrors');
44

5+
export interface RelativeGraphQLError {
6+
relativePath: Array<string | number>;
7+
graphQLError: GraphQLError;
8+
}
9+
510
export function relocatedError(originalError: GraphQLError, path?: ReadonlyArray<string | number>): GraphQLError {
611
return new GraphQLError(
712
originalError.message,
@@ -14,33 +19,55 @@ export function relocatedError(originalError: GraphQLError, path?: ReadonlyArray
1419
);
1520
}
1621

17-
export function slicedError(originalError: GraphQLError) {
18-
return relocatedError(originalError, originalError.path != null ? originalError.path.slice(1) : undefined);
22+
export function sliceRelativeError(error: RelativeGraphQLError): RelativeGraphQLError {
23+
return {
24+
...error,
25+
relativePath: error.relativePath?.slice(1),
26+
};
27+
}
28+
29+
export function toRelativeErrors(
30+
errors: ReadonlyArray<GraphQLError>,
31+
info: GraphQLResolveInfo
32+
): Array<RelativeGraphQLError> {
33+
return errors.map(error => {
34+
const relativePath = error.path?.slice() || [];
35+
const sourcePath = info != null ? responsePathAsArray(info.path) : [];
36+
return {
37+
relativePath,
38+
graphQLError: relocatedError(error, sourcePath.concat(relativePath.slice(1))),
39+
};
40+
});
1941
}
2042

21-
export function getErrorsByPathSegment(errors: ReadonlyArray<GraphQLError>): Record<string, Array<GraphQLError>> {
22-
const record = Object.create(null);
43+
export function setErrors(result: any, errors: Array<RelativeGraphQLError>) {
44+
result[ERROR_SYMBOL] = errors;
45+
}
46+
47+
export function getErrorsByPathSegment(
48+
errors: Array<RelativeGraphQLError>
49+
): Record<string, Array<RelativeGraphQLError>> {
50+
const record: Record<string, Array<RelativeGraphQLError>> = Object.create(null);
2351
errors.forEach(error => {
24-
if (!error.path || error.path.length < 2) {
52+
if (!error.relativePath || error.relativePath.length < 2) {
2553
return;
2654
}
2755

28-
const pathSegment = error.path[1];
56+
const pathSegment = error.relativePath[1];
2957

30-
const current = pathSegment in record ? record[pathSegment] : [];
31-
current.push(slicedError(error));
58+
const current: Array<RelativeGraphQLError> = pathSegment in record ? record[pathSegment] : [];
59+
current.push({
60+
relativePath: error.relativePath.slice(1),
61+
graphQLError: error.graphQLError,
62+
});
3263
record[pathSegment] = current;
3364
});
3465

3566
return record;
3667
}
3768

38-
export function setErrors(result: any, errors: Array<GraphQLError>) {
39-
result[ERROR_SYMBOL] = errors;
40-
}
41-
42-
export function getErrors(result: any, pathSegment: string): Array<GraphQLError> {
43-
const errors = result != null ? result[ERROR_SYMBOL] : result;
69+
export function getErrors(result: any, pathSegment: string | number): Array<RelativeGraphQLError> {
70+
const errors: Array<RelativeGraphQLError> = result != null ? result[ERROR_SYMBOL] : result;
4471

4572
if (!Array.isArray(errors)) {
4673
return null;
@@ -49,8 +76,8 @@ export function getErrors(result: any, pathSegment: string): Array<GraphQLError>
4976
const fieldErrors = [];
5077

5178
for (const error of errors) {
52-
if (!error.path || error.path[0] === pathSegment) {
53-
fieldErrors.push(error);
79+
if (!error.relativePath || error.relativePath.length < 2 || error.relativePath[1] === pathSegment) {
80+
fieldErrors.push(sliceRelativeError(error));
5481
}
5582
}
5683

0 commit comments

Comments
 (0)