From 7d96c2671b147be9c6c295d559fa364f44c0ac58 Mon Sep 17 00:00:00 2001 From: Innei Date: Wed, 25 Jan 2023 19:27:42 +0800 Subject: [PATCH] fix: add pure fetch adaptor Signed-off-by: Innei --- .../__tests__/adaptors/fetch.spec.ts | 14 +++ packages/api-client/adaptors/fetch.ts | 102 ++++++++++++++++++ src/modules/configs/configs.service.ts | 12 ++- 3 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 packages/api-client/__tests__/adaptors/fetch.spec.ts create mode 100644 packages/api-client/adaptors/fetch.ts diff --git a/packages/api-client/__tests__/adaptors/fetch.spec.ts b/packages/api-client/__tests__/adaptors/fetch.spec.ts new file mode 100644 index 00000000..9e367280 --- /dev/null +++ b/packages/api-client/__tests__/adaptors/fetch.spec.ts @@ -0,0 +1,14 @@ +import FormData from 'form-data' +import fetch from 'node-fetch' + +import { fetchAdaptor } from '~/adaptors/fetch' + +import { testAdaptor } from '../helpers/adaptor-test' + +describe('test fetch adaptor', () => { + beforeAll(() => { + global.fetch = fetch as any + global.FormData = FormData as any + }) + testAdaptor(fetchAdaptor) +}) diff --git a/packages/api-client/adaptors/fetch.ts b/packages/api-client/adaptors/fetch.ts new file mode 100644 index 00000000..4ca1e6a3 --- /dev/null +++ b/packages/api-client/adaptors/fetch.ts @@ -0,0 +1,102 @@ +import { IRequestAdapter } from '~/interfaces/adapter' +import { RequestOptions } from '~/interfaces/instance' + +const jsonDataAttachResponse = async (response: Response) => { + const cloned = response.clone() + let data: any = {} + const contentType = + cloned.headers.get('Content-Type')?.split(';')[0].trim() || '' + + switch (contentType) { + case 'application/json': { + data = await cloned.json() + break + } + default: { + // const clonedAgain = cloned.clone() + // data = await cloned.json().catch(() => clonedAgain.text()) + data = await cloned.text() + break + } + } + + const nextResponse = Object.assign({}, response, { + data, + }) + + if (response.ok) { + return nextResponse + } else { + return Promise.reject(nextResponse) + } +} + +/** + * transform options to fetch options + * @param options + * @returns + */ +const parseOptions = (options: Partial): RequestInit => { + const { headers = {}, data, ...rest } = options + + if (typeof data === 'object' && !(data instanceof FormData)) { + const key = 'Content-Type' + const value = 'application/json' + if (Array.isArray(headers)) { + headers.push([key, value]) + } else if (Object.prototype.toString.call(headers) === '[object Object]') { + headers[key] = value + } else if (headers instanceof Headers) { + headers.append(key, value) + } + } + + return { + headers, + body: typeof data === 'object' ? JSON.stringify(data) : data, + ...rest, + } +} +// @ts-ignore +export const fetchAdaptor: IRequestAdapter = + Object.preventExtensions({ + get default() { + return fetch + }, + async delete(url, options) { + const data = await fetch(url, { + ...options, + method: 'DELETE', + }) + return jsonDataAttachResponse(data) + }, + async get(url, options) { + const response = await fetch(url, { + ...options, + method: 'GET', + }) + return jsonDataAttachResponse(response) + }, + async patch(url, options) { + const response = await fetch(url, { + ...parseOptions(options), + method: 'PATCH', + }) + return jsonDataAttachResponse(response) + }, + async post(url, options) { + const response = await fetch(url, { + ...parseOptions(options), + method: 'POST', + }) + return jsonDataAttachResponse(response) + }, + async put(url, options) { + const response = await fetch(url, { + ...parseOptions(options), + method: 'PUT', + }) + return jsonDataAttachResponse(response) + }, + responseWrapper: {} as any as Response, + }) diff --git a/src/modules/configs/configs.service.ts b/src/modules/configs/configs.service.ts index 751adbd9..71cc4062 100644 --- a/src/modules/configs/configs.service.ts +++ b/src/modules/configs/configs.service.ts @@ -192,11 +192,12 @@ export class ConfigsService { return newData } - validOptions: ValidatorOptions = { + private validateOptions: ValidatorOptions = { whitelist: true, forbidNonWhitelisted: true, } - validate = new ValidationPipe(this.validOptions) + private validate = new ValidationPipe(this.validateOptions) + async patchAndValid( key: T, value: Partial, @@ -247,7 +248,12 @@ export class ConfigsService { private validWithDto(dto: ClassConstructor, value: any) { const validModel = plainToInstance(dto, value) - const errors = validateSync(validModel, this.validOptions) + const errors = Array.isArray(validModel) + ? (validModel as Array).reduce( + (acc, item) => acc.concat(validateSync(item, this.validateOptions)), + [], + ) + : validateSync(validModel, this.validateOptions) if (errors.length > 0) { const error = this.validate.createExceptionFactory()(errors as any[]) throw error