├── .npmignore ├── test ├── fixtures │ └── apps │ │ └── swagger-decorator-test │ │ ├── typings │ │ ├── index.d.ts │ │ ├── app │ │ │ ├── service │ │ │ │ └── index.d.ts │ │ │ └── controller │ │ │ │ └── index.d.ts │ │ └── config │ │ │ └── index.d.ts │ │ ├── package.json │ │ ├── config │ │ ├── plugin.ts │ │ └── config.default.ts │ │ └── app │ │ ├── service │ │ └── Test.ts │ │ ├── router.ts │ │ └── controller │ │ ├── home.ts │ │ └── test.ts └── index.test.ts ├── dist ├── swaggerHTML.d.ts ├── index.d.ts ├── validate.d.ts ├── utils.d.ts ├── wrapper.d.ts ├── swaggerTemplate.d.ts ├── swaggerJSON.d.ts ├── index.js ├── decorator.d.ts ├── swaggerTemplate.js ├── utils.js ├── swaggerHTML.js ├── swaggerJSON.js ├── validate.js ├── decorator.js └── wrapper.js ├── .travis.yml ├── lib ├── index.ts ├── swaggerTemplate.ts ├── utils.ts ├── swaggerJSON.ts ├── validate.ts ├── decorator.ts ├── swaggerHTML.ts └── wrapper.ts ├── .gitignore ├── appveyor.yml ├── .autod.conf.js ├── tsconfig.json ├── tsconfig_publish.json ├── tslint.json ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | logs 2 | lib 3 | run 4 | test 5 | app 6 | -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'egg' { 2 | 3 | } -------------------------------------------------------------------------------- /dist/swaggerHTML.d.ts: -------------------------------------------------------------------------------- 1 | declare const swaggerHTML: (apiPath: string) => string; 2 | export default swaggerHTML; 3 | -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swagger-decorator-test", 3 | "version": "0.0.1", 4 | "egg": { 5 | "typescript": true 6 | } 7 | } -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/config/plugin.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | // nunjucks: { 5 | // enable: true, 6 | // package: 'egg-view-nunjucks', 7 | // }, 8 | }; 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | install: 6 | - npm i npminstall && npminstall 7 | script: 8 | - npm run ci 9 | after_script: 10 | - npminstall codecov && codecov 11 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export { wrapper, makeSwaggerRouter } from './wrapper'; 2 | export { request, summary, params, desc, description, query, path, body, tags, apiObjects, middlewares, formData, responses } from './decorator'; 3 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export {wrapper, makeSwaggerRouter} from './wrapper'; 2 | 3 | export { request, summary, params, desc, description, query, path, body, tags, 4 | apiObjects, middlewares, formData, responses } from './decorator'; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | node_modules/ 4 | coverage/ 5 | .idea/ 6 | run/ 7 | logs/ 8 | .DS_Store 9 | .vscode 10 | *.swp 11 | *.lock 12 | .history 13 | 14 | lib/**/*.js 15 | app/**/*.js 16 | test/**/*.js 17 | config/**/*.js 18 | app/**/*.map 19 | test/**/*.map 20 | config/**/*.map -------------------------------------------------------------------------------- /dist/validate.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * validate the input values 3 | * @param {Object} input the input object, like request.query, request.body and so on. 4 | * @param {Object} expect the expect value, Ex: { name: { required: true, type: String }} 5 | */ 6 | export default function (input: any, expect: any): any; 7 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/app/service/Test.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'egg'; 2 | 3 | /** 4 | * Test Service 5 | */ 6 | export default class Test extends Service { 7 | 8 | /** 9 | * sayHi to you 10 | * @param name - your name 11 | */ 12 | public async sayHi(name: string) { 13 | return `hi, ${name}`; 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/typings/app/service/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file was auto created by egg-ts-helper 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; // Make sure ts to import egg declaration at first 5 | import Test from '../../../app/service/Test'; 6 | 7 | declare module 'egg' { 8 | interface IService { 9 | test: Test; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /dist/utils.d.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | /** 3 | * eg. /api/{id} -> /api/:id 4 | * @param {String} path 5 | */ 6 | declare const convertPath: (path: any) => any; 7 | declare const getPath: (prefix: string, path: string) => string; 8 | declare function loadSwaggerClassesToContext(app: Application): void; 9 | export { convertPath, getPath, loadSwaggerClassesToContext }; 10 | -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/typings/app/controller/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file was auto created by egg-ts-helper 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; // Make sure ts to import egg declaration at first 5 | import Home from '../../../app/controller/home'; 6 | import Test from '../../../app/controller/test'; 7 | 8 | declare module 'egg' { 9 | interface IController { 10 | home: Home; 11 | test: Test; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'ues strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | plugin: 'autod-egg', 6 | prefix: '^', 7 | devprefix: '^', 8 | exclude: [ 9 | 'test/fixtures', 10 | 'coverage', 11 | ], 12 | dep: [ 13 | 'egg', 14 | 'egg-scripts', 15 | ], 16 | devdep: [ 17 | 'autod', 18 | 'autod-egg', 19 | 'egg-bin', 20 | 'tslib', 21 | 'typescript', 22 | ], 23 | keep: [ 24 | ], 25 | semver: [ 26 | ], 27 | test: 'scripts', 28 | }; 29 | -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/typings/config/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file was auto created by egg-ts-helper 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; // Make sure ts to import egg declaration at first 5 | import { EggAppConfig } from 'egg'; 6 | import ExportConfigDefault from '../../config/config.default'; 7 | type ConfigDefault = ReturnType; 8 | declare module 'egg' { 9 | type NewEggAppConfig = ConfigDefault; 10 | interface EggAppConfig extends NewEggAppConfig { } 11 | } -------------------------------------------------------------------------------- /dist/wrapper.d.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | import { WrapperOptions } from './swaggerJSON'; 3 | declare module 'egg' { 4 | interface Application { 5 | createAnonymousContext(req?: any): Context; 6 | swaggerControllerClasses: {}; 7 | } 8 | interface Context { 9 | validatedQuery?: {}; 10 | validatedParams?: {}; 11 | validatedBody?: {}; 12 | } 13 | } 14 | declare const wrapper: (app: Application, options?: WrapperOptions | undefined) => void; 15 | declare const makeSwaggerRouter: (app: Application) => void; 16 | export { wrapper, makeSwaggerRouter }; 17 | -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/config/config.default.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { EggAppConfig } from 'egg'; 4 | 5 | export default (appInfo: EggAppConfig) => { 6 | const config: any = {}; 7 | 8 | // app special config 9 | config.sourceUrl = `https://github.com/eggjs/examples/tree/master/${appInfo.name}`; 10 | 11 | // override config from framework / plugin 12 | // use for cookie sign key, should change to your own and keep security 13 | config.keys = appInfo.name + '_1523508510075_3084'; 14 | 15 | // add your config here 16 | config.middleware = []; 17 | config.security = { 18 | csrf: {enable: false} 19 | } 20 | return config; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/swaggerTemplate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * init swagger definitions 3 | * @param {String} title 4 | * @param {String} description 5 | * @param {String} version 6 | * @param {Object} options other options for swagger definition 7 | */ 8 | 9 | export default ( 10 | title: string, 11 | description: string, 12 | version: string, 13 | options = {}, 14 | ) => (Object.assign( 15 | { 16 | info: { title, description, version }, 17 | paths: {}, 18 | responses: {}, 19 | }, 20 | { 21 | definitions: {}, 22 | tags: [], 23 | swagger: '2.0', 24 | securityDefinitions: { 25 | ApiKeyAuth: { 26 | type: 'apiKey', 27 | in: 'header', 28 | name: 'Authorization', 29 | }, 30 | }, 31 | }, 32 | options, 33 | 34 | )); 35 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | import * as _path from 'path'; 3 | /** 4 | * eg. /api/{id} -> /api/:id 5 | * @param {String} path 6 | */ 7 | const convertPath = (path) => { 8 | const re = new RegExp('{(.*?)}', 'g'); 9 | return path.replace(re, ':$1'); 10 | }; 11 | 12 | const getPath = (prefix : string, path : string) => `${prefix}${path}`.replace('//', '/'); 13 | 14 | function loadSwaggerClassesToContext(app: Application) { 15 | const opt = { 16 | call: false, 17 | caseStyle: 'lower', 18 | directory: _path.join(app.config.baseDir, 'app/controller'), 19 | typescript: true, 20 | }; 21 | app.loader.loadToApp(opt.directory, 'swaggerControllerClasses', opt); 22 | } 23 | 24 | export { convertPath, getPath, loadSwaggerClassesToContext }; 25 | -------------------------------------------------------------------------------- /dist/swaggerTemplate.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * init swagger definitions 3 | * @param {String} title 4 | * @param {String} description 5 | * @param {String} version 6 | * @param {Object} options other options for swagger definition 7 | */ 8 | declare const _default: (title: string, description: string, version: string, options?: {}) => { 9 | info: { 10 | title: string; 11 | description: string; 12 | version: string; 13 | }; 14 | paths: {}; 15 | responses: {}; 16 | } & { 17 | definitions: {}; 18 | tags: never[]; 19 | swagger: string; 20 | securityDefinitions: { 21 | ApiKeyAuth: { 22 | type: string; 23 | in: string; 24 | name: string; 25 | }; 26 | }; 27 | }; 28 | export default _default; 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "strict": true, 7 | "noImplicitAny": false, 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "charset": "utf8", 11 | "allowJs": false, 12 | "pretty": true, 13 | "noEmitOnError": false, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "allowUnreachableCode": false, 17 | "allowUnusedLabels": false, 18 | "strictPropertyInitialization": false, 19 | "noFallthroughCasesInSwitch": true, 20 | "skipLibCheck": true, 21 | "skipDefaultLibCheck": true, 22 | "inlineSourceMap": true, 23 | "importHelpers": true 24 | }, 25 | "exclude": [ 26 | "app/public", 27 | "app/views" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig_publish.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "strict": true, 7 | "outDir": "./dist", 8 | "noImplicitAny": false, 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "charset": "utf8", 12 | "allowJs": false, 13 | "pretty": true, 14 | "noEmitOnError": false, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "allowUnreachableCode": false, 18 | "allowUnusedLabels": false, 19 | "strictPropertyInitialization": false, 20 | "noFallthroughCasesInSwitch": true, 21 | "skipLibCheck": true, 22 | "skipDefaultLibCheck": true, 23 | "inlineSourceMap": true, 24 | "importHelpers": true, 25 | "declaration": true 26 | }, 27 | "include": [ 28 | "lib/**/*" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:latest", 3 | "rules": { 4 | "quotemark": [ 5 | true, 6 | "single", 7 | "jsx-double" 8 | ], 9 | "no-console": [ 10 | true, 11 | "dir", 12 | "log", 13 | "error", 14 | "warn" 15 | ], 16 | "space-before-function-paren": false, 17 | "interface-name": [ 18 | true, 19 | "never-prefix" 20 | ], 21 | "adjacent-overload-signatures": true, 22 | "member-access": [ 23 | false 24 | ], 25 | "member-ordering": [ 26 | true, 27 | { 28 | "order": "fields-first" 29 | } 30 | ], 31 | "object-literal-sort-keys": false, 32 | "max-classes-per-file": [ 33 | true, 34 | 10 35 | ], 36 | "variable-name": [ 37 | true, 38 | "allow-leading-underscore" 39 | ], 40 | "align": [true, "statements"] 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/app/router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | import { wrapper, makeSwaggerRouter } from '../../../../../lib'; 3 | export default (app: Application) => { 4 | wrapper(app, { 5 | // // [optional] default is /swagger-html 6 | // swaggerHtmlEndpoint: '/sw', 7 | // // [optional] default is /swagger-json 8 | // swaggerJsonEndpoint: '/sj', 9 | // // [optional] default is false. if true, will call makeSwaggerRouter(app) automatically 10 | // makeswaggerRouter: false, 11 | 12 | // title: 'foo', 13 | // version: 'v1.0.0', 14 | // description: 'bar', 15 | 16 | // swaggerOptions: { 17 | // // [optional] used for swagger options 18 | // securityDefinitions: { 19 | // ApiKeyAuth: { 20 | // type: 'apiKey', 21 | // in: 'header', 22 | // name: 'AAA' 23 | // } 24 | // }, 25 | // } 26 | }); 27 | makeSwaggerRouter(app); 28 | }; 29 | -------------------------------------------------------------------------------- /dist/swaggerJSON.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * build swagger json from apiObjects 3 | */ 4 | export interface WrapperOptions { 5 | title?: string; 6 | description?: string; 7 | version?: string; 8 | prefix?: string; 9 | swaggerOptions?: any; 10 | swaggerJsonEndpoint?: string; 11 | swaggerHtmlEndpoint?: string; 12 | makeSwaggerRouter?: boolean; 13 | [param: string]: any; 14 | } 15 | export interface Response { 16 | [name: number]: any; 17 | } 18 | declare const swaggerJSON: (options: WrapperOptions, apiObjects: any) => { 19 | info: { 20 | title: string; 21 | description: string; 22 | version: string; 23 | }; 24 | paths: {}; 25 | responses: {}; 26 | } & { 27 | definitions: {}; 28 | tags: never[]; 29 | swagger: string; 30 | securityDefinitions: { 31 | ApiKeyAuth: { 32 | type: string; 33 | in: string; 34 | name: string; 35 | }; 36 | }; 37 | }; 38 | export default swaggerJSON; 39 | -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/app/controller/home.ts: -------------------------------------------------------------------------------- 1 | import { Context, Controller } from 'egg'; 2 | import { middlewares, request, responses, tags } from '../../../../../../lib'; 3 | 4 | const tag = tags(['Home']); 5 | 6 | const logTime = () => async (ctx: Context, next) => { 7 | ctx.logger.info(`start: ${new Date()}`); 8 | await next(); 9 | ctx.logger.info(`end: ${new Date()}`); 10 | }; 11 | 12 | export default class HomeController extends Controller { 13 | @request('GET', '/') 14 | @middlewares([logTime()]) 15 | @tag 16 | @responses({ 17 | 200: { 18 | description: 'success', 19 | schema: { 20 | type: 'object', 21 | properties: { 22 | msg: { type: 'string', example: 'here is a msg' }, 23 | }, 24 | }, 25 | }, 26 | }) 27 | public async index() { 28 | const { ctx, service } = this; 29 | ctx.body = await service.test.sayHi('egg'); 30 | } 31 | 32 | public async notused() { 33 | const { ctx, service } = this; 34 | ctx.body = await service.test.sayHi('egg'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var wrapper_1 = require("./wrapper"); 4 | exports.wrapper = wrapper_1.wrapper; 5 | exports.makeSwaggerRouter = wrapper_1.makeSwaggerRouter; 6 | var decorator_1 = require("./decorator"); 7 | exports.request = decorator_1.request; 8 | exports.summary = decorator_1.summary; 9 | exports.params = decorator_1.params; 10 | exports.desc = decorator_1.desc; 11 | exports.description = decorator_1.description; 12 | exports.query = decorator_1.query; 13 | exports.path = decorator_1.path; 14 | exports.body = decorator_1.body; 15 | exports.tags = decorator_1.tags; 16 | exports.apiObjects = decorator_1.apiObjects; 17 | exports.middlewares = decorator_1.middlewares; 18 | exports.formData = decorator_1.formData; 19 | exports.responses = decorator_1.responses; 20 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9saWIvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxxQ0FBcUQ7QUFBN0MsNEJBQUEsT0FBTyxDQUFBO0FBQUUsc0NBQUEsaUJBQWlCLENBQUE7QUFFbEMseUNBQ29FO0FBRDNELDhCQUFBLE9BQU8sQ0FBQTtBQUFFLDhCQUFBLE9BQU8sQ0FBQTtBQUFFLDZCQUFBLE1BQU0sQ0FBQTtBQUFFLDJCQUFBLElBQUksQ0FBQTtBQUFFLGtDQUFBLFdBQVcsQ0FBQTtBQUFFLDRCQUFBLEtBQUssQ0FBQTtBQUFFLDJCQUFBLElBQUksQ0FBQTtBQUFFLDJCQUFBLElBQUksQ0FBQTtBQUFFLDJCQUFBLElBQUksQ0FBQTtBQUMzRSxpQ0FBQSxVQUFVLENBQUE7QUFBRSxrQ0FBQSxXQUFXLENBQUE7QUFBRSwrQkFBQSxRQUFRLENBQUE7QUFBRSxnQ0FBQSxTQUFTLENBQUEifQ== -------------------------------------------------------------------------------- /dist/decorator.d.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Response } from './swaggerJSON'; 3 | /** 4 | * used for building swagger docs object 5 | */ 6 | declare const apiObjects: {}; 7 | declare const request: (method: any, path: any) => (target: any, name: any, descriptor: any) => any; 8 | declare const middlewares: (val: any) => (target: any, name: any, descriptor: any) => any; 9 | declare const responses: (res?: Response) => (target: any, name: any, descriptor: any) => any; 10 | declare const desc: _.CurriedFunction2 any>; 11 | declare const description: _.CurriedFunction1 any>; 12 | declare const summary: _.CurriedFunction1 any>; 13 | declare const tags: _.CurriedFunction1 any>; 14 | declare const params: _.CurriedFunction2 any>; 15 | declare const query: _.CurriedFunction1 any>; 16 | declare const path: _.CurriedFunction1 any>; 17 | declare const body: _.CurriedFunction1 any>; 18 | declare const formData: _.CurriedFunction1 any>; 19 | export { request, summary, params, desc, description, query, path, body, tags, apiObjects, middlewares, formData, responses, }; 20 | -------------------------------------------------------------------------------- /dist/swaggerTemplate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * init swagger definitions 4 | * @param {String} title 5 | * @param {String} description 6 | * @param {String} version 7 | * @param {Object} options other options for swagger definition 8 | */ 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.default = (title, description, version, options = {}) => (Object.assign({ 11 | info: { title, description, version }, 12 | paths: {}, 13 | responses: {}, 14 | }, { 15 | definitions: {}, 16 | tags: [], 17 | swagger: '2.0', 18 | securityDefinitions: { 19 | ApiKeyAuth: { 20 | type: 'apiKey', 21 | in: 'header', 22 | name: 'Authorization', 23 | }, 24 | }, 25 | }, options)); 26 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3dhZ2dlclRlbXBsYXRlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vbGliL3N3YWdnZXJUZW1wbGF0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7OztHQU1HOztBQUVILGtCQUFlLENBQ2IsS0FBYSxFQUNiLFdBQW1CLEVBQ25CLE9BQWUsRUFDZixPQUFPLEdBQUcsRUFBRSxFQUNaLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQ2pCO0lBQ0UsSUFBSSxFQUFFLEVBQUUsS0FBSyxFQUFFLFdBQVcsRUFBRSxPQUFPLEVBQUU7SUFDckMsS0FBSyxFQUFFLEVBQUU7SUFDVCxTQUFTLEVBQUUsRUFBRTtDQUNkLEVBQ0Q7SUFDRSxXQUFXLEVBQUUsRUFBRTtJQUNmLElBQUksRUFBRSxFQUFFO0lBQ1IsT0FBTyxFQUFFLEtBQUs7SUFDZCxtQkFBbUIsRUFBRTtRQUNuQixVQUFVLEVBQUU7WUFDVixJQUFJLEVBQUUsUUFBUTtZQUNkLEVBQUUsRUFBRSxRQUFRO1lBQ1osSUFBSSxFQUFFLGVBQWU7U0FDdEI7S0FDRjtDQUNGLEVBQ0QsT0FBTyxDQUVSLENBQUMsQ0FBQyJ9 -------------------------------------------------------------------------------- /dist/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const _path = require("path"); 4 | /** 5 | * eg. /api/{id} -> /api/:id 6 | * @param {String} path 7 | */ 8 | const convertPath = (path) => { 9 | const re = new RegExp('{(.*?)}', 'g'); 10 | return path.replace(re, ':$1'); 11 | }; 12 | exports.convertPath = convertPath; 13 | const getPath = (prefix, path) => `${prefix}${path}`.replace('//', '/'); 14 | exports.getPath = getPath; 15 | function loadSwaggerClassesToContext(app) { 16 | const opt = { 17 | call: false, 18 | caseStyle: 'lower', 19 | directory: _path.join(app.config.baseDir, 'app/controller'), 20 | typescript: true, 21 | }; 22 | app.loader.loadToApp(opt.directory, 'swaggerControllerClasses', opt); 23 | } 24 | exports.loadSwaggerClassesToContext = loadSwaggerClassesToContext; 25 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9saWIvdXRpbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFDQSw4QkFBOEI7QUFDOUI7OztHQUdHO0FBQ0gsTUFBTSxXQUFXLEdBQUcsQ0FBQyxJQUFJLEVBQUUsRUFBRTtJQUMzQixNQUFNLEVBQUUsR0FBRyxJQUFJLE1BQU0sQ0FBQyxTQUFTLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDdEMsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQztBQUNqQyxDQUFDLENBQUM7QUFjTyxrQ0FBVztBQVpwQixNQUFNLE9BQU8sR0FBRyxDQUFDLE1BQWUsRUFBRSxJQUFhLEVBQUUsRUFBRSxDQUFDLEdBQUcsTUFBTSxHQUFHLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUM7QUFZcEUsMEJBQU87QUFWN0IscUNBQXFDLEdBQWdCO0lBQ25ELE1BQU0sR0FBRyxHQUFHO1FBQ1YsSUFBSSxFQUFFLEtBQUs7UUFDWCxTQUFTLEVBQUUsT0FBTztRQUNsQixTQUFTLEVBQUUsS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxnQkFBZ0IsQ0FBQztRQUMzRCxVQUFVLEVBQUUsSUFBSTtLQUNqQixDQUFDO0lBQ0YsR0FBRyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLFNBQVMsRUFBRSwwQkFBMEIsRUFBRSxHQUFHLENBQUMsQ0FBQztBQUN2RSxDQUFDO0FBRThCLGtFQUEyQiJ9 -------------------------------------------------------------------------------- /test/fixtures/apps/swagger-decorator-test/app/controller/test.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Context } from 'egg'; 2 | import { request, summary, query, path, body, tags, responses, middlewares, description } from '../../../../../../lib'; 3 | 4 | const testTag = tags(['test']); 5 | 6 | const userSchema = { 7 | name: { type: 'string', required: true }, 8 | gender: { type: 'string', required: false, example: 'male' }, 9 | }; 10 | 11 | const resp = { 12 | 200: { 13 | description: 'custom success' 14 | }, 15 | 400: { 16 | decription: 'custom error' 17 | } 18 | } 19 | 20 | export default class Test extends Controller { 21 | @request('get', '/users') 22 | @description('get user list') 23 | @testTag 24 | @middlewares(async (ctx: Context, next) => { ctx.logger.info('mid'); await next() }) 25 | @query({ 26 | type: { type: 'string', required: false, default: 'a', description: 'type' } 27 | }) 28 | public async getUsers() { 29 | const { ctx } = this; 30 | const users = [{ user1: { name: 'xxx' } }] 31 | ctx.body = { users }; 32 | } 33 | 34 | @request('get', '/users/{id}') 35 | @summary('get user info by id') 36 | @testTag 37 | @path({ 38 | id: { type: 'number', required: true, default: 1, description: 'id' } 39 | }) 40 | public async getUser() { 41 | const { id } = this.ctx.params; 42 | const result = { user: { id } } 43 | this.ctx.body = result; 44 | } 45 | 46 | @request('post', '/users') 47 | @testTag 48 | @body(userSchema) 49 | @responses(resp) 50 | public async postUser() { 51 | const body = this.ctx.request.body; 52 | this.ctx.body = body; 53 | } 54 | 55 | @request('get', '/enum') 56 | @testTag 57 | @query({ 58 | role: { type: 'string', enum: ['1', '2', '3'], required: true } 59 | }) 60 | @responses(resp) 61 | public async testEnum() { 62 | const { ctx } = this; 63 | console.log(ctx.query); 64 | ctx.body = { msg: 'enum passed' }; 65 | } 66 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egg-swagger-decorator", 3 | "version": "0.1.2", 4 | "description": "using decorator to make router definitions and automatically generate swagger doc for egg.js", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "egg": { 8 | "typescript": true 9 | }, 10 | "eggPlugin": { 11 | "name": "swaggerDecorator" 12 | }, 13 | "scripts": { 14 | "start": "egg-scripts start --baseDir=./test/fixtures/apps/swagger-decorator-test --title=egg-server-tt", 15 | "stop": "egg-scripts stop --title=egg-server-tt", 16 | "dev": "egg-bin dev --baseDir=./test/fixtures/apps/swagger-decorator-test -r egg-ts-helper/register", 17 | "debug": "egg-bin debug --baseDir=./test/fixtures/apps/swagger-decorator-test -r egg-ts-helper/register", 18 | "test-local": "egg-bin test -r egg-ts-helper/register", 19 | "test": "npm run lint -- --fix && npm run test-local", 20 | "cov": "egg-bin cov -r egg-ts-helper/register", 21 | "tsc": "ets --cwd=./test/fixtures/apps/swagger-decorator-test && tsc -p tsconfig.json", 22 | "ci": "npm run lint && npm run cov && npm run tsc", 23 | "autod": "autod", 24 | "lint": "tslint .", 25 | "clean": "ets clean", 26 | "build": "tsc -p tsconfig_publish.json", 27 | "pub": "npm run build && npm publish" 28 | }, 29 | "dependencies": { 30 | "lodash": "^4.17.5" 31 | }, 32 | "devDependencies": { 33 | "@types/lodash": "^4.14.106", 34 | "@types/mocha": "^2.2.40", 35 | "@types/node": "^7.0.12", 36 | "@types/supertest": "^2.0.0", 37 | "autod": "^3.0.1", 38 | "autod-egg": "^1.1.0", 39 | "egg-bin": "^4.6.3", 40 | "egg-ci": "^1.8.0", 41 | "egg-mock": "^3.17.0", 42 | "egg-ts-helper": "^1.4.2", 43 | "tslib": "^1.9.0", 44 | "tslint": "^4.0.0", 45 | "typescript": "^2.8.1", 46 | "egg": "^2.6.0", 47 | "egg-scripts": "^2.6.0" 48 | }, 49 | "engines": { 50 | "node": ">=8.9.0" 51 | }, 52 | "ci": { 53 | "version": "8" 54 | }, 55 | "repository": "https://github.com/Cody2333/koa-swagger-decorator", 56 | "eslintIgnore": [ 57 | "coverage" 58 | ], 59 | "keywords": [ 60 | "decorator", 61 | "swagger", 62 | "egg", 63 | "eggPlugin", 64 | "egg-plugin" 65 | ], 66 | "author": "Cody", 67 | "license": "MIT" 68 | } 69 | -------------------------------------------------------------------------------- /lib/swaggerJSON.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import init from './swaggerTemplate'; 3 | import { getPath } from './utils'; 4 | /** 5 | * build swagger json from apiObjects 6 | */ 7 | export interface WrapperOptions { 8 | title?: string; 9 | description?: string; 10 | version?: string; 11 | prefix?: string; 12 | swaggerOptions?: any; 13 | swaggerJsonEndpoint?: string; 14 | swaggerHtmlEndpoint?: string; 15 | makeSwaggerRouter?: boolean; 16 | [param: string]: any; 17 | } 18 | 19 | export interface Response { 20 | [name: number]: any; 21 | } 22 | const swaggerJSON = (options: WrapperOptions, apiObjects) => { 23 | 24 | const { title = 'API DOC', description = 'API DOC', version = '1.0.0', prefix = '', swaggerOptions = {} } = options; 25 | 26 | const resultJSON = init(title, description, version, swaggerOptions); 27 | 28 | _.chain(apiObjects) 29 | .forEach((value) => { 30 | if (!Object.keys(value).includes('request')) { throw new Error('missing [request] field'); } 31 | 32 | const { method } = value.request; 33 | let { path } = value.request; 34 | path = getPath(prefix, path); // 根据前缀补全path 35 | const summary = value.summary 36 | ? value.summary 37 | : ''; 38 | const apiDescription = value.description 39 | ? value.description 40 | : summary; 41 | const defaultResp: Response = { 42 | 200: { 43 | description: 'success', 44 | }, 45 | }; 46 | const responses: Response = value.responses 47 | ? value.responses 48 | : defaultResp; 49 | const { 50 | query = [], 51 | path: pathParams = [], 52 | body = [], 53 | tags, 54 | formData = [], 55 | security, 56 | } = value; 57 | 58 | const parameters = [ 59 | ...pathParams, 60 | ...query, 61 | ...formData, 62 | ...body, 63 | ]; 64 | 65 | // init path object first 66 | if (!resultJSON.paths[path]) { resultJSON.paths[path] = {}; } 67 | 68 | // add content type [multipart/form-data] to support file upload 69 | const consumes = formData.length > 0 70 | ? ['multipart/form-data'] 71 | : undefined; 72 | 73 | resultJSON.paths[path][method] = { 74 | consumes, 75 | summary, 76 | description: apiDescription, 77 | parameters, 78 | responses, 79 | tags, 80 | security, 81 | }; 82 | }).value(); 83 | return resultJSON; 84 | }; 85 | 86 | export default swaggerJSON; 87 | -------------------------------------------------------------------------------- /lib/validate.ts: -------------------------------------------------------------------------------- 1 | class InputError extends Error { 2 | /** 3 | * Constructor 4 | * @param {string} field the error field in request parameters. 5 | */ 6 | constructor(field) { 7 | super(`incorrect field: '${field}', please check again!`); 8 | this['field'] = field; 9 | this['status'] = 400; 10 | } 11 | } 12 | 13 | /** 14 | * validate the input values 15 | * @param {Object} input the input object, like request.query, request.body and so on. 16 | * @param {Object} expect the expect value, Ex: { name: { required: true, type: String }} 17 | */ 18 | export default function (input, expect) { 19 | Object.keys(expect).forEach((key) => { 20 | if (expect[key] === undefined) { 21 | delete input[key]; 22 | return; 23 | } 24 | // if this key is required but not in input. 25 | if (expect[key].required && input[key] === undefined) { 26 | throw new InputError(key); 27 | } 28 | // if this key is in input, but the type is wrong 29 | // first check the number type 30 | if (input[key] !== undefined && expect[key].type === 'number') { 31 | if (typeof input[key] === 'number') { 32 | return; 33 | } 34 | if (isNaN(Number(input[key]))) { 35 | throw new InputError(key); 36 | } 37 | input[key] = Number(input[key]); 38 | return; 39 | } 40 | // second check the boolean type 41 | if (input[key] !== undefined && expect[key].type === 'boolean') { 42 | if (typeof input[key] === 'boolean') { 43 | return; 44 | } 45 | if (input[key] === 'true') { 46 | input[key] = true; 47 | return; 48 | } 49 | if (input[key] === 'false') { 50 | input[key] = false; 51 | return; 52 | } 53 | throw new InputError(key); 54 | } 55 | // third check the string type 56 | if (input[key] !== undefined && expect[key].type === 'string') { 57 | if (typeof input[key] !== 'string') { 58 | input[key] = String(input[key]); 59 | return; 60 | } 61 | if (Array.isArray(expect[key].enum) && expect[key].enum.length) { 62 | if (!expect[key].enum.includes(input[key])) throw new InputError(key); 63 | } 64 | } 65 | // forth check the object type 66 | if (input[key] !== undefined && expect[key].type === 'object') { 67 | if (typeof input[key] === 'object') { 68 | return; 69 | } 70 | throw new InputError(key); 71 | } 72 | // last check the array type 73 | if (input[key] !== undefined && expect[key].type === 'array') { 74 | if (input[key] instanceof Array) { 75 | return; 76 | } 77 | throw new InputError(key); 78 | } 79 | // if this key is not in input and need a default value 80 | if (input[key] === undefined && expect[key].default !== undefined) { 81 | input[key] = expect[key].default; 82 | } 83 | }); 84 | return input; 85 | } 86 | -------------------------------------------------------------------------------- /lib/decorator.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | import { Response } from './swaggerJSON'; 4 | /** 5 | * used for building swagger docs object 6 | */ 7 | const apiObjects = {}; 8 | 9 | const _addToApiObject = (target, name, apiObj, content) => { 10 | const key = `${target.constructor.name}-${name}`; 11 | if (!apiObj[key]) { 12 | apiObj[key] = {}; 13 | } 14 | Object.assign(apiObj[key], content); 15 | }; 16 | 17 | const _desc = (type, text) => (target, name, descriptor) => { 18 | descriptor.value[type] = text; 19 | _addToApiObject(target, name, apiObjects, { [type]: text }); 20 | return descriptor; 21 | }; 22 | 23 | const _params = (type, parameters) => (target, name, descriptor) => { 24 | if (!descriptor.value.parameters) { 25 | descriptor.value.parameters = {}; 26 | } 27 | descriptor.value.parameters[type] = parameters; 28 | 29 | // additional wrapper for body 30 | let swaggerParameters = parameters; 31 | if (type === 'body') { 32 | swaggerParameters = [{ 33 | name: 'data', 34 | description: 'request body', 35 | schema: { 36 | type: 'object', 37 | properties: parameters, 38 | }, 39 | }]; 40 | } else { 41 | swaggerParameters = Object.keys(swaggerParameters).map((key) => { 42 | return Object.assign({ name: key }, swaggerParameters[key]); 43 | }); 44 | } 45 | swaggerParameters.forEach((item) => { 46 | item.in = type; 47 | }); 48 | 49 | _addToApiObject(target, name, apiObjects, { [type]: swaggerParameters }); 50 | return descriptor; 51 | }; 52 | 53 | const request = (method, path) => (target, name, descriptor) => { 54 | method = _.toLower(method); 55 | descriptor.value.method = method; 56 | descriptor.value.path = path; 57 | _addToApiObject(target, name, apiObjects, { 58 | request: { method, path }, 59 | security: [{ ApiKeyAuth: [] }], 60 | }); 61 | return descriptor; 62 | }; 63 | 64 | const middlewares = (val) => (target, name, descriptor) => { 65 | if (!target || !name) { throw new Error(); } 66 | descriptor.value.middlewares = val; 67 | return descriptor; 68 | }; 69 | 70 | const responses = (res: Response = { 200: { description: 'success' } }) => (target, name, descriptor) => { 71 | descriptor.value.responses = res; 72 | _addToApiObject(target, name, apiObjects, { responses: res }); 73 | return descriptor; 74 | }; 75 | const desc = _.curry(_desc); 76 | 77 | // description and summary 78 | const description = desc('description'); 79 | 80 | const summary = desc('summary'); 81 | 82 | const tags = desc('tags'); 83 | 84 | const params = _.curry(_params); 85 | 86 | // below are [parameters] 87 | 88 | // query params 89 | const query = params('query'); 90 | 91 | // path params 92 | const path = params('path'); 93 | 94 | // body params 95 | const body = params('body'); 96 | 97 | // formData params 98 | const formData = params('formData'); 99 | 100 | export { 101 | request, summary, params, desc, description, query, path, body, tags, 102 | apiObjects, middlewares, formData, responses, 103 | }; 104 | -------------------------------------------------------------------------------- /lib/swaggerHTML.ts: -------------------------------------------------------------------------------- 1 | const swaggerHTML = (apiPath: string) => 2 | ` 3 | 4 | 5 | 6 | 7 | 8 | Swagger UI 9 | 10 | 11 | 12 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | 71 | 72 | 90 | 91 | 92 | 93 | 94 | `; 95 | 96 | export default swaggerHTML; -------------------------------------------------------------------------------- /dist/swaggerHTML.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const swaggerHTML = (apiPath) => ` 4 | 5 | 6 | 7 | 8 | 9 | Swagger UI 10 | 11 | 12 | 13 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
70 | 71 | 72 | 73 | 91 | 92 | 93 | 94 | 95 | `; 96 | exports.default = swaggerHTML; 97 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3dhZ2dlckhUTUwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9saWIvc3dhZ2dlckhUTUwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxNQUFNLFdBQVcsR0FBRyxDQUFDLE9BQWUsRUFBRSxFQUFFLENBQ3RDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztjQTBFWSxPQUFPOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Q0FrQnBCLENBQUM7QUFFRixrQkFBZSxXQUFXLENBQUMifQ== -------------------------------------------------------------------------------- /dist/swaggerJSON.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const _ = require("lodash"); 4 | const swaggerTemplate_1 = require("./swaggerTemplate"); 5 | const utils_1 = require("./utils"); 6 | const swaggerJSON = (options, apiObjects) => { 7 | const { title = 'API DOC', description = 'API DOC', version = '1.0.0', prefix = '', swaggerOptions = {} } = options; 8 | const resultJSON = swaggerTemplate_1.default(title, description, version, swaggerOptions); 9 | _.chain(apiObjects) 10 | .forEach((value) => { 11 | if (!Object.keys(value).includes('request')) { 12 | throw new Error('missing [request] field'); 13 | } 14 | const { method } = value.request; 15 | let { path } = value.request; 16 | path = utils_1.getPath(prefix, path); // 根据前缀补全path 17 | const summary = value.summary 18 | ? value.summary 19 | : ''; 20 | const apiDescription = value.description 21 | ? value.description 22 | : summary; 23 | const defaultResp = { 24 | 200: { 25 | description: 'success', 26 | }, 27 | }; 28 | const responses = value.responses 29 | ? value.responses 30 | : defaultResp; 31 | const { query = [], path: pathParams = [], body = [], tags, formData = [], security, } = value; 32 | const parameters = [ 33 | ...pathParams, 34 | ...query, 35 | ...formData, 36 | ...body, 37 | ]; 38 | // init path object first 39 | if (!resultJSON.paths[path]) { 40 | resultJSON.paths[path] = {}; 41 | } 42 | // add content type [multipart/form-data] to support file upload 43 | const consumes = formData.length > 0 44 | ? ['multipart/form-data'] 45 | : undefined; 46 | resultJSON.paths[path][method] = { 47 | consumes, 48 | summary, 49 | description: apiDescription, 50 | parameters, 51 | responses, 52 | tags, 53 | security, 54 | }; 55 | }).value(); 56 | return resultJSON; 57 | }; 58 | exports.default = swaggerJSON; 59 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3dhZ2dlckpTT04uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9saWIvc3dhZ2dlckpTT04udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSw0QkFBNEI7QUFDNUIsdURBQXFDO0FBQ3JDLG1DQUFrQztBQW1CbEMsTUFBTSxXQUFXLEdBQUcsQ0FBQyxPQUF1QixFQUFFLFVBQVUsRUFBRSxFQUFFO0lBRTFELE1BQU0sRUFBRSxLQUFLLEdBQUcsU0FBUyxFQUFFLFdBQVcsR0FBRyxTQUFTLEVBQUUsT0FBTyxHQUFHLE9BQU8sRUFBRSxNQUFNLEdBQUcsRUFBRSxFQUFFLGNBQWMsR0FBRyxFQUFFLEVBQUUsR0FBRyxPQUFPLENBQUM7SUFFcEgsTUFBTSxVQUFVLEdBQUcseUJBQUksQ0FBQyxLQUFLLEVBQUUsV0FBVyxFQUFFLE9BQU8sRUFBRSxjQUFjLENBQUMsQ0FBQztJQUVyRSxDQUFDLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQztTQUNoQixPQUFPLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRTtRQUNqQixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLEVBQUU7WUFBRSxNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixDQUFDLENBQUM7U0FBRTtRQUU1RixNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQztRQUNqQyxJQUFJLEVBQUUsSUFBSSxFQUFFLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQztRQUM3QixJQUFJLEdBQUcsZUFBTyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDLGFBQWE7UUFDM0MsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLE9BQU87WUFDM0IsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPO1lBQ2YsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUNQLE1BQU0sY0FBYyxHQUFHLEtBQUssQ0FBQyxXQUFXO1lBQ3RDLENBQUMsQ0FBQyxLQUFLLENBQUMsV0FBVztZQUNuQixDQUFDLENBQUMsT0FBTyxDQUFDO1FBQ1osTUFBTSxXQUFXLEdBQWE7WUFDNUIsR0FBRyxFQUFFO2dCQUNILFdBQVcsRUFBRSxTQUFTO2FBQ3ZCO1NBQ0YsQ0FBQztRQUNGLE1BQU0sU0FBUyxHQUFhLEtBQUssQ0FBQyxTQUFTO1lBQ3pDLENBQUMsQ0FBQyxLQUFLLENBQUMsU0FBUztZQUNqQixDQUFDLENBQUMsV0FBVyxDQUFDO1FBQ2hCLE1BQU0sRUFDSixLQUFLLEdBQUcsRUFBRSxFQUNWLElBQUksRUFBRSxVQUFVLEdBQUcsRUFBRSxFQUNyQixJQUFJLEdBQUcsRUFBRSxFQUNULElBQUksRUFDSixRQUFRLEdBQUcsRUFBRSxFQUNiLFFBQVEsR0FDVCxHQUFHLEtBQUssQ0FBQztRQUVWLE1BQU0sVUFBVSxHQUFHO1lBQ2pCLEdBQUcsVUFBVTtZQUNiLEdBQUcsS0FBSztZQUNSLEdBQUcsUUFBUTtZQUNYLEdBQUcsSUFBSTtTQUNSLENBQUM7UUFFRix5QkFBeUI7UUFDekIsSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUU7WUFBRSxVQUFVLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztTQUFFO1FBRTdELGdFQUFnRTtRQUNoRSxNQUFNLFFBQVEsR0FBRyxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUM7WUFDbEMsQ0FBQyxDQUFDLENBQUMscUJBQXFCLENBQUM7WUFDekIsQ0FBQyxDQUFDLFNBQVMsQ0FBQztRQUVkLFVBQVUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUc7WUFDL0IsUUFBUTtZQUNSLE9BQU87WUFDUCxXQUFXLEVBQUUsY0FBYztZQUMzQixVQUFVO1lBQ1YsU0FBUztZQUNULElBQUk7WUFDSixRQUFRO1NBQ1QsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ2IsT0FBTyxVQUFVLENBQUM7QUFDcEIsQ0FBQyxDQUFDO0FBRUYsa0JBQWUsV0FBVyxDQUFDIn0= -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egg-swagger-decorator [npm-url] 2 | > using decorator to auto generate swagger json docs 3 | 4 | ## Installation 5 | 6 | 7 | ```bash 8 | npm install egg-swagger-decorator 9 | ``` 10 | 11 | ## Introduction 12 | 13 | ### egg Swagger Decorator 14 | 15 | using decorator to auto generate swagger json docs 16 | 17 | based on [Swagger OpenAPI Specification 2.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md) 18 | 19 | ## Example 20 | 21 | ``` 22 | // using commonds below to start and test the example server 23 | 24 | git clone https://github.com/Cody2333/egg-swagger-decorator.git 25 | 26 | cd egg-swagger-decorator 27 | 28 | npm install 29 | 30 | npm run dev 31 | 32 | finally open: 33 | http://localhost:7001/swagger-html 34 | 35 | ``` 36 | 37 | ### Requirements 38 | 39 | - egg 40 | - typescript > 2.8 41 | 42 | ### Introduction 43 | 44 | ``` 45 | // router.js 46 | import { Application } from 'egg'; 47 | import { wrapper } from '../lib'; 48 | export default (app: Application) => { 49 | wrapper(app, { 50 | // // [optional] default is /swagger-html 51 | // swaggerHtmlEndpoint: '/sw', 52 | // // [optional] default is /swagger-json 53 | // swaggerJsonEndpoint: '/sj', 54 | // // [optional] default is false. if true, will call makeSwaggerRouter(app) automatically 55 | // makeswaggerRouter: false, 56 | 57 | title: 'foo', 58 | version: 'v1.0.0', 59 | description: 'bar', 60 | 61 | }); 62 | makeSwaggerRouter(app); 63 | }; 64 | 65 | ``` 66 | 67 | #### using decorator to make api definition 68 | 69 | ``` 70 | // controller/test.ts 71 | 72 | import { Controller } from 'egg'; 73 | import { request, summary, query, path, body, tags } from 'egg-swagger-decorator'; 74 | 75 | const testTag = tags(['test']); 76 | 77 | const userSchema = { 78 | name: { type: 'string', required: true }, 79 | gender: { type: 'string', required: false, example: 'male' }, 80 | groups: { 81 | type: 'array', 82 | required: true, 83 | items: { type: 'string', example: 'group1' } 84 | } 85 | }; 86 | 87 | export default class Test extends Controller{ 88 | @request('get', '/users') 89 | @summary('get user list') 90 | @testTag 91 | @query({ 92 | type: { type: 'number', required: true, default: 1, description: 'type' } 93 | }) 94 | public async getUsers() { 95 | const { ctx } = this; 96 | const users = [{user1: {name: 'xxx'}}] 97 | ctx.body = { users }; 98 | } 99 | 100 | @request('get', '/users/{id}') 101 | @summary('get user info by id') 102 | @testTag 103 | @path({ 104 | id: { type: 'number', required: true, default: 1, description: 'id' } 105 | }) 106 | public async getUser() { 107 | const { id } = this.ctx.params; 108 | const user = {user1: {id, name: 'xxx'}} 109 | this.ctx.body = { user }; 110 | } 111 | 112 | @request('post', '/users') 113 | @testTag 114 | @body(userSchema) 115 | public async postUser() { 116 | const body = this.ctx.request.body; 117 | this.ctx.body = { result: body }; 118 | } 119 | 120 | public async temp(ctx) { 121 | ctx.body = { result: 'success' }; 122 | } 123 | } 124 | 125 | ``` 126 | 127 | #### avaliable annotations: 128 | 129 | - tags 130 | - query 131 | - path 132 | - body 133 | - formData 134 | - middlewares 135 | - summary 136 | - description 137 | - responses 138 | 139 | ``` 140 | 141 | request // @request('POST', '/users') 142 | 143 | tags // @tags(['example']) 144 | 145 | query // @query({limit: {type: 'number', required: true, default: 10, description: 'desc'}}) 146 | 147 | path // @path({limit: {type: 'number', required: true, default: 10, description: 'desc'}}) 148 | 149 | body // @body({groups: {type: 'array', required: true, items: { type: 'string', example: 'group1' }}}) 150 | 151 | formData // @formData({file: {type: 'file', required: true, description: 'file content'}}) 152 | 153 | middlewares 154 | // support koa middlewares. 155 | // eg. @middlewares([func1,func2]) 156 | 157 | summary // @summary('api summary') 158 | 159 | description // @description('api description') 160 | 161 | responses 162 | // @responses({ 200: { description: 'success'}, 400: { description: 'error'}}) 163 | // responses is optional 164 | ``` 165 | 166 | 167 | 168 | ##### runing the project and it will generate docs through swagger ui 169 | 170 | ![image.png](http://upload-images.jianshu.io/upload_images/2563527-4b6ed895183a0055.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 171 | ## License 172 | 173 | © MIT 174 | 175 | 176 | [npm-url]: https://npmjs.org/package/egg-swagger-decorator 177 | -------------------------------------------------------------------------------- /lib/wrapper.ts: -------------------------------------------------------------------------------- 1 | import { Application, Controller, Router } from 'egg'; 2 | import * as _ from 'lodash'; 3 | import {apiObjects} from './decorator'; 4 | import swaggerHTML from './swaggerHTML'; 5 | import swaggerJSON from './swaggerJSON'; 6 | import {WrapperOptions} from './swaggerJSON'; 7 | import {convertPath, getPath, loadSwaggerClassesToContext} from './utils'; 8 | import validate from './validate'; 9 | /** 10 | * allowed http methods 11 | */ 12 | const reqMethods = ['get', 'post', 'put', 'patch', 'delete']; 13 | 14 | interface Parameters { 15 | query?: {}; 16 | path?: {}; 17 | body?: {}; 18 | [param: string]: any; 19 | } 20 | 21 | const validator = (parameters: Parameters) => async(ctx, next) => { 22 | if (!parameters) { 23 | await next(); 24 | return; 25 | } 26 | 27 | if (parameters.query) { 28 | ctx.validatedQuery = validate(ctx.request.query, parameters.query); 29 | } 30 | if (parameters.path) { 31 | ctx.validatedParams = validate(ctx.params, parameters.path); 32 | } 33 | if (parameters.body) { 34 | ctx.validatedBody = validate(ctx.request.body, parameters.body); 35 | } 36 | await next(); 37 | }; 38 | 39 | const handleSwagger = (router : Router, options: WrapperOptions) => { 40 | const { 41 | swaggerJsonEndpoint = '/swagger-json', 42 | swaggerHtmlEndpoint = '/swagger-html', 43 | prefix = '' 44 | } = options; 45 | 46 | // setup swagger router 47 | router.get(swaggerJsonEndpoint, async(ctx) => { 48 | ctx.body = swaggerJSON(options, apiObjects); 49 | }); 50 | router.get(swaggerHtmlEndpoint, async(ctx) => { 51 | ctx.body = swaggerHTML(getPath(prefix, swaggerJsonEndpoint)); 52 | }); 53 | }; 54 | 55 | declare module 'egg' { 56 | interface Application { 57 | createAnonymousContext(req?: any): Context; 58 | swaggerControllerClasses: {}; 59 | } 60 | interface Context { 61 | validatedQuery?: {}; 62 | validatedParams?: {}; 63 | validatedBody?: {}; 64 | } 65 | } 66 | 67 | const handleMap = (app : Application, ControllerClass : typeof Controller) => { 68 | const anonymousContext = app.createAnonymousContext(); 69 | const router = app.router; 70 | const c : Controller = new ControllerClass(Object.assign(anonymousContext)); 71 | const methods : string[] = Object.getOwnPropertyNames(Object.getPrototypeOf(c)); 72 | // remove useless field in class object: constructor, length, name, prototype 73 | _.pull(methods, 'name', 'constructor', 'length', 'prototype', 'pathName', 'fullPath'); 74 | // map all method in methods 75 | methods 76 | // filter methods without @request decorator 77 | .filter((item) => { 78 | const {path, method} = c[item]; 79 | if (!path && !method) { 80 | return false; 81 | } 82 | return true; 83 | }) 84 | // add router 85 | .forEach((item) => { 86 | const {path, method} = c[item]; 87 | let { 88 | middlewares = [] 89 | } = c[item]; 90 | if (typeof middlewares === 'function') { 91 | middlewares = [middlewares]; 92 | } 93 | if (!Array.isArray(middlewares)) { 94 | throw new Error('middlewares params must be an array or function'); 95 | } 96 | middlewares.forEach((item) => { 97 | if (typeof item !== 'function') { 98 | throw new Error('item in middlewares must be a function'); 99 | } 100 | }); 101 | if (!reqMethods.includes(method)) { 102 | throw new Error(`illegal API: ${method} ${path} at [${item}]`); 103 | } 104 | const chain = [ 105 | `${convertPath(path)}`, 106 | validator(c[item].parameters), 107 | ...middlewares, 108 | async (ctx) => { 109 | const c = new ControllerClass(ctx); 110 | await c[item](); 111 | } 112 | ]; 113 | router[method](...chain); 114 | }); 115 | }; 116 | 117 | const handleMapDir = (app: Application) => { 118 | loadSwaggerClassesToContext(app); 119 | const classes = app.swaggerControllerClasses; 120 | Object.keys(classes).forEach((name) => {handleMap(app, classes[name]);}); 121 | }; 122 | 123 | const wrapper = (app : Application, options?: WrapperOptions) => { 124 | const opts: WrapperOptions = { 125 | title: 'API DOC', 126 | description: 'API DOC', 127 | version: 'v1.0.0', 128 | prefix: '', 129 | swaggerJsonEndpoint: '/swagger-json', 130 | swaggerHtmlEndpoint: '/swagger-html', 131 | makeSwaggerRouter: false, 132 | }; 133 | Object.assign(opts, options || {}); 134 | 135 | const {router} = app; 136 | if (makeSwaggerRouter) {handleMapDir(app); } 137 | handleSwagger(router, opts); 138 | }; 139 | const makeSwaggerRouter = (app: Application) => handleMapDir(app); 140 | export {wrapper, makeSwaggerRouter}; 141 | -------------------------------------------------------------------------------- /dist/validate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class InputError extends Error { 4 | /** 5 | * Constructor 6 | * @param {string} field the error field in request parameters. 7 | */ 8 | constructor(field) { 9 | super(`incorrect field: '${field}', please check again!`); 10 | this['field'] = field; 11 | this['status'] = 400; 12 | } 13 | } 14 | /** 15 | * validate the input values 16 | * @param {Object} input the input object, like request.query, request.body and so on. 17 | * @param {Object} expect the expect value, Ex: { name: { required: true, type: String }} 18 | */ 19 | function default_1(input, expect) { 20 | Object.keys(expect).forEach((key) => { 21 | if (expect[key] === undefined) { 22 | delete input[key]; 23 | return; 24 | } 25 | // if this key is required but not in input. 26 | if (expect[key].required && input[key] === undefined) { 27 | throw new InputError(key); 28 | } 29 | // if this key is in input, but the type is wrong 30 | // first check the number type 31 | if (input[key] !== undefined && expect[key].type === 'number') { 32 | if (typeof input[key] === 'number') { 33 | return; 34 | } 35 | if (isNaN(Number(input[key]))) { 36 | throw new InputError(key); 37 | } 38 | input[key] = Number(input[key]); 39 | return; 40 | } 41 | // second check the boolean type 42 | if (input[key] !== undefined && expect[key].type === 'boolean') { 43 | if (typeof input[key] === 'boolean') { 44 | return; 45 | } 46 | if (input[key] === 'true') { 47 | input[key] = true; 48 | return; 49 | } 50 | if (input[key] === 'false') { 51 | input[key] = false; 52 | return; 53 | } 54 | throw new InputError(key); 55 | } 56 | // third check the string type 57 | if (input[key] !== undefined && expect[key].type === 'string') { 58 | if (typeof input[key] !== 'string') { 59 | input[key] = String(input[key]); 60 | return; 61 | } 62 | if (Array.isArray(expect[key].enum) && expect[key].enum.length) { 63 | if (!expect[key].enum.includes(input[key])) 64 | throw new InputError(key); 65 | } 66 | } 67 | // forth check the object type 68 | if (input[key] !== undefined && expect[key].type === 'object') { 69 | if (typeof input[key] === 'object') { 70 | return; 71 | } 72 | throw new InputError(key); 73 | } 74 | // last check the array type 75 | if (input[key] !== undefined && expect[key].type === 'array') { 76 | if (input[key] instanceof Array) { 77 | return; 78 | } 79 | throw new InputError(key); 80 | } 81 | // if this key is not in input and need a default value 82 | if (input[key] === undefined && expect[key].default !== undefined) { 83 | input[key] = expect[key].default; 84 | } 85 | }); 86 | return input; 87 | } 88 | exports.default = default_1; 89 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9saWIvdmFsaWRhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxnQkFBaUIsU0FBUSxLQUFLO0lBQzVCOzs7T0FHRztJQUNILFlBQVksS0FBSztRQUNmLEtBQUssQ0FBQyxxQkFBcUIsS0FBSyx3QkFBd0IsQ0FBQyxDQUFDO1FBQzFELElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxLQUFLLENBQUM7UUFDdEIsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLEdBQUcsQ0FBQztJQUN2QixDQUFDO0NBQ0Y7QUFFRDs7OztHQUlHO0FBQ0gsbUJBQXlCLEtBQUssRUFBRSxNQUFNO0lBQ3BDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUU7UUFDbEMsSUFBSSxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssU0FBUyxFQUFFO1lBQzdCLE9BQU8sS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ2xCLE9BQU87U0FDUjtRQUNELDRDQUE0QztRQUM1QyxJQUFJLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxRQUFRLElBQUksS0FBSyxDQUFDLEdBQUcsQ0FBQyxLQUFLLFNBQVMsRUFBRTtZQUNwRCxNQUFNLElBQUksVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1NBQzNCO1FBQ0QsaURBQWlEO1FBQ2pELDhCQUE4QjtRQUM5QixJQUFJLEtBQUssQ0FBQyxHQUFHLENBQUMsS0FBSyxTQUFTLElBQUksTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksS0FBSyxRQUFRLEVBQUU7WUFDN0QsSUFBSSxPQUFPLEtBQUssQ0FBQyxHQUFHLENBQUMsS0FBSyxRQUFRLEVBQUU7Z0JBQ2xDLE9BQU87YUFDUjtZQUNELElBQUksS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFO2dCQUM3QixNQUFNLElBQUksVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDO2FBQzNCO1lBQ0QsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztZQUNoQyxPQUFPO1NBQ1I7UUFDRCxnQ0FBZ0M7UUFDaEMsSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLEtBQUssU0FBUyxJQUFJLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEtBQUssU0FBUyxFQUFFO1lBQzlELElBQUksT0FBTyxLQUFLLENBQUMsR0FBRyxDQUFDLEtBQUssU0FBUyxFQUFFO2dCQUNuQyxPQUFPO2FBQ1I7WUFDRCxJQUFJLEtBQUssQ0FBQyxHQUFHLENBQUMsS0FBSyxNQUFNLEVBQUU7Z0JBQ3pCLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUM7Z0JBQ2xCLE9BQU87YUFDUjtZQUNELElBQUksS0FBSyxDQUFDLEdBQUcsQ0FBQyxLQUFLLE9BQU8sRUFBRTtnQkFDMUIsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFHLEtBQUssQ0FBQztnQkFDbkIsT0FBTzthQUNSO1lBQ0QsTUFBTSxJQUFJLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztTQUMzQjtRQUNELDhCQUE4QjtRQUM5QixJQUFJLEtBQUssQ0FBQyxHQUFHLENBQUMsS0FBSyxTQUFTLElBQUksTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksS0FBSyxRQUFRLEVBQUU7WUFDN0QsSUFBSSxPQUFPLEtBQUssQ0FBQyxHQUFHLENBQUMsS0FBSyxRQUFRLEVBQUU7Z0JBQ2xDLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7Z0JBQ2hDLE9BQU87YUFDUjtZQUNELElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUU7Z0JBQzlELElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7b0JBQUUsTUFBTSxJQUFJLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQzthQUN2RTtTQUNGO1FBQ0QsOEJBQThCO1FBQzlCLElBQUksS0FBSyxDQUFDLEdBQUcsQ0FBQyxLQUFLLFNBQVMsSUFBSSxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxLQUFLLFFBQVEsRUFBRTtZQUM3RCxJQUFJLE9BQU8sS0FBSyxDQUFDLEdBQUcsQ0FBQyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsT0FBTzthQUNSO1lBQ0QsTUFBTSxJQUFJLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztTQUMzQjtRQUNELDRCQUE0QjtRQUM1QixJQUFJLEtBQUssQ0FBQyxHQUFHLENBQUMsS0FBSyxTQUFTLElBQUksTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksS0FBSyxPQUFPLEVBQUU7WUFDNUQsSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLFlBQVksS0FBSyxFQUFFO2dCQUMvQixPQUFPO2FBQ1I7WUFDRCxNQUFNLElBQUksVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1NBQzNCO1FBQ0QsdURBQXVEO1FBQ3ZELElBQUksS0FBSyxDQUFDLEdBQUcsQ0FBQyxLQUFLLFNBQVMsSUFBSSxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsT0FBTyxLQUFLLFNBQVMsRUFBRTtZQUNqRSxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQztTQUNsQztJQUNILENBQUMsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxLQUFLLENBQUM7QUFDZixDQUFDO0FBbkVELDRCQW1FQyJ9 -------------------------------------------------------------------------------- /dist/decorator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const _ = require("lodash"); 4 | /** 5 | * used for building swagger docs object 6 | */ 7 | const apiObjects = {}; 8 | exports.apiObjects = apiObjects; 9 | const _addToApiObject = (target, name, apiObj, content) => { 10 | const key = `${target.constructor.name}-${name}`; 11 | if (!apiObj[key]) { 12 | apiObj[key] = {}; 13 | } 14 | Object.assign(apiObj[key], content); 15 | }; 16 | const _desc = (type, text) => (target, name, descriptor) => { 17 | descriptor.value[type] = text; 18 | _addToApiObject(target, name, apiObjects, { [type]: text }); 19 | return descriptor; 20 | }; 21 | const _params = (type, parameters) => (target, name, descriptor) => { 22 | if (!descriptor.value.parameters) { 23 | descriptor.value.parameters = {}; 24 | } 25 | descriptor.value.parameters[type] = parameters; 26 | // additional wrapper for body 27 | let swaggerParameters = parameters; 28 | if (type === 'body') { 29 | swaggerParameters = [{ 30 | name: 'data', 31 | description: 'request body', 32 | schema: { 33 | type: 'object', 34 | properties: parameters, 35 | }, 36 | }]; 37 | } 38 | else { 39 | swaggerParameters = Object.keys(swaggerParameters).map((key) => { 40 | return Object.assign({ name: key }, swaggerParameters[key]); 41 | }); 42 | } 43 | swaggerParameters.forEach((item) => { 44 | item.in = type; 45 | }); 46 | _addToApiObject(target, name, apiObjects, { [type]: swaggerParameters }); 47 | return descriptor; 48 | }; 49 | const request = (method, path) => (target, name, descriptor) => { 50 | method = _.toLower(method); 51 | descriptor.value.method = method; 52 | descriptor.value.path = path; 53 | _addToApiObject(target, name, apiObjects, { 54 | request: { method, path }, 55 | security: [{ ApiKeyAuth: [] }], 56 | }); 57 | return descriptor; 58 | }; 59 | exports.request = request; 60 | const middlewares = (val) => (target, name, descriptor) => { 61 | if (!target || !name) { 62 | throw new Error(); 63 | } 64 | descriptor.value.middlewares = val; 65 | return descriptor; 66 | }; 67 | exports.middlewares = middlewares; 68 | const responses = (res = { 200: { description: 'success' } }) => (target, name, descriptor) => { 69 | descriptor.value.responses = res; 70 | _addToApiObject(target, name, apiObjects, { responses: res }); 71 | return descriptor; 72 | }; 73 | exports.responses = responses; 74 | const desc = _.curry(_desc); 75 | exports.desc = desc; 76 | // description and summary 77 | const description = desc('description'); 78 | exports.description = description; 79 | const summary = desc('summary'); 80 | exports.summary = summary; 81 | const tags = desc('tags'); 82 | exports.tags = tags; 83 | const params = _.curry(_params); 84 | exports.params = params; 85 | // below are [parameters] 86 | // query params 87 | const query = params('query'); 88 | exports.query = query; 89 | // path params 90 | const path = params('path'); 91 | exports.path = path; 92 | // body params 93 | const body = params('body'); 94 | exports.body = body; 95 | // formData params 96 | const formData = params('formData'); 97 | exports.formData = formData; 98 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVjb3JhdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vbGliL2RlY29yYXRvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLDRCQUE0QjtBQUc1Qjs7R0FFRztBQUNILE1BQU0sVUFBVSxHQUFHLEVBQUUsQ0FBQztBQStGcEIsZ0NBQVU7QUE3RlosTUFBTSxlQUFlLEdBQUcsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsRUFBRTtJQUN4RCxNQUFNLEdBQUcsR0FBRyxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsSUFBSSxJQUFJLElBQUksRUFBRSxDQUFDO0lBQ2pELElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUU7UUFDaEIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsQ0FBQztLQUNsQjtJQUNELE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ3RDLENBQUMsQ0FBQztBQUVGLE1BQU0sS0FBSyxHQUFHLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRSxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxFQUFFO0lBQ3pELFVBQVUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDO0lBQzlCLGVBQWUsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUM1RCxPQUFPLFVBQVUsQ0FBQztBQUNwQixDQUFDLENBQUM7QUFFRixNQUFNLE9BQU8sR0FBRyxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUUsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsRUFBRTtJQUNqRSxJQUFJLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxVQUFVLEVB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 -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import {mock} from 'egg-mock/bootstrap'; 3 | import * as assert from 'assert'; 4 | import {getPath, convertPath, loadSwaggerClassesToContext} from '../lib/utils'; 5 | import validate from '../lib/validate'; 6 | import {Context} from 'egg'; 7 | 8 | describe('test/app/lib.test.ts', () => { 9 | 10 | describe('Api Test:', () => { 11 | 12 | let app; 13 | before(() => { 14 | app = mock.app({baseDir: 'apps/swagger-decorator-test'}); 15 | return app.ready(); 16 | }); 17 | 18 | after(() => app.close()); 19 | afterEach(mock.restore); 20 | 21 | describe('Load swagger classes to app', () => { 22 | it('should inject app/controller classes to app.swaggerControllerClasses', async () => { 23 | loadSwaggerClassesToContext(app); 24 | assert(typeof app.swaggerControllerClasses === 'object') 25 | }) 26 | }) 27 | 28 | describe('Swagger Doc Api:', () => { 29 | it('GET /api/swagger-html should return success for swagger ui page', async() => { 30 | await app 31 | .httpRequest() 32 | .get('/swagger-html') 33 | .expect(200) 34 | }); 35 | it('GET /api/swagger-json should return success for swagger json data', async() => { 36 | await app 37 | .httpRequest() 38 | .get('/swagger-json') 39 | .expect(200) 40 | }); 41 | }); 42 | 43 | describe('Controller Test:', () => { 44 | 45 | it('should GET /', async() => { 46 | const result = await app 47 | .httpRequest() 48 | .get('/') 49 | .expect(200); 50 | assert(result.text === 'hi, egg'); 51 | }); 52 | 53 | it('should GET /users', async() => { 54 | await app 55 | .httpRequest() 56 | .get('/users') 57 | .expect(200); 58 | }); 59 | it('should GET /users/{id}', async() => { 60 | await app 61 | .httpRequest() 62 | .get('/users/10') 63 | .expect(200) 64 | .expect({ 65 | user: { 66 | id: 10 67 | } 68 | }); 69 | }); 70 | it('should POST /users', async() => { 71 | app.mockCsrf(); 72 | await app 73 | .httpRequest() 74 | .post('/users') 75 | .type('json') 76 | .send({name: 'xxx'}) 77 | .expect(200) 78 | .expect({name: 'xxx'}) 79 | }); 80 | }); 81 | 82 | describe('Service Test:', () => { 83 | let ctx : Context; 84 | before(async() => { 85 | ctx = app.mockContext(); 86 | }); 87 | it('sayHi', async() => { 88 | const result = await ctx 89 | .service 90 | .test 91 | .sayHi('egg'); 92 | assert(result === 'hi, egg'); 93 | }); 94 | }); 95 | }); 96 | 97 | describe('Function Test:', () => { 98 | describe('ConvertPath:', () => { 99 | it('should convert /api/{p1}/user/{p2} -> /api/:p1/user/:p2', () => { 100 | const r = convertPath('/api/{p1}/user/{p2}'); 101 | assert(r === '/api/:p1/user/:p2'); 102 | }); 103 | }); 104 | 105 | describe('GetPath:', () => { 106 | it('should convert /api + /user -> /api/user', () => { 107 | const r = getPath('/api', '/user'); 108 | assert(r === '/api/user'); 109 | }); 110 | }); 111 | describe('Validate:', () => { 112 | it('should return validated input when meets expects requirement', () => { 113 | const input = { 114 | foo: '1', 115 | bar: 'fwq', 116 | fpk: false, 117 | nax: 12, 118 | qoa: [ 119 | 1, 2 120 | ], 121 | baz: { 122 | b: 'f' 123 | }, 124 | addon: 'ttt', 125 | boo: 'true', 126 | coo: 'false', 127 | sst: { 128 | what: 'a' 129 | } 130 | } 131 | const expect = { 132 | nax: {type: 'number'}, 133 | foo: { 134 | type: 'number', 135 | required: true 136 | }, 137 | bar: { 138 | type: 'string', 139 | required: false 140 | }, 141 | baz: { 142 | type: 'object', 143 | required: true 144 | }, 145 | qoa: { 146 | type: 'array', 147 | required: true 148 | }, 149 | fpk: { 150 | type: 'boolean', 151 | required: true 152 | }, 153 | boo: { 154 | type: 'boolean', 155 | }, 156 | coo: { 157 | type: 'boolean', 158 | }, 159 | default: { 160 | type: 'string', 161 | required: false, 162 | default: 'ddd' 163 | }, 164 | sst: { 165 | type: 'string', 166 | }, 167 | addon: undefined 168 | } 169 | const validatedInput = validate(input, expect); 170 | assert(validatedInput.foo === 1) 171 | assert(validatedInput.default === 'ddd') 172 | assert(!validatedInput.addon) 173 | assert(validatedInput.boo === true) 174 | assert(validatedInput.coo === false) 175 | assert(typeof validatedInput.sst === 'string') 176 | }); 177 | it('should throw error when no required input', () => { 178 | const input = {} 179 | const expect = { 180 | foo: { 181 | type: 'string', 182 | required: true 183 | } 184 | } 185 | try { 186 | validate(input, expect); 187 | throw new Error(); 188 | } catch (err) { 189 | assert(err.message === "incorrect field: 'foo', please check again!") 190 | } 191 | }); 192 | it('should throw error when not a number while type=number', () => { 193 | const input = {foo: 'r'} 194 | const expect = {foo: {type: 'number'}} 195 | try { 196 | validate(input, expect); 197 | throw new Error(); 198 | } catch (err) { 199 | assert(err.message === "incorrect field: 'foo', please check again!") 200 | } 201 | }); 202 | it('should throw error when not a boolean while type=boolean', () => { 203 | const input = {foo: 'r'} 204 | const expect = {foo: {type: 'boolean'}} 205 | try { 206 | validate(input, expect); 207 | throw new Error(); 208 | } catch (err) { 209 | assert(err.message === "incorrect field: 'foo', please check again!") 210 | } 211 | }); 212 | it('should throw error when not a number while type=object', () => { 213 | const input = {foo: 'r'} 214 | const expect = {foo: {type: 'object'}} 215 | try { 216 | validate(input, expect); 217 | throw new Error(); 218 | } catch (err) { 219 | assert(err.message === "incorrect field: 'foo', please check again!") 220 | } 221 | }); 222 | it('should throw error when not a number while type=array', () => { 223 | const input = {foo: 'r'} 224 | const expect = {foo: {type: 'array'}} 225 | try { 226 | validate(input, expect); 227 | throw new Error(); 228 | } catch (err) { 229 | assert(err.message === "incorrect field: 'foo', please check again!") 230 | } 231 | }); 232 | 233 | it('should throw error when enum not a array', () => { 234 | const input = { foo: '1' }; 235 | const expect = { 236 | foo: { type: 'string', enum: 'enum is a string' } 237 | }; 238 | try { 239 | validate(input, expect); 240 | } catch (err) { 241 | assert(err.message === "incorrect field: 'foo', please check again!") 242 | } 243 | }) 244 | 245 | it('should throw error when enum is an empty array', () => { 246 | const input = { foo: '1' }; 247 | const expect = { 248 | foo: { type: 'string', enum: [] } 249 | }; 250 | try { 251 | validate(input, expect); 252 | } catch (err) { 253 | assert(err.message === "incorrect field: 'foo', please check again!") 254 | } 255 | }) 256 | 257 | 258 | it('should throw error when enum doesnt includes input an empty array', () => { 259 | const input = { foo: '1' }; 260 | const expect = { 261 | foo: { type: 'string', enum: ['2', '3'] } 262 | }; 263 | try { 264 | validate(input, expect); 265 | } catch (err) { 266 | assert(err.message === "incorrect field: 'foo', please check again!") 267 | } 268 | }) 269 | }); 270 | }); 271 | }); 272 | -------------------------------------------------------------------------------- /dist/wrapper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const _ = require("lodash"); 4 | const decorator_1 = require("./decorator"); 5 | const swaggerHTML_1 = require("./swaggerHTML"); 6 | const swaggerJSON_1 = require("./swaggerJSON"); 7 | const utils_1 = require("./utils"); 8 | const validate_1 = require("./validate"); 9 | /** 10 | * allowed http methods 11 | */ 12 | const reqMethods = ['get', 'post', 'put', 'patch', 'delete']; 13 | const validator = (parameters) => async (ctx, next) => { 14 | if (!parameters) { 15 | await next(); 16 | return; 17 | } 18 | if (parameters.query) { 19 | ctx.validatedQuery = validate_1.default(ctx.request.query, parameters.query); 20 | } 21 | if (parameters.path) { 22 | ctx.validatedParams = validate_1.default(ctx.params, parameters.path); 23 | } 24 | if (parameters.body) { 25 | ctx.validatedBody = validate_1.default(ctx.request.body, parameters.body); 26 | } 27 | await next(); 28 | }; 29 | const handleSwagger = (router, options) => { 30 | const { swaggerJsonEndpoint = '/swagger-json', swaggerHtmlEndpoint = '/swagger-html', prefix = '' } = options; 31 | // setup swagger router 32 | router.get(swaggerJsonEndpoint, async (ctx) => { 33 | ctx.body = swaggerJSON_1.default(options, decorator_1.apiObjects); 34 | }); 35 | router.get(swaggerHtmlEndpoint, async (ctx) => { 36 | ctx.body = swaggerHTML_1.default(utils_1.getPath(prefix, swaggerJsonEndpoint)); 37 | }); 38 | }; 39 | const handleMap = (app, ControllerClass) => { 40 | const anonymousContext = app.createAnonymousContext(); 41 | const router = app.router; 42 | const c = new ControllerClass(Object.assign(anonymousContext)); 43 | const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(c)); 44 | // remove useless field in class object: constructor, length, name, prototype 45 | _.pull(methods, 'name', 'constructor', 'length', 'prototype', 'pathName', 'fullPath'); 46 | // map all method in methods 47 | methods 48 | // filter methods without @request decorator 49 | .filter((item) => { 50 | const { path, method } = c[item]; 51 | if (!path && !method) { 52 | return false; 53 | } 54 | return true; 55 | }) 56 | // add router 57 | .forEach((item) => { 58 | const { path, method } = c[item]; 59 | let { middlewares = [] } = c[item]; 60 | if (typeof middlewares === 'function') { 61 | middlewares = [middlewares]; 62 | } 63 | if (!Array.isArray(middlewares)) { 64 | throw new Error('middlewares params must be an array or function'); 65 | } 66 | middlewares.forEach((item) => { 67 | if (typeof item !== 'function') { 68 | throw new Error('item in middlewares must be a function'); 69 | } 70 | }); 71 | if (!reqMethods.includes(method)) { 72 | throw new Error(`illegal API: ${method} ${path} at [${item}]`); 73 | } 74 | const chain = [ 75 | `${utils_1.convertPath(path)}`, 76 | validator(c[item].parameters), 77 | ...middlewares, 78 | async (ctx) => { 79 | const c = new ControllerClass(ctx); 80 | await c[item](); 81 | } 82 | ]; 83 | router[method](...chain); 84 | }); 85 | }; 86 | const handleMapDir = (app) => { 87 | utils_1.loadSwaggerClassesToContext(app); 88 | const classes = app.swaggerControllerClasses; 89 | Object.keys(classes).forEach((name) => { handleMap(app, classes[name]); }); 90 | }; 91 | const wrapper = (app, options) => { 92 | const opts = { 93 | title: 'API DOC', 94 | description: 'API DOC', 95 | version: 'v1.0.0', 96 | prefix: '', 97 | swaggerJsonEndpoint: '/swagger-json', 98 | swaggerHtmlEndpoint: '/swagger-html', 99 | makeSwaggerRouter: false, 100 | }; 101 | Object.assign(opts, options || {}); 102 | const { router } = app; 103 | if (makeSwaggerRouter) { 104 | handleMapDir(app); 105 | } 106 | handleSwagger(router, opts); 107 | }; 108 | exports.wrapper = wrapper; 109 | const makeSwaggerRouter = (app) => handleMapDir(app); 110 | exports.makeSwaggerRouter = makeSwaggerRouter; 111 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid3JhcHBlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL2xpYi93cmFwcGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQ0EsNEJBQTRCO0FBQzVCLDJDQUF1QztBQUN2QywrQ0FBd0M7QUFDeEMsK0NBQXdDO0FBRXhDLG1DQUEwRTtBQUMxRSx5Q0FBa0M7QUFDbEM7O0dBRUc7QUFDSCxNQUFNLFVBQVUsR0FBRyxDQUFDLEtBQUssRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxRQUFRLENBQUMsQ0FBQztBQVM3RCxNQUFNLFNBQVMsR0FBRyxDQUFDLFVBQXNCLEVBQUUsRUFBRSxDQUFDLEtBQUssRUFBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLEVBQUU7SUFDL0QsSUFBSSxDQUFDLFVBQVUsRUFBRTtRQUNmLE1BQU0sSUFBSSxFQUFFLENBQUM7UUFDYixPQUFPO0tBQ1I7SUFFRCxJQUFJLFVBQVUsQ0FBQyxLQUFLLEVBQUU7UUFDcEIsR0FBRyxDQUFDLGNBQWMsR0FBRyxrQkFBUSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQztLQUNwRTtJQUNELElBQUksVUFBVSxDQUFDLElBQUksRUFBRTtRQUNuQixHQUFHLENBQUMsZUFBZSxHQUFHLGtCQUFRLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUMsSUFBSSxDQUFDLENBQUM7S0FDN0Q7SUFDRCxJQUFJLFVBQVUsQ0FBQyxJQUFJLEVBQUU7UUFDbkIsR0FBRyxDQUFDLGFBQWEsR0FBRyxrQkFBUSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQztLQUNqRTtJQUNELE1BQU0sSUFBSSxFQUFFLENBQUM7QUFDZixDQUFDLENBQUM7QUFFRixNQUFNLGFBQWEsR0FBRyxDQUFDLE1BQWUsRUFBRSxPQUF1QixFQUFFLEVBQUU7SUFDakUsTUFBTSxF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 --------------------------------------------------------------------------------