Files
core_ng/server/api/generator.ts
T
Hide_D 81d16aaba2 refac: api 시스템 교체
- procDecorator의 타입 복잡도를 낮춤
- 타입 에러가 살짝 더 깔끔함
2023-08-09 16:10:53 +00:00

139 lines
4.5 KiB
TypeScript

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<Q extends Exclude<RawArgType, undefined>>(req: Request, argValidator?: ClassType<Q>): Promise<Q> {
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<Q extends Exclude<RawArgType, undefined>>(req: Request, argValidator?: ClassType<Q>): Promise<Q> {
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<void> {
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<N extends APINamespace, Q extends AnyAPIExecuter>(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<Q>;
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<Q>;
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;
}