Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve source error extensions when merging schemas #1074

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "graphql-tools",
"version": "4.0.4",
"version": "5.0.0-rc.1",
"description": "Useful tools to create and manipulate GraphQL schemas.",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand Down
6 changes: 5 additions & 1 deletion src/stitching/defaultMergedResolver.ts
Expand Up @@ -15,7 +15,11 @@ const defaultMergedResolver: GraphQLFieldResolver<any, any> = (parent, args, con
const errorResult = getErrorsFromParent(parent, responseKey);

if (errorResult.kind === 'OWN') {
throw locatedError(new Error(errorResult.error.message), info.fieldNodes, responsePathAsArray(info.path));
throw locatedError(
errorResult.error.originalError || new Error(errorResult.error.message),
info.fieldNodes,
responsePathAsArray(info.path),
);
}

let result = parent[responseKey];
Expand Down
17 changes: 11 additions & 6 deletions src/stitching/errors.ts
Expand Up @@ -2,7 +2,6 @@ import {
GraphQLResolveInfo,
responsePathAsArray,
ExecutionResult,
GraphQLFormattedError,
GraphQLError,
} from 'graphql';
import { locatedError } from 'graphql/error';
Expand All @@ -18,7 +17,7 @@ if (
ERROR_SYMBOL = '@@__subSchemaErrors';
}

export function annotateWithChildrenErrors(object: any, childrenErrors: ReadonlyArray<GraphQLFormattedError>): any {
export function annotateWithChildrenErrors(object: any, childrenErrors: ReadonlyArray<GraphQLError>): any {
if (!childrenErrors || childrenErrors.length === 0) {
// Nothing to see here, move along
return object;
Expand All @@ -33,10 +32,16 @@ export function annotateWithChildrenErrors(object: any, childrenErrors: Readonly
}
const index = error.path[1];
const current = byIndex[index] || [];

// It's important to keep the `originalError`, to make sure
// error stacktrace's and custom `extensions` are preserved from
// source schemas, when merged.
current.push({
...error,
path: error.path.slice(1)
path: error.path.slice(1),
originalError: error.originalError,
});

byIndex[index] = current;
});

Expand All @@ -62,10 +67,10 @@ export function getErrorsFromParent(
}
| {
kind: 'CHILDREN';
errors?: Array<GraphQLFormattedError>;
errors?: Array<GraphQLError>;
} {
const errors = (object && object[ERROR_SYMBOL]) || [];
const childrenErrors: Array<GraphQLFormattedError> = [];
const childrenErrors: Array<GraphQLError> = [];

for (const error of errors) {
if (!error.path || (error.path.length === 1 && error.path[0] === fieldName)) {
Expand Down Expand Up @@ -114,7 +119,7 @@ export function checkResultAndHandleErrors(

let resultObject = result.data[responseKey];
if (result.errors) {
resultObject = annotateWithChildrenErrors(resultObject, result.errors as ReadonlyArray<GraphQLFormattedError>);
resultObject = annotateWithChildrenErrors(resultObject, result.errors as ReadonlyArray<GraphQLError>);
}
return resultObject;
}
Expand Down
32 changes: 32 additions & 0 deletions src/test/testMergeSchemas.ts
Expand Up @@ -2315,6 +2315,38 @@ fragment BookingFragment on Booking {
],
});
});

it(
'should preserve custom error extensions from the original schema, ' +
'when merging schemas',
async () => {
const propertyQuery = `
query {
properties(limit: 1) {
error
}
}
`;

const propertyResult = await graphql(
propertySchema,
propertyQuery,
);

const mergedResult = await graphql(
mergedSchema,
propertyQuery,
);

[propertyResult, mergedResult].forEach((result) => {
expect(result.errors).to.exist;
expect(result.errors.length > 0).to.be.true;
const error = result.errors[0];
expect(error.extensions).to.exist;
expect(error.extensions.code).to.equal('SOME_CUSTOM_CODE');
});
}
);
});

describe('types in schema extensions', () => {
Expand Down
6 changes: 5 additions & 1 deletion src/test/testingSchemas.ts
Expand Up @@ -407,7 +407,11 @@ const propertyResolvers: IResolvers = {

Property: {
error() {
throw new Error('Property.error error');
const error = new Error('Property.error error');
(error as any).extensions = {
code: 'SOME_CUSTOM_CODE',
};
throw error;
},
},
};
Expand Down