111 lines
3.2 KiB
TypeScript
111 lines
3.2 KiB
TypeScript
import { flow, fromPairs, get } from 'lodash';
|
|
import { map } from 'lodash/fp';
|
|
import { fromJS } from 'immutable';
|
|
import unsentRequest from './unsentRequest';
|
|
import APIError from './APIError';
|
|
|
|
type Formatter = (res: Response) => Promise<string | Blob | unknown>;
|
|
|
|
export const filterByPropExtension = (extension: string, propName: string) => <T>(arr: T[]) =>
|
|
arr.filter(el =>
|
|
get(el, propName, '').endsWith(extension.startsWith('.') ? extension : `.${extension}`),
|
|
);
|
|
|
|
const catchFormatErrors = (format: string, formatter: Formatter) => (res: Response) => {
|
|
try {
|
|
return formatter(res);
|
|
} catch (err) {
|
|
throw new Error(
|
|
`Response cannot be parsed into the expected format (${format}): ${err.message}`,
|
|
);
|
|
}
|
|
};
|
|
|
|
const responseFormatters = fromJS({
|
|
json: async (res: Response) => {
|
|
const contentType = res.headers.get('Content-Type') || '';
|
|
if (!contentType.startsWith('application/json') && !contentType.startsWith('text/json')) {
|
|
throw new Error(`${contentType} is not a valid JSON Content-Type`);
|
|
}
|
|
return res.json();
|
|
},
|
|
text: async (res: Response) => res.text(),
|
|
blob: async (res: Response) => res.blob(),
|
|
}).mapEntries(([format, formatter]: [string, Formatter]) => [
|
|
format,
|
|
catchFormatErrors(format, formatter),
|
|
]);
|
|
|
|
export const parseResponse = async (
|
|
res: Response,
|
|
{ expectingOk = true, format = 'text', apiName = '' },
|
|
) => {
|
|
let body;
|
|
try {
|
|
const formatter = responseFormatters.get(format, false);
|
|
if (!formatter) {
|
|
throw new Error(`${format} is not a supported response format.`);
|
|
}
|
|
body = await formatter(res);
|
|
} catch (err) {
|
|
throw new APIError(err.message, res.status, apiName);
|
|
}
|
|
if (expectingOk && !res.ok) {
|
|
const isJSON = format === 'json';
|
|
const message = isJSON ? body.message || body.msg || body.error?.message : body;
|
|
throw new APIError(isJSON && message ? message : body, res.status, apiName);
|
|
}
|
|
return body;
|
|
};
|
|
|
|
export const responseParser = (options: {
|
|
expectingOk?: boolean;
|
|
format: string;
|
|
apiName: string;
|
|
}) => (res: Response) => parseResponse(res, options);
|
|
|
|
export const parseLinkHeader = flow([
|
|
linksString => linksString.split(','),
|
|
map((str: string) => str.trim().split(';')),
|
|
map(([linkStr, keyStr]) => [
|
|
keyStr.match(/rel="(.*?)"/)[1],
|
|
linkStr
|
|
.trim()
|
|
.match(/<(.*?)>/)[1]
|
|
.replace(/\+/g, '%20'),
|
|
]),
|
|
fromPairs,
|
|
]);
|
|
|
|
export const getAllResponses = async (
|
|
url: string,
|
|
options: { headers?: {} } = {},
|
|
linkHeaderRelName: string,
|
|
nextUrlProcessor: (url: string) => string,
|
|
) => {
|
|
const maxResponses = 30;
|
|
let responseCount = 1;
|
|
|
|
let req = unsentRequest.fromFetchArguments(url, options);
|
|
|
|
const pageResponses = [];
|
|
|
|
while (req && responseCount < maxResponses) {
|
|
const pageResponse = await unsentRequest.performRequest(req);
|
|
const linkHeader = pageResponse.headers.get('Link');
|
|
const nextURL = linkHeader && parseLinkHeader(linkHeader)[linkHeaderRelName];
|
|
|
|
const { headers = {} } = options;
|
|
req = nextURL && unsentRequest.fromFetchArguments(nextUrlProcessor(nextURL), { headers });
|
|
pageResponses.push(pageResponse);
|
|
responseCount++;
|
|
}
|
|
|
|
return pageResponses;
|
|
};
|
|
|
|
export const getPathDepth = (path: string) => {
|
|
const depth = path.split('/').length;
|
|
return depth;
|
|
};
|