Skip to content

Commit c6a9eaa

Browse files
committed
fix: parse JSON body when Content-Type has charset
Or other parameters. In accordance with RFC 1341[^1], Content-Type contains first and foremost the media type. It can be followed by key-value pairs of parameters. This change supports extracting the media type and effectively discarding the other parameters as these are currently irrelevant to body parsing. The result media type is then matched against previously used JSON type tests. Closes #229 Closes #341 [^1]:https://www.rfc-editor.org/rfc/rfc1341
1 parent 9ac8acf commit c6a9eaa

File tree

3 files changed

+53
-3
lines changed

3 files changed

+53
-3
lines changed

.changeset/unlucky-geese-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@apollo/datasource-rest': patch
3+
---
4+
5+
Parse JSON body when Content-Type has charset

src/RESTDataSource.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,14 +303,14 @@ export abstract class RESTDataSource<CO extends CacheOptions = CacheOptions> {
303303
protected parseBody(response: FetcherResponse): Promise<object | string> {
304304
const contentType = response.headers.get('Content-Type');
305305
const contentLength = response.headers.get('Content-Length');
306+
const mediaType = contentType?.split(';')?.[0];
306307
if (
307308
// As one might expect, a "204 No Content" is empty! This means there
308309
// isn't enough to `JSON.parse`, and trying will result in an error.
309310
response.status !== 204 &&
310311
contentLength !== '0' &&
311-
contentType &&
312-
(contentType.startsWith('application/json') ||
313-
contentType.endsWith('+json'))
312+
(mediaType?.startsWith('application/json') ||
313+
mediaType?.endsWith('+json'))
314314
) {
315315
return response.json();
316316
} else {

src/__tests__/RESTDataSource.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,51 @@ describe('RESTDataSource', () => {
784784
expect(data).toEqual({ foo: 'bar' });
785785
});
786786

787+
it('returns data as parsed JSON when Content-Type ends in +json and has charset provided', async () => {
788+
const dataSource = new (class extends RESTDataSource {
789+
override baseURL = 'https://api.example.com';
790+
791+
getFoo() {
792+
return this.get('foo');
793+
}
794+
})();
795+
796+
nock(apiUrl)
797+
.get('/foo')
798+
.reply(
799+
200,
800+
{ foo: 'bar' },
801+
{ 'content-type': 'application/vnd.api+json; charset=utf-8' },
802+
);
803+
804+
const data = await dataSource.getFoo();
805+
806+
expect(data).toEqual({ foo: 'bar' });
807+
});
808+
809+
it('returns data as parsed JSON when Content-Type ends in +json and has many parameters provided', async () => {
810+
const dataSource = new (class extends RESTDataSource {
811+
override baseURL = 'https://api.example.com';
812+
813+
getFoo() {
814+
return this.get('foo');
815+
}
816+
})();
817+
818+
nock(apiUrl).get('/foo').reply(
819+
200,
820+
{ foo: 'bar' },
821+
{
822+
'content-type':
823+
'application/vnd.api+json; charset=utf-8; boundary=ExampleBoundaryString',
824+
},
825+
);
826+
827+
const data = await dataSource.getFoo();
828+
829+
expect(data).toEqual({ foo: 'bar' });
830+
});
831+
787832
it('returns data as a string when Content-Type is text/plain', async () => {
788833
const dataSource = new (class extends RESTDataSource {
789834
override baseURL = 'https://api.example.com';

0 commit comments

Comments
 (0)