2018-06-15 08:13:33 +02:00
|
|
|
import { HTTPCache } from './HTTPCache';
|
2018-06-20 14:04:22 +02:00
|
|
|
import {
|
|
|
|
ApolloError,
|
|
|
|
AuthenticationError,
|
|
|
|
ForbiddenError,
|
|
|
|
} from 'apollo-server-errors';
|
2018-06-15 08:13:33 +02:00
|
|
|
|
|
|
|
export abstract class RESTDataSource<TContext = any> {
|
|
|
|
abstract baseURL: string;
|
|
|
|
|
|
|
|
httpCache!: HTTPCache;
|
|
|
|
context!: TContext;
|
|
|
|
|
2018-06-20 14:04:22 +02:00
|
|
|
protected willSendRequest?(request: Request): void;
|
2018-06-15 08:13:33 +02:00
|
|
|
|
2018-06-20 14:04:22 +02:00
|
|
|
protected async didReceiveErrorResponse<TResult = any>(
|
|
|
|
response: Response,
|
|
|
|
): Promise<TResult> {
|
|
|
|
const message = `${response.status} ${
|
|
|
|
response.statusText
|
|
|
|
}: ${await response.text()}`;
|
|
|
|
|
|
|
|
if (response.status === 401) {
|
|
|
|
throw new AuthenticationError(message);
|
|
|
|
} else if (response.status === 403) {
|
|
|
|
throw new ForbiddenError(message);
|
|
|
|
} else {
|
|
|
|
throw new ApolloError(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected async get<TResult = any>(
|
2018-06-15 08:13:33 +02:00
|
|
|
path: string,
|
2018-06-19 10:14:59 +02:00
|
|
|
params?: URLSearchParamsInit,
|
2018-06-15 08:13:33 +02:00
|
|
|
options?: RequestInit,
|
2018-06-20 14:04:22 +02:00
|
|
|
): Promise<TResult> {
|
|
|
|
return this.fetch<TResult>(
|
2018-06-19 09:40:42 +02:00
|
|
|
path,
|
|
|
|
params,
|
|
|
|
Object.assign({ method: 'GET' }, options),
|
|
|
|
);
|
2018-06-15 08:13:33 +02:00
|
|
|
}
|
|
|
|
|
2018-06-20 14:04:22 +02:00
|
|
|
protected async post<TResult = any>(
|
2018-06-15 08:13:33 +02:00
|
|
|
path: string,
|
2018-06-19 10:14:59 +02:00
|
|
|
params?: URLSearchParamsInit,
|
2018-06-15 08:13:33 +02:00
|
|
|
options?: RequestInit,
|
2018-06-20 14:04:22 +02:00
|
|
|
): Promise<TResult> {
|
|
|
|
return this.fetch<TResult>(
|
2018-06-19 09:40:42 +02:00
|
|
|
path,
|
|
|
|
params,
|
|
|
|
Object.assign({ method: 'POST' }, options),
|
|
|
|
);
|
2018-06-15 08:13:33 +02:00
|
|
|
}
|
|
|
|
|
2018-06-20 14:04:22 +02:00
|
|
|
protected async patch<TResult = any>(
|
2018-06-15 08:13:33 +02:00
|
|
|
path: string,
|
2018-06-19 10:14:59 +02:00
|
|
|
params?: URLSearchParamsInit,
|
2018-06-15 08:13:33 +02:00
|
|
|
options?: RequestInit,
|
2018-06-20 14:04:22 +02:00
|
|
|
): Promise<TResult> {
|
|
|
|
return this.fetch<TResult>(
|
2018-06-19 09:40:42 +02:00
|
|
|
path,
|
|
|
|
params,
|
|
|
|
Object.assign({ method: 'PATCH' }, options),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-06-20 14:04:22 +02:00
|
|
|
protected async put<TResult = any>(
|
2018-06-19 09:40:42 +02:00
|
|
|
path: string,
|
2018-06-19 10:14:59 +02:00
|
|
|
params?: URLSearchParamsInit,
|
2018-06-19 09:40:42 +02:00
|
|
|
options?: RequestInit,
|
2018-06-20 14:04:22 +02:00
|
|
|
): Promise<TResult> {
|
|
|
|
return this.fetch<TResult>(
|
2018-06-19 09:40:42 +02:00
|
|
|
path,
|
|
|
|
params,
|
|
|
|
Object.assign({ method: 'PUT' }, options),
|
|
|
|
);
|
2018-06-15 08:13:33 +02:00
|
|
|
}
|
|
|
|
|
2018-06-20 14:04:22 +02:00
|
|
|
protected async delete<TResult = any>(
|
2018-06-15 08:13:33 +02:00
|
|
|
path: string,
|
2018-06-19 10:14:59 +02:00
|
|
|
params?: URLSearchParamsInit,
|
2018-06-15 08:13:33 +02:00
|
|
|
options?: RequestInit,
|
2018-06-20 14:04:22 +02:00
|
|
|
): Promise<TResult> {
|
|
|
|
return this.fetch<TResult>(
|
2018-06-15 08:13:33 +02:00
|
|
|
path,
|
|
|
|
params,
|
|
|
|
Object.assign({ method: 'DELETE' }, options),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-06-20 14:04:22 +02:00
|
|
|
private async fetch<TResult>(
|
2018-06-15 08:13:33 +02:00
|
|
|
path: string,
|
2018-06-19 10:14:59 +02:00
|
|
|
params?: URLSearchParamsInit,
|
2018-06-15 08:13:33 +02:00
|
|
|
init?: RequestInit,
|
2018-06-20 14:04:22 +02:00
|
|
|
): Promise<TResult> {
|
2018-06-15 08:13:33 +02:00
|
|
|
const url = new URL(path, this.baseURL);
|
|
|
|
|
2018-06-19 10:14:59 +02:00
|
|
|
if (params) {
|
2018-06-15 08:13:33 +02:00
|
|
|
// Append params to existing params in the path
|
|
|
|
for (const [name, value] of new URLSearchParams(params)) {
|
|
|
|
url.searchParams.append(name, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.trace(`${(init && init.method) || 'GET'} ${url}`, async () => {
|
|
|
|
const request = new Request(String(url));
|
2018-06-19 13:23:14 +02:00
|
|
|
|
2018-06-15 08:13:33 +02:00
|
|
|
if (this.willSendRequest) {
|
|
|
|
this.willSendRequest(request);
|
|
|
|
}
|
2018-06-19 13:23:14 +02:00
|
|
|
|
2018-06-15 08:13:33 +02:00
|
|
|
const response = await this.httpCache.fetch(request, init);
|
|
|
|
if (response.ok) {
|
2018-06-19 13:23:14 +02:00
|
|
|
const contentType = response.headers.get('Content-Type');
|
|
|
|
|
|
|
|
if (contentType && contentType.startsWith('application/json')) {
|
|
|
|
return response.json();
|
|
|
|
} else {
|
|
|
|
return response.text();
|
|
|
|
}
|
2018-06-15 08:13:33 +02:00
|
|
|
} else {
|
2018-06-20 14:04:22 +02:00
|
|
|
return this.didReceiveErrorResponse(response);
|
2018-06-15 08:13:33 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-06-20 14:04:22 +02:00
|
|
|
private async trace<TResult>(
|
2018-06-15 08:13:33 +02:00
|
|
|
label: string,
|
2018-06-20 14:04:22 +02:00
|
|
|
fn: () => Promise<TResult>,
|
|
|
|
): Promise<TResult> {
|
2018-06-20 12:00:11 +02:00
|
|
|
if (process && process.env && process.env.NODE_ENV === 'development') {
|
|
|
|
// We're not using console.time because that isn't supported on Cloudflare
|
|
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
|
|
return await fn();
|
|
|
|
} finally {
|
|
|
|
const duration = Date.now() - startTime;
|
|
|
|
console.log(`${label} (${duration}ms)`);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return fn();
|
2018-06-15 08:13:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|