Skip to content

Commit

Permalink
New throwIfResponseIsError hook (#115)
Browse files Browse the repository at this point in the history
We previously removed `didReceiveResponse` which let you override the
main "is it an error or not" logic. This restores it in an
error-specific way. This means you can throw errors even on 200s if you
want (or vice versa).

This also changes the `errorFromResponse` hook to take options instead
of a single argument, in case you want the error to reflect more data.

One negative change is you can no longer prevent `parseBody` from being
called, though you can always override `parseBody` to do less.

Fixes #32.
  • Loading branch information
glasser authored Dec 9, 2022
1 parent 6ebc093 commit be4371f
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-yaks-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@apollo/datasource-rest': major
---

The `errorFromResponse` method now receives an options object with `url`, `request`, `response`, and `parsedBody` rather than just a response, and the body has already been parsed.
5 changes: 5 additions & 0 deletions .changeset/silly-apricots-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@apollo/datasource-rest': minor
---

New `throwIfResponseIsError` hook allows you to control whether a response should be returned or thrown as an error. Partially replaces the removed `didReceiveResponse` hook.
39 changes: 31 additions & 8 deletions src/RESTDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,27 @@ export abstract class RESTDataSource {
);
}

protected async errorFromResponse(response: FetcherResponse) {
const body = await this.parseBody(response);
protected async throwIfResponseIsError(options: {
url: URL;
request: RequestOptions;
response: FetcherResponse;
parsedBody: unknown;
}) {
if (options.response.ok) {
return;
}
throw await this.errorFromResponse(options);
}

protected async errorFromResponse({
response,
parsedBody,
}: {
url: URL;
request: RequestOptions;
response: FetcherResponse;
parsedBody: unknown;
}) {
return new GraphQLError(`${response.status}: ${response.statusText}`, {
extensions: {
...(response.status === 401
Expand All @@ -264,7 +282,7 @@ export abstract class RESTDataSource {
url: response.url,
status: response.status,
statusText: response.statusText,
body,
body: parsedBody,
},
},
});
Expand Down Expand Up @@ -372,11 +390,16 @@ export abstract class RESTDataSource {
outgoingRequest.httpCacheSemanticsCachePolicyOptions,
});

if (response.ok) {
return (await this.parseBody(response)) as TResult;
} else {
throw await this.errorFromResponse(response);
}
const parsedBody = await this.parseBody(response);

await this.throwIfResponseIsError({
url,
request: outgoingRequest,
response,
parsedBody,
});

return parsedBody as TResult;
} catch (error) {
this.didEncounterError(error as Error, outgoingRequest);
throw error;
Expand Down
29 changes: 29 additions & 0 deletions src/__tests__/RESTDataSource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,35 @@ describe('RESTDataSource', () => {
});

describe('error handling', () => {
it('can throw on 200 with throwIfResponseIsError', async () => {
const dataSource = new (class extends RESTDataSource {
override baseURL = 'https://api.example.com';

getFoo() {
return this.get('foo');
}

protected override async throwIfResponseIsError(
options: Parameters<RESTDataSource['throwIfResponseIsError']>[0],
): Promise<void> {
throw await this.errorFromResponse(options);
}
})();

nock(apiUrl).get('/foo').reply(200, 'Invalid token');

const result = dataSource.getFoo();
await expect(result).rejects.toThrow(GraphQLError);
await expect(result).rejects.toMatchObject({
extensions: {
response: {
status: 200,
body: 'Invalid token',
},
},
});
});

it('throws an UNAUTHENTICATED error when the response status is 401', async () => {
const dataSource = new (class extends RESTDataSource {
override baseURL = 'https://api.example.com';
Expand Down

0 comments on commit be4371f

Please sign in to comment.