Created
September 6, 2022 22:17
-
-
Save tonyxiao/9df2d9ce50096a1adf96c5437a084d06 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* istanbul ignore file */ | |
/* tslint:disable */ | |
/* eslint-disable */ | |
import axios from 'axios'; | |
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; | |
import FormData from 'form-data'; | |
import { ApiError } from './ApiError'; | |
import type { ApiRequestOptions } from './ApiRequestOptions'; | |
import type { ApiResult } from './ApiResult'; | |
import { CancelablePromise } from './CancelablePromise'; | |
import type { OnCancel } from './CancelablePromise'; | |
import type { OpenAPIConfig } from './OpenAPI'; | |
const isDefined = <T>(value: T | null | undefined): value is Exclude<T, null | undefined> => { | |
return value !== undefined && value !== null; | |
}; | |
const isString = (value: any): value is string => { | |
return typeof value === 'string'; | |
}; | |
const isStringWithValue = (value: any): value is string => { | |
return isString(value) && value !== ''; | |
}; | |
const isBlob = (value: any): value is Blob => { | |
return ( | |
typeof value === 'object' && | |
typeof value.type === 'string' && | |
typeof value.stream === 'function' && | |
typeof value.arrayBuffer === 'function' && | |
typeof value.constructor === 'function' && | |
typeof value.constructor.name === 'string' && | |
/^(Blob|File)$/.test(value.constructor.name) && | |
/^(Blob|File)$/.test(value[Symbol.toStringTag]) | |
); | |
}; | |
const isFormData = (value: any): value is FormData => { | |
return value instanceof FormData; | |
}; | |
const isSuccess = (status: number): boolean => { | |
return status >= 200 && status < 300; | |
}; | |
const base64 = (str: string): string => { | |
try { | |
return btoa(str); | |
} catch (err) { | |
// @ts-ignore | |
return Buffer.from(str).toString('base64'); | |
} | |
}; | |
const getQueryString = (params: Record<string, any>): string => { | |
const qs: string[] = []; | |
const append = (key: string, value: any) => { | |
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); | |
}; | |
const process = (key: string, value: any) => { | |
if (isDefined(value)) { | |
if (Array.isArray(value)) { | |
value.forEach(v => { | |
process(key, v); | |
}); | |
} else if (typeof value === 'object') { | |
Object.entries(value).forEach(([k, v]) => { | |
process(`${key}[${k}]`, v); | |
}); | |
} else { | |
append(key, value); | |
} | |
} | |
}; | |
Object.entries(params).forEach(([key, value]) => { | |
process(key, value); | |
}); | |
if (qs.length > 0) { | |
return `?${qs.join('&')}`; | |
} | |
return ''; | |
}; | |
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { | |
const encoder = config.ENCODE_PATH || encodeURI; | |
const path = options.url | |
.replace('{api-version}', config.VERSION) | |
.replace(/{(.*?)}/g, (substring: string, group: string) => { | |
if (options.path?.hasOwnProperty(group)) { | |
return encoder(String(options.path[group])); | |
} | |
return substring; | |
}); | |
const url = `${config.BASE}${path}`; | |
if (options.query) { | |
return `${url}${getQueryString(options.query)}`; | |
} | |
return url; | |
}; | |
const getFormData = (options: ApiRequestOptions): FormData | undefined => { | |
if (options.formData) { | |
const formData = new FormData(); | |
const process = (key: string, value: any) => { | |
if (isString(value) || isBlob(value)) { | |
formData.append(key, value); | |
} else { | |
formData.append(key, JSON.stringify(value)); | |
} | |
}; | |
Object.entries(options.formData) | |
.filter(([_, value]) => isDefined(value)) | |
.forEach(([key, value]) => { | |
if (Array.isArray(value)) { | |
value.forEach(v => process(key, v)); | |
} else { | |
process(key, value); | |
} | |
}); | |
return formData; | |
} | |
return undefined; | |
}; | |
type Resolver<T> = (options: ApiRequestOptions) => Promise<T>; | |
const resolve = async <T>(options: ApiRequestOptions, resolver?: T | Resolver<T>): Promise<T | undefined> => { | |
if (typeof resolver === 'function') { | |
return (resolver as Resolver<T>)(options); | |
} | |
return resolver; | |
}; | |
const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, formData?: FormData): Promise<Record<string, string>> => { | |
const token = await resolve(options, config.TOKEN); | |
const username = await resolve(options, config.USERNAME); | |
const password = await resolve(options, config.PASSWORD); | |
const additionalHeaders = await resolve(options, config.HEADERS); | |
const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {} | |
const headers = Object.entries({ | |
Accept: 'application/json', | |
...additionalHeaders, | |
...options.headers, | |
...formHeaders, | |
}) | |
.filter(([_, value]) => isDefined(value)) | |
.reduce((headers, [key, value]) => ({ | |
...headers, | |
[key]: String(value), | |
}), {} as Record<string, string>); | |
if (isStringWithValue(token)) { | |
headers['Authorization'] = `Bearer ${token}`; | |
} | |
if (isStringWithValue(username) && isStringWithValue(password)) { | |
const credentials = base64(`${username}:${password}`); | |
headers['Authorization'] = `Basic ${credentials}`; | |
} | |
if (options.body) { | |
if (options.mediaType) { | |
headers['Content-Type'] = options.mediaType; | |
} else if (isBlob(options.body)) { | |
headers['Content-Type'] = options.body.type || 'application/octet-stream'; | |
} else if (isString(options.body)) { | |
headers['Content-Type'] = 'text/plain'; | |
} else if (!isFormData(options.body)) { | |
headers['Content-Type'] = 'application/json'; | |
} | |
} | |
return headers; | |
}; | |
const getRequestBody = (options: ApiRequestOptions): any => { | |
if (options.body) { | |
return options.body; | |
} | |
return undefined; | |
}; | |
const sendRequest = async <T>( | |
config: OpenAPIConfig, | |
options: ApiRequestOptions, | |
url: string, | |
body: any, | |
formData: FormData | undefined, | |
headers: Record<string, string>, | |
onCancel: OnCancel | |
): Promise<AxiosResponse<T>> => { | |
const source = axios.CancelToken.source(); | |
const requestConfig: AxiosRequestConfig = { | |
url, | |
headers, | |
data: body ?? formData, | |
method: options.method, | |
withCredentials: config.WITH_CREDENTIALS, | |
cancelToken: source.token, | |
}; | |
onCancel(() => source.cancel('The user aborted a request.')); | |
try { | |
return await axios.request(requestConfig); | |
} catch (error) { | |
const axiosError = error as AxiosError<T>; | |
if (axiosError.response) { | |
return axiosError.response; | |
} | |
throw error; | |
} | |
}; | |
const getResponseHeader = (response: AxiosResponse<any>, responseHeader?: string): string | undefined => { | |
if (responseHeader) { | |
const content = response.headers[responseHeader]; | |
if (isString(content)) { | |
return content; | |
} | |
} | |
return undefined; | |
}; | |
const getResponseBody = (response: AxiosResponse<any>): any => { | |
if (response.status !== 204) { | |
return response.data; | |
} | |
return undefined; | |
}; | |
const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { | |
const errors: Record<number, string> = { | |
400: 'Bad Request', | |
401: 'Unauthorized', | |
403: 'Forbidden', | |
404: 'Not Found', | |
500: 'Internal Server Error', | |
502: 'Bad Gateway', | |
503: 'Service Unavailable', | |
...options.errors, | |
} | |
const error = errors[result.status]; | |
if (error) { | |
throw new ApiError(options, result, error); | |
} | |
if (!result.ok) { | |
throw new ApiError(options, result, 'Generic Error'); | |
} | |
}; | |
/** | |
* Request method | |
* @param config The OpenAPI configuration object | |
* @param options The request options from the service | |
* @returns CancelablePromise<T> | |
* @throws ApiError | |
*/ | |
export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise<T> => { | |
return new CancelablePromise(async (resolve, reject, onCancel) => { | |
try { | |
const url = getUrl(config, options); | |
const formData = getFormData(options); | |
const body = getRequestBody(options); | |
const headers = await getHeaders(config, options, formData); | |
if (!onCancel.isCancelled) { | |
const response = await sendRequest<T>(config, options, url, body, formData, headers, onCancel); | |
const responseBody = getResponseBody(response); | |
const responseHeader = getResponseHeader(response, options.responseHeader); | |
const result: ApiResult = { | |
url, | |
ok: isSuccess(response.status), | |
status: response.status, | |
statusText: response.statusText, | |
body: responseHeader ?? responseBody, | |
}; | |
catchErrorCodes(options, result); | |
resolve(result.body); | |
} | |
} catch (error) { | |
reject(error); | |
} | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment