diff --git a/packages/apollo-datasource-rest/src/RESTDataSource.ts b/packages/apollo-datasource-rest/src/RESTDataSource.ts index d599b322..fc2ecd7d 100644 --- a/packages/apollo-datasource-rest/src/RESTDataSource.ts +++ b/packages/apollo-datasource-rest/src/RESTDataSource.ts @@ -5,6 +5,12 @@ import { ForbiddenError, } from 'apollo-server-errors'; +export type RequestOptions = RequestInit & { + params?: URLSearchParamsInit; + body: Body; +}; +export type Body = BodyInit | object; + export abstract class RESTDataSource { abstract baseURL: string; @@ -32,68 +38,64 @@ export abstract class RESTDataSource { protected async get( path: string, params?: URLSearchParamsInit, - options?: RequestInit, + options?: RequestOptions, ): Promise { return this.fetch( path, - params, - Object.assign({ method: 'GET' }, options), + Object.assign({ method: 'GET', params }, options), ); } protected async post( path: string, - params?: URLSearchParamsInit, - options?: RequestInit, + body?: Body, + options?: RequestOptions, ): Promise { return this.fetch( path, - params, - Object.assign({ method: 'POST' }, options), + Object.assign({ method: 'POST', body }, options), ); } protected async patch( path: string, - params?: URLSearchParamsInit, - options?: RequestInit, + body?: Body, + options?: RequestOptions, ): Promise { return this.fetch( path, - params, - Object.assign({ method: 'PATCH' }, options), + Object.assign({ method: 'PATCH', body }, options), ); } protected async put( path: string, - params?: URLSearchParamsInit, - options?: RequestInit, + body?: Body, + options?: RequestOptions, ): Promise { return this.fetch( path, - params, - Object.assign({ method: 'PUT' }, options), + Object.assign({ method: 'PUT', body }, options), ); } protected async delete( path: string, params?: URLSearchParamsInit, - options?: RequestInit, + options?: RequestOptions, ): Promise { return this.fetch( path, - params, - Object.assign({ method: 'DELETE' }, options), + Object.assign({ method: 'DELETE', params }, options), ); } private async fetch( path: string, - params?: URLSearchParamsInit, - init?: RequestInit, + options: RequestOptions, ): Promise { + const { params, ...init } = options; + const normalizedBaseURL = this.baseURL.endsWith('/') ? this.baseURL : this.baseURL.concat('/'); @@ -106,14 +108,27 @@ export abstract class RESTDataSource { } } - return this.trace(`${(init && init.method) || 'GET'} ${url}`, async () => { - const request = new Request(String(url)); + // We accept arbitrary objects as body and serialize them as JSON + if ( + init.body !== undefined && + typeof init.body !== 'string' && + !(init.body instanceof ArrayBuffer) + ) { + init.body = JSON.stringify(init.body); + if (!(init.headers instanceof Headers)) { + init.headers = new Headers(init.headers); + } + init.headers.set('Content-Type', 'application/json'); + } + + return this.trace(`${init.method || 'GET'} ${url}`, async () => { + const request = new Request(String(url), init); if (this.willSendRequest) { this.willSendRequest(request); } - const response = await this.httpCache.fetch(request, init); + const response = await this.httpCache.fetch(request); if (response.ok) { const contentType = response.headers.get('Content-Type'); diff --git a/packages/apollo-datasource-rest/src/__tests__/RESTDataSource.test.ts b/packages/apollo-datasource-rest/src/__tests__/RESTDataSource.test.ts index a8d5600f..1b03828d 100644 --- a/packages/apollo-datasource-rest/src/__tests__/RESTDataSource.test.ts +++ b/packages/apollo-datasource-rest/src/__tests__/RESTDataSource.test.ts @@ -183,6 +183,29 @@ describe('RESTDataSource', () => { ); }); + it('allows passing a request body', async () => { + const dataSource = new class extends RESTDataSource { + baseURL = 'https://api.example.com'; + + postFoo(foo) { + return this.post('foo', foo); + } + }(); + + dataSource.httpCache = httpCache; + + fetch.mockJSONResponseOnce(); + + await dataSource.postFoo({ foo: 'bar' }); + + expect(fetch.mock.calls.length).toEqual(1); + expect(fetch.mock.calls[0][0].url).toEqual('https://api.example.com/foo'); + expect(fetch.mock.calls[0][0].body).toEqual(JSON.stringify({ foo: 'bar' })); + expect(fetch.mock.calls[0][0].headers.get('Content-Type')).toEqual( + 'application/json', + ); + }); + for (const method of ['GET', 'POST', 'PATCH', 'PUT', 'DELETE']) { const dataSource = new class extends RESTDataSource { baseURL = 'https://api.example.com';