From b684a1f4fb70090f8a38127e400d59e96ea6e165 Mon Sep 17 00:00:00 2001 From: york yao Date: Sun, 25 Nov 2018 16:45:24 +0800 Subject: [PATCH] feat: add part of swagger doc generation --- README.md | 3 +- clean-scripts.config.js | 2 +- demo/cases.gql | 7 ++++ demo/cases.ml | 7 ++++ demo/cases.mongoose.ts | 19 +++++++++ demo/cases.proto | 7 ++++ demo/cases.re | 8 ++++ demo/cases.rs | 8 ++++ demo/cases.swagger.json | 57 +++++++++++++++++++++++++++ demo/cases.ts | 13 +++++++ demo/debug.json | 74 ++++++++++++++++++++++++++++++++++++ demo/root-type.ts | 13 +++++++ online/index.less | 3 +- online/index.template.html | 1 + online/index.ts | 4 ++ online/variables.ts | 15 +++++++- src/core.ts | 5 +++ src/index.ts | 8 ++++ src/swagger-doc-generator.ts | 61 +++++++++++++++++++++++++++++ 19 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 demo/cases.swagger.json create mode 100644 src/swagger-doc-generator.ts diff --git a/README.md b/README.md index 47b32e5..baaa628 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Downloads](https://img.shields.io/npm/dm/types-as-schema.svg)](https://www.npmjs.com/package/types-as-schema) [![type-coverage](https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fplantain-00%2Ftypes-as-schema%2Fmaster%2Fpackage.json)](https://github.com/plantain-00/types-as-schema) -Genetate json scheme, protobuf file, graphQL/mongoose(alpha) schema and reasonml(alpha)/ocaml(alpha)/rust(alpha) types from typescript types. +Genetate json scheme, protobuf file, graphQL/mongoose(alpha) schema, reasonml(alpha)/ocaml(alpha)/rust(alpha) types and swagger(alhpa) doc from typescript types. ## supported types features @@ -48,6 +48,7 @@ parameters | description `--ocaml` | generated ocaml types file `--rust` | generated rust types file `--mongoose` | generated mongoose schema file +`--swagger` | generated swagger json file `--debug` | generated file with debug information in it `--watch` or `-w` | watch mode diff --git a/clean-scripts.config.js b/clean-scripts.config.js index fcafcd0..a735bd0 100644 --- a/clean-scripts.config.js +++ b/clean-scripts.config.js @@ -31,7 +31,7 @@ module.exports = { }, revStaticCommand, { - default: 'node ./dist/index.js demo/cases.ts demo/case2.ts --json demo/ --debug demo/debug.json --protobuf demo/cases.proto --graphql-root-type demo/root-type.ts --graphql demo/cases.gql --reason demo/cases.re --ocaml demo/cases.ml --rust demo/cases.rs --mongoose demo/cases.mongoose.ts', + default: 'node ./dist/index.js demo/cases.ts demo/case2.ts --json demo/ --debug demo/debug.json --protobuf demo/cases.proto --graphql-root-type demo/root-type.ts --graphql demo/cases.gql --reason demo/cases.re --ocaml demo/cases.ml --rust demo/cases.rs --mongoose demo/cases.mongoose.ts --swagger demo/cases.swagger.json', logTool: 'node ./dist/index.js demo/log-tool/types.ts --json demo/log-tool/ --debug demo/log-tool/debug.json --protobuf demo/log-tool/protocol.proto --graphql demo/log-tool/protocol.gql', matchCalculator: 'node ./dist/index.js demo/match-calculator/types.ts --json demo/match-calculator/ --debug demo/match-calculator/debug.json', baogame: 'node ./dist/index.js demo/baogame/common.ts --protobuf demo/baogame/protocol.proto --graphql demo/baogame/protocol.gql --debug demo/baogame/debug.json' diff --git a/demo/cases.gql b/demo/cases.gql index 5dbd910..f88e175 100644 --- a/demo/cases.gql +++ b/demo/cases.gql @@ -276,6 +276,13 @@ type Result3 { result3: String! } +type Pet { + id: Float + name: String! + photoUrls: [String]! + status: String! +} + type OuterType { outerType: Float! } diff --git a/demo/cases.ml b/demo/cases.ml index 0bb62c4..5449efb 100644 --- a/demo/cases.ml +++ b/demo/cases.ml @@ -259,6 +259,13 @@ type result3 = { result3: string; } +type pet = { + id: float option; + name: string; + photoUrls: string list; + status: string; +} + type outerType = { outerType: float; } diff --git a/demo/cases.mongoose.ts b/demo/cases.mongoose.ts index 40a32a8..3413817 100644 --- a/demo/cases.mongoose.ts +++ b/demo/cases.mongoose.ts @@ -725,6 +725,25 @@ export const result3Schema = { }, } +export const petSchema = { + id: { + type: Schema.Types.Number, + required: false + }, + name: { + type: Schema.Types.String, + required: true + }, + photoUrls: { + type: Schema.Types.Mixed, + required: true + }, + status: { + type: Schema.Types.Mixed, + required: true + }, +} + export const outerTypeSchema = { outerType: { type: Schema.Types.Number, diff --git a/demo/cases.proto b/demo/cases.proto index 71bd6e1..14fda33 100644 --- a/demo/cases.proto +++ b/demo/cases.proto @@ -266,6 +266,13 @@ message Result3 { string result3 = 1; } +message Pet { + double id = 1; + string name = 2; + repeated string photoUrls = 3; + string status = 4; +} + message OuterType { double outerType = 1; } diff --git a/demo/cases.re b/demo/cases.re index 3114171..9ca496a 100644 --- a/demo/cases.re +++ b/demo/cases.re @@ -297,6 +297,14 @@ type result3 = { result3: string, }; +type pet = { + . + id: option(float), + name: string, + photoUrls: list(string), + status: string, +}; + type outerType = { . outerType: float, diff --git a/demo/cases.rs b/demo/cases.rs index 48ccf8c..2e9fbe7 100644 --- a/demo/cases.rs +++ b/demo/cases.rs @@ -351,6 +351,14 @@ pub struct Result3 { #[serde(rename = "result3")] pub result_3: String, } +#[derive(Serialize, Deserialize, Debug)] +pub struct Pet { + pub id: Option, + pub name: String, + #[serde(rename = "photoUrls")] pub photo_urls: Vec, + pub status: String, +} + #[derive(Serialize, Deserialize, Debug)] pub struct OuterType { #[serde(rename = "outerType")] pub outer_type: f32, diff --git a/demo/cases.swagger.json b/demo/cases.swagger.json new file mode 100644 index 0000000..af949ad --- /dev/null +++ b/demo/cases.swagger.json @@ -0,0 +1,57 @@ +{ + "swagger": "2.0", + "paths": { + "/pet/{petId}": { + "get": { + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "schema": { + "$ref": "#/definitions/Pet" + } + } + } + } + } + }, + "definitions": { + "Pet": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "photoUrls": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "required": [ + "name", + "photoUrls", + "status" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/demo/cases.ts b/demo/cases.ts index e9bfbe2..eac5337 100644 --- a/demo/cases.ts +++ b/demo/cases.ts @@ -398,3 +398,16 @@ type Result2 = Result3 interface Result3 { result3: string } + +interface Pet { + id?: number + name: string + photoUrls: string[] + status: 'available' | 'pending' | 'sold' +} + +/** + * @method get + * @path "/pet/{id}" + */ +export declare function getPetById(petId: number): Promise diff --git a/demo/debug.json b/demo/debug.json index 2fd4437..3cfc3b1 100644 --- a/demo/debug.json +++ b/demo/debug.json @@ -3073,6 +3073,80 @@ "character": 0 } }, + { + "kind": "object", + "name": "Pet", + "members": [ + { + "name": "id", + "type": { + "kind": "number", + "type": "number", + "position": { + "file": "demo/cases.ts", + "line": 402, + "character": 7 + } + }, + "optional": true + }, + { + "name": "name", + "type": { + "kind": "string", + "position": { + "file": "demo/cases.ts", + "line": 403, + "character": 8 + } + } + }, + { + "name": "photoUrls", + "type": { + "kind": "array", + "type": { + "kind": "string", + "position": { + "file": "demo/cases.ts", + "line": 404, + "character": 13 + } + }, + "position": { + "file": "demo/cases.ts", + "line": 404, + "character": 13 + } + } + }, + { + "name": "status", + "type": { + "kind": "enum", + "type": "string", + "name": "string", + "enums": [ + "available", + "pending", + "sold" + ], + "position": { + "file": "demo/cases.ts", + "line": 405, + "character": 10 + } + } + } + ], + "minProperties": 3, + "maxProperties": 4, + "position": { + "file": "demo/cases.ts", + "line": 401, + "character": 0 + } + }, { "kind": "object", "name": "OuterType", diff --git a/demo/root-type.ts b/demo/root-type.ts index b65bdf9..63ea6f3 100644 --- a/demo/root-type.ts +++ b/demo/root-type.ts @@ -298,6 +298,13 @@ export interface Result3 { result3: string } +export interface Pet { + id?: number + name: string + photoUrls: Array + status: string +} + export interface OuterType { outerType: number } @@ -532,6 +539,12 @@ export interface ApolloResolvers { Result3?: { result3?(parent: any, input: {}, context: TContext, info: GraphQLResolveInfo): any, }, + Pet?: { + id?(parent: any, input: {}, context: TContext, info: GraphQLResolveInfo): any, + name?(parent: any, input: {}, context: TContext, info: GraphQLResolveInfo): any, + photoUrls?(parent: any, input: {}, context: TContext, info: GraphQLResolveInfo): any, + status?(parent: any, input: {}, context: TContext, info: GraphQLResolveInfo): any, + }, OuterType?: { outerType?(parent: any, input: {}, context: TContext, info: GraphQLResolveInfo): any, }, diff --git a/online/index.less b/online/index.less index 0ac1875..22044ea 100644 --- a/online/index.less +++ b/online/index.less @@ -68,7 +68,8 @@ .ocaml-types, .rust-types, .mongoose-schema, - .graphql-root-type { + .graphql-root-type, + .swagger-doc { display: block; padding: 9.5px; margin: 3px 0 10px 3px; diff --git a/online/index.template.html b/online/index.template.html index 58c3a4c..9f2b7a0 100644 --- a/online/index.template.html +++ b/online/index.template.html @@ -15,5 +15,6 @@
{{rustTypes}}
{{mongooseSchema}}
{{graphqlRootType}}
+
{{swaggerDoc}}
diff --git a/online/index.ts b/online/index.ts index b30e68f..b2d0fa0 100644 --- a/online/index.ts +++ b/online/index.ts @@ -21,6 +21,7 @@ export class App extends Vue { rustTypes = '' mongooseSchema = '' graphqlRootType = '' + swaggerDoc = '' private innerSource = localStorage.getItem(localStorageKey) || demoCasesTs private jsonSchemas: { entry: string; content: string }[] = [] @@ -75,6 +76,9 @@ export class App extends Vue { this.graphqlRootType = generator.generateGraphqlRootType('.') this.options.push('graphql root type') + + this.swaggerDoc = generator.generateSwaggerDoc() + this.options.push('swagger doc') } } } diff --git a/online/variables.ts b/online/variables.ts index 2fc3ab4..e648ea6 100644 --- a/online/variables.ts +++ b/online/variables.ts @@ -405,9 +405,22 @@ type Result2 = Result3 interface Result3 { result3: string } + +interface Pet { + id?: number + name: string + photoUrls: string[] + status: 'available' | 'pending' | 'sold' +} + +/** + * @method get + * @path "/pet/{id}" + */ +export declare function getPetById(petId: number): Promise ` // @ts-ignore -export function indexTemplateHtml(this: App) {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"app"},[_c('textarea',{directives:[{name:"model",rawName:"v-model",value:(_vm.source),expression:"source"}],staticClass:"source",domProps:{"value":(_vm.source)},on:{"input":function($event){if($event.target.composing){ return; }_vm.source=$event.target.value}}}),_vm._v(" "),_c('div',{staticClass:"result"},[_c('button',{on:{"click":function($event){_vm.generate()}}},[_vm._v("generate")]),_vm._v(" "),_c('div',{staticClass:"options"},[_c('select',{directives:[{name:"model",rawName:"v-model",value:(_vm.selectedOption),expression:"selectedOption"}],on:{"change":function($event){var $$selectedVal = Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = "_value" in o ? o._value : o.value;return val}); _vm.selectedOption=$event.target.multiple ? $$selectedVal : $$selectedVal[0]}}},_vm._l((_vm.options),function(option){return _c('option',{key:option,domProps:{"value":option}},[_vm._v(_vm._s(option))])}))]),_vm._v(" "),(_vm.selectedOption === 'protobuf')?_c('pre',{staticClass:"protobuf"},[_vm._v(_vm._s(_vm.protobuf))]):_vm._e(),_vm._v(" "),(_vm.jsonSchema)?_c('pre',{staticClass:"json-schema"},[_vm._v(_vm._s(_vm.jsonSchema))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'graphql schema')?_c('pre',{staticClass:"graphql-schema"},[_vm._v(_vm._s(_vm.graphqlSchema))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'reason types')?_c('pre',{staticClass:"reason-types"},[_vm._v(_vm._s(_vm.reasonTypes))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'ocaml types')?_c('pre',{staticClass:"ocaml-types"},[_vm._v(_vm._s(_vm.ocamlTypes))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'rust types')?_c('pre',{staticClass:"rust-types"},[_vm._v(_vm._s(_vm.rustTypes))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'mongoose schema')?_c('pre',{staticClass:"mongoose-schema"},[_vm._v(_vm._s(_vm.mongooseSchema))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'graphql root type')?_c('pre',{staticClass:"graphql-root-type"},[_vm._v(_vm._s(_vm.graphqlRootType))]):_vm._e()])])} +export function indexTemplateHtml(this: App) {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"app"},[_c('textarea',{directives:[{name:"model",rawName:"v-model",value:(_vm.source),expression:"source"}],staticClass:"source",domProps:{"value":(_vm.source)},on:{"input":function($event){if($event.target.composing){ return; }_vm.source=$event.target.value}}}),_vm._v(" "),_c('div',{staticClass:"result"},[_c('button',{on:{"click":function($event){_vm.generate()}}},[_vm._v("generate")]),_vm._v(" "),_c('div',{staticClass:"options"},[_c('select',{directives:[{name:"model",rawName:"v-model",value:(_vm.selectedOption),expression:"selectedOption"}],on:{"change":function($event){var $$selectedVal = Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = "_value" in o ? o._value : o.value;return val}); _vm.selectedOption=$event.target.multiple ? $$selectedVal : $$selectedVal[0]}}},_vm._l((_vm.options),function(option){return _c('option',{key:option,domProps:{"value":option}},[_vm._v(_vm._s(option))])}))]),_vm._v(" "),(_vm.selectedOption === 'protobuf')?_c('pre',{staticClass:"protobuf"},[_vm._v(_vm._s(_vm.protobuf))]):_vm._e(),_vm._v(" "),(_vm.jsonSchema)?_c('pre',{staticClass:"json-schema"},[_vm._v(_vm._s(_vm.jsonSchema))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'graphql schema')?_c('pre',{staticClass:"graphql-schema"},[_vm._v(_vm._s(_vm.graphqlSchema))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'reason types')?_c('pre',{staticClass:"reason-types"},[_vm._v(_vm._s(_vm.reasonTypes))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'ocaml types')?_c('pre',{staticClass:"ocaml-types"},[_vm._v(_vm._s(_vm.ocamlTypes))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'rust types')?_c('pre',{staticClass:"rust-types"},[_vm._v(_vm._s(_vm.rustTypes))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'mongoose schema')?_c('pre',{staticClass:"mongoose-schema"},[_vm._v(_vm._s(_vm.mongooseSchema))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'graphql root type')?_c('pre',{staticClass:"graphql-root-type"},[_vm._v(_vm._s(_vm.graphqlRootType))]):_vm._e(),_vm._v(" "),(_vm.selectedOption === 'swagger doc')?_c('pre',{staticClass:"swagger-doc"},[_vm._v(_vm._s(_vm.swaggerDoc))]):_vm._e()])])} // @ts-ignore export var indexTemplateHtmlStatic = [ ] // tslint:enable diff --git a/src/core.ts b/src/core.ts index 306fe2c..bfa218c 100644 --- a/src/core.ts +++ b/src/core.ts @@ -9,6 +9,7 @@ import { generateRustTypes } from './rust-type-generator' import { generateMongooseSchema } from './mongoose-schema-generator' import { TypeDeclaration } from './utils' import { generateGraphqlRootType } from './graphql-root-type-generator' +import { generateSwaggerDoc } from './swagger-doc-generator' export class Generator { declarations: TypeDeclaration[] = [] @@ -49,6 +50,10 @@ export class Generator { generateMongooseSchema() { return generateMongooseSchema(this.declarations) } + + generateSwaggerDoc() { + return generateSwaggerDoc(this.declarations) + } } export { ArrayDefinition, ObjectDefinition, UndefinedDefinition } diff --git a/src/index.ts b/src/index.ts index 1f40ffa..14e02d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,6 +30,7 @@ async function executeCommandLine() { rustPath, jsonPath, mongoosePath, + swaggerPath, debugPath, filePaths, watchMode @@ -103,6 +104,11 @@ async function executeCommandLine() { fs.writeFileSync(mongoosePath, mongooseContent) } + if (swaggerPath) { + const swaggerDoc = generator.generateSwaggerDoc() + fs.writeFileSync(swaggerPath, swaggerDoc) + } + if (jsonPath) { generateJsonSchemas(generator) } @@ -137,6 +143,7 @@ function parseParameters(argv: minimist.ParsedArgs) { const rustPath = parseParameter(argv, 'rust') const jsonPath = parseParameter(argv, 'json') const mongoosePath = parseParameter(argv, 'mongoose') + const swaggerPath = parseParameter(argv, 'swagger') const debugPath = parseParameter(argv, 'debug') const filePaths = argv._ @@ -156,6 +163,7 @@ function parseParameters(argv: minimist.ParsedArgs) { rustPath, jsonPath, mongoosePath, + swaggerPath, debugPath, filePaths, watchMode diff --git a/src/swagger-doc-generator.ts b/src/swagger-doc-generator.ts new file mode 100644 index 0000000..6c6abe9 --- /dev/null +++ b/src/swagger-doc-generator.ts @@ -0,0 +1,61 @@ +import { TypeDeclaration } from './utils' + +export function generateSwaggerDoc(_typeDeclarations: TypeDeclaration[]) { + return `{ + "swagger": "2.0", + "paths": { + "/pet/{petId}": { + "get": { + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "schema": { + "$ref": "#/definitions/Pet" + } + } + } + } + } + }, + "definitions": { + "Pet": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "photoUrls": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "required": [ + "name", + "photoUrls", + "status" + ], + "additionalProperties": false + } + } +}` +}