Skip to content

Commit

Permalink
gateway RemoteGraphQLDataSource: throw GraphQLError, not ApolloError
Browse files Browse the repository at this point in the history
This is part of
apollographql/apollo-server#6057 (which is
itself part of
apollographql/apollo-server#6719). We are
trying to break the dependency of Gateway on Server so that (among other
things) it is easier to have a single version of Gateway that works with
both the current AS3 and the upcoming AS4.

In AS4, we are removing the ApolloError class and its subclasses.
Instead, we will just use GraphQLError directly. See:

https://www.apollographql.com/docs/apollo-server/v4/migration#apolloerror
https://www.apollographql.com/docs/apollo-server/v4/migration#built-in-error-classes
apollographql/apollo-server#6355
apollographql/apollo-server#6705

This commit changes RemoteGraphQLDataSource to throw GraphQLError
instead of ApolloError. The `code` extension will still be the same.
(The `name` field of the thrown Error will no longer be eg
`AuthenticationError`, though; this does not affect the error as
serialized in GraphQL.)

This is technically slightly backwards-incompatible (eg, the method
errorFromResponse is public and now returns GraphQLError instead of the
tighter ApolloError) but this doesn't seem likely to affect many users.
We can adjust based on feedback if necessary.
  • Loading branch information
glasser committed Aug 2, 2022
1 parent 5843173 commit 23ad214
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 36 deletions.
1 change: 0 additions & 1 deletion gateway-js/package.json
Expand Up @@ -38,7 +38,6 @@
"apollo-reporting-protobuf": "^0.8.0 || ^3.0.0",
"apollo-server-caching": "^0.7.0 || ^3.0.0",
"apollo-server-core": "^2.23.0 || ^3.0.0",
"apollo-server-errors": "^2.5.0 || ^3.0.0",
"apollo-server-types": "^0.9.0 || ^3.0.0",
"async-retry": "^1.3.3",
"loglevel": "^1.6.1",
Expand Down
31 changes: 12 additions & 19 deletions gateway-js/src/datasources/RemoteGraphQLDataSource.ts
Expand Up @@ -7,18 +7,14 @@ import {
CacheScope,
CachePolicy,
} from 'apollo-server-types';
import {
ApolloError,
AuthenticationError,
ForbiddenError,
} from 'apollo-server-errors';
import { isObject } from '../utilities/predicates';
import { GraphQLDataSource, GraphQLDataSourceProcessOptions, GraphQLDataSourceRequestKind } from './types';
import { createHash } from '@apollo/utils.createhash';
import { parseCacheControlHeader } from './parseCacheControlHeader';
import fetcher from 'make-fetch-happen';
import { Headers as NodeFetchHeaders, Request as NodeFetchRequest } from 'node-fetch';
import { Fetcher, FetcherRequestInit, FetcherResponse } from '@apollo/utils.fetcher';
import { GraphQLError, GraphQLErrorExtensions } from 'graphql';

export class RemoteGraphQLDataSource<
TContext extends Record<string, any> = Record<string, any>,
Expand Down Expand Up @@ -302,28 +298,25 @@ export class RemoteGraphQLDataSource<
}

public async errorFromResponse(response: FetcherResponse) {
const message = `${response.status}: ${response.statusText}`;

let error: ApolloError;
if (response.status === 401) {
error = new AuthenticationError(message);
} else if (response.status === 403) {
error = new ForbiddenError(message);
} else {
error = new ApolloError(message);
}

const body = await this.parseBody(response);

Object.assign(error.extensions, {
const extensions: GraphQLErrorExtensions = {
response: {
url: response.url,
status: response.status,
statusText: response.statusText,
body,
},
});
};

return error;
if (response.status === 401) {
extensions.code = 'UNAUTHENTICATED';
} else if (response.status === 403) {
extensions.code = 'FORBIDDEN';
}

return new GraphQLError(`${response.status}: ${response.statusText}`, {
extensions,
});
}
}
@@ -1,15 +1,10 @@
import {
ApolloError,
AuthenticationError,
ForbiddenError,
} from 'apollo-server-errors';

import { RemoteGraphQLDataSource } from '../RemoteGraphQLDataSource';
import { Response, Headers } from 'node-fetch';
import { GraphQLRequestContext } from 'apollo-server-types';
import { GraphQLDataSourceRequestKind } from '../types';
import { nockBeforeEach, nockAfterEach } from '../../__tests__/nockAssertions';
import nock from 'nock';
import { GraphQLError } from 'graphql';

beforeEach(nockBeforeEach);
afterEach(nockAfterEach);
Expand Down Expand Up @@ -461,15 +456,15 @@ describe('didEncounterError', () => {
context,
});

await expect(result).rejects.toThrow(AuthenticationError);
await expect(result).rejects.toThrow(GraphQLError);
expect(context).toMatchObject({
timingData: [{ time: 1616446845234 }],
});
});
});

describe('error handling', () => {
it('throws an AuthenticationError when the response status is 401', async () => {
it('throws error with code UNAUTHENTICATED when the response status is 401', async () => {
const DataSource = new RemoteGraphQLDataSource({
url: 'https://api.example.com/foo',
});
Expand All @@ -480,7 +475,7 @@ describe('error handling', () => {
...defaultProcessOptions,
request: { query: '{ me { name } }' },
});
await expect(result).rejects.toThrow(AuthenticationError);
await expect(result).rejects.toThrow(GraphQLError);
await expect(result).rejects.toMatchObject({
extensions: {
code: 'UNAUTHENTICATED',
Expand All @@ -492,7 +487,7 @@ describe('error handling', () => {
});
});

it('throws a ForbiddenError when the response status is 403', async () => {
it('throws an error with code FORBIDDEN when the response status is 403', async () => {
const DataSource = new RemoteGraphQLDataSource({
url: 'https://api.example.com/foo',
});
Expand All @@ -503,7 +498,7 @@ describe('error handling', () => {
...defaultProcessOptions,
request: { query: '{ me { name } }' },
});
await expect(result).rejects.toThrow(ForbiddenError);
await expect(result).rejects.toThrow(GraphQLError);
await expect(result).rejects.toMatchObject({
extensions: {
code: 'FORBIDDEN',
Expand All @@ -515,7 +510,7 @@ describe('error handling', () => {
});
});

it('throws an ApolloError when the response status is 500', async () => {
it('throws a GraphQLError when the response status is 500', async () => {
const DataSource = new RemoteGraphQLDataSource({
url: 'https://api.example.com/foo',
});
Expand All @@ -526,7 +521,7 @@ describe('error handling', () => {
...defaultProcessOptions,
request: { query: '{ me { name } }' },
});
await expect(result).rejects.toThrow(ApolloError);
await expect(result).rejects.toThrow(GraphQLError);
await expect(result).rejects.toMatchObject({
extensions: {
response: {
Expand Down Expand Up @@ -560,7 +555,7 @@ describe('error handling', () => {
...defaultProcessOptions,
request: { query: '{ me { name } }' },
});
await expect(result).rejects.toThrow(ApolloError);
await expect(result).rejects.toThrow(GraphQLError);
await expect(result).rejects.toMatchObject({
extensions: {
response: {
Expand Down
2 changes: 0 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 23ad214

Please sign in to comment.