核心文件
wrapper.ts
- 用于依据 decorator 函数执行后得到的数据进行 router 的自动化构建,同时根据 middlewares 函数的结果在 router 构建时添加相对应的中间件。
- 基于 swaggerObject 构建 swagger 文档
swaggerObject.ts
swagger 文档可以使用 json 描述, swaggerObject 暴露出一个 SwaggerObject 的实例,提供添加对应的 swagger 字段的方法供 decorator 使用,并且暴露出 data 字段表示整个 swagger 对象,用于构建 swagger json 文档
decorators.ts
定义使用的 decorator,如 @request,@summary 等。由于 swagger 文档的构建中,对个字段会有相似的结构,如 summary 与 description 的格式相同,所以为了使得 decorator 函数更加简洁,使用curry化的方式优化代码结构。如下
const _desc = (type: string, text: string | any[]) => (target: any, name: string, descriptor: PropertyDescriptor) => {
descriptor.value[type] = text;
swaggerObject.add(target, name, { [type]: text });
return descriptor;
};
const desc = _.curry(_desc);
const description = desc('description');
const summary = desc('summary');
const tags = desc('tags');
这些 decorator 会需要处理两件事,一是对于 swaggerObject 要添加相关字段内容,二是对于 router 构建要添加相关字段内容,如 @request 需要添加 path,method 两个字段到绑定的方法中,使得 wapper.js 中可以根据这些信息构建 router
validate/index.ts
validate/check.ts
这两个文件用于对 decorator 定义的参数进行校验。关键难点在于对于 swagger 中 array 和 object 嵌套结构的校验,利用递归的方式实现。参考代码如下,在 cObject 中调用 check 校验嵌套数据。
const check = (input: any, expect: Expect) => {
const cond = _.cond([
[_.equals('string'), () => cString(input, expect)],
[_.equals('boolean'), () => cBool(input, expect)],
[_.equals('number'), () => cNum(input, expect)],
[_.equals('object'), () => cObject(input, expect)],
[_.equals('array'), () => cArray(input, expect)],
[_.T, () => ({ is: true, val: input })] // 其他类型不做校验,直接返回原数据
]);
return cond(expect.type);
};
// /**
// * 对 Object 做检验, 支持嵌套数据
// {
// aaaa: 'hh',
// bbbb: 'qq',
// }
// { // expect:
// type: 'object',
// properties: {
// aaaa: { type: 'string', example: 'http://www.baidu.com', required: true },
// bbbb: { type: 'string', example: 'Bob' }
// c: { type: 'object', properties: {ii: {type: 'string'}, jj: {type: 'number'}} }
// }
// }
// */
const cObject = (input: any, expect: Expect = {}) => {
if (!cRequired(input, expect).is) return { is: false };
const res = { is: true, val: input };
if (!is.object(input)) return { is: false };
if (!expect.properties) return res;
for (const key of Object.keys(expect.properties)) {
// ignore empty key if not required
if (!expect.properties[key].required && input[key] === undefined) {
continue; // eslint-disable-line
}
const { is, val } = check(input[key], expect.properties[key]);
if (!is) {
console.log('error object properties:', key); // TODO need to update error debug info
res.is = false;
break;
}
input[key] = is ? val : input[key];
}
return res;
};