diff --git a/packages/cc-server-binding/.gitignore b/packages/cc-server-binding/.gitignore new file mode 100644 index 0000000..d856d44 --- /dev/null +++ b/packages/cc-server-binding/.gitignore @@ -0,0 +1,4 @@ +/node_modules +/dist +/package-lock.json +/yarn.lock diff --git a/packages/cc-server-binding/README.md b/packages/cc-server-binding/README.md new file mode 100644 index 0000000..96c7b54 --- /dev/null +++ b/packages/cc-server-binding/README.md @@ -0,0 +1,11 @@ +# `cc-server-binding` + +> TODO: description + +## Usage + +``` +const ccServerBinding = require('cc-server-binding'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/cc-server-binding/package.json b/packages/cc-server-binding/package.json new file mode 100644 index 0000000..5d49ffb --- /dev/null +++ b/packages/cc-server-binding/package.json @@ -0,0 +1,33 @@ +{ + "name": "cc-server-binding", + "version": "0.2.5", + "description": "> TODO: description", + "author": "MiaoWoo ", + "homepage": "https://github.com/502647092/cc-server-parent#readme", + "license": "ISC", + "main": "dist/index.js", + "publishConfig": { + "registry": "https://repo.yumc.pw/repository/npm/" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/502647092/cc-server-parent.git" + }, + "scripts": { + "dev": "npx ts-node src/index.ts", + "build": "rimraf dist && npx tsc", + "test": "echo \"Error: run tests from root\" && exit 1" + }, + "dependencies": { + "express": "^4.17.1", + "reflect-metadata": "^0.1.13" + }, + "devDependencies": { + "mocha": "^6.1.4", + "rimraf": "^2.6.3", + "typescript": "^3.5.1" + }, + "bugs": { + "url": "https://github.com/502647092/cc-server-parent/issues" + } +} \ No newline at end of file diff --git a/packages/cc-server-binding/src/activation.ts b/packages/cc-server-binding/src/activation.ts new file mode 100644 index 0000000..4f96c6b --- /dev/null +++ b/packages/cc-server-binding/src/activation.ts @@ -0,0 +1,69 @@ +import { Container, interfaces as inversify_interfaces } from 'inversify' +import { TYPE, interfaces as express_interfaces } from 'inversify-express-utils' +import { METADATA_KEY, VAILD_TYPE } from './constants' +import { interfaces } from './interfaces' +import { getVaildMethodMetadata, getVaildControllerMetadata, getVaildModelMetadata } from './utils' + +let handler = { + apply: function(target: Function, thisArgument: Object, argumentsList: any[]) { + let methodParams = getVaildMethodMetadata(target); + let [req, res, next] = [...argumentsList.slice(-3)]; + try { + // loop @Valid params + for (const param of methodParams) { + // get function argument value + let origin = argumentsList[param.index] + let props = getVaildModelMetadata(param.type); + for (const prop of props) { + let propValue = origin[prop.name]; + switch (prop.type) { + case VAILD_TYPE.NOT_BLANK: + if (!propValue) { + throw new Error(prop.message); + } + break; + case VAILD_TYPE.NOT_NULL: + if (propValue == undefined) { + throw new Error(prop.message); + } + break; + default: + throw new Error('Unkonw Vaild Type!') + } + } + } + return target.apply(thisArgument, argumentsList); + } catch (ex) { + res.status(400).json({ + status: 400, + message: ex.message + }) + } + } +} + +function rebuildServer(container: Container) { + // get all controller + let controllers = container.getAll(TYPE.Controller) + // for loop controllers and inject proxy to each method + for (const controller of controllers) { + container.rebind(TYPE.Controller) + .to(controller.constructor as any) + .inSingletonScope() + .whenTargetNamed(controller.constructor.name) + .onActivation((ctx: inversify_interfaces.Context, controller: express_interfaces.Controller) => { + let prop = controller.constructor.prototype + // funcs => { create: [ { index: 0, type: [Function: ExampleModel] } ] } + let funcs = getVaildControllerMetadata(controller.constructor); + //Reflect.getMetadata(METADATA_KEY.controllerParameter, controller.constructor); + for (const func in funcs) { + Reflect.defineMetadata(METADATA_KEY.vaildMethod, funcs[func], prop[func]) + // Define Proxy handle model vaild + Reflect.defineProperty(prop, func, { value: new Proxy(prop[func], handler) }) + } + return controller; + }); + } +} + +export { rebuildServer } \ No newline at end of file diff --git a/packages/cc-server-binding/src/constants.ts b/packages/cc-server-binding/src/constants.ts new file mode 100644 index 0000000..25a266e --- /dev/null +++ b/packages/cc-server-binding/src/constants.ts @@ -0,0 +1,10 @@ +export const METADATA_KEY = { + vaildModel: "cc-server-binding:vaild-model", + vaildMethod: "cc-server-binding:vaild-method", + vaildController: "cc-server-binding:vaild-controller" +}; + +export enum VAILD_TYPE { + NOT_BLANK, + NOT_NULL +} diff --git a/packages/cc-server-binding/src/decorators.ts b/packages/cc-server-binding/src/decorators.ts new file mode 100644 index 0000000..898dce0 --- /dev/null +++ b/packages/cc-server-binding/src/decorators.ts @@ -0,0 +1,50 @@ +import 'reflect-metadata' +import { METADATA_KEY, VAILD_TYPE } from './constants'; +import { getVaildControllerMetadata, getVaildModelMetadata } from './utils' +import { interfaces } from './interfaces' +/** + * ParameterVaild + */ +export function Vaild(): ParameterDecorator { + return (controller: Object, methodName: string, index: number): void => { + var type = Reflect.getMetadata("design:paramtypes", controller, methodName)[index]; + let metadataList = getVaildControllerMetadata(controller.constructor); + let parameterMetadataList = metadataList[methodName] || []; + let parameterMetadata: interfaces.ParameterMetadata = { + index: index, + type: type + }; + parameterMetadataList.unshift(parameterMetadata); + metadataList[methodName] = parameterMetadataList; + Reflect.defineMetadata(METADATA_KEY.vaildController, metadataList, controller.constructor); + } +} + +/** + * Vaild Blank String + * @param message Error Message + */ +export const NotBlank: (message?: string) => PropertyDecorator = vaildDecoratorFactory(VAILD_TYPE.NOT_BLANK); +/** + * Vaild Null Param + * @param message Error Message + */ +export const NotNull: (message?: string) => PropertyDecorator = vaildDecoratorFactory(VAILD_TYPE.NOT_NULL); + +function vaildDecoratorFactory(type: VAILD_TYPE): () => PropertyDecorator { + return function(message?: string): PropertyDecorator { + return vaildProperty(type, message); + }; +} + +function vaildProperty(type: VAILD_TYPE, message?: string): PropertyDecorator { + return (model: Object, propertyKey: string) => { + let metadataList: interfaces.PropertyMetadata[] = getVaildModelMetadata(model.constructor); + metadataList.push({ + name: propertyKey, + message: message || `model ${model.constructor.name} property ${propertyKey} vaild failed => ${VAILD_TYPE[type]}`, + type: type + }) + Reflect.defineMetadata(METADATA_KEY.vaildModel, metadataList, model.constructor); + } +} diff --git a/packages/cc-server-binding/src/index.ts b/packages/cc-server-binding/src/index.ts new file mode 100644 index 0000000..a4b91ad --- /dev/null +++ b/packages/cc-server-binding/src/index.ts @@ -0,0 +1,4 @@ +export * from './decorators' +export * from './constants'; +export * from './utils' +export * from './activation' \ No newline at end of file diff --git a/packages/cc-server-binding/src/interfaces.ts b/packages/cc-server-binding/src/interfaces.ts new file mode 100644 index 0000000..720b97a --- /dev/null +++ b/packages/cc-server-binding/src/interfaces.ts @@ -0,0 +1,20 @@ +import { VAILD_TYPE } from './constants' + +namespace interfaces { + export interface MethodMetadata { + [methodName: string]: ParameterMetadata[]; + } + + export interface ParameterMetadata { + index: number; + type: NewableFunction; + } + + export interface PropertyMetadata { + name: string; + message: string; + type: VAILD_TYPE; + } +} + +export { interfaces }; \ No newline at end of file diff --git a/packages/cc-server-binding/src/utils.ts b/packages/cc-server-binding/src/utils.ts new file mode 100644 index 0000000..acb672d --- /dev/null +++ b/packages/cc-server-binding/src/utils.ts @@ -0,0 +1,32 @@ +import { METADATA_KEY } from './constants' +import { interfaces } from './interfaces' + +function getVaildControllerMetadata(model: Object) { + let controllerMetadata: interfaces.MethodMetadata = Reflect.getMetadata( + METADATA_KEY.vaildController, + model + ) || {}; + return controllerMetadata; +} + +function getVaildMethodMetadata(constructor: any) { + let parameterMetadata: interfaces.ParameterMetadata[] = Reflect.getMetadata( + METADATA_KEY.vaildMethod, + constructor + ) || []; + return parameterMetadata; +} + +function getVaildModelMetadata(model: Object) { + let propertyMetadata: interfaces.PropertyMetadata[] = Reflect.getMetadata( + METADATA_KEY.vaildModel, + model + ) || []; + return propertyMetadata; +} + +export { + getVaildControllerMetadata, + getVaildMethodMetadata, + getVaildModelMetadata +} \ No newline at end of file diff --git a/packages/cc-server-binding/tsconfig.json b/packages/cc-server-binding/tsconfig.json new file mode 100644 index 0000000..7aae5d2 --- /dev/null +++ b/packages/cc-server-binding/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "src", + "outDir": "dist" + } +} \ No newline at end of file