import { Router, type Request, type Response } from 'express'; import type { AnyAPIExecuter, APINamespace } from './defs.js'; import type { RawArgType } from '@strpc/def'; import type { SafeParseReturnType, ZodType } from 'zod'; import { flatten } from 'lodash-es'; const PRINT_API_CALL = true; // eslint-disable-next-line @typescript-eslint/no-explicit-any async function parseParam(req: Request, argValidator?: ZodType): Promise> { if (!argValidator) { return { success: true, data: req.query as Q } } const query = req.query; return await argValidator.safeParseAsync(query); } // eslint-disable-next-line @typescript-eslint/no-explicit-any async function parseBody(req: Request, argValidator?: ZodType): Promise> { if (!argValidator) { return { success: true, data: req.body as Q } } const query = req.body; return await argValidator.safeParseAsync(query); } async function apiRun(query: Q, req: Request, res: Response, api: AnyAPIExecuter): Promise { const [preResult, ctx] = await api.preDecorator({}, req, res); if (!preResult.every((v) => v.result)) { const lastErr = preResult.pop() as typeof preResult[0]; const [postResult,] = await api.postDecorator(ctx, preResult, req, res, false); const postErrors = postResult.filter((obj) => !obj.result); if (postErrors.length) { //회수조차 불가능? if (PRINT_API_CALL) { console.log(JSON.stringify([(new Date).toISOString(), req.ip, req.path, false, 'preDecorator', lastErr.type, lastErr.info])); } res.json({ result: false, path: req.path, reason: lastErr.info, recovery: lastErr.recovery, detail: { type: 'decorator', preDecorator: [lastErr.type, lastErr.info], postDecorator: flatten(postErrors.map((obj) => [obj.type, obj.info])), }, }); return; } if (PRINT_API_CALL) { console.log(JSON.stringify([(new Date).toISOString(), req.ip, req.path, false, 'preDecorator', lastErr.type, lastErr.info])); } res.json({ result: false, path: req.path, reason: lastErr.info, recovery: lastErr.recovery, detail: { type: 'decorator', preDecorator: [lastErr.type, lastErr.info], }, }) return; } const result = await api(query, ctx, req, res); const [postResult,] = await api.postDecorator(ctx, preResult, req, res, true); const postErrors = postResult.filter((obj) => !obj.result); if (postErrors.length) { const lastErr = postErrors.pop() as typeof postErrors[0]; if (PRINT_API_CALL) { console.log(JSON.stringify([(new Date).toISOString(), req.ip, req.path, false, 'postDecorator', lastErr.type, lastErr.info])); } if (result === true) { //NOTE: 이미 api에서 response를 보낸 특이 케이스. return; } res.json({ result: false, path: req.path, reason: lastErr?.info, recovery: lastErr?.recovery, detail: { type: 'decorator', preDecorator: flatten(postErrors.map((obj) => [obj.type, obj.info])), }, originalResult: result, }); return; } if (PRINT_API_CALL) { if(result === true){ console.log(JSON.stringify([(new Date).toISOString(), req.ip, req.path, true, 'passThrough'])); } else{ let tmp_result = true; let tmp_reason = undefined; if('result' in result){ tmp_result = result.result; } if('reason' in result){ tmp_reason = result.reason; } console.log(JSON.stringify([(new Date).toISOString(), req.ip, req.path, tmp_result, tmp_reason])); } } if (result !== true) { res.json(result); } } export function buildAPISystem(api: N | Q): Router { const router = Router(); if (typeof api === 'function') { throw 'root api cannot be function'; } for (const [key, value] of Object.entries(api)) { const rkey = `/${key}`; if (typeof value !== 'function') { router.use(rkey, buildAPISystem(value)); continue; } const executer = value; if (!executer.httpMethod) { throw new Error('APIExecuter.reqType is not defined'); } const parser = executer.httpMethod === 'get' ? parseParam : parseBody; router[executer.httpMethod](rkey, async (req, res) => { const queryResult = await parser(req, executer.argValidator); if (!queryResult.success) { res.json({ result: false, reason: `invalid parameter: ${queryResult.error.message}`, error: queryResult.error }); return; } await apiRun(queryResult.data, req, res, executer); }); } return router; }