openapi-gen-typescript
Advanced tools
Comparing version 0.3.3 to 0.3.4
@@ -1,2 +0,3 @@ | ||
export declare const AllMethods: string[]; | ||
import { methods } from './utils/type'; | ||
export declare const AllMethods: methods[]; | ||
export declare const SortList: string[]; | ||
@@ -3,0 +4,0 @@ export declare const NotModifyCode = "/* tslint:disable */\n/**\n* This file was automatically generated by openapi-gen-typescript.\n* DO NOT MODIFY IT BY HAND.\n*/\n"; |
@@ -1,19 +0,4 @@ | ||
import { OpenAPI, OpenAPIV3 } from 'openapi-types'; | ||
import { ETemplateCode } from './constants'; | ||
import OperationObject = OpenAPIV3.OperationObject; | ||
declare type PostScriptReturnType = { | ||
[key in ETemplateCode]: string; | ||
} | { | ||
[key: string]: string; | ||
}; | ||
export declare function gen(options: { | ||
url?: string; | ||
path?: string; | ||
version: string; | ||
object?: OpenAPI.Document; | ||
outputDir: string; | ||
fetchModuleFile?: string; | ||
pascalCase?: boolean; | ||
handlePostScript?: (obj: OperationObject, method?: string) => PostScriptReturnType; | ||
}): Promise<void>; | ||
export {}; | ||
import { IGenParmas } from './utils/type'; | ||
export declare function gen(options: IGenParmas): Promise<void>; | ||
export declare const genDirWithPaths: (props: import("./utils/type").IPathsGenProp) => import("./utils/type").IHandelGenPathResult; | ||
export declare const genDirWithTags: (props: import("./utils/type").ITagsGenProp) => import("./utils/type").IHandelGenPathResult; |
@@ -11,371 +11,23 @@ "use strict"; | ||
}; | ||
var __rest = (this && this.__rest) || function (s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
} | ||
return t; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.gen = void 0; | ||
// @ts-ignore | ||
const swagger2openapi = require("swagger2openapi"); | ||
const swaggerParser = require("@apidevtools/swagger-parser"); | ||
// @ts-ignore | ||
const mkdirp = require("mkdirp"); | ||
const camelcase = require("camelcase"); | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
const transform_1 = require("./schemaToTypes/transform"); | ||
const util_1 = require("./util"); | ||
const constants_1 = require("./constants"); | ||
const axios_1 = require("axios"); | ||
const _ = require("lodash"); | ||
function getCamelcase(urlPath, options) { | ||
return camelcase(urlPath.split('/').join('_'), options); | ||
} | ||
function getCodeFromParameter(parameter, name) { | ||
const { description, required } = parameter; | ||
let code = ''; | ||
if (description) { | ||
code += `/* ${description} */\n`; | ||
} | ||
code += `${name}${!!required ? '' : '?'}: string;`; | ||
return code; | ||
} | ||
function getCodeFromParameters(parameters, name, exportKey = false) { | ||
exports.genDirWithTags = exports.genDirWithPaths = exports.gen = void 0; | ||
const genCodes_1 = require("./utils/genCodes"); | ||
const fileStream_1 = require("./utils/fileStream"); | ||
const getFilePath_1 = require("./utils/getFilePath"); | ||
const getInterfaceInfo_1 = require("./utils/getInterfaceInfo"); | ||
function gen(options) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!parameters) { | ||
return ''; | ||
} | ||
const bodyCode = yield Promise.all(Object.keys(parameters).map(parameterName => { | ||
return getCodeFromParameter(parameters[parameterName], parameterName); | ||
})); | ||
return `${exportKey ? 'export' : ''} interface ${name} {\n${bodyCode.join('\n')}\n}`; | ||
}); | ||
} | ||
function getCodeFromContent(content, typeNamePrefix, comment = '', responseTypeNames = []) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (!content) { | ||
return ''; | ||
} | ||
const contentCode = yield Promise.all(Object.keys(content).map((mediaType, index) => __awaiter(this, void 0, void 0, function* () { | ||
const responseTypeName = `${typeNamePrefix}${index > 0 ? getCamelcase(mediaType, { pascalCase: true }) : ''}`; | ||
let jsonSchema = transform_1.transform(content[mediaType].schema); | ||
if (jsonSchema.lastIndexOf('[]') === jsonSchema.length - 2) { | ||
jsonSchema = jsonSchema.replace(/\(|\)|(\[\])+/g, ''); | ||
responseTypeNames.push(`${responseTypeName}[]`); | ||
} | ||
else if (/^\(([\s\S]+)\)$/.test(jsonSchema)) { | ||
jsonSchema = jsonSchema.replace(/^\(([\s\S]+)\)$/, '$1'); | ||
responseTypeNames.push(responseTypeName); | ||
} | ||
else { | ||
responseTypeNames.push(responseTypeName); | ||
} | ||
return `export type ${responseTypeName} = ${jsonSchema}`; | ||
}))); | ||
return contentCode.join('\n'); | ||
}); | ||
} | ||
function getTagWithPaths(allTags, pathsMap) { | ||
const commonTag = { | ||
name: 'common', | ||
description: 'common tag', | ||
}; | ||
const customTags = allTags ? allTags.concat([commonTag]) : [commonTag]; | ||
const commonfilterPaths = Object.keys(pathsMap).filter(namespaceName => { | ||
let filter = true; | ||
customTags.forEach(currTag => { | ||
if (pathsMap[namespaceName].tags.includes(currTag.name)) { | ||
filter = false; | ||
} | ||
const { fetchModuleFile = `${__dirname}/defaultFetch.ts`, outputDir, pascalCase = true, } = options; | ||
const openApiData = yield getInterfaceInfo_1.getOpenApiDoc(options); | ||
const { fileCodeList, pathsCode } = yield genCodes_1.genCodes({ openApiData, options }); | ||
const { schemasClassCode, schemasTypesCode } = getInterfaceInfo_1.handleSchema({ pascalCase, openApiData }); | ||
yield fileStream_1.deleteFolderRecursive(outputDir); | ||
yield fileStream_1.writeFileFromIFileCode({ | ||
outputDir, | ||
fileCodeList, | ||
fetchModuleFile, | ||
schemasClassCode, | ||
schemasTypesCode, | ||
pathsCode, | ||
}); | ||
return filter; | ||
}); | ||
return customTags.map(currTag => { | ||
const pathsInCurrTag = {}; | ||
let filterPaths = Object.keys(pathsMap).filter(namespaceName => pathsMap[namespaceName].tags.includes(currTag.name)); | ||
if (currTag.name === 'common') { | ||
filterPaths = filterPaths.concat(commonfilterPaths); | ||
} | ||
filterPaths.map(namespaceName => { | ||
pathsInCurrTag[namespaceName] = pathsMap[namespaceName]; | ||
}); | ||
return Object.assign(Object.assign({}, currTag), { pathsInCurrTag }); | ||
}); | ||
} | ||
function getContentFromComponents(openApiData, ref, typename, arr) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const splitRef = ref.replace(/^[^/]+\/components\//, '').split('/'); | ||
const result = _.get(openApiData.components, splitRef.join('.')); | ||
const { content, description } = result; | ||
const requestBodyCode = yield getCodeFromContent(content, typename, description, arr); | ||
return requestBodyCode; | ||
}); | ||
} | ||
function gen(options) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { url, path: filePath, version, object, fetchModuleFile = `${__dirname}/defaultFetch.ts`, outputDir, pascalCase = true, } = options; | ||
let openApiData; | ||
if (url || filePath || object) { | ||
const { dereference, parse } = swaggerParser; | ||
// convertUrl响应速度很慢,改为使用convertObj | ||
const { convertObj, convertFile } = swagger2openapi; | ||
let params; | ||
let openapi; | ||
if (version === '2') { | ||
if (url) { | ||
try { | ||
const result = yield axios_1.default.get(url); | ||
if (result.status !== 200) { | ||
throw Error(`未返回正确的status code ${result.status}: ${url}`); | ||
} | ||
params = result.data; | ||
} | ||
catch (e) { | ||
console.error('e :>> ', e.message); | ||
} | ||
openapi = yield convertObj(params, { | ||
patch: true, | ||
}); | ||
} | ||
if (filePath) { | ||
params = filePath; | ||
openapi = yield convertFile(params, { | ||
patch: true, | ||
}); | ||
} | ||
if (object) { | ||
params = object; | ||
openapi = yield convertObj(params, { | ||
patch: true, | ||
}); | ||
} | ||
openApiData = openapi.openapi || (yield dereference(openapi.openapi)); | ||
} | ||
else { | ||
openApiData = (yield parse(params)); | ||
} | ||
} | ||
else { | ||
throw 'option: url or filePath or object must be specified one'; | ||
} | ||
let baseUrl = ''; | ||
if (openApiData.servers) { | ||
baseUrl = openApiData.servers[0].url; | ||
} | ||
const schemasTypesCode = []; | ||
const schemasClassCode = []; | ||
const { schemas } = openApiData.components || {}; | ||
if (schemas) { | ||
Object.keys(schemas).forEach(schemaKey => { | ||
const schemaObject = schemas[schemaKey]; | ||
if (pascalCase) { | ||
schemaKey = camelcase(schemaKey, { pascalCase: true }); | ||
} | ||
const transformObject = transform_1.transform(schemaObject); | ||
schemasTypesCode.push(`export type ${schemaKey} = ${transformObject}`); | ||
const classObject = transformObject.replace(/[()]/g, '').replace(/components.schemas./g, ''); | ||
schemasClassCode.push(`export class ${schemaKey} ${classObject}\n`); | ||
}); | ||
} | ||
const { paths, tags: allTags } = openApiData; | ||
const pathsCode = []; | ||
const pathsMap = {}; | ||
yield Promise.all(Object.keys(paths).map((urlPath) => __awaiter(this, void 0, void 0, function* () { | ||
const pathsObject = paths[urlPath]; | ||
const filterMethods = constants_1.AllMethods.filter(method => !!pathsObject[method]); | ||
const pathsTypesCode = []; | ||
yield Promise.all(filterMethods.map((method) => __awaiter(this, void 0, void 0, function* () { | ||
const objectElement = pathsObject[method]; | ||
const { operationId, parameters = [], requestBody = {}, responses, summary, tags, } = objectElement; | ||
let namespaceName = operationId || | ||
`${method.toLowerCase()}${getCamelcase(urlPath, { | ||
pascalCase: true, | ||
})}`; | ||
namespaceName = camelcase(namespaceName.replace(/[^a-zA-Z0-9_]/g, ''), { | ||
pascalCase: true, | ||
}); | ||
// request parameter | ||
const requestPath = {}; | ||
const requestHeaders = {}; | ||
const requestCookies = {}; | ||
const requestQuery = {}; | ||
parameters.forEach(parameter => { | ||
const _a = parameter, { in: keyIn, name } = _a, otherParams = __rest(_a, ["in", "name"]); | ||
switch (keyIn) { | ||
case 'path': | ||
requestPath[name] = otherParams; | ||
break; | ||
case 'query': | ||
requestQuery[name] = otherParams; | ||
break; | ||
case 'cookie': | ||
requestCookies[name] = otherParams; | ||
break; | ||
case 'header': | ||
if (['CONTENT-TYPE', 'COOKIE'].indexOf(name.toUpperCase()) === -1) { | ||
requestHeaders[name] = otherParams; | ||
} | ||
break; | ||
} | ||
}); | ||
const requestPathCode = yield getCodeFromParameters(requestPath, 'Path', true); | ||
const requestQueryCode = yield getCodeFromParameters(requestQuery, 'Query', true); | ||
const requestCookieCode = yield getCodeFromParameters(requestCookies, 'Cookie', true); | ||
const requestHeaderCode = yield getCodeFromParameters(requestHeaders, 'RequestHeader', true); | ||
// request body | ||
const { $ref: requestRef, content, required: requestBodyRequired, description: requestBodyDescription, } = requestBody; | ||
let requestBodyCode = ''; | ||
const requestBodyTypeNames = []; | ||
if (requestRef) { | ||
requestBodyCode = yield getContentFromComponents(openApiData, requestRef, `Body`, requestBodyTypeNames); | ||
} | ||
else { | ||
requestBodyCode = yield getCodeFromContent(content, `Body`, requestBodyDescription, requestBodyTypeNames); | ||
} | ||
// response | ||
const responseTypeNames = []; | ||
const responsesArr = Object.keys(responses); | ||
const responsesCode = (yield Promise.all(responsesArr.map((statusCode) => __awaiter(this, void 0, void 0, function* () { | ||
const responsesObjectElement = responses[statusCode]; | ||
const { $ref, content, description } = responsesObjectElement; | ||
const typeNamePrefix = `Response${camelcase(statusCode, { | ||
pascalCase: true, | ||
})}`; | ||
if ($ref) { | ||
const responseCode = yield getContentFromComponents(openApiData, requestRef, typeNamePrefix, requestBodyTypeNames); | ||
return responseCode; | ||
} | ||
else { | ||
// response | ||
const responseCode = yield getCodeFromContent(content, typeNamePrefix, description, responseTypeNames); | ||
return responseCode; | ||
} | ||
})))).join('\n'); | ||
const requestFuncTypeCode = ` | ||
export const request = async (options: { | ||
path?: Path; | ||
query?: Query; | ||
body${requestBodyRequired ? '' : '?'}: ${requestBodyTypeNames.length > 0 ? requestBodyTypeNames.join('|') : 'any'}; | ||
headers?: RequestHeader; | ||
cookie?: Cookie; | ||
}, otherOptions?: any): Promise<{ body: ${responseTypeNames.length > 0 ? responseTypeNames.join('|') : 'any'} }> => { | ||
let resolvedUrl = '${(baseUrl + urlPath).replace('//', '/')}'; | ||
${_.isEmpty(requestPath) | ||
? '' | ||
: `if (!!options.path) { | ||
Object.keys(options.path).map(key => { | ||
const regex = new RegExp(\`({(\${key})})|(:(\${key}))\`, 'g'); | ||
resolvedUrl = url.replace(regex, options.path[key]); | ||
}); | ||
}`} | ||
return fetchImpl({ | ||
url: resolvedUrl, | ||
method: '${method.toLowerCase()}', | ||
...options, | ||
...otherOptions | ||
}); | ||
}; | ||
`; | ||
const requestUrl = `export const url = \`${(baseUrl + urlPath).replace('//', '/')}\``; | ||
let exportObj = { | ||
requestUrl, | ||
requestPathCode, | ||
requestQueryCode, | ||
requestHeaderCode, | ||
requestCookieCode, | ||
requestBodyCode, | ||
responsesCode, | ||
requestFuncTypeCode, | ||
}; | ||
if (options.handlePostScript) { | ||
const result = yield options.handlePostScript(objectElement, method); | ||
exportObj = Object.assign({}, exportObj, result); | ||
} | ||
const exportArr = []; | ||
constants_1.SortList.forEach(item => { | ||
exportArr.push(exportObj[item]); | ||
}); | ||
Object.keys(exportObj).forEach(item => { | ||
if (!constants_1.SortList.includes(item)) { | ||
exportArr.unshift(exportObj[item]); | ||
} | ||
}); | ||
const pathsTypesArr = exportArr.map(exp => { | ||
return exp | ||
.replace(/export const request = async/, 'export type request =') | ||
.replace(/: Promise<([^>]+)>((\s|\S)+)/g, '=> Promise<$1>;'); | ||
}); | ||
pathsTypesCode.push(`export namespace ${namespaceName} {\n${pathsTypesArr.join('\n')}\n}`); | ||
const generateClassArr = exportArr.map(exp => { | ||
const exp1 = exp.replace(/ interface | type = /g, ' class '); | ||
const exp2 = exp1.replace(/ type ([^=]+) = components.([a-zA-Z0-9._]+)[;{}]?/g, ' class $1 extends $2 {}'); | ||
const exp3 = exp2.replace(/ type ([^=]+) = {/g, ' class $1 {'); | ||
const exp4 = exp3.replace(/components.schemas/g, 'schemas'); | ||
return exp4; | ||
}); | ||
pathsMap[namespaceName] = { | ||
summary, | ||
tags: tags || [], | ||
code: generateClassArr.join('\n'), | ||
}; | ||
}))); | ||
pathsCode.push(pathsTypesCode.join('\n')); | ||
}))); | ||
yield util_1.deleteFolderRecursive(outputDir); | ||
// generate code | ||
yield mkdirp(outputDir); | ||
const tagWithPaths = getTagWithPaths(allTags, pathsMap); | ||
yield Promise.all(tagWithPaths.map((currTag) => __awaiter(this, void 0, void 0, function* () { | ||
const currMap = currTag.pathsInCurrTag; | ||
if (Object.keys(currMap).length > 0) { | ||
const currTagNameDir = `${outputDir}/${currTag.name}`; | ||
yield mkdirp(currTagNameDir); | ||
const namespaceNameArr = []; | ||
Object.keys(currMap).map((namespaceName) => { | ||
const { summary, code } = currMap[namespaceName]; | ||
const pathCode = [ | ||
`/** | ||
* @namespace ${namespaceName} | ||
* @summary ${summary} | ||
*/\n`, | ||
`import fetchImpl from '${path | ||
.relative(currTagNameDir, fetchModuleFile) | ||
.replace(/\.ts$/, '')}';` | ||
.split(path.sep) | ||
.join('/'), | ||
schemasClassCode.length > 0 ? `import * as schemas from '../schemas';\n` : '\n', | ||
code, | ||
].join('\n'); | ||
namespaceNameArr.push(namespaceName); | ||
fs.writeFileSync(`${currTagNameDir}/${namespaceName}.ts`, util_1.format(pathCode)); | ||
}); | ||
const tagCode = [ | ||
`/** | ||
* @description ${currTag.description} | ||
*/\n`, | ||
...namespaceNameArr.map(key => `import * as ${key} from './${key}';`), | ||
`\nexport { | ||
${namespaceNameArr.join(',\n')} | ||
}`, | ||
].join('\n'); | ||
fs.writeFileSync(`${currTagNameDir}/index.ts`, util_1.format(tagCode)); | ||
} | ||
}))); | ||
const typesCode = [ | ||
constants_1.NotModifyCode, | ||
`export namespace components { export namespace schemas { ${schemasTypesCode.join('\n')} } } `, | ||
`export namespace Api { ${pathsCode.join('\n')} } `, | ||
].join('\n'); | ||
fs.writeFileSync(`${outputDir}/index.ts`, util_1.format(typesCode)); | ||
if (schemasClassCode.length > 0) { | ||
const schemasCode = [constants_1.NotModifyCode, schemasClassCode.join('\n')].join('\n'); | ||
fs.writeFileSync(`${outputDir}/schemas.ts`, util_1.format(schemasCode)); | ||
} | ||
console.info(`Generate code successful in directory: ${outputDir}`); | ||
@@ -385,1 +37,3 @@ }); | ||
exports.gen = gen; | ||
exports.genDirWithPaths = getFilePath_1.genPaths; | ||
exports.genDirWithTags = getFilePath_1.genTags; |
{ | ||
"name": "openapi-gen-typescript", | ||
"version": "0.3.3", | ||
"version": "0.3.4", | ||
"main": "dist/index.js", | ||
@@ -5,0 +5,0 @@ "types": "dist/index.d.ts", |
@@ -22,5 +22,5 @@ # openapi-gen-typescript | ||
| object | The docs of fetch openapi or swagger data | | ||
| version | The version of Swagger or OpenApi, example: `2`, `3` | | ||
| outputDir | Dir of output files | | ||
| fetchModuleFile | Fetch impl file path | | ||
| handleGenPath | Processing generated paths | | ||
| handlePostScript | post script to customize the result | |
@@ -1,3 +0,5 @@ | ||
export const AllMethods: string[] = ['get', 'post', 'options', 'put', 'delete', 'patch', 'head']; | ||
import { methods } from './utils/type'; | ||
export const AllMethods: methods[] = ['get', 'post', 'options', 'put', 'delete', 'patch', 'head']; | ||
export const SortList = [ | ||
@@ -4,0 +6,0 @@ 'requestUrl', |
532
src/index.ts
// @ts-ignore | ||
import * as swagger2openapi from 'swagger2openapi'; | ||
import * as swaggerParser from '@apidevtools/swagger-parser'; | ||
// @ts-ignore | ||
import * as mkdirp from 'mkdirp'; | ||
import * as camelcase from 'camelcase'; | ||
import { Options } from 'camelcase'; | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
import { transform } from './schemaToTypes/transform'; | ||
import { format, deleteFolderRecursive } from './util'; | ||
import { IJsonSchema, OpenAPI, OpenAPIV3 } from 'openapi-types'; | ||
import { AllMethods, SortList, NotModifyCode, ETemplateCode } from './constants'; | ||
import ParameterBaseObject = OpenAPIV3.ParameterBaseObject; | ||
import MediaTypeObject = OpenAPIV3.MediaTypeObject; | ||
import OperationObject = OpenAPIV3.OperationObject; | ||
import PathItemObject = OpenAPIV3.PathItemObject; | ||
import ResponseObject = OpenAPIV3.ResponseObject; | ||
import ReferenceObject = OpenAPIV3.ReferenceObject; | ||
import ParameterObject = OpenAPIV3.ParameterObject; | ||
import RequestBodyObject = OpenAPIV3.RequestBodyObject; | ||
import Axios from 'axios'; | ||
import { OpenAPIV3 } from 'openapi-types'; | ||
import * as _ from 'lodash'; | ||
import { IGenParmas } from './utils/type'; | ||
import { genCodes } from './utils/genCodes'; | ||
import { deleteFolderRecursive, writeFileFromIFileCode } from './utils/fileStream'; | ||
import { genPaths, genTags } from './utils/getFilePath'; | ||
import { getOpenApiDoc, handleSchema } from './utils/getInterfaceInfo'; | ||
type ContentObject = { | ||
[media: string]: MediaTypeObject; | ||
}; | ||
type PostScriptReturnType = | ||
| { | ||
[key in ETemplateCode]: string; | ||
} | ||
| { | ||
[key: string]: string; | ||
}; | ||
function getCamelcase(urlPath: string, options?: Options): string { | ||
return camelcase(urlPath.split('/').join('_'), options); | ||
} | ||
function getCodeFromParameter(parameter: ParameterBaseObject, name: string): string { | ||
const { description, required } = parameter; | ||
let code = ''; | ||
if (description) { | ||
code += `/* ${description} */\n`; | ||
} | ||
code += `${name}${!!required ? '' : '?'}: string;`; | ||
return code; | ||
} | ||
interface IParameterMap { | ||
[name: string]: ParameterBaseObject; | ||
} | ||
interface IPathMapContent { | ||
summary: string | undefined; | ||
tags: string[]; | ||
code: string; | ||
} | ||
interface IPathMap { | ||
[key: string]: IPathMapContent; | ||
} | ||
async function getCodeFromParameters( | ||
parameters: IParameterMap | undefined, | ||
name: string, | ||
exportKey: boolean = false, | ||
): Promise<string> { | ||
if (!parameters) { | ||
return ''; | ||
} | ||
const bodyCode = await Promise.all( | ||
Object.keys(parameters).map(parameterName => { | ||
return getCodeFromParameter(parameters[parameterName], parameterName); | ||
}), | ||
); | ||
return `${exportKey ? 'export' : ''} interface ${name} {\n${bodyCode.join('\n')}\n}`; | ||
} | ||
async function getCodeFromContent( | ||
content: ContentObject, | ||
typeNamePrefix: string, | ||
comment: string = '', | ||
responseTypeNames: string[] = [], | ||
): Promise<string> { | ||
if (!content) { | ||
return ''; | ||
} | ||
const contentCode = await Promise.all( | ||
Object.keys(content).map(async (mediaType, index) => { | ||
const responseTypeName = `${typeNamePrefix}${ | ||
index > 0 ? getCamelcase(mediaType, { pascalCase: true }) : '' | ||
}`; | ||
let jsonSchema = transform((content[mediaType] as MediaTypeObject).schema as IJsonSchema); | ||
if (jsonSchema.lastIndexOf('[]') === jsonSchema.length - 2) { | ||
jsonSchema = jsonSchema.replace(/\(|\)|(\[\])+/g, ''); | ||
responseTypeNames.push(`${responseTypeName}[]`); | ||
} else if (/^\(([\s\S]+)\)$/.test(jsonSchema)) { | ||
jsonSchema = jsonSchema.replace(/^\(([\s\S]+)\)$/, '$1'); | ||
responseTypeNames.push(responseTypeName); | ||
} else { | ||
responseTypeNames.push(responseTypeName); | ||
} | ||
return `export type ${responseTypeName} = ${jsonSchema}`; | ||
}), | ||
); | ||
return contentCode.join('\n'); | ||
} | ||
function getTagWithPaths(allTags: OpenAPIV3.TagObject[] | undefined, pathsMap: IPathMap) { | ||
const commonTag: OpenAPIV3.TagObject = { | ||
name: 'common', | ||
description: 'common tag', | ||
}; | ||
const customTags = allTags ? allTags.concat([commonTag]) : [commonTag]; | ||
const commonfilterPaths = Object.keys(pathsMap).filter(namespaceName => { | ||
let filter = true; | ||
customTags.forEach(currTag => { | ||
if (pathsMap[namespaceName].tags.includes(currTag.name)) { | ||
filter = false; | ||
} | ||
}); | ||
return filter; | ||
}); | ||
return customTags.map(currTag => { | ||
const pathsInCurrTag: { [key: string]: any } = {}; | ||
let filterPaths = Object.keys(pathsMap).filter(namespaceName => | ||
pathsMap[namespaceName].tags.includes(currTag.name), | ||
); | ||
if (currTag.name === 'common') { | ||
filterPaths = filterPaths.concat(commonfilterPaths); | ||
} | ||
filterPaths.map(namespaceName => { | ||
pathsInCurrTag[namespaceName] = pathsMap[namespaceName]; | ||
}); | ||
return { | ||
...currTag, | ||
pathsInCurrTag, | ||
}; | ||
}); | ||
} | ||
async function getContentFromComponents( | ||
openApiData: OpenAPIV3.Document, | ||
ref: string, | ||
typename: string, | ||
arr: string[], | ||
): Promise<string> { | ||
const splitRef = ref.replace(/^[^/]+\/components\//, '').split('/'); | ||
const result = _.get(openApiData.components, splitRef.join('.')); | ||
const { content, description }: ReferenceObject & RequestBodyObject = result as any; | ||
const requestBodyCode = await getCodeFromContent(content, typename, description, arr); | ||
return requestBodyCode; | ||
} | ||
export async function gen(options: { | ||
url?: string; | ||
path?: string; | ||
version: string; | ||
object?: OpenAPI.Document; | ||
// dir of output files | ||
outputDir: string; | ||
// fetch impl file path | ||
fetchModuleFile?: string; | ||
pascalCase?: boolean; | ||
handlePostScript?: (obj: OperationObject, method?: string) => PostScriptReturnType; | ||
}) { | ||
export async function gen(options: IGenParmas) { | ||
const { | ||
url, | ||
path: filePath, | ||
version, | ||
object, | ||
fetchModuleFile = `${__dirname}/defaultFetch.ts`, | ||
@@ -185,347 +20,24 @@ outputDir, | ||
let openApiData: OpenAPIV3.Document; | ||
if (url || filePath || object) { | ||
const { dereference, parse } = swaggerParser; | ||
// convertUrl响应速度很慢,改为使用convertObj | ||
const { convertObj, convertFile } = swagger2openapi; | ||
let params: any; | ||
let openapi: any; | ||
if (version === '2') { | ||
if (url) { | ||
try { | ||
const result = await Axios.get(url); | ||
if (result.status !== 200) { | ||
throw Error(`未返回正确的status code ${result.status}: ${url}`); | ||
} | ||
params = result.data; | ||
} catch (e) { | ||
console.error('e :>> ', e.message); | ||
} | ||
openapi = await convertObj(params, { | ||
patch: true, | ||
}); | ||
} | ||
if (filePath) { | ||
params = filePath; | ||
openapi = await convertFile(params, { | ||
patch: true, | ||
}); | ||
} | ||
if (object) { | ||
params = object; | ||
openapi = await convertObj(params, { | ||
patch: true, | ||
}); | ||
} | ||
openApiData = openapi.openapi || (await dereference(openapi.openapi)); | ||
} else { | ||
openApiData = (await parse(params)) as OpenAPIV3.Document; | ||
} | ||
} else { | ||
throw 'option: url or filePath or object must be specified one'; | ||
} | ||
const openApiData: OpenAPIV3.Document = await getOpenApiDoc(options); | ||
let baseUrl = ''; | ||
if (openApiData.servers) { | ||
baseUrl = openApiData.servers[0].url; | ||
} | ||
const { fileCodeList, pathsCode } = await genCodes({ openApiData, options }); | ||
const schemasTypesCode: string[] = []; | ||
const schemasClassCode: string[] = []; | ||
const { schemas } = openApiData.components || {}; | ||
if (schemas) { | ||
Object.keys(schemas).forEach(schemaKey => { | ||
const schemaObject = schemas[schemaKey] as IJsonSchema; | ||
if (pascalCase) { | ||
schemaKey = camelcase(schemaKey, { pascalCase: true }); | ||
} | ||
const transformObject = transform(schemaObject); | ||
schemasTypesCode.push(`export type ${schemaKey} = ${transformObject}`); | ||
const classObject = transformObject.replace(/[()]/g, '').replace(/components.schemas./g, ''); | ||
schemasClassCode.push(`export class ${schemaKey} ${classObject}\n`); | ||
}); | ||
} | ||
const { schemasClassCode, schemasTypesCode } = handleSchema({ pascalCase, openApiData }); | ||
const { paths, tags: allTags } = openApiData; | ||
const pathsCode: string[] = []; | ||
const pathsMap: IPathMap = {}; | ||
await Promise.all( | ||
Object.keys(paths).map(async urlPath => { | ||
const pathsObject: PathItemObject = paths[urlPath] as any; | ||
const filterMethods = AllMethods.filter(method => !!(pathsObject as any)[method]); | ||
const pathsTypesCode: string[] = []; | ||
await Promise.all( | ||
filterMethods.map(async method => { | ||
const objectElement: OperationObject = (pathsObject as any)[method] as OperationObject; | ||
const { | ||
operationId, | ||
parameters = [], | ||
requestBody = {}, | ||
responses, | ||
summary, | ||
tags, | ||
} = objectElement; | ||
let namespaceName = | ||
operationId || | ||
`${method.toLowerCase()}${getCamelcase(urlPath, { | ||
pascalCase: true, | ||
})}`; | ||
namespaceName = camelcase(namespaceName.replace(/[^a-zA-Z0-9_]/g, ''), { | ||
pascalCase: true, | ||
}); | ||
// request parameter | ||
const requestPath: IParameterMap = {}; | ||
const requestHeaders: IParameterMap = {}; | ||
const requestCookies: IParameterMap = {}; | ||
const requestQuery: IParameterMap = {}; | ||
parameters.forEach(parameter => { | ||
const { in: keyIn, name, ...otherParams } = parameter as ParameterObject; | ||
switch (keyIn) { | ||
case 'path': | ||
requestPath[name] = otherParams; | ||
break; | ||
case 'query': | ||
requestQuery[name] = otherParams; | ||
break; | ||
case 'cookie': | ||
requestCookies[name] = otherParams; | ||
break; | ||
case 'header': | ||
if (['CONTENT-TYPE', 'COOKIE'].indexOf(name.toUpperCase()) === -1) { | ||
requestHeaders[name] = otherParams; | ||
} | ||
break; | ||
} | ||
}); | ||
const requestPathCode = await getCodeFromParameters(requestPath, 'Path', true); | ||
const requestQueryCode = await getCodeFromParameters(requestQuery, 'Query', true); | ||
const requestCookieCode = await getCodeFromParameters(requestCookies, 'Cookie', true); | ||
const requestHeaderCode = await getCodeFromParameters( | ||
requestHeaders, | ||
'RequestHeader', | ||
true, | ||
); | ||
// request body | ||
const { | ||
$ref: requestRef, | ||
content, | ||
required: requestBodyRequired, | ||
description: requestBodyDescription, | ||
}: ReferenceObject & RequestBodyObject = requestBody as any; | ||
let requestBodyCode = ''; | ||
const requestBodyTypeNames: string[] = []; | ||
if (requestRef) { | ||
requestBodyCode = await getContentFromComponents( | ||
openApiData, | ||
requestRef, | ||
`Body`, | ||
requestBodyTypeNames, | ||
); | ||
} else { | ||
requestBodyCode = await getCodeFromContent( | ||
content, | ||
`Body`, | ||
requestBodyDescription, | ||
requestBodyTypeNames, | ||
); | ||
} | ||
// response | ||
const responseTypeNames: string[] = []; | ||
const responsesArr = Object.keys(responses as Object); | ||
const responsesCode = ( | ||
await Promise.all( | ||
responsesArr.map(async statusCode => { | ||
const responsesObjectElement: ResponseObject & ReferenceObject = (responses as any)[ | ||
statusCode | ||
]; | ||
const { $ref, content, description } = responsesObjectElement; | ||
const typeNamePrefix = `Response${camelcase(statusCode, { | ||
pascalCase: true, | ||
})}`; | ||
if ($ref) { | ||
const responseCode = await getContentFromComponents( | ||
openApiData, | ||
requestRef, | ||
typeNamePrefix, | ||
requestBodyTypeNames, | ||
); | ||
return responseCode; | ||
} else { | ||
// response | ||
const responseCode = await getCodeFromContent( | ||
content as ContentObject, | ||
typeNamePrefix, | ||
description, | ||
responseTypeNames, | ||
); | ||
return responseCode; | ||
} | ||
}), | ||
) | ||
).join('\n'); | ||
const requestFuncTypeCode = ` | ||
export const request = async (options: { | ||
path?: Path; | ||
query?: Query; | ||
body${requestBodyRequired ? '' : '?'}: ${ | ||
requestBodyTypeNames.length > 0 ? requestBodyTypeNames.join('|') : 'any' | ||
}; | ||
headers?: RequestHeader; | ||
cookie?: Cookie; | ||
}, otherOptions?: any): Promise<{ body: ${ | ||
responseTypeNames.length > 0 ? responseTypeNames.join('|') : 'any' | ||
} }> => { | ||
let resolvedUrl = '${(baseUrl + urlPath).replace('//', '/')}'; | ||
${ | ||
_.isEmpty(requestPath) | ||
? '' | ||
: `if (!!options.path) { | ||
Object.keys(options.path).map(key => { | ||
const regex = new RegExp(\`({(\${key})})|(:(\${key}))\`, 'g'); | ||
resolvedUrl = url.replace(regex, options.path[key]); | ||
}); | ||
}` | ||
} | ||
return fetchImpl({ | ||
url: resolvedUrl, | ||
method: '${method.toLowerCase()}', | ||
...options, | ||
...otherOptions | ||
}); | ||
}; | ||
`; | ||
const requestUrl = `export const url = \`${(baseUrl + urlPath).replace('//', '/')}\``; | ||
let exportObj: { [key: string]: string } = { | ||
requestUrl, | ||
requestPathCode, | ||
requestQueryCode, | ||
requestHeaderCode, | ||
requestCookieCode, | ||
requestBodyCode, | ||
responsesCode, | ||
requestFuncTypeCode, | ||
}; | ||
if (options.handlePostScript) { | ||
const result = await options.handlePostScript(objectElement, method); | ||
exportObj = Object.assign({}, exportObj, result); | ||
} | ||
const exportArr: string[] = []; | ||
SortList.forEach(item => { | ||
exportArr.push(exportObj[item]); | ||
}); | ||
Object.keys(exportObj).forEach(item => { | ||
if (!SortList.includes(item)) { | ||
exportArr.unshift(exportObj[item]); | ||
} | ||
}); | ||
const pathsTypesArr = exportArr.map(exp => { | ||
return exp | ||
.replace(/export const request = async/, 'export type request =') | ||
.replace(/: Promise<([^>]+)>((\s|\S)+)/g, '=> Promise<$1>;'); | ||
}); | ||
pathsTypesCode.push( | ||
`export namespace ${namespaceName} {\n${pathsTypesArr.join('\n')}\n}`, | ||
); | ||
const generateClassArr = exportArr.map(exp => { | ||
const exp1 = exp.replace(/ interface | type = /g, ' class '); | ||
const exp2 = exp1.replace( | ||
/ type ([^=]+) = components.([a-zA-Z0-9._]+)[;{}]?/g, | ||
' class $1 extends $2 {}', | ||
); | ||
const exp3 = exp2.replace(/ type ([^=]+) = {/g, ' class $1 {'); | ||
const exp4 = exp3.replace(/components.schemas/g, 'schemas'); | ||
return exp4; | ||
}); | ||
pathsMap[namespaceName] = { | ||
summary, | ||
tags: tags || [], | ||
code: generateClassArr.join('\n'), | ||
}; | ||
}), | ||
); | ||
pathsCode.push(pathsTypesCode.join('\n')); | ||
}), | ||
); | ||
await deleteFolderRecursive(outputDir); | ||
// generate code | ||
await mkdirp(outputDir); | ||
await writeFileFromIFileCode({ | ||
outputDir, | ||
fileCodeList, | ||
fetchModuleFile, | ||
schemasClassCode, | ||
schemasTypesCode, | ||
pathsCode, | ||
}); | ||
const tagWithPaths = getTagWithPaths(allTags, pathsMap); | ||
console.info(`Generate code successful in directory: ${outputDir}`); | ||
} | ||
await Promise.all( | ||
tagWithPaths.map(async currTag => { | ||
const currMap = currTag.pathsInCurrTag; | ||
if (Object.keys(currMap).length > 0) { | ||
const currTagNameDir = `${outputDir}/${currTag.name}`; | ||
await mkdirp(currTagNameDir); | ||
const namespaceNameArr: string[] = []; | ||
Object.keys(currMap).map((namespaceName: string) => { | ||
const { summary, code } = currMap[namespaceName]; | ||
const pathCode = [ | ||
`/** | ||
* @namespace ${namespaceName} | ||
* @summary ${summary} | ||
*/\n`, | ||
`import fetchImpl from '${path | ||
.relative(currTagNameDir, fetchModuleFile) | ||
.replace(/\.ts$/, '')}';` | ||
.split(path.sep) | ||
.join('/'), | ||
schemasClassCode.length > 0 ? `import * as schemas from '../schemas';\n` : '\n', | ||
code, | ||
].join('\n'); | ||
namespaceNameArr.push(namespaceName); | ||
fs.writeFileSync(`${currTagNameDir}/${namespaceName}.ts`, format(pathCode)); | ||
}); | ||
const tagCode = [ | ||
`/** | ||
* @description ${currTag.description} | ||
*/\n`, | ||
...namespaceNameArr.map(key => `import * as ${key} from './${key}';`), | ||
`\nexport { | ||
${namespaceNameArr.join(',\n')} | ||
}`, | ||
].join('\n'); | ||
export const genDirWithPaths = genPaths; | ||
fs.writeFileSync(`${currTagNameDir}/index.ts`, format(tagCode)); | ||
} | ||
}), | ||
); | ||
const typesCode = [ | ||
NotModifyCode, | ||
`export namespace components { export namespace schemas { ${schemasTypesCode.join('\n')} } } `, | ||
`export namespace Api { ${pathsCode.join('\n')} } `, | ||
].join('\n'); | ||
fs.writeFileSync(`${outputDir}/index.ts`, format(typesCode)); | ||
if (schemasClassCode.length > 0) { | ||
const schemasCode = [NotModifyCode, schemasClassCode.join('\n')].join('\n'); | ||
fs.writeFileSync(`${outputDir}/schemas.ts`, format(schemasCode)); | ||
} | ||
console.info(`Generate code successful in directory: ${outputDir}`); | ||
} | ||
export const genDirWithTags = genTags; |
@@ -14,3 +14,4 @@ { | ||
"sourceMap": false, | ||
"outDir": "dist" | ||
"outDir": "dist", | ||
"baseUrl": "src" | ||
}, | ||
@@ -17,0 +18,0 @@ "include": [ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
90751
43
2135
2
2