├── .coveralls.yml ├── .prettierignore ├── example ├── src │ └── page │ │ └── index.js ├── plugin.js ├── plugin │ └── test.js ├── package.json ├── .miladyrc.js ├── .umirc.js └── data │ └── swagger2.json ├── .yarnrc ├── .eslintignore ├── .prettierrc.js ├── .fatherrc.js ├── src ├── utils │ ├── common.ts │ ├── handelStr.test.ts │ └── handelStr.ts ├── plugins │ ├── serviceTs │ │ ├── http.ts.tpl │ │ ├── example.ts │ │ ├── index.test.ts │ │ ├── indexB.ts │ │ └── index.ts │ ├── serviceJs │ │ └── index.ts │ └── mock │ │ └── index.ts ├── typings.d.ts └── index.ts ├── .travis.yml ├── .gitignore ├── .editorconfig ├── .eslintrc.js ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── tsconfig.json ├── LICENSE ├── bin └── milady.js ├── package.json └── README.md /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /lib 3 | /out 4 | -------------------------------------------------------------------------------- /example/src/page/index.js: -------------------------------------------------------------------------------- 1 | export default () =>
测试页面,运行用于查看mock数据
; 2 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | # Using the official mirror source 2 | registry "https://registry.yarnpkg.com" 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /scripts 2 | /config 3 | **/node_modules/** 4 | /lib 5 | /es 6 | /template 7 | /example 8 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.prettier, 5 | }; 6 | -------------------------------------------------------------------------------- /.fatherrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | target: 'node', 3 | nodeVersion: 8, 4 | cjs: { type: 'babel' }, 5 | disableTypeCheck: true, 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/common.ts: -------------------------------------------------------------------------------- 1 | export function isUrl(params: string) { 2 | const routeReg = /^https?:\/\/[^/:]+(:\d*)?(\/#)?([^?]*)/; 3 | return routeReg.test(params); 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | - '10' 5 | before_script: npm run eslint 6 | script: npm run build && npm run test 7 | after_script: yarn run coverage 8 | -------------------------------------------------------------------------------- /example/plugin.js: -------------------------------------------------------------------------------- 1 | import milady from '../lib'; 2 | 3 | export default function(api, opts = {}) { 4 | api.registerCommand('codegen', {}, args => { 5 | milady(opts); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /example/plugin/test.js: -------------------------------------------------------------------------------- 1 | function handelData(params) { 2 | console.log(123); 3 | return []; 4 | } 5 | 6 | exports.default = { 7 | outPath: 'out', // 输出目录路径 8 | handelData, 9 | }; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /lib 4 | *.tgz 5 | *.log 6 | package-lock.json 7 | yarn.lock 8 | # 输出文件 9 | example/out 10 | example/mock 11 | example/*/services 12 | example/src/page/.umi 13 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "scripts": { 5 | "start": "umi dev", 6 | "milady": "node --inspect-brk ../bin/milady.js", 7 | "codegen": "node --inspect-brk ./node_modules/.bin/umi codegen" 8 | }, 9 | "devDependencies": { 10 | "umi": "^2.8.8" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /src/utils/handelStr.test.ts: -------------------------------------------------------------------------------- 1 | import { handelRef, strToKey } from './handelStr'; 2 | 3 | test('handelRef', () => { 4 | expect(handelRef('#/definitions/aa')).toEqual('aa'); 5 | }); 6 | test('strToKey', () => { 7 | expect(strToKey('#/definitions/aa')).toEqual('definitionsaa'); 8 | expect(strToKey('你会12aa')).toEqual('你会12aa'); 9 | expect(strToKey('')).toEqual(''); 10 | }); 11 | -------------------------------------------------------------------------------- /example/.miladyrc.js: -------------------------------------------------------------------------------- 1 | exports.default = { 2 | // dataSource: './data/swagger2.json', //推荐的,优先加载命令行的url,命令行没有再加载配置的url 3 | // plugins: [{ url: './plugin/test.js' }], //可选的, 4 | // defaultPlugins: { 5 | // mock: { enabled: false }, 6 | // serviceJs: { enabled: false }, 7 | // serviceTs: { enabled: false }, 8 | // serviceT: { enabled: false }, 9 | // }, 10 | }; 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.default, 5 | rules: { 6 | ...fabric.default.rules, 7 | '@typescript-eslint/camelcase': 0, 8 | '@typescript-eslint/class-name-casing': 0, 9 | '@typescript-eslint/no-explicit-any':0, 10 | 'no-console':0 11 | }, 12 | globals: { 13 | page: true, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/plugins/serviceTs/http.ts.tpl: -------------------------------------------------------------------------------- 1 | <%= InterfaceDefinition %> 2 | 3 | /** 4 | * @tags <%= FunctionTags %> 5 | * @summary <%= FunctionSummary %> 6 | * @description <%= FunctionDescription %> 7 | * @param params <%= FunctionParams %> 8 | */ 9 | export async function <%= FunctionName %>(params?: <%= FunctionParams %>): Promise<<%= FunctionPromise %>> { 10 | return request(<%= FunctionUrl %>, <%= FunctionParamsMethod %>); 11 | } 12 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 贡献 2 | 3 | ## 安装 4 | 5 | 在 git 克隆 repo 后安装依赖, 6 | 7 | ```zsh 8 | yarn 9 | ``` 10 | 11 | 打包 12 | 13 | ```zsh 14 | yarn run build 15 | ``` 16 | 17 | 全局链接包(等同于全局安装包) 18 | 19 | ```zsh 20 | yarn link 21 | ``` 22 | 23 | ## 命令行调试 24 | 25 | 切换目录到 example 26 | 27 | ```zsh 28 | cd exameple 29 | ``` 30 | 31 | 使用命令调试 milady 32 | 33 | ```zsh 34 | yarn run milady 35 | ``` 36 | 37 | ## 引入调试 38 | 39 | 安装包 40 | 41 | ```zsh 42 | yarn 43 | ``` 44 | 45 | 使用命令调试 umi 插件(调试引用) 46 | 47 | ```zsh 48 | yarn run codegen 49 | ``` 50 | -------------------------------------------------------------------------------- /example/.umirc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: [ 3 | [ 4 | './plugin.js', 5 | { 6 | swaggerUrl: 'https://petstore.swagger.io/v2/swagger.json', //推荐的,优先加载命令行的url,命令行没有再加载配置的url 7 | // plugins: [ 8 | // { 9 | // outPath: 'mock', //输出目录路径 10 | // handelData: params => { 11 | // return [{ fileName: 'test.txt', fileStr: '1' }]; 12 | // }, //传入swagger数据,返回集合fileName是生成的文件名,fileStr是生成的文件内容 13 | // }, 14 | // ], //可选的 15 | }, 16 | ], 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | declare module '*.scss'; 4 | declare module '*.sass'; 5 | declare module '*.svg'; 6 | declare module '*.png'; 7 | declare module '*.jpg'; 8 | declare module '*.jpeg'; 9 | declare module '*.gif'; 10 | declare module '*.bmp'; 11 | declare module '*.tiff'; 12 | declare module '*.json'; 13 | declare module 'rc-animate'; 14 | declare module 'omit.js'; 15 | declare module 'react-copy-to-clipboard'; 16 | declare module 'react-fittext'; 17 | declare module '@antv/data-set'; 18 | declare module 'nzh/cn'; 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | ## Background(背景) 10 | 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 对问题的清晰而简明的描述。我总是很遗憾当… 12 | 13 | ## Proposal(建议) 14 | 15 | Describe the solution you'd like, better to provide some pseudo code. 描述您想要的解决方案,最好提供一些伪代码。 16 | 17 | ## Additional context(附加上下文) 18 | 19 | Add any other context or screenshots about the feature request here. 在此处添加有关功能请求的任何其他上下文或屏幕截图。 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "jsx": "react", 9 | "experimentalDecorators": true, 10 | "noImplicitAny": true, 11 | "preserveConstEnums": true, 12 | "outDir": "lib/", 13 | "baseUrl": ".", 14 | "strict": true, 15 | "strictNullChecks": true, 16 | "paths": { 17 | "@/*": ["./src/*"] 18 | } 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["node_modules", "**/*.spec.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What kind of change does this PR introduce(这个 PR 引入了什么样的变化)? 2 | 3 | - [ ] Bugfix(修正错误) 4 | - [ ] Feature(新功能) 5 | - [ ] Refactor(重构) 6 | - [ ] Build-related changes(与构建相关的更改) 7 | - [ ] Other, please describe(其他,请描述): 8 | 9 | ## Does this PR introduce a breaking change(这次 PR 引入了一个重大变化吗)? 10 | 11 | - [ ] Yes(是) 12 | - [ ] No(否) 13 | 14 | If yes, please describe the impact and migration path for existing applications(如果是,请描述现有应用程序的影响和迁移路径): 15 | 16 | ## The PR fulfills these requirements(PR 符合以下要求) 17 | 18 | - [ ] All tests are passing(所有测试都通过) 19 | 20 | ## Other information(其他信息) 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | ## What happens(发生了什么)? 10 | 11 | A clear and concise description of what the bug is(对错误的清晰而简明的描述). 12 | 13 | ## Mini Showcase Repository(迷你展示库) 14 | 15 | > Provide a mini GitHub repository which can reproduce the issue(提供一个可以重现问题的小型 Github 存储库). 16 | 17 | 18 | 19 | ## How To Reproduce(如何重现) 20 | 21 | **Steps to reproduce the behavior(重现行为的步骤):** 1. 2. 22 | 23 | **Expected behavior(预期行为)** 1. 2. 24 | 25 | ## Context(上下文) 26 | 27 | - **milady Version**: 28 | - **Node Version**: 29 | - **Platform(操作系统平台)**: 30 | -------------------------------------------------------------------------------- /src/plugins/serviceTs/example.ts: -------------------------------------------------------------------------------- 1 | /* 示例文件作为插件参考 */ 2 | import request from 'umi-request'; 3 | 4 | interface VisitData { 5 | /** 6 | * @description 横轴值 7 | */ 8 | x: string; 9 | /** 10 | * @description 纵轴值 11 | */ 12 | y: string; 13 | } 14 | 15 | interface ApiFormsQuery { 16 | /** 17 | * @description token 18 | */ 19 | type: string; 20 | } 21 | 22 | /** 23 | * @tags 表单接口 24 | * @summary 表单接口 25 | * @description 表单:不知道是啥,这就是一个描述 26 | * @param params ApiFormsQuery 27 | * @returns {visitData:VisitData} 28 | */ 29 | export async function apiForms(params?: ApiFormsQuery): Promise { 30 | return request('/api/forms', { 31 | method: 'POST', 32 | data: params, 33 | }); 34 | } 35 | 36 | /** 37 | * @tags 表单接口 38 | * @summary 表单接口 39 | * @description 表单:不知道是啥,这就是一个描述 40 | * @param params ApiFormsQuery 41 | * @returns {visitData:VisitData} 42 | */ 43 | export async function menu(params?: ApiFormsQuery): Promise { 44 | return request('/api/menu', { params }); 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 alita 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/utils/handelStr.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 把$ref字段处理成definitions里面的键 3 | * @param params 4 | */ 5 | export function handelRef(params: string) { 6 | let str = params.replace('#/definitions/', ''); 7 | str = strToKey(str); 8 | return str; 9 | } 10 | 11 | /** 12 | * @dec 将字符串转成js合法变量 13 | * @param params 需要转成合法变量的字符串 14 | */ 15 | export function strToKey(params: string) { 16 | const reg = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/; // 匹配中文、字母、数字 17 | if (reg.test(params)) { 18 | return params; 19 | } 20 | if (params) { 21 | return params.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, ''); 22 | } 23 | console.log('strToKey执行异常,参数为空'); 24 | return params; 25 | } 26 | 27 | export function apiToName(api: string, method?: string) { 28 | const name = strToKey(wordToHump(api, '/')); 29 | return method ? name + method.toLocaleUpperCase() : name; 30 | } 31 | 32 | export function wordToHump(params: string, delimiter: string) { 33 | return `${params 34 | .split(delimiter) 35 | .map((item, index) => { 36 | if (item) { 37 | if (index > 1) { 38 | const arr = item.split(''); 39 | arr[0] = arr[0].toLocaleUpperCase(); 40 | return arr.join(''); 41 | } 42 | } 43 | return item; 44 | }) 45 | .join('')}`; 46 | } 47 | -------------------------------------------------------------------------------- /src/plugins/serviceJs/index.ts: -------------------------------------------------------------------------------- 1 | import { apiToName } from '../../utils/handelStr'; 2 | 3 | function main(SwaggerData: { paths: any }) { 4 | const { paths } = SwaggerData; 5 | let str = "import { stringify } from 'qs';\nimport request from '@/utils/request';\n"; 6 | Object.keys(paths).forEach(api => { 7 | Object.keys(paths[api]).forEach(method => { 8 | if (api !== '/') { 9 | const { description } = paths[api][method]; 10 | str = str.concat( 11 | services( 12 | apiToName(api, method), 13 | api, 14 | description && description.replace(/\n/g, ''), 15 | method, 16 | ), 17 | ); 18 | } 19 | }); 20 | }); 21 | const file = [{ fileName: 'api.js', fileStr: str }]; 22 | return file; 23 | } 24 | 25 | function services(name: string, api: string, desc: string, method: string) { 26 | if (method === 'post') { 27 | return `\nexport async function ${name}(params) {\n return request('${api}', {\n method: 'POST',\n body: params,\n });\n} // ${desc}\n`; 28 | } 29 | return `\nexport async function ${name}(params) {\n return request(\`${api}?\${stringify(params)}\`);\n} // ${desc}\n`; 30 | } 31 | 32 | export default { 33 | outPath: 'src/services', 34 | handelData: main, 35 | }; 36 | -------------------------------------------------------------------------------- /bin/milady.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const yParser = require('yargs-parser'); 5 | // eslint-disable-next-line @typescript-eslint/no-var-requires 6 | const { join } = require('path'); 7 | // eslint-disable-next-line @typescript-eslint/no-var-requires 8 | const { existsSync } = require('fs'); 9 | 10 | function checkVersion() { 11 | const nodeVersion = process.versions.node; 12 | const versions = nodeVersion.split('.'); 13 | const major = versions[0]; 14 | const minor = versions[1]; 15 | if (major * 10 + minor * 1 < 65) { 16 | // eslint-disable-next-line no-console 17 | console.log(`Node version must >= 6.5, but got ${major}.${minor}`); 18 | process.exit(1); 19 | } 20 | } 21 | function getArgv() { 22 | const argv = yParser(process.argv.slice(2)); 23 | const [url] = argv._; 24 | return url; 25 | } 26 | function getParams() { 27 | let config = { 28 | dataSource: '', 29 | }; 30 | const path = join(process.cwd(), '.miladyrc.js'); 31 | if (existsSync(path)) { 32 | try { 33 | // eslint-disable-next-line global-require,import/no-dynamic-require 34 | config = require(path).default; 35 | } catch (error) { 36 | console.log(error); 37 | } 38 | } 39 | config.dataSource = getArgv() ? getArgv() : config.dataSource; 40 | return config; 41 | } 42 | checkVersion(); 43 | const milady = require('../lib/index').default; 44 | 45 | milady(getParams()); 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "milady", 3 | "version": "0.0.5", 4 | "description": "Generate the front-end code by parsing the interface document", 5 | "main": "lib/index.js", 6 | "bin": { 7 | "milady": "./bin/milady.js" 8 | }, 9 | "scripts": { 10 | "build": "father-build", 11 | "publish": "npm publish --access public", 12 | "prettier": "prettier -c --write **/*", 13 | "eslint": "eslint --ext .js,.jsx,.ts,.tsx --format=pretty ./", 14 | "test": "umi-test", 15 | "lint-staged": "lint-staged", 16 | "coverage": "cat ./coverage/lcov.info | coveralls" 17 | }, 18 | "husky": { 19 | "hooks": { 20 | "pre-commit": "npm run lint-staged" 21 | } 22 | }, 23 | "lint-staged": { 24 | "**/*.{js,jsx,tsx,ts,less,md,json}": [ 25 | "npm run prettier", 26 | "git add" 27 | ], 28 | "**/*.{js,jsx,ts,tsx}": "npm run eslint" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/alitajs/codegen.git" 33 | }, 34 | "keywords": [ 35 | "codegen", 36 | "umi", 37 | "alitajs" 38 | ], 39 | "files": [ 40 | "lib", 41 | "template", 42 | "bin" 43 | ], 44 | "author": "xiaohuoni", 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/alitajs/codegen/issues" 48 | }, 49 | "homepage": "https://github.com/alitajs/codegen#readme", 50 | "dependencies": { 51 | "@types/node-fetch": "^2.3.7", 52 | "@types/signale": "^1.2.1", 53 | "fs": "^0.0.1-security", 54 | "fs-extra": "^8.0.1", 55 | "node-fetch": "^2.6.0", 56 | "path": "^0.12.7", 57 | "signale": "^1.4.0", 58 | "typescript": "^3.5.2", 59 | "umi-request": "^1.0.8", 60 | "yargs-parser": "^13.1.1" 61 | }, 62 | "devDependencies": { 63 | "@types/fs-extra": "^7.0.0", 64 | "@types/jest": "^24.0.15", 65 | "@umijs/fabric": "^1.0.9", 66 | "father-build": "^1.3.2", 67 | "husky": "^2.4.1", 68 | "lint-staged": "^8.2.1", 69 | "prettier": "^1.18.2", 70 | "umi-test": "^1.6.1" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # milady 2 | 3 | Generate the front-end code by parsing the interface document 4 | 5 | ## what 6 | 7 | 这是一个通过 swagger 的 url 自动化生成代码的项目。 8 | 9 | ## why 10 | 11 | 因为使用 umi 途中需要写很多重复的 service 文件和 mock 文件,故建立此项目来自动化这些重复的工作。 12 | 13 | ## how 14 | 15 | ### 通过引用使用 16 | 17 | 安装 18 | 19 | ```js 20 | npm i milady 21 | ``` 22 | 23 | 使用 24 | 25 | ```js 26 | import milady from 'milady'; 27 | const config = { 28 | swaggerUrl: '', //必填,用于获取数据 29 | }; 30 | milady(config); //传入配置参数,调用milady方法生成文件 31 | ``` 32 | 33 | ### 通过命令行使用 34 | 35 | 安装 36 | 37 | ```js 38 | npm i milady -D 39 | ``` 40 | 41 | 使用: 42 | 43 | - 执行命令`milady [swaggerUrl]`生成文件 44 | - 可选择设置配置`.miladyrc.js`文件进行高级设置,配置文件如下: 45 | 46 | ```js 47 | exports.default = { 48 | swaggerUrl: '', //推荐的,优先加载命令行的url,命令行没有再加载配置的url 49 | }; 50 | ``` 51 | 52 | ### 配置说明 53 | 54 | ### swaggerUrl 55 | 56 | 字段类型:string 57 | 58 | 说明:swagger 数据源的 url 59 | 60 | 例子: 61 | 62 | ```js 63 | exports.default = { 64 | swaggerUrl: '', 65 | }; 66 | ``` 67 | 68 | ### defaultPlugins 69 | 70 | 字段类型:object 71 | 72 | 说明:控制 milady 内置插件开启或关闭,目前支持 umi 的 mock 数据文件和 service 文件 js 版生成。enabled 为 false 则不会加载插件。 73 | 74 | 例子: 75 | 76 | ```js 77 | exports.default = { 78 | swaggerUrl: '', 79 | defaultPlugins: { 80 | serviceJs: { enabled: true }, 81 | mock: { enabled: false }, 82 | }, 83 | }; 84 | ``` 85 | 86 | ### plugins 87 | 88 | 字段类型:array 89 | 90 | 说明:用于设置自己写的外部插件,url 为相对于根目录的插件目录。 91 | 92 | 例子: 93 | 94 | ```js 95 | exports.default = { 96 | swaggerUrl: '', 97 | plugins: [{ url: './plugin/detail.js' }], 98 | }; 99 | ``` 100 | 101 | ### 插件开发 102 | 103 | 说明: 104 | 105 | 1. 插件文件需要返回一个默认值对象,outPath 为输出到相对于根目录位置,handelData 为处理数据的方法。 106 | 2. handelData 有一个参数,是 swagger 数据源的值。handelData 必须返回一个固定格式集合,数组里面一个对象代表一个会生成的文件,fileName 是生成文件的名字(需要后缀),fileStr 是文件内容。 107 | 3. 你可以根据 swagger 数据处理出你喜欢的字符串来生成文件,也可以用这个方法作为生成文件的方法。 108 | 109 | 插件文件格式: 110 | 111 | ```js 112 | function handelData(params) { 113 | return [ 114 | { 115 | fileName: 'check.txt', 116 | fileStr: '测试字符串', 117 | }, 118 | ]; 119 | } 120 | exports.default = { 121 | outPath: './out', // 输出目录路径 122 | handelData, 123 | }; 124 | ``` 125 | -------------------------------------------------------------------------------- /src/plugins/serviceTs/index.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | changeApi, 3 | changeParam, 4 | changeText, 5 | generateHead, 6 | generateInterfaceName, 7 | generateName, 8 | generatePromise, 9 | generateType, 10 | toUpperCase, 11 | } from '.'; 12 | 13 | test('changeText', () => { 14 | expect(changeText('a_b')).toEqual('aB'); 15 | expect(changeText('a_b')).toEqual('aB'); 16 | expect(changeText('a_Bc_d')).toEqual('aBcD'); 17 | }); 18 | 19 | test('changeApi', () => { 20 | expect(changeApi('/api/test')).toEqual("'/api/test'"); 21 | // eslint-disable-next-line no-template-curly-in-string 22 | expect(changeApi('/api/test/{id}')).toEqual('`/api/test/${params.id}`'); 23 | }); 24 | 25 | test('changeParam', () => { 26 | expect(changeParam('/api/test'.split('/'))).toEqual(['', 'api', 'test']); 27 | expect(changeParam('/api/test/{id}'.split('/'))).toEqual(['', 'api', 'test', 'id']); 28 | expect(changeParam('/api/test/{id}/{bb}'.split('/'))).toEqual(['', 'api', 'test', 'id', 'bb']); 29 | }); 30 | 31 | test('generateName', () => { 32 | expect(generateName('/api/test')).toEqual('apiTest'); 33 | expect(generateName('/api/test/bcd')).toEqual('testBcd'); 34 | expect(generateName('/api/test/{id}/{px}')).toEqual('idPx'); 35 | }); 36 | test('toUpperCase', () => { 37 | expect(toUpperCase('jnjsa')).toEqual('Jnjsa'); 38 | expect(toUpperCase('cjsn')).toEqual('Cjsn'); 39 | expect(toUpperCase('_as')).toEqual('_as'); 40 | expect(toUpperCase('1dsa')).toEqual('1dsa'); 41 | }); 42 | 43 | test('generateType', () => { 44 | const data = { 45 | type: 'array', 46 | items: { 47 | $ref: '#/definitions/PlaystationGamePrice', 48 | }, 49 | }; 50 | expect(generateType(data)).toEqual('PlaystationGamePrice []'); 51 | const data1 = { 52 | type: 'string', 53 | }; 54 | expect(generateType(data1)).toEqual('string'); 55 | const data2 = { 56 | $ref: '#/definitions/PlaystationGamePrice', 57 | }; 58 | expect(generateType(data2)).toEqual('PlaystationGamePrice'); 59 | const data3 = { 60 | type: 'integer', 61 | }; 62 | expect(generateType(data3)).toEqual('number'); 63 | }); 64 | 65 | test('generateHead', () => { 66 | const data = { 67 | host: 'alitajs.com', 68 | basePath: '/v1', 69 | info: { 70 | version: '1.0.0', 71 | }, 72 | }; 73 | expect(generateHead(data)).toEqual(` 74 | /** 75 | * This file is automatically generated using Alitajs/codegen 76 | * Host: alitajs.com 77 | * BasePath: /v1 78 | * Version: 1.0.0 79 | * Description: 这个文件是使用脚本自动生成的,原则上来说,你不需要手动修改它 80 | * Others: 81 | **/ 82 | import request from 'umi-request';\n 83 | `); 84 | }); 85 | 86 | test('generatePromise', () => { 87 | expect( 88 | generatePromise({ 89 | $ref: '#/definitions/ResultWrapper«ListResult«PlaystationGamePrice»»', 90 | }), 91 | ).toEqual('ResultWrapperListResultPlaystationGamePrice'); 92 | expect(generatePromise({ $ref: '#/definitions/分页数据«优惠券对象»' })).toEqual( 93 | 'TemporaryVariable0', 94 | ); 95 | expect(generatePromise({ type: 'string' })).toEqual('string'); 96 | }); 97 | 98 | test('generateInterfaceName', () => { 99 | expect(generateInterfaceName('分页数据«优惠券对象»')).toEqual('TemporaryVariable0'); 100 | expect(generateInterfaceName('AnalysisData«List»')).toEqual('AnalysisDataList'); 101 | }); 102 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import { join } from 'path'; 3 | import { outputFileSync, readdirSync } from 'fs-extra'; 4 | import signale from 'signale'; 5 | import { isUrl } from './utils/common'; 6 | 7 | const loadPlugins: string[] = []; 8 | export interface DefaultPluginsConfig { 9 | enabled: boolean; 10 | } 11 | export interface DefaultPlugins { 12 | [index: string]: DefaultPluginsConfig; 13 | } 14 | 15 | // eslint-disable-next-line space-before-function-paren 16 | export default async function({ 17 | dataSource, 18 | plugins = [], 19 | defaultPlugins = { 20 | mock: { enabled: true }, 21 | serviceJs: { enabled: false }, 22 | serviceTs: { enabled: true }, 23 | serviceT: { enabled: false }, 24 | }, 25 | }: { 26 | dataSource: string; 27 | plugins: any[]; 28 | defaultPlugins: DefaultPlugins; 29 | }) { 30 | signale.time('milady'); 31 | if (!dataSource) { 32 | signale.log('必须携带URL地址,如milady https://xx.x.x/abc/v2/api-docs#/'); 33 | return; 34 | } 35 | /* 获取数据 */ 36 | let data: any; 37 | if (isUrl(dataSource)) { 38 | data = await getData(dataSource); 39 | } else { 40 | const str = join(process.cwd(), dataSource); 41 | // eslint-disable-next-line global-require,import/no-dynamic-require 42 | data = require(str); 43 | } 44 | /* 处理数据 */ 45 | const files = handleData(data, defaultPlugins, plugins); 46 | /* 生成代码 */ 47 | codeGen(files); 48 | signale.timeEnd('milady'); 49 | } 50 | async function getData(dataSource: string) { 51 | const res = await fetch(dataSource); 52 | const data = await res.json(); 53 | const error = await data.catch; 54 | if (error) { 55 | signale.error(error); 56 | } 57 | return data; 58 | } 59 | function handleData( 60 | SwaggerData: { tags?: never[] | undefined; paths: any; definitions: any }, 61 | defaultPlugins: DefaultPlugins, 62 | plugins: any[], 63 | ) { 64 | const directory: any = []; 65 | /* 加载默认插件 */ 66 | const defaultPluginsName = readdirSync(join(__dirname, '/plugins')); 67 | defaultPluginsName.forEach((item: any) => { 68 | const defaultPlugin = defaultPlugins[item]; 69 | const str: string = join(__dirname, '/plugins', item); 70 | // eslint-disable-next-line global-require,import/no-dynamic-require 71 | const defaultPluginFile = require(str).default; 72 | if (defaultPlugin.enabled) { 73 | directory.push({ 74 | outPath: defaultPluginFile.outPath, 75 | file: defaultPluginFile.handelData(SwaggerData), 76 | }); 77 | loadPlugins.push(item); 78 | } 79 | }); 80 | /* 加载配置插件 */ 81 | if (Object.prototype.toString.call(plugins) === '[object Array]') { 82 | if (plugins.length !== 0) { 83 | plugins.forEach(element => { 84 | if (element.hasOwnProperty('url')) { 85 | const str: string = join(process.cwd(), element.url); 86 | // eslint-disable-next-line global-require,import/no-dynamic-require 87 | const pluginsFile = require(str).default; 88 | directory.push({ 89 | outPath: pluginsFile.outPath, 90 | file: pluginsFile.handelData(SwaggerData), 91 | }); 92 | loadPlugins.push(element.url.split('/').pop()); 93 | } else { 94 | signale.error('插件字段类型有误!'); 95 | } 96 | }); 97 | } 98 | } else { 99 | signale.error('请检查插件格式是否为数组!'); 100 | } // 检查插件类型 101 | 102 | return directory; 103 | } 104 | function generate(outPath: string, fileArr: { fileName: string; fileStr: string }[]) { 105 | fileArr.forEach(element => { 106 | let paths = outPath || 'out/'; 107 | paths = join(paths, element.fileName); 108 | outputFileSync(paths, element.fileStr, 'utf-8'); 109 | }); 110 | } 111 | function codeGen(files: any[]) { 112 | files.forEach(element => { 113 | generate(element.outPath, element.file); 114 | }); 115 | signale.complete('文件创建完成'); 116 | signale.complete(`加载插件列表:${loadPlugins.join('、')}`); 117 | } 118 | -------------------------------------------------------------------------------- /src/plugins/mock/index.ts: -------------------------------------------------------------------------------- 1 | import { handelRef, strToKey } from '../../utils/handelStr'; 2 | 3 | function main(SwaggerData: { paths: any; definitions: any }) { 4 | const { paths, definitions } = SwaggerData; 5 | const indexStr = generateIndex(paths); 6 | const dataStr = generateData(definitions); 7 | const file = [ 8 | { fileName: 'index.js', fileStr: indexStr }, 9 | { fileName: 'data.js', fileStr: dataStr }, 10 | ]; 11 | return file; 12 | } 13 | 14 | /* 生成接口文件 */ 15 | function generateIndex(paths: any) { 16 | let indexStr = "import data from './data';\n\nexport default {\n"; 17 | Object.keys(paths).forEach(api => { 18 | Object.keys(paths[api]).forEach(method => { 19 | if (method === 'post' || method === 'get') { 20 | const { description, responses } = paths[api][method]; 21 | const data = getIndexValue(responses); 22 | 23 | indexStr = indexStr.concat( 24 | ` '${method.toUpperCase()} ${api}': {\n code: 0,\n message: '成功',\n data: ${data},\n }, // ${description && 25 | description.replace(/\n/g, '')}\n`, 26 | ); 27 | } 28 | }); 29 | }); 30 | indexStr = indexStr.concat('};\n'); 31 | return indexStr; 32 | } // 生成接口文件 33 | function getIndexValue(responses: any) { 34 | let res; 35 | if (responses['200'] && responses['200'].schema) { 36 | if (responses['200'].schema.$ref) { 37 | res = `data.${handelRef(responses['200'].schema.$ref)}`; 38 | } else if (responses['200'].schema.items && responses['200'].schema.items.$ref) { 39 | res = `[data.${handelRef(responses['200'].schema.items.$ref)}]`; 40 | } 41 | } else { 42 | res = null; 43 | } 44 | return res; 45 | } 46 | 47 | /* 生成数据文件 */ 48 | let up = true; // 排序 49 | function generateData(definitions: any) { 50 | let dataStr = ''; 51 | dataStr = dataStr.concat(generateObjectStr(Object.keys(definitions))); 52 | dataStr = dataStr.concat(generateBody(definitions)); 53 | dataStr = dataStr.concat('export default obj;\n'); 54 | return dataStr; 55 | } // 生成数据文件 56 | function generateBody(definitions: any) { 57 | let dataStr = ''; 58 | Object.keys(definitions).forEach(dataName => { 59 | const dataContents = definitions[dataName].properties; 60 | up = true; 61 | const str = generateDataStr(dataName, dataContents); 62 | if (up) { 63 | dataStr = str.concat(dataStr); 64 | } else { 65 | dataStr = dataStr.concat(str); 66 | } 67 | }); 68 | return dataStr; 69 | } 70 | function generateDataStr(dataName: any, dataContents: any) { 71 | if (!dataContents) { 72 | console.log(dataContents); 73 | return ''; 74 | } 75 | let str = `obj.${strToKey(dataName)} = {\n`; 76 | Object.keys(dataContents).forEach(propertiesName => { 77 | str = str.concat( 78 | ` ${propertiesName}: ${generateValueStr(dataContents[propertiesName])}, // ${ 79 | dataContents[propertiesName].description 80 | ? dataContents[propertiesName].description.replace(/\n/g, '').replace(/\s/g, '') 81 | : '无' 82 | }\n`, 83 | ); 84 | }); 85 | str = str.concat('};\n'); 86 | return str; 87 | } // 生成数据 88 | function generateValueStr(params: any) { 89 | let dataStr; 90 | let data = null; 91 | if (params.items && params.items.$ref) { 92 | data = handelRef(params.items.$ref); 93 | } 94 | switch (params.type) { 95 | case 'array': 96 | dataStr = `[obj.${data}]`; 97 | up = false; 98 | break; 99 | case 'object': 100 | dataStr = `obj.${data}`; 101 | up = false; 102 | break; 103 | case 'string': 104 | dataStr = '1'; 105 | break; 106 | case 'integer': 107 | dataStr = 1; 108 | break; 109 | case 'boolean': 110 | dataStr = true; 111 | break; 112 | case 'number': 113 | dataStr = 1; 114 | break; 115 | case 'undefined': 116 | dataStr = undefined; 117 | break; 118 | default: 119 | dataStr = "'没有type值'"; 120 | break; 121 | } 122 | return dataStr; 123 | } // 生成数据值 124 | function generateObjectStr(params: any[]) { 125 | let str = 'const obj = {\n'; 126 | params.forEach(element => { 127 | str = str.concat(` ${strToKey(element)}: null,\n`); 128 | }); 129 | str = str.concat('};\n'); 130 | return str; 131 | } // 生成定义导出数据对象 132 | export default { 133 | outPath: 'mock/', 134 | handelData: main, 135 | }; 136 | -------------------------------------------------------------------------------- /src/plugins/serviceTs/indexB.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { join } from 'path'; 3 | import { handelRef, strToKey } from '../../utils/handelStr'; 4 | 5 | function main(SwaggerData: { paths: any; definitions: any }) { 6 | const { paths, definitions } = SwaggerData; 7 | const indexStr = generateIndex(paths); 8 | const dataStr = generateData(definitions); 9 | const str = indexStr + dataStr; 10 | const file = [{ fileName: 'api.ts', fileStr: str }]; 11 | return file; 12 | } 13 | 14 | /* 生成接口文件 */ 15 | function generateIndex(paths: any) { 16 | let indexStr = ''; 17 | Object.keys(paths).forEach(api => { 18 | Object.keys(paths[api]).forEach(method => { 19 | if (method === 'post' || method === 'get') { 20 | const { description, responses, summary, tags, parameters } = paths[api][method]; 21 | const interfaceGet = ''; 22 | const requestInterfaceName = ''; 23 | const responseInterfaceName = ''; 24 | const requestBody = ''; 25 | const data = getIndexValue(responses); 26 | const tpl = join(__dirname, './http.ts.tpl'); 27 | let tplContent = readFileSync(tpl, 'utf-8'); 28 | tplContent = tplContent 29 | .replace('<%= InterfaceGet %>', interfaceGet) 30 | .replace('<%= FunctionTags %>', JSON.stringify(tags)) 31 | .replace('<%= FunctionSummary %>', summary) 32 | .replace('<%= FunctionDescription %>', description) 33 | .replace(new RegExp('<%= FunctionParams %>', 'g'), strToKey(requestInterfaceName)) 34 | .replace('<%= FunctionName %>', strToKey(api)) 35 | .replace('<%= FunctionPromise %>', strToKey(responseInterfaceName)) 36 | .replace('<%= FunctionUrl %>', api) 37 | .replace('<%= FunctionParamsMethod %>', requestBody); 38 | indexStr = indexStr.concat(tplContent); 39 | } 40 | }); 41 | }); 42 | indexStr = indexStr.concat('};\n'); 43 | return indexStr; 44 | } // 生成接口文件 45 | function getIndexValue(responses: any) { 46 | let res; 47 | if (responses['200'] && responses['200'].schema) { 48 | if (responses['200'].schema.$ref) { 49 | res = `data.${handelRef(responses['200'].schema.$ref)}`; 50 | } else if (responses['200'].schema.items && responses['200'].schema.items.$ref) { 51 | res = `[data.${handelRef(responses['200'].schema.items.$ref)}]`; 52 | } 53 | } else { 54 | res = null; 55 | } 56 | return res; 57 | } 58 | 59 | /* 生成请求文件 */ 60 | let up = true; // 排序 61 | function generateData(definitions: any) { 62 | let dataStr = ''; 63 | dataStr = dataStr.concat(generateObjectStr(Object.keys(definitions))); 64 | dataStr = dataStr.concat(generateBody(definitions)); 65 | dataStr = dataStr.concat('export default obj;\n'); 66 | return dataStr; 67 | } // 生成数据文件 68 | function generateBody(definitions: any) { 69 | let dataStr = ''; 70 | Object.keys(definitions).forEach(dataName => { 71 | const dataContents = definitions[dataName].properties; 72 | up = true; 73 | const str = generateDataStr(dataName, dataContents); 74 | if (up) { 75 | dataStr = str.concat(dataStr); 76 | } else { 77 | dataStr = dataStr.concat(str); 78 | } 79 | }); 80 | return dataStr; 81 | } 82 | function generateDataStr(dataName: any, dataContents: any) { 83 | if (!dataContents) { 84 | console.log(dataContents); 85 | return ''; 86 | } 87 | let str = `obj.${strToKey(dataName)} = {\n`; 88 | Object.keys(dataContents).forEach(propertiesName => { 89 | str = str.concat( 90 | ` ${propertiesName}: ${generateValueStr(dataContents[propertiesName])}, // ${ 91 | dataContents[propertiesName].description 92 | ? dataContents[propertiesName].description.replace(/\n/g, '').replace(/\s/g, '') 93 | : '无' 94 | }\n`, 95 | ); 96 | }); 97 | str = str.concat('};\n'); 98 | return str; 99 | } // 生成数据 100 | function generateValueStr(params: any) { 101 | let dataStr; 102 | let data = null; 103 | if (params.items && params.items.$ref) { 104 | data = handelRef(params.items.$ref); 105 | } 106 | switch (params.type) { 107 | case 'array': 108 | dataStr = `[obj.${data}]`; 109 | up = false; 110 | break; 111 | case 'object': 112 | dataStr = `obj.${data}`; 113 | up = false; 114 | break; 115 | case 'string': 116 | dataStr = '1'; 117 | break; 118 | case 'integer': 119 | dataStr = 1; 120 | break; 121 | case 'boolean': 122 | dataStr = true; 123 | break; 124 | case 'number': 125 | dataStr = 1; 126 | break; 127 | case 'undefined': 128 | dataStr = undefined; 129 | break; 130 | default: 131 | dataStr = "'没有type值'"; 132 | break; 133 | } 134 | return dataStr; 135 | } // 生成数据值 136 | function generateObjectStr(params: any[]) { 137 | let str = 'const obj = {\n'; 138 | params.forEach(element => { 139 | str = str.concat(` ${strToKey(element)}: null,\n`); 140 | }); 141 | str = str.concat('};\n'); 142 | return str; 143 | } // 生成定义导出数据对象 144 | export default { 145 | outPath: 'mock/', 146 | handelData: main, 147 | }; 148 | -------------------------------------------------------------------------------- /src/plugins/serviceTs/index.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import { readFileSync } from 'fs'; 4 | import { strToKey, apiToName } from '../../utils/handelStr'; 5 | 6 | const GetMethodString = '{\n params\n }'; 7 | const PostMethodString = "{\n method: 'POST',\n data: params\n }"; 8 | 9 | export function changeText(input: string) { 10 | if (input.includes('_')) { 11 | const texts = input.split('_'); 12 | let str = texts.splice(0, 1)[0]; 13 | texts.forEach(t => { 14 | str += toUpperCase(t); 15 | }); 16 | return str; 17 | } 18 | return input; 19 | } 20 | let additionalParameters = {} as any; 21 | export function changeApi(input: string) { 22 | const hasParams = input.includes('{'); 23 | const nameArr = input.split('/'); 24 | const newArr = nameArr.map(i => { 25 | let iStr = i; 26 | if (i.includes('{')) { 27 | const param = i.replace(new RegExp('{', 'g'), '').replace(new RegExp('}', 'g'), ''); 28 | additionalParameters[param] = param; 29 | iStr = `\${params.${param}}`; 30 | } 31 | return iStr; 32 | }); 33 | return hasParams ? `\`${newArr.join('/')}\`` : `'${newArr.join('/')}'`; 34 | } 35 | export function changeParam(nameArr: any[]) { 36 | const newArr = nameArr.map(i => { 37 | let iStr = i; 38 | if (i.includes('{')) { 39 | iStr = i.replace(new RegExp('{', 'g'), '').replace(new RegExp('}', 'g'), ''); 40 | } 41 | return iStr; 42 | }); 43 | return newArr; 44 | } 45 | export function generateName(api: string) { 46 | const nameArr = changeParam(api.split('/')); 47 | let name = nameArr[nameArr.length - 2] 48 | ? nameArr[nameArr.length - 2] + toUpperCase(nameArr[nameArr.length - 1]) 49 | : nameArr[nameArr.length - 1]; 50 | name = name.replace(/-/g, '_'); 51 | return changeText(name); 52 | } 53 | export function toUpperCase(str: string) { 54 | return str.replace(str[0], str[0].toUpperCase()); 55 | } 56 | export function generateType({ type, items, $ref }: any) { 57 | if (type === 'integer') { 58 | return 'number'; 59 | } 60 | if (type === 'array') { 61 | if (items.$ref) { 62 | return `${generateInterfaceName(items.$ref)} []`; 63 | } 64 | if (items.type) { 65 | return `${items.type} []`; 66 | } 67 | return '[]'; 68 | } 69 | if ($ref) { 70 | return generateInterfaceName($ref); 71 | } 72 | return type; 73 | } 74 | export function generateHead(data: any) { 75 | return ` 76 | /** 77 | * This file is automatically generated using Alitajs/codegen 78 | * Host: ${data.host} 79 | * BasePath: ${data.basePath} 80 | * Version: ${data.info.version} 81 | * Description: 这个文件是使用脚本自动生成的,原则上来说,你不需要手动修改它 82 | * Others: 83 | **/ 84 | import request from 'umi-request';\n 85 | `; 86 | } 87 | const hasChineseArr = {} as any; 88 | let hasChineseCount = 0; 89 | export function generatePromise(resData: any) { 90 | let promise = ''; 91 | if (!resData) { 92 | return 'any'; 93 | } 94 | if (resData.$ref) { 95 | promise = resData.$ref 96 | .replace('#/definitions/', '') 97 | .replace(new RegExp('«', 'g'), '') 98 | .replace(new RegExp('»', 'g'), ''); 99 | } else if (resData.item) { 100 | promise = resData.item.$ref 101 | .replace('#/definitions/', '') 102 | .replace(new RegExp('«', 'g'), '') 103 | .replace(new RegExp('»', 'g'), ''); 104 | } else if (resData.type) { 105 | promise = resData.type; 106 | } 107 | const hasChinese = /[^\u4e00-\u9fa5]+/.test(promise); 108 | if (!hasChinese) { 109 | if (hasChineseArr[promise]) { 110 | promise = hasChineseArr[promise]; 111 | } else { 112 | hasChineseArr[promise] = `TemporaryVariable${hasChineseCount}`; 113 | promise = `TemporaryVariable${hasChineseCount}`; 114 | hasChineseCount += 1; 115 | } 116 | } 117 | if (resData.item) { 118 | promise += '[]'; 119 | } 120 | return promise; 121 | } 122 | export function generateInterfaceName(text: string) { 123 | let input = text; 124 | input = input 125 | .replace('#/definitions/', '') 126 | .replace(new RegExp('«', 'g'), '') 127 | .replace(new RegExp('»', 'g'), ''); 128 | const hasChinese = /[^\u4e00-\u9fa5]+/.test(input); 129 | if (!hasChinese) { 130 | if (hasChineseArr[input]) { 131 | input = hasChineseArr[input]; 132 | } else { 133 | hasChineseArr[input] = `TemporaryVariable${hasChineseCount}`; 134 | input = `TemporaryVariable${hasChineseCount}`; 135 | hasChineseCount += 1; 136 | } 137 | } 138 | return changeText(input); 139 | } 140 | export function generateServiceFiles(SwaggerData: { 141 | tags?: never[] | undefined; 142 | paths: any; 143 | definitions: any; 144 | }) { 145 | const { tags = [], paths, definitions } = SwaggerData; 146 | let outPutStr = generateHead(SwaggerData); 147 | /* 生成接口文件 */ 148 | Object.keys(definitions).forEach(defItem => { 149 | outPutStr += `interface ${strToKey(generateInterfaceName(defItem))} {\n`; 150 | const properties = definitions[defItem].properties || {}; 151 | Object.keys(properties).forEach(subDefItem => { 152 | let defItemStr = ' /**\n'; 153 | defItemStr += ` * @description ${properties[subDefItem].description || ''}\n`; 154 | defItemStr += ' **/\n'; 155 | defItemStr += ` ${subDefItem}: ${strToKey(generateType(properties[subDefItem]))};\n`; 156 | outPutStr += defItemStr; 157 | }); 158 | outPutStr += '}\n'; 159 | }); 160 | /* 生成请求文件 */ 161 | Object.keys(paths).forEach(item => { 162 | const itemData = paths[item]; 163 | Object.keys(itemData).forEach(subItem => { 164 | additionalParameters = {}; 165 | const subItemData = itemData[subItem]; 166 | const { summary, description, tags: subTags, responses, parameters = [] } = subItemData; 167 | const resData = responses['200'] && responses['200'].schema; 168 | const url = changeApi(item); 169 | const name = apiToName(item, subItem); 170 | const params = `${toUpperCase(name)}Query`; 171 | const paramsMethod = subItem === 'get' ? GetMethodString : PostMethodString; 172 | let itemTargs = subTags || []; 173 | itemTargs = itemTargs.map((t: string) => { 174 | let tStr = t; 175 | tags.forEach((tag: { name: string; description: string }) => { 176 | if (tag.name === t) { 177 | tStr = tag.description; 178 | } 179 | }); 180 | return tStr; 181 | }); 182 | const promise = generatePromise(resData); 183 | 184 | let definition = `interface ${params} {\n`; 185 | parameters.map((p: { in: string; description: any; name: any; type: any }) => { 186 | if (p.in === 'query') { 187 | let pStr = ' /**\n'; 188 | pStr += ` * @description ${p.description || ''}\n`; 189 | pStr += ' **/\n'; 190 | pStr += ` ${p.name}: ${generateType(p)};\n`; 191 | definition += pStr; 192 | Object.keys(additionalParameters).map(ap => { 193 | let apStr = ' /**\n'; 194 | apStr += ' * @description 请求地址中追加的参数\n'; 195 | apStr += ' **/\n'; 196 | apStr += ` ${ap}: string;\n`; 197 | definition += apStr; 198 | return ap; 199 | }); 200 | } 201 | return p; 202 | }); 203 | definition += '}\n'; 204 | const tpl = join(__dirname, './http.ts.tpl'); 205 | let tplContent = readFileSync(tpl, 'utf-8'); 206 | tplContent = tplContent 207 | .replace('<%= InterfaceDefinition %>', definition) 208 | .replace('<%= FunctionTags %>', JSON.stringify(itemTargs)) 209 | .replace('<%= FunctionSummary %>', summary) 210 | .replace('<%= FunctionDescription %>', description) 211 | .replace(new RegExp('<%= FunctionParams %>', 'g'), strToKey(params)) 212 | .replace('<%= FunctionName %>', strToKey(name)) 213 | .replace('<%= FunctionPromise %>', strToKey(promise)) 214 | .replace('<%= FunctionUrl %>', url) 215 | .replace('<%= FunctionParamsMethod %>', paramsMethod); 216 | outPutStr += tplContent; 217 | }); 218 | }); 219 | const file = [{ fileName: 'api.ts', fileStr: outPutStr }]; 220 | return file; 221 | } 222 | export default { 223 | outPath: 'src/services', 224 | handelData: generateServiceFiles, 225 | }; 226 | -------------------------------------------------------------------------------- /example/data/swagger2.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", 5 | "version": "1.0.0", 6 | "title": "Swagger Petstore", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { "email": "apiteam@swagger.io" }, 9 | "license": { "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" } 10 | }, 11 | "host": "petstore.swagger.io", 12 | "basePath": "/v2", 13 | "tags": [ 14 | { 15 | "name": "pet", 16 | "description": "Everything about your Pets", 17 | "externalDocs": { "description": "Find out more", "url": "http://swagger.io" } 18 | }, 19 | { "name": "store", "description": "Access to Petstore orders" }, 20 | { 21 | "name": "user", 22 | "description": "Operations about user", 23 | "externalDocs": { "description": "Find out more about our store", "url": "http://swagger.io" } 24 | } 25 | ], 26 | "schemes": ["https", "http"], 27 | "paths": { 28 | "/pet": { 29 | "post": { 30 | "tags": ["pet"], 31 | "summary": "Add a new pet to the store", 32 | "description": "", 33 | "operationId": "addPet", 34 | "consumes": ["application/json", "application/xml"], 35 | "produces": ["application/xml", "application/json"], 36 | "parameters": [ 37 | { 38 | "in": "body", 39 | "name": "body", 40 | "description": "Pet object that needs to be added to the store", 41 | "required": true, 42 | "schema": { "$ref": "#/definitions/Pet" } 43 | } 44 | ], 45 | "responses": { "405": { "description": "Invalid input" } }, 46 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] 47 | }, 48 | "put": { 49 | "tags": ["pet"], 50 | "summary": "Update an existing pet", 51 | "description": "", 52 | "operationId": "updatePet", 53 | "consumes": ["application/json", "application/xml"], 54 | "produces": ["application/xml", "application/json"], 55 | "parameters": [ 56 | { 57 | "in": "body", 58 | "name": "body", 59 | "description": "Pet object that needs to be added to the store", 60 | "required": true, 61 | "schema": { "$ref": "#/definitions/Pet" } 62 | } 63 | ], 64 | "responses": { 65 | "400": { "description": "Invalid ID supplied" }, 66 | "404": { "description": "Pet not found" }, 67 | "405": { "description": "Validation exception" } 68 | }, 69 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] 70 | } 71 | }, 72 | "/pet/findByStatus": { 73 | "get": { 74 | "tags": ["pet"], 75 | "summary": "Finds Pets by status", 76 | "description": "Multiple status values can be provided with comma separated strings", 77 | "operationId": "findPetsByStatus", 78 | "produces": ["application/xml", "application/json"], 79 | "parameters": [ 80 | { 81 | "name": "status", 82 | "in": "query", 83 | "description": "Status values that need to be considered for filter", 84 | "required": true, 85 | "type": "array", 86 | "items": { 87 | "type": "string", 88 | "enum": ["available", "pending", "sold"], 89 | "default": "available" 90 | }, 91 | "collectionFormat": "multi" 92 | } 93 | ], 94 | "responses": { 95 | "200": { 96 | "description": "successful operation", 97 | "schema": { "type": "array", "items": { "$ref": "#/definitions/Pet" } } 98 | }, 99 | "400": { "description": "Invalid status value" } 100 | }, 101 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] 102 | } 103 | }, 104 | "/pet/findByTags": { 105 | "get": { 106 | "tags": ["pet"], 107 | "summary": "Finds Pets by tags", 108 | "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", 109 | "operationId": "findPetsByTags", 110 | "produces": ["application/xml", "application/json"], 111 | "parameters": [ 112 | { 113 | "name": "tags", 114 | "in": "query", 115 | "description": "Tags to filter by", 116 | "required": true, 117 | "type": "array", 118 | "items": { "type": "string" }, 119 | "collectionFormat": "multi" 120 | } 121 | ], 122 | "responses": { 123 | "200": { 124 | "description": "successful operation", 125 | "schema": { "type": "array", "items": { "$ref": "#/definitions/Pet" } } 126 | }, 127 | "400": { "description": "Invalid tag value" } 128 | }, 129 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }], 130 | "deprecated": true 131 | } 132 | }, 133 | "/pet/{petId}": { 134 | "get": { 135 | "tags": ["pet"], 136 | "summary": "Find pet by ID", 137 | "description": "Returns a single pet", 138 | "operationId": "getPetById", 139 | "produces": ["application/xml", "application/json"], 140 | "parameters": [ 141 | { 142 | "name": "petId", 143 | "in": "path", 144 | "description": "ID of pet to return", 145 | "required": true, 146 | "type": "integer", 147 | "format": "int64" 148 | } 149 | ], 150 | "responses": { 151 | "200": { 152 | "description": "successful operation", 153 | "schema": { "$ref": "#/definitions/Pet" } 154 | }, 155 | "400": { "description": "Invalid ID supplied" }, 156 | "404": { "description": "Pet not found" } 157 | }, 158 | "security": [{ "api_key": [] }] 159 | }, 160 | "post": { 161 | "tags": ["pet"], 162 | "summary": "Updates a pet in the store with form data", 163 | "description": "", 164 | "operationId": "updatePetWithForm", 165 | "consumes": ["application/x-www-form-urlencoded"], 166 | "produces": ["application/xml", "application/json"], 167 | "parameters": [ 168 | { 169 | "name": "petId", 170 | "in": "path", 171 | "description": "ID of pet that needs to be updated", 172 | "required": true, 173 | "type": "integer", 174 | "format": "int64" 175 | }, 176 | { 177 | "name": "name", 178 | "in": "formData", 179 | "description": "Updated name of the pet", 180 | "required": false, 181 | "type": "string" 182 | }, 183 | { 184 | "name": "status", 185 | "in": "formData", 186 | "description": "Updated status of the pet", 187 | "required": false, 188 | "type": "string" 189 | } 190 | ], 191 | "responses": { "405": { "description": "Invalid input" } }, 192 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] 193 | }, 194 | "delete": { 195 | "tags": ["pet"], 196 | "summary": "Deletes a pet", 197 | "description": "", 198 | "operationId": "deletePet", 199 | "produces": ["application/xml", "application/json"], 200 | "parameters": [ 201 | { "name": "api_key", "in": "header", "required": false, "type": "string" }, 202 | { 203 | "name": "petId", 204 | "in": "path", 205 | "description": "Pet id to delete", 206 | "required": true, 207 | "type": "integer", 208 | "format": "int64" 209 | } 210 | ], 211 | "responses": { 212 | "400": { "description": "Invalid ID supplied" }, 213 | "404": { "description": "Pet not found" } 214 | }, 215 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] 216 | } 217 | }, 218 | "/pet/{petId}/uploadImage": { 219 | "post": { 220 | "tags": ["pet"], 221 | "summary": "uploads an image", 222 | "description": "", 223 | "operationId": "uploadFile", 224 | "consumes": ["multipart/form-data"], 225 | "produces": ["application/json"], 226 | "parameters": [ 227 | { 228 | "name": "petId", 229 | "in": "path", 230 | "description": "ID of pet to update", 231 | "required": true, 232 | "type": "integer", 233 | "format": "int64" 234 | }, 235 | { 236 | "name": "additionalMetadata", 237 | "in": "formData", 238 | "description": "Additional data to pass to server", 239 | "required": false, 240 | "type": "string" 241 | }, 242 | { 243 | "name": "file", 244 | "in": "formData", 245 | "description": "file to upload", 246 | "required": false, 247 | "type": "file" 248 | } 249 | ], 250 | "responses": { 251 | "200": { 252 | "description": "successful operation", 253 | "schema": { "$ref": "#/definitions/ApiResponse" } 254 | } 255 | }, 256 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }] 257 | } 258 | }, 259 | "/store/inventory": { 260 | "get": { 261 | "tags": ["store"], 262 | "summary": "Returns pet inventories by status", 263 | "description": "Returns a map of status codes to quantities", 264 | "operationId": "getInventory", 265 | "produces": ["application/json"], 266 | "parameters": [], 267 | "responses": { 268 | "200": { 269 | "description": "successful operation", 270 | "schema": { 271 | "type": "object", 272 | "additionalProperties": { "type": "integer", "format": "int32" } 273 | } 274 | } 275 | }, 276 | "security": [{ "api_key": [] }] 277 | } 278 | }, 279 | "/store/order": { 280 | "post": { 281 | "tags": ["store"], 282 | "summary": "Place an order for a pet", 283 | "description": "", 284 | "operationId": "placeOrder", 285 | "produces": ["application/xml", "application/json"], 286 | "parameters": [ 287 | { 288 | "in": "body", 289 | "name": "body", 290 | "description": "order placed for purchasing the pet", 291 | "required": true, 292 | "schema": { "$ref": "#/definitions/Order" } 293 | } 294 | ], 295 | "responses": { 296 | "200": { 297 | "description": "successful operation", 298 | "schema": { "$ref": "#/definitions/Order" } 299 | }, 300 | "400": { "description": "Invalid Order" } 301 | } 302 | } 303 | }, 304 | "/store/order/{orderId}": { 305 | "get": { 306 | "tags": ["store"], 307 | "summary": "Find purchase order by ID", 308 | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", 309 | "operationId": "getOrderById", 310 | "produces": ["application/xml", "application/json"], 311 | "parameters": [ 312 | { 313 | "name": "orderId", 314 | "in": "path", 315 | "description": "ID of pet that needs to be fetched", 316 | "required": true, 317 | "type": "integer", 318 | "maximum": 10.0, 319 | "minimum": 1.0, 320 | "format": "int64" 321 | } 322 | ], 323 | "responses": { 324 | "200": { 325 | "description": "successful operation", 326 | "schema": { "$ref": "#/definitions/Order" } 327 | }, 328 | "400": { "description": "Invalid ID supplied" }, 329 | "404": { "description": "Order not found" } 330 | } 331 | }, 332 | "delete": { 333 | "tags": ["store"], 334 | "summary": "Delete purchase order by ID", 335 | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", 336 | "operationId": "deleteOrder", 337 | "produces": ["application/xml", "application/json"], 338 | "parameters": [ 339 | { 340 | "name": "orderId", 341 | "in": "path", 342 | "description": "ID of the order that needs to be deleted", 343 | "required": true, 344 | "type": "integer", 345 | "minimum": 1.0, 346 | "format": "int64" 347 | } 348 | ], 349 | "responses": { 350 | "400": { "description": "Invalid ID supplied" }, 351 | "404": { "description": "Order not found" } 352 | } 353 | } 354 | }, 355 | "/user": { 356 | "post": { 357 | "tags": ["user"], 358 | "summary": "Create user", 359 | "description": "This can only be done by the logged in user.", 360 | "operationId": "createUser", 361 | "produces": ["application/xml", "application/json"], 362 | "parameters": [ 363 | { 364 | "in": "body", 365 | "name": "body", 366 | "description": "Created user object", 367 | "required": true, 368 | "schema": { "$ref": "#/definitions/User" } 369 | } 370 | ], 371 | "responses": { "default": { "description": "successful operation" } } 372 | } 373 | }, 374 | "/user/createWithArray": { 375 | "post": { 376 | "tags": ["user"], 377 | "summary": "Creates list of users with given input array", 378 | "description": "", 379 | "operationId": "createUsersWithArrayInput", 380 | "produces": ["application/xml", "application/json"], 381 | "parameters": [ 382 | { 383 | "in": "body", 384 | "name": "body", 385 | "description": "List of user object", 386 | "required": true, 387 | "schema": { "type": "array", "items": { "$ref": "#/definitions/User" } } 388 | } 389 | ], 390 | "responses": { "default": { "description": "successful operation" } } 391 | } 392 | }, 393 | "/user/createWithList": { 394 | "post": { 395 | "tags": ["user"], 396 | "summary": "Creates list of users with given input array", 397 | "description": "", 398 | "operationId": "createUsersWithListInput", 399 | "produces": ["application/xml", "application/json"], 400 | "parameters": [ 401 | { 402 | "in": "body", 403 | "name": "body", 404 | "description": "List of user object", 405 | "required": true, 406 | "schema": { "type": "array", "items": { "$ref": "#/definitions/User" } } 407 | } 408 | ], 409 | "responses": { "default": { "description": "successful operation" } } 410 | } 411 | }, 412 | "/user/login": { 413 | "get": { 414 | "tags": ["user"], 415 | "summary": "Logs user into the system", 416 | "description": "", 417 | "operationId": "loginUser", 418 | "produces": ["application/xml", "application/json"], 419 | "parameters": [ 420 | { 421 | "name": "username", 422 | "in": "query", 423 | "description": "The user name for login", 424 | "required": true, 425 | "type": "string" 426 | }, 427 | { 428 | "name": "password", 429 | "in": "query", 430 | "description": "The password for login in clear text", 431 | "required": true, 432 | "type": "string" 433 | } 434 | ], 435 | "responses": { 436 | "200": { 437 | "description": "successful operation", 438 | "schema": { "type": "string" }, 439 | "headers": { 440 | "X-Rate-Limit": { 441 | "type": "integer", 442 | "format": "int32", 443 | "description": "calls per hour allowed by the user" 444 | }, 445 | "X-Expires-After": { 446 | "type": "string", 447 | "format": "date-time", 448 | "description": "date in UTC when token expires" 449 | } 450 | } 451 | }, 452 | "400": { "description": "Invalid username/password supplied" } 453 | } 454 | } 455 | }, 456 | "/user/logout": { 457 | "get": { 458 | "tags": ["user"], 459 | "summary": "Logs out current logged in user session", 460 | "description": "", 461 | "operationId": "logoutUser", 462 | "produces": ["application/xml", "application/json"], 463 | "parameters": [], 464 | "responses": { "default": { "description": "successful operation" } } 465 | } 466 | }, 467 | "/user/{username}": { 468 | "get": { 469 | "tags": ["user"], 470 | "summary": "Get user by user name", 471 | "description": "", 472 | "operationId": "getUserByName", 473 | "produces": ["application/xml", "application/json"], 474 | "parameters": [ 475 | { 476 | "name": "username", 477 | "in": "path", 478 | "description": "The name that needs to be fetched. Use user1 for testing. ", 479 | "required": true, 480 | "type": "string" 481 | } 482 | ], 483 | "responses": { 484 | "200": { 485 | "description": "successful operation", 486 | "schema": { "$ref": "#/definitions/User" } 487 | }, 488 | "400": { "description": "Invalid username supplied" }, 489 | "404": { "description": "User not found" } 490 | } 491 | }, 492 | "put": { 493 | "tags": ["user"], 494 | "summary": "Updated user", 495 | "description": "This can only be done by the logged in user.", 496 | "operationId": "updateUser", 497 | "produces": ["application/xml", "application/json"], 498 | "parameters": [ 499 | { 500 | "name": "username", 501 | "in": "path", 502 | "description": "name that need to be updated", 503 | "required": true, 504 | "type": "string" 505 | }, 506 | { 507 | "in": "body", 508 | "name": "body", 509 | "description": "Updated user object", 510 | "required": true, 511 | "schema": { "$ref": "#/definitions/User" } 512 | } 513 | ], 514 | "responses": { 515 | "400": { "description": "Invalid user supplied" }, 516 | "404": { "description": "User not found" } 517 | } 518 | }, 519 | "delete": { 520 | "tags": ["user"], 521 | "summary": "Delete user", 522 | "description": "This can only be done by the logged in user.", 523 | "operationId": "deleteUser", 524 | "produces": ["application/xml", "application/json"], 525 | "parameters": [ 526 | { 527 | "name": "username", 528 | "in": "path", 529 | "description": "The name that needs to be deleted", 530 | "required": true, 531 | "type": "string" 532 | } 533 | ], 534 | "responses": { 535 | "400": { "description": "Invalid username supplied" }, 536 | "404": { "description": "User not found" } 537 | } 538 | } 539 | } 540 | }, 541 | "securityDefinitions": { 542 | "petstore_auth": { 543 | "type": "oauth2", 544 | "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", 545 | "flow": "implicit", 546 | "scopes": { "write:pets": "modify pets in your account", "read:pets": "read your pets" } 547 | }, 548 | "api_key": { "type": "apiKey", "name": "api_key", "in": "header" } 549 | }, 550 | "definitions": { 551 | "Order": { 552 | "type": "object", 553 | "properties": { 554 | "id": { "type": "integer", "format": "int64" }, 555 | "petId": { "type": "integer", "format": "int64" }, 556 | "quantity": { "type": "integer", "format": "int32" }, 557 | "shipDate": { "type": "string", "format": "date-time" }, 558 | "status": { 559 | "type": "string", 560 | "description": "Order Status", 561 | "enum": ["placed", "approved", "delivered"] 562 | }, 563 | "complete": { "type": "boolean", "default": false } 564 | }, 565 | "xml": { "name": "Order" } 566 | }, 567 | "User": { 568 | "type": "object", 569 | "properties": { 570 | "id": { "type": "integer", "format": "int64" }, 571 | "username": { "type": "string" }, 572 | "firstName": { "type": "string" }, 573 | "lastName": { "type": "string" }, 574 | "email": { "type": "string" }, 575 | "password": { "type": "string" }, 576 | "phone": { "type": "string" }, 577 | "userStatus": { "type": "integer", "format": "int32", "description": "User Status" } 578 | }, 579 | "xml": { "name": "User" } 580 | }, 581 | "Category": { 582 | "type": "object", 583 | "properties": { 584 | "id": { "type": "integer", "format": "int64" }, 585 | "name": { "type": "string" } 586 | }, 587 | "xml": { "name": "Category" } 588 | }, 589 | "Tag": { 590 | "type": "object", 591 | "properties": { 592 | "id": { "type": "integer", "format": "int64" }, 593 | "name": { "type": "string" } 594 | }, 595 | "xml": { "name": "Tag" } 596 | }, 597 | "Pet": { 598 | "type": "object", 599 | "required": ["name", "photoUrls"], 600 | "properties": { 601 | "id": { "type": "integer", "format": "int64" }, 602 | "category": { "$ref": "#/definitions/Category" }, 603 | "name": { "type": "string", "example": "doggie" }, 604 | "photoUrls": { 605 | "type": "array", 606 | "xml": { "name": "photoUrl", "wrapped": true }, 607 | "items": { "type": "string" } 608 | }, 609 | "tags": { 610 | "type": "array", 611 | "xml": { "name": "tag", "wrapped": true }, 612 | "items": { "$ref": "#/definitions/Tag" } 613 | }, 614 | "status": { 615 | "type": "string", 616 | "description": "pet status in the store", 617 | "enum": ["available", "pending", "sold"] 618 | } 619 | }, 620 | "xml": { "name": "Pet" } 621 | }, 622 | "ApiResponse": { 623 | "type": "object", 624 | "properties": { 625 | "code": { "type": "integer", "format": "int32" }, 626 | "type": { "type": "string" }, 627 | "message": { "type": "string" } 628 | } 629 | } 630 | }, 631 | "externalDocs": { "description": "Find out more about Swagger", "url": "http://swagger.io" } 632 | } 633 | --------------------------------------------------------------------------------