api 설계 변경
This commit is contained in:
+47
-67
@@ -1,53 +1,48 @@
|
||||
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';
|
||||
import type { AnyAPIExecuter, APINamespace } from './defs.js';
|
||||
import type { RawArgType } from '../apiStructure/defs.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
async function parseParam<Q extends Exclude<RawArgType, undefined>>(req: Request, argValidator?: ClassType<Q>): Promise<Q> {
|
||||
async function parseParam<Q extends Exclude<RawArgType, undefined>>(req: Request, argValidator?: z.ZodType<Q>): Promise<z.SafeParseReturnType<Q, Q>> {
|
||||
if (!argValidator) {
|
||||
return req.query as Q;
|
||||
return {
|
||||
success: true,
|
||||
data: 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;
|
||||
return await argValidator.safeParseAsync(query);
|
||||
}
|
||||
|
||||
async function parseBody<Q extends Exclude<RawArgType, undefined>>(req: Request, argValidator?: ClassType<Q>): Promise<Q> {
|
||||
async function parseBody<Q extends Exclude<RawArgType, undefined>>(req: Request, argValidator?: z.ZodType<Q>): Promise<z.SafeParseReturnType<Q, Q>> {
|
||||
if (!argValidator) {
|
||||
return req.body as Q;
|
||||
return {
|
||||
success: true,
|
||||
data: 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;
|
||||
return await argValidator.safeParseAsync(query);
|
||||
}
|
||||
|
||||
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 lastErr = preResult.pop() as typeof preResult[0];
|
||||
|
||||
const [postResult,] = await api.postDecorator(ctx, preResult, req, res, false);
|
||||
if (!postResult.every((v) => v.result)) {
|
||||
const postErrors = postResult.filter((obj)=>!obj.result);
|
||||
if (postErrors.length) {
|
||||
//회수조차 불가능?
|
||||
const lastPostErr = postResult[postResult.length - 1];
|
||||
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: ${lastPostErr.type} ${lastPostErr.info}`,
|
||||
reason: `preDecorator: (${lastErr.type})${lastErr.info}, postDecorator: ${postErrorsStr}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -55,22 +50,25 @@ async function apiRun(query: object, req: Request, res: Response, api: AnyAPIExe
|
||||
res.json({
|
||||
result: false,
|
||||
path: req.path,
|
||||
reason: `preDecorator: ${lastErr.type} ${lastErr.info}`,
|
||||
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)) {
|
||||
const postErrors = postResult.filter((obj)=>!obj.result);
|
||||
if(postErrors.length) {
|
||||
if(result === true) {
|
||||
//NOTE: 이미 api에서 response를 보낸 특이 케이스.
|
||||
return;
|
||||
}
|
||||
const lastPostErr = postResult[postResult.length - 1];
|
||||
const postErrorsStr = postErrors.map((obj)=>{
|
||||
return `(${obj.type})${obj.info}`
|
||||
}).join(', ');
|
||||
res.json({
|
||||
result: false,
|
||||
path: req.path,
|
||||
reason: `postDecorator: ${lastPostErr.type} ${lastPostErr.info}`,
|
||||
reason: `postDecorator: ${postErrorsStr}`,
|
||||
originalResult: result,
|
||||
});
|
||||
return;
|
||||
@@ -89,8 +87,10 @@ export function buildAPISystem<N extends APINamespace, Q extends AnyAPIExecuter>
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(api)) {
|
||||
const rkey = `/${key}`;
|
||||
if (typeof value !== 'function') {
|
||||
router.use(key, buildAPISystem(value));
|
||||
router.use(rkey, buildAPISystem(value));
|
||||
|
||||
continue;
|
||||
}
|
||||
const executer = value;
|
||||
@@ -99,40 +99,20 @@ export function buildAPISystem<N extends APINamespace, Q extends AnyAPIExecuter>
|
||||
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;
|
||||
}
|
||||
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(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);
|
||||
});
|
||||
}
|
||||
await apiRun(queryResult.data, req, res, executer);
|
||||
});
|
||||
}
|
||||
return router;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user