JavaScript 中基於 swagger-decorator 的自動實體類構建與 Swagger 接口文檔生成是筆者對於開源項目 swagger-decorator 的描述,對於不反感使用註解的項目中利用 swagger-decorator 添加合適的實體類或者接口類註解,從而實現支持嵌套地實體類校驗與生成、Sequelize 等 ORM 模型生成、基於 Swagger 的接口文檔生成等等功能。若是有對 JavaScript 語法使用尚存不明的能夠參考 JavaScript 學習與實踐資料索引或者現代 JavaScript 開發:語法基礎與實踐技巧系列文章。javascript
swagger-decorator 的初衷是爲了簡化 JavaScript 應用開發,筆者在編寫 JavaScript 應用(Web 前端 & Node.js)時發現咱們常常須要重複地建立實體類、添加註釋或者進行類型校驗,swagger-decorator 但願可以讓開發者一處註解、多處使用。須要強調的是,在筆者多年的 Java 應用開發中也感覺到,過多過分的註解反而會大大削弱代碼的可讀性,所以筆者也建議應該在合適的時候舒心地使用
swagger-decorator,而不是本末倒置,一味地追求註解覆蓋率。swagger-decorator 已經能夠用於實體類生成與校驗、Sequelize ORM 實體類生成、面向 Koa 的路由註解與 Swagger 文檔自動生成。咱們能夠使用 yarn 或者 npm 安裝 swagger-decorator 依賴,須要注意的是,由於咱們在開發中還會用到註解語法,所以還須要添加 babel-plugin-transform-decorators-legacy 插件以進行語法兼容轉化。html
# 使用 npm 安裝依賴 $ npm install swagger-decorator -S $ # 使用 yarn 安裝依賴 $ yarn add swagger-decorator $ yarn add babel-plugin-transform-decorators-legacy -D # 導入須要的工具函數 import { wrappingKoaRouter, entityProperty, ... } from "swagger-decorator";
swagger-decorator 的核心 API 便是對於實體類的註解,該註解不會改變實體類的任何屬性表現,只是會將註解限定的屬性特性記錄在內置的 innerEntityObject 單例中以供後用。屬性註解 entityProperty 的方法說明以下:前端
/** * Description 建立某個屬性的描述 * @param type 基礎類型 self - 表示爲自身 * @param description 描述 * @param required 是否爲必要參數 * @param defaultValue 默認值 * @param pattern * @param primaryKey 是否爲主鍵 * @returns {Function} */ export function entityProperty({ // 生成接口文檔須要的參數 type = "string", description = "", required = false, defaultValue = undefined, // 進行校驗所須要的參數 pattern = undefined, // 進行數據庫鏈接須要的參數 primaryKey = false }) {}
簡單的用戶實體類註解以下,這裏的數據類型 type 支持 Swagger 默認的字符格式的類型描述,也支持直接使用 JavaScript 類名或者 JavaScript 數組。java
// @flow import { entityProperty } from "../../src/entity/decorator"; import UserProperty from "./UserProperty"; /** * Description 用戶實體類 */ export default class User { // 編號 @entityProperty({ type: "integer", description: "user id, auto-generated", required: true }) id: string = 0; // 姓名 @entityProperty({ type: "string", description: "user name, 3~12 characters", required: false }) name: string = "name"; // 郵箱 @entityProperty({ type: "string", description: "user email", pattern: "email", required: false }) email: string = "email"; // 屬性 @entityProperty({ type: UserProperty, description: "user property", required: false }) property: UserProperty = new UserProperty(); } export default class UserProperty { // 朋友列表 @entityProperty({ type: ["number"], description: "user friends, which is user ids", required: false }) friends: [number]; }
Swagger 內置數據類型定義:git
Common Name | type |
format |
Comments |
---|---|---|---|
integer | integer |
int32 |
signed 32 bits |
long | integer |
int64 |
signed 64 bits |
float | number |
float |
|
double | number |
double |
|
string | string |
||
byte | string |
byte |
base64 encoded characters |
binary | string |
binary |
any sequence of octets |
boolean | boolean |
||
date | string |
date |
As defined by full-date - RFC3339 |
dateTime | string |
date-time |
As defined by date-time - RFC3339 |
password | string |
password |
Used to hint UIs the input needs to be obscured. |
實體類定義完畢以後,咱們首先能夠使用 instantiate 函數爲實體類生成實例;不一樣於直接使用 new 關鍵字建立,instantiate 可以根據指定屬性的數據類型或者格式進行校驗,同時可以迭代生成嵌套地子對象。github
/** * Description 從實體類中生成對象,而且進行數據校驗;注意,這裏會進行遞歸生成,即對實體類對象一樣進行生成 * @param EntityClass 實體類 * @param data 數據對象 * @param ignore 是否忽略校驗 * @param strict 是否忽略非預約義類屬性 * @throws 當校驗失敗,會拋出異常 */ export function instantiate( EntityClass: Function, data: { [string]: any }, { ignore = false, strict = true }: { ignore: boolean, strict: boolean } = {} ): Object {}
這裏爲了方便描述使用 Jest 測試用例說明不一樣的使用場景:shell
describe("測試實體類實例化函數", () => { test("測試 User 類實例化校驗", () => { expect(() => { instantiate(User, { name: "name" }).toThrowError(/validate fail!/); }); let user = instantiate(User, { id: 0, name: "name", email: "a@q.com" }); // 判斷是否爲 User 實例 expect(user).toBeInstanceOf(User); }); test("測試 ignore 參數能夠容許忽略校驗", () => { instantiate( User, { name: "name" }, { ignore: true } ); }); test("測試 strict 參數能夠控制是否忽略額外參數", () => { let user = instantiate( User, { name: "name", external: "external" }, { ignore: true, strict: true } ); expect(user).not.toHaveProperty("external", "external"); user = instantiate( User, { name: "name", external: "external" }, { ignore: true, strict: false } ); expect(user).toHaveProperty("external", "external"); }); }); describe("測試嵌套實例生成", () => { test("測試能夠遞歸生成嵌套實體類", () => { let user = instantiate(User, { id: 0, property: { friends: [0] } }); expect(user.property).toBeInstanceOf(UserProperty); }); });
Sequelize 是 Node.js 應用中經常使用的 ORM 框架,swagger-decorator 提供了 generateSequelizeModel 函數以方便從實體類中利用現有的信息生成 Sequelize 對象模型;generateSequelizeModel 的第一個參數輸入實體類,第二個參數輸入須要覆寫的模型屬性,第三個參數設置額外屬性,譬如是否須要將駝峯命名轉化爲下劃線命名等等。數據庫
const originUserSequelizeModel = generateSequelizeModel( User, { _id: { primaryKey: true } }, { mappingCamelCaseToUnderScore: true } ); const UserSequelizeModel = sequelize.define( "b_user", originUserSequelizeModel, { timestamps: false, underscored: true, freezeTableName: true } ); UserSequelizeModel.findAll({ attributes: { exclude: [] } }).then(users => { console.log(users); });
筆者習慣使用 Flow 做爲 JavaScript 的靜態類型檢測工具,所以筆者添加了 flowToDecorator 函數以自動地從 Flow 聲明的類文件中提取出類型信息;內部原理參考現代 JavaScript 開發:語法基礎與實踐技巧 一書中的 JavaScript 語法樹與代碼轉化章節。該函數的使用方式爲:npm
// @flow import { flowToDecorator } from '../../../../src/transform/entity/flow/flow'; test('測試從 Flow 中提取出數據類型而且轉化爲 Swagger 接口類', () => { flowToDecorator('./TestEntity.js', './TestEntity.transformed.js').then( codeStr => { console.log(codeStr); }, err => { console.error(err); } ); });
這裏對應的 TestEntity 爲:json
// @flow import AnotherEntity from "./AnotherEntity"; class Entity { // Comment stringProperty: string = 0; classProperty: Entity = null; rawProperty; @entityProperty({ type: "string", description: "this is property description", required: true }) decoratedProperty; }
轉化後的實體類爲:
// @flow import { entityProperty } from 'swagger-decorator'; import AnotherEntity from './AnotherEntity'; class Entity { // Comment @entityProperty({ type: 'string', required: false, description: 'Comment' }) stringProperty: string = 0; @entityProperty({ type: Entity, required: false }) classProperty: Entity = null; @entityProperty({ type: 'string', required: false }) rawProperty; @entityProperty({ type: 'string', description: 'this is property description', required: true }) decoratedProperty; }
對於 Swagger 文檔規範能夠參考 OpenAPI Specification ,而對於 swagger-decorator 的實際使用能夠參考本項目的使用示例或者 基於 Koa2 的 Node.js 應用模板 。
import { wrappingKoaRouter } from "swagger-decorator"; ... const Router = require("koa-router"); const router = new Router(); wrappingKoaRouter(router, "localhost:8080", "/api", { title: "Node Server Boilerplate", version: "0.0.1", description: "Koa2, koa-router,Webpack" }); // define default route router.get("/", async function(ctx, next) { ctx.body = { msg: "Node Server Boilerplate" }; }); // use scan to auto add method in class router.scan(UserController);
export default class UserController extends UserControllerDoc { @apiRequestMapping("get", "/users") @apiDescription("get all users list") static async getUsers(ctx, next): [User] { ctx.body = [new User()]; } @apiRequestMapping("get", "/user/:id") @apiDescription("get user object by id, only access self or friends") static async getUserByID(ctx, next): User { ctx.body = new User(); } @apiRequestMapping("post", "/user") @apiDescription("create new user") static async postUser(): number { ctx.body = { statusCode: 200 }; } }
在 UserController 中是負責具體的業務實現,爲了不過多的註解文檔對於代碼可讀性的干擾,筆者建議是將除了路徑與描述以外的信息放置到父類中聲明;swagger-decorator 會自動從某個接口類的直接父類中提取出同名方法的描述文檔。
export default class UserControllerDoc { @apiResponse(200, "get users successfully", [User]) static async getUsers(ctx, next): [User] {} @pathParameter({ name: "id", description: "user id", type: "integer", defaultValue: 1 }) @queryParameter({ name: "tags", description: "user tags, for filtering users", required: false, type: "array", items: ["string"] }) @apiResponse(200, "get user successfully", User) static async getUserByID(ctx, next): User {} @bodyParameter({ name: "user", description: "the new user object, must include user name", required: true, schema: User }) @apiResponse(200, "create new user successfully", { statusCode: 200 }) static async postUser(): number {} }
run your application and open swagger docs (PS. swagger-decorator contains Swagger UI):
/swagger
/swagger/api.json