import { Router, type Request, type Response } from 'express'; import type { AnyAPIExecuter, APINamespace, ClassType } from './defs.js'; import { plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; import type { ExtractQuery, RawArgType } from '../apiStructure/defs.js'; import type { DecoratorResult, Empty, ProcDecorator, ProcDecoratorChain, ResolveChain } from './ProcDecorator/base.js'; import clamp from 'lodash-es/clamp.js'; async function parseParam>(req: Request, argValidator?: ClassType): Promise { if (!argValidator) { return req.query as Q; } const query = req.query; const classObject = plainToInstance(argValidator, query); const errors = await validate(classObject); if (errors.length) { throw errors; } return classObject as Q; } async function parseBody>(req: Request, argValidator?: ClassType): Promise { if (!argValidator) { return req.body as Q; } const query = req.body; const classObject = plainToInstance(argValidator, query); const errors = await validate(classObject); if (errors.length) { throw errors; } return classObject as Q; } async function apiRun(query: object, req: Request, res: Response, api: AnyAPIExecuter): Promise { const [preResult, ctx] = await api.preDecorator({}, req, res); if (!preResult.every((v) => v.result)) { const lastErr = preResult[preResult.length - 1]; const [postResult,] = await api.postDecorator(ctx, preResult, req, res, false); if (!postResult.every((v) => v.result)) { //회수조차 불가능? const lastPostErr = postResult[postResult.length - 1]; res.json({ result: false, path: req.path, reason: `preDecorator: ${lastErr.type} ${lastErr.info}, postDecorator: ${lastPostErr.type} ${lastPostErr.info}`, }); return; } res.json({ result: false, path: req.path, reason: `preDecorator: ${lastErr.type} ${lastErr.info}`, }) return; } const result = await api(query, ctx, req, res); const [postResult,] = await api.postDecorator(ctx, preResult, req, res, true); if(!postResult.every((v) => v.result)) { if(result === true) { //NOTE: 이미 api에서 response를 보낸 특이 케이스. return; } const lastPostErr = postResult[postResult.length - 1]; res.json({ result: false, path: req.path, reason: `postDecorator: ${lastPostErr.type} ${lastPostErr.info}`, originalResult: result, }); return; } 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)) { if (typeof value !== 'function') { router.use(key, buildAPISystem(value)); continue; } const executer = value; if (!executer.httpMethod) { throw new Error('APIExecuter.reqType is not defined'); } if (executer.httpMethod === 'get') { router[executer.httpMethod](key, async (req, res) => { let query: ExtractQuery; try { query = await parseParam(req, executer.argValidator); } catch (e) { res.json({ result: false, reason: `invalid parameter: ${e}`, }); return; } await apiRun(query, req, res, executer); }); } else { router[executer.httpMethod](key, async (req, res) => { let query: ExtractQuery; try { query = await parseBody(req, executer.argValidator); } catch (e) { res.json({ result: false, reason: `invalid parameter: ${e}`, }); return; } await apiRun(query, req, res, executer); }); } } return router; }