monorepo 버전 준비
## @strpc 기존 RPC를 package화 ### @strpc/express express의 middleware + router 결함 ## @sammo 게임 전체 - server, client - gateway_server, gateway_client
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
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<Q extends RawArgType>(req: Request, argValidator?: ZodType<Q>): Promise<SafeParseReturnType<Q, Q>> {
|
||||
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<Q extends RawArgType>(req: Request, argValidator?: ZodType<Q>): Promise<SafeParseReturnType<Q, Q>> {
|
||||
if (!argValidator) {
|
||||
return {
|
||||
success: true,
|
||||
data: req.body as Q
|
||||
}
|
||||
}
|
||||
|
||||
const query = req.body;
|
||||
return await argValidator.safeParseAsync(query);
|
||||
}
|
||||
|
||||
async function apiRun<Q extends RawArgType>(query: Q, 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.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<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)) {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user