Skip to content

Commit

Permalink
properly handle remote (POJO) errors (#1572)
Browse files Browse the repository at this point in the history
* test(stitch): add a reproduction for issue #1571

* fix

note that even for testing purposes, remote links should return Error objects with paths, which can conveniently be created using the GraphQLError constructor

* properly handle remote (POJO) errors

closes #1571

* fix imports

Co-authored-by: Yaacov Rydzinski <yaacovCR@gmail.com>
  • Loading branch information
alfaproject and yaacovCR committed Jun 2, 2020
1 parent e5186d5 commit b493948
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 35 deletions.
4 changes: 2 additions & 2 deletions packages/delegate/src/results/handleNull.ts
@@ -1,6 +1,6 @@
import { GraphQLError } from 'graphql';

import { getErrorsByPathSegment, CombinedError } from '@graphql-tools/utils';
import { getErrorsByPathSegment, CombinedError, relocatedError } from '@graphql-tools/utils';

export function handleNull(errors: ReadonlyArray<GraphQLError>) {
if (errors.length) {
Expand All @@ -10,7 +10,7 @@ export function handleNull(errors: ReadonlyArray<GraphQLError>) {
return combinedError;
}
const error = errors[0];
return error.originalError || error;
return error.originalError || relocatedError(error, null);
} else if (errors.some(error => typeof error.path[1] === 'string')) {
const childErrors = getErrorsByPathSegment(errors);

Expand Down
64 changes: 61 additions & 3 deletions packages/stitch/tests/errors.test.ts
@@ -1,8 +1,9 @@
import { graphql } from 'graphql';
import { graphql, GraphQLError, buildSchema } from 'graphql';

import { Executor } from '@graphql-tools/delegate';
import { makeExecutableSchema } from '@graphql-tools/schema';

import { stitchSchemas } from '../src/stitchSchemas';
import { stitchSchemas } from '@graphql-tools/stitch';
import { ExecutionResult } from '@graphql-tools/utils';

describe('passes along errors for missing fields on list', () => {
test('if non-null', async () => {
Expand Down Expand Up @@ -143,3 +144,60 @@ describe('passes along errors when list field errors', () => {
expect(stitchedResult).toEqual(originalResult);
});
});

describe('passes along errors for remote schemas', () => {
it('it works', async () => {
const typeDefs = `
type Test {
field: String!
}
type Query {
test: Test!
}
`;

const schema = buildSchema(typeDefs)

const executor: Executor = () => ({
data: {
test: null
},
errors: [
{
message: 'INVALID_CREDENTIALS',
path: ['test'],
} as unknown as GraphQLError
],
}) as ExecutionResult<any>;

const stitchedSchema = stitchSchemas({
schemas: [{
schema,
executor,
}]
});

const expectedResult: ExecutionResult<any> = {
data: null,
errors: [
new GraphQLError(
'INVALID_CREDENTIALS',
undefined,
undefined,
undefined,
['test'],
)
],
};

const query = `{
test {
field
}
}`

const result = await graphql(stitchedSchema, query);
expect(result).toEqual(expectedResult);
});
});
42 changes: 28 additions & 14 deletions packages/stitch/tests/fixtures/schemas.ts
Expand Up @@ -16,6 +16,7 @@ import { introspectSchema } from '@graphql-tools/wrap';
import {
IResolvers,
ExecutionResult,
mapAsyncIterator,
} from '@graphql-tools/utils';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue';
Expand Down Expand Up @@ -680,23 +681,36 @@ export const subscriptionSchema: GraphQLSchema = makeExecutableSchema({
});

function makeExecutorFromSchema(schema: GraphQLSchema) {
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => graphql(
schema,
print(document),
null,
context,
variables,
) as PromiseOrValue<ExecutionResult<TReturn>>;
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => {
const result = graphql(
schema,
print(document),
null,
context,
variables,
) as PromiseOrValue<ExecutionResult<TReturn>>;
if (result instanceof Promise) {
return result.then(originalResult => JSON.parse(JSON.stringify(originalResult)));
}
return JSON.parse(JSON.stringify(result));
};
}

function makeSubscriberFromSchema(schema: GraphQLSchema) {
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => subscribe(
schema,
document,
null,
context,
variables,
) as Promise<AsyncIterator<ExecutionResult<TReturn>> | ExecutionResult<TReturn>>
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => {
const result = subscribe(
schema,
document,
null,
context,
variables,
) as Promise<AsyncIterator<ExecutionResult<TReturn>> | ExecutionResult<TReturn>>;
if (result instanceof Promise) {
return result.then(asyncIterator =>
mapAsyncIterator(asyncIterator as AsyncIterator<ExecutionResult>, (originalResult: ExecutionResult<TReturn>) => JSON.parse(JSON.stringify(originalResult))));
}
return JSON.parse(JSON.stringify(result));
};
}

export async function makeSchemaRemote(
Expand Down
4 changes: 2 additions & 2 deletions packages/utils/src/errors.ts
Expand Up @@ -2,13 +2,13 @@ import { GraphQLError } from 'graphql';

export const ERROR_SYMBOL = Symbol('subschemaErrors');

export function relocatedError(originalError: GraphQLError, path: ReadonlyArray<string | number>): GraphQLError {
export function relocatedError(originalError: GraphQLError, path?: ReadonlyArray<string | number>): GraphQLError {
return new GraphQLError(
originalError.message,
originalError.nodes,
originalError.source,
originalError.positions,
path != null ? path : originalError.path,
path === null ? undefined : path === undefined ? originalError.path : path,
originalError.originalError,
originalError.extensions
);
Expand Down
42 changes: 28 additions & 14 deletions packages/wrap/tests/fixtures/schemas.ts
Expand Up @@ -16,6 +16,7 @@ import { introspectSchema } from '../../src/introspect';
import {
IResolvers,
ExecutionResult,
mapAsyncIterator,
} from '@graphql-tools/utils';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { SubschemaConfig, ExecutionParams } from '@graphql-tools/delegate';
Expand Down Expand Up @@ -680,23 +681,36 @@ export const subscriptionSchema: GraphQLSchema = makeExecutableSchema({
});

function makeExecutorFromSchema(schema: GraphQLSchema) {
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => graphql(
schema,
print(document),
null,
context,
variables,
) as PromiseOrValue<ExecutionResult<TReturn>>;
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => {
const result = graphql(
schema,
print(document),
null,
context,
variables,
) as PromiseOrValue<ExecutionResult<TReturn>>;
if (result instanceof Promise) {
return result.then(originalResult => JSON.parse(JSON.stringify(originalResult)));
}
return JSON.parse(JSON.stringify(result));
};
}

function makeSubscriberFromSchema(schema: GraphQLSchema) {
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => subscribe(
schema,
document,
null,
context,
variables,
) as Promise<AsyncIterator<ExecutionResult<TReturn>> | ExecutionResult<TReturn>>
return async <TReturn, TArgs, TContext>({ document, variables, context }: ExecutionParams<TArgs, TContext>) => {
const result = subscribe(
schema,
document,
null,
context,
variables,
) as Promise<AsyncIterator<ExecutionResult<TReturn>> | ExecutionResult<TReturn>>;
if (result instanceof Promise) {
return result.then(asyncIterator =>
mapAsyncIterator(asyncIterator as AsyncIterator<ExecutionResult>, (originalResult: ExecutionResult<TReturn>) => JSON.parse(JSON.stringify(originalResult))));
}
return JSON.parse(JSON.stringify(result));
};
}

export async function makeSchemaRemote(
Expand Down

0 comments on commit b493948

Please sign in to comment.