decorator-x
v0.0.1
Published
decorator for entity instantiation & validation, auto-generate swagger docs & graphql schema
Downloads
7
Maintainers
Readme
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.
# 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
/**
* 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:
// @flow
import { entityProperty } from "../../src/entity/decorator";
import UserProperty from "./UserProperty";
/**
* Description User Entity
*/
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 Inner Datatype:
| 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. |
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.
/**
* 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:
describe("test instantiate", () => {
test("test validation", () => {
expect(() => {
instantiate(User, {
name: "name"
}).toThrowError(/validate fail!/);
});
let user = instantiate(User, {
id: 0,
name: "name",
email: "[email protected]"
});
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
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;
@entityProperty({
type: "string",
description: "this is property description",
required: true
})
decoratedProperty;
}
Transformed Entity:
// @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;
}
API Decorator and Swagger Doc Generation
Wrapping router
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
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
};
}
}
too much decorator in UserController may decrease the readability of code, so we can move some description to its parent class:
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
- run your application and open swagger docs (PS. swagger-decorator contains Swagger UI):
/swagger
/swagger/api.json