import { Router, type Request, type Response } from 'express'; import type { AnyAPIExecuter, APINamespace } from './defs.js'; import type { RawArgType } from '../apiStructure/defs.js'; import { z } from 'zod'; async function parseParam>(req: Request, argValidator?: z.ZodType): Promise> { if (!argValidator) { return { success: true, data: req.query as Q } } const query = req.query; return await argValidator.safeParseAsync(query); } async function parseBody>(req: Request, argValidator?: z.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: 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.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) { //회수조차 불가능? const postErrorsStr = postErrors.map((obj)=>{ return `(${obj.type})${obj.info}` }).join(', '); res.json({ result: false, path: req.path, reason: `preDecorator: (${lastErr.type})${lastErr.info}, postDecorator: ${postErrorsStr}`, }); 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); const postErrors = postResult.filter((obj)=>!obj.result); if(postErrors.length) { if(result === true) { //NOTE: 이미 api에서 response를 보낸 특이 케이스. return; } const postErrorsStr = postErrors.map((obj)=>{ return `(${obj.type})${obj.info}` }).join(', '); res.json({ result: false, path: req.path, reason: `postDecorator: ${postErrorsStr}`, 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)) { 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; }