decorator-x
Version:
decorator for entity instantiation & validation, auto-generate swagger docs & graphql schema
454 lines (362 loc) • 11.4 kB
Markdown

[](https://badge.fury.io/js/swagger-decorator)
[中文版本](https://github.com/wxyyxc1992/Modern-JavaScript-Entity/blob/master/swagger-decorator/README.md) | [English Version](https://github.com/wxyyxc1992/Modern-JavaScript-Entity/blob/master/swagger-decorator/README.en.md)
> Decorate Once, Use Everywhere - Decorator For JavaScript and Node.js Application
# swagger-decorator
Original intention of swagger-decorator is to simplify JavaScript Application(Web & Node.js)development,by reusing information defined in the entity (plain JavaScript class). Entity decorated by swagger-decorator can be used in instance creation and validation, Sequelize ORM Model generation, autogenerated Swagger doc for Koa2, etc. However, things will develop in opposite direction when they become extreme, so if you feel exhausted with too much decorator, just stop using it.
```shell
# use npm to install
$ npm install swagger-decorator -S
$ npm install babel-plugin-transform-decorators-legacy -D
# use yarn to install
$ yarn add swagger-decorator
$ yarn add babel-plugin-transform-decorators-legacy -D
# import function or decorator you need
import {
wrappingKoaRouter,
entityProperty,
...
} from "swagger-decorator";
```
# entity decorator
```javascript
/**
* Description description for this property
* @param type
* @param description
* @param required
* @param defaultValue
* @param pattern
* @param primaryKey
* @returns {Function}
*/
export function entityProperty({
// these params will be used in swagger doc generation
type = "string",
description = "",
required = false,
defaultValue = undefined,
// this param is used in validation
pattern = undefined,
// this param is used in orm model
primaryKey = false
}) {}
```
We can use entityProperty to define simple User entity class:
```javascript
// @flow
import { entityProperty } from "../../src/entity/decorator";
import UserProperty from "./UserProperty";
/**
* Description User Entity
*/
export default class User {
id: string = 0;
name: string = "name";
email: string = "email";
property: UserProperty = new UserProperty();
}
export default class UserProperty {
friends: [number];
}
```
Swagger Inner Datatype:
| Common Name | [`type`](http://swagger.io/specification/#dataTypeType) | [`format`](http://swagger.io/specification/#dataTypeFormat) | 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](http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14) |
| dateTime | `string` | `date-time` | As defined by `date-time` - [RFC3339](http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14) |
| password | `string` | `password` | Used to hint UIs the input needs to be obscured. |
## instance generation and validation
After defining entity class, we can use `instantiate` to generate instance for this entity class. Different from the `new` keyword, `instantiate` will assign property with entity instance other than plain object recrusively; and it will also validate data.
```javascript
/**
* 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 {}
```
Here we use jest:
```javascript
describe("test instantiate", () => {
test("test validation", () => {
expect(() => {
instantiate(User, {
name: "name"
}).toThrowError(/validate fail!/);
});
let user = instantiate(User, {
id: 0,
name: "name",
email: "a@q.com"
});
expect(user).toBeInstanceOf(User);
});
test("test ignore param, which is used to ignore validation", () => {
instantiate(
User,
{
name: "name"
},
{
ignore: true
}
);
});
test("test strict param, if set true will ignore external property which isn't defined in entity class", () => {
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 nested entity property", () => {
test("test User", () => {
let user = instantiate(User, {
id: 0,
property: {
friends: [0]
}
});
expect(user.property).toBeInstanceOf(UserProperty);
});
});
```
## Sequelize Model
```javascript
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);
});
```
## generate entity class from flow type
Here we use babel and baylon to extract type information from flow file:
```
// @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);
}
);
});
```
Soucrce Entity:
```
// @flow
import AnotherEntity from "./AnotherEntity";
class Entity {
// Comment
stringProperty: string = 0;
classProperty: Entity = null;
rawProperty;
decoratedProperty;
}
```
Transformed Entity:
```
// @flow
import { entityProperty } from 'swagger-decorator';
import AnotherEntity from './AnotherEntity';
class Entity {
// Comment
stringProperty: string = 0;
classProperty: Entity = null;
rawProperty;
decoratedProperty;
}
```
# API Decorator and Swagger Doc Generation
## Wrapping router
```javascript
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);
```
## Defining API with decorator
```javascript
export default class UserController extends UserControllerDoc {
static async getUsers(ctx, next): [User] {
ctx.body = [new User()];
}
static async getUserByID(ctx, next): User {
ctx.body = new User();
}
static async postUser(): number {
ctx.body = {
statusCode: 200
};
}
}
```
too much decorator in UserController may decrease the readability of code, so we can move some description to its parent class:
```javascript
export default class UserControllerDoc {
static async getUsers(ctx, next): [User] {}
static async getUserByID(ctx, next): User {}
static async postUser(): number {}
}
```
## run your application
- run your application and open swagger docs (PS. swagger-decorator contains Swagger UI):
```text
/swagger
```

```text
/swagger/api.json
```
