Allow passing a body to POST, PATCH and PUT in data source

This commit is contained in:
Martijn Walraven 2018-06-25 21:50:05 +02:00
parent f3fd52da21
commit b6e2096876
2 changed files with 61 additions and 23 deletions

View file

@ -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<TContext = any> {
abstract baseURL: string;
@ -32,68 +38,64 @@ export abstract class RESTDataSource<TContext = any> {
protected async get<TResult = any>(
path: string,
params?: URLSearchParamsInit,
options?: RequestInit,
options?: RequestOptions,
): Promise<TResult> {
return this.fetch<TResult>(
path,
params,
Object.assign({ method: 'GET' }, options),
Object.assign({ method: 'GET', params }, options),
);
}
protected async post<TResult = any>(
path: string,
params?: URLSearchParamsInit,
options?: RequestInit,
body?: Body,
options?: RequestOptions,
): Promise<TResult> {
return this.fetch<TResult>(
path,
params,
Object.assign({ method: 'POST' }, options),
Object.assign({ method: 'POST', body }, options),
);
}
protected async patch<TResult = any>(
path: string,
params?: URLSearchParamsInit,
options?: RequestInit,
body?: Body,
options?: RequestOptions,
): Promise<TResult> {
return this.fetch<TResult>(
path,
params,
Object.assign({ method: 'PATCH' }, options),
Object.assign({ method: 'PATCH', body }, options),
);
}
protected async put<TResult = any>(
path: string,
params?: URLSearchParamsInit,
options?: RequestInit,
body?: Body,
options?: RequestOptions,
): Promise<TResult> {
return this.fetch<TResult>(
path,
params,
Object.assign({ method: 'PUT' }, options),
Object.assign({ method: 'PUT', body }, options),
);
}
protected async delete<TResult = any>(
path: string,
params?: URLSearchParamsInit,
options?: RequestInit,
options?: RequestOptions,
): Promise<TResult> {
return this.fetch<TResult>(
path,
params,
Object.assign({ method: 'DELETE' }, options),
Object.assign({ method: 'DELETE', params }, options),
);
}
private async fetch<TResult>(
path: string,
params?: URLSearchParamsInit,
init?: RequestInit,
options: RequestOptions,
): Promise<TResult> {
const { params, ...init } = options;
const normalizedBaseURL = this.baseURL.endsWith('/')
? this.baseURL
: this.baseURL.concat('/');
@ -106,14 +108,27 @@ export abstract class RESTDataSource<TContext = any> {
}
}
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');

View file

@ -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';