import { APIError, isArray } from '@jolibox/common'; import { ResponseType, StandardResponse } from '@jolibox/types'; import { Track } from '../report'; import { reportError } from '../report/errors/report'; import { Type, typeAsserts, Validator } from './validator'; import { createUserValidateAPIError } from '../report/errors'; let traceId = 1; /** * TODO: 埋点逻辑 */ type Response = T | StandardResponse; type SyncFunction = ( ...inputs: Params ) => Return | { code: ResponseType; data: Return }; type AsyncFunction = ( ...inputs: Params ) => Promise | Return>; interface APIFactory { createSyncAPI( method: string, config: { implement: SyncFunction; } ): (...inputs: Params) => StandardResponse; createAPI( method: string, config: { implement: Params extends undefined ? () => Promise : AsyncFunction; } ): (...inputs: unknown[]) => Promise>; } export const createAPIFactory = (track: Track) => { /** * create Sync API */ function createSyncAPI< ParamsSchema extends Validator, Inputs extends Type & unknown[], Return = void >( method: string, config: { paramsSchema?: ParamsSchema; implement: (...inputs: Inputs) => Return | { code: ResponseType; data: Return }; } ): (...inputs: Inputs) => StandardResponse { return (...inputs) => { const startTime = Date.now(); const reportExtra = { method, trace_id: traceId }; traceId += 1; let status: ResponseType = 'SUCCESS'; let errMsg = `${method}:ok`; let errNo = 0; try { // 类型校验 if (config.paramsSchema) { try { typeAsserts(config.paramsSchema, inputs, 'params'); } catch (err: unknown) { const error = err as { message: string }; throw createUserValidateAPIError(`${method}:fail ${error.message}`); } } const res = config.implement(...inputs); let data = res as Return; let message = `${method}:ok`; if (res && 'code' in (res as { code: ResponseType; data: Return })) { const { code, data: originData, message: originMessage } = res as { code: ResponseType; data: Return; message: string }; status = code; data = originData; message = originMessage; } return { code: status, message, data }; } catch (error) { const _error = error as { code?: number; message?: string; errNo?: number; errMsg?: string }; errNo = _error.code ?? _error.errNo ?? 2000; status = 'FAILURE'; const msg = _error.message ?? _error.errMsg ?? `${error}`; errMsg = `${method}:${status} ${msg.replace(/^\S+:(fail|cancel)\s?/, '')}`; reportError(new APIError(errMsg, errNo)); return { code: status, message: errMsg }; } finally { const duration = Date.now() - startTime; // track('apiInvoked', { ...reportExtra, duration, status }); } }; } /** * Async API */ function createAPI< ParamsSchema extends Validator, Inputs extends Type & unknown[], Return >( method: string, config: { paramsSchema?: ParamsSchema; implement: Inputs extends undefined ? () => Promise> : (...params: Inputs) => Promise>; } ): (...inputs: unknown[]) => Promise> { // 异步 API return async (..._inputs) => { const inputs = isArray(_inputs) ? _inputs : [_inputs]; const startTime = Date.now(); const reportExtra = { method, trace_id: traceId }; traceId += 1; let message = `${method}:ok`; let status: ResponseType = 'SUCCESS'; const res: StandardResponse = { code: status, message }; try { if (config.paramsSchema) { try { typeAsserts(config.paramsSchema, inputs, 'params'); } catch (error) { throw createUserValidateAPIError((error as Error).message); } } const data = await config.implement(...inputs); let ret = data as Return; if (ret && 'code' in (ret as unknown as { code: ResponseType; data: Return })) { const { code, data: originData, message: originMessage } = ret as unknown as { code: ResponseType; data: Return; message: string }; status = code; message = originMessage; ret = originData; } Object.assign(res, { code: status, message, data: ret }); return res; } catch (error: any) { const _error = error as { code?: number; message?: string; errNo?: number; errMsg?: string }; const errNo = _error.code ?? _error.errNo ?? 2000; status = 'FAILURE'; const msg = _error.message ?? _error.errMsg ?? `${error}`; const errMsg = `${method}:${status} ${msg.replace(/^\S+:(fail|cancel)\s?/, '')}`; Object.assign(res, { code: status, message: errMsg }); reportError(new APIError(errMsg, errNo)); return res; } finally { const duration = Date.now() - startTime; // track('apiInvoked', { ...reportExtra, duration, status }); } }; } return { createAPI, createSyncAPI }; };