JavaScript 中基於 swagger-decorator 的自動實體類構建與 Swagger 接口文檔生成

JavaScript 中基於 swagger-decorator 的自動實體類構建與 Swagger 接口文檔生成是筆者對於開源項目 swagger-decorator 的描述,對於不反感使用註解的項目中利用 swagger-decorator 添加合適的實體類或者接口類註解,從而實現支持嵌套地實體類校驗與生成、Sequelize 等 ORM 模型生成、基於 Swagger 的接口文檔生成等等功能。若是有對 JavaScript 語法使用尚存不明的能夠參考 JavaScript 學習與實踐資料索引或者現代 JavaScript 開發:語法基礎與實踐技巧系列文章。javascript

swagger-decorator: 一處註解,多處使用

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 模型生成

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 類型聲明中自動生成註解

筆者習慣使用 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 文檔生成

對於 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

相關文章
相關標籤/搜索