UNPKG

openapi-ts-request

Version:

Swagger2/OpenAPI3/Apifox to TypeScript/JavaScript, request client(support any client), request mock service, enum and enum translation, react-query/vue-query, type field label, JSON Schemas

966 lines 56.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const fs_1 = require("fs"); const glob_1 = require("glob"); const lodash_1 = require("lodash"); const minimatch_1 = require("minimatch"); const nunjucks_1 = tslib_1.__importDefault(require("nunjucks")); const path_1 = require("path"); const rimraf_1 = require("rimraf"); const config_1 = require("../config"); const log_1 = tslib_1.__importDefault(require("../log")); const util_1 = require("../util"); const config_2 = require("./config"); const file_1 = require("./file"); const merge_1 = require("./merge"); const patchSchema_1 = require("./patchSchema"); const util_2 = require("./util"); class ServiceGenerator { constructor(config, openAPIData) { var _a, _b, _c, _d, _e; this.apiData = {}; this.classNameList = []; this.schemaList = []; this.interfaceTPConfigs = []; this.config = Object.assign({ templatesFolder: (0, path_1.join)(__dirname, '../../', 'templates') }, config); this.generateInfoLog(); const includeTags = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.includeTags) || []; const includePaths = ((_b = this.config) === null || _b === void 0 ? void 0 : _b.includePaths) || []; const excludeTags = ((_c = this.config) === null || _c === void 0 ? void 0 : _c.excludeTags) || []; const excludePaths = ((_d = this.config) === null || _d === void 0 ? void 0 : _d.excludePaths) || []; const priorityRule = config_1.PriorityRule[config.priorityRule]; if ((_e = this.config.hook) === null || _e === void 0 ? void 0 : _e.afterOpenApiDataInited) { this.openAPIData = this.config.hook.afterOpenApiDataInited(openAPIData) || openAPIData; } else { this.openAPIData = openAPIData; } // 用 tag 分组 paths, { [tag]: [pathMap, pathMap] } outerLoop: for (const pathKey in this.openAPIData.paths) { // 这里判断paths switch (priorityRule) { case config_1.PriorityRule.include: { // includePaths and includeTags is empty, 直接跳过 if ((0, lodash_1.isEmpty)(includeTags) && (0, lodash_1.isEmpty)(includePaths)) { this.log('priorityRule include need includeTags or includePaths'); break outerLoop; } if (!(0, lodash_1.isEmpty)(includePaths) && !this.validateRegexp(pathKey, includePaths)) { continue; } break; } case config_1.PriorityRule.exclude: { if (this.validateRegexp(pathKey, excludePaths)) { continue; } break; } case config_1.PriorityRule.both: { // includePaths and includeTags is empty,直接跳过 if ((0, lodash_1.isEmpty)(includeTags) && (0, lodash_1.isEmpty)(includePaths)) { this.log('priorityRule both need includeTags or includePaths'); break outerLoop; } const outIncludePaths = !(0, lodash_1.isEmpty)(includePaths) && !this.validateRegexp(pathKey, includePaths); const inExcludePaths = !(0, lodash_1.isEmpty)(excludePaths) && this.validateRegexp(pathKey, excludePaths); if (outIncludePaths || inExcludePaths) { continue; } break; } default: throw new Error('priorityRule must be "include" or "exclude" or "include"'); } const pathItem = this.openAPIData.paths[pathKey]; (0, lodash_1.forEach)(config_2.methods, (method) => { var _a; const operationObject = pathItem[method]; if (!operationObject) { return; } const hookCustomFileNames = ((_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customFileNames) || util_2.getDefaultFileTag; const tags = hookCustomFileNames(operationObject, pathKey, method); // 这里判断tags tags.forEach((tag) => { if (!tag) { return; } if (priorityRule === config_1.PriorityRule.include) { // includeTags 为空,不会匹配任何path,故跳过 if ((0, lodash_1.isEmpty)(includeTags)) { this.log('priorityRule include need includeTags or includePaths'); return; } if (!this.validateRegexp(tag, includeTags)) { return; } } if (priorityRule === config_1.PriorityRule.exclude) { if (this.validateRegexp(tag, excludeTags)) { return; } } if (priorityRule === config_1.PriorityRule.both) { // includeTags is empty 没有配置, 直接跳过 if ((0, lodash_1.isEmpty)(includeTags)) { this.log('priorityRule both need includeTags or includePaths'); return; } const outIncludeTags = !(0, lodash_1.isEmpty)(includeTags) && !this.validateRegexp(tag, includeTags); const inExcludeTags = !(0, lodash_1.isEmpty)(excludeTags) && this.validateRegexp(tag, excludeTags); if (outIncludeTags || inExcludeTags) { return; } } const tagTypeName = (0, util_2.resolveTypeName)(tag); const tagKey = this.config.isCamelCase ? (0, util_1.camelCase)(tagTypeName) : (0, lodash_1.lowerFirst)(tagTypeName); if (!this.apiData[tagKey]) { this.apiData[tagKey] = []; } this.apiData[tagKey].push(Object.assign({ path: pathKey, method }, operationObject)); }); }); } } genFile() { var _a, _b, _c, _d; if (this.config.full) { try { (0, glob_1.globSync)(`${this.config.serversPath}/**/*`) .filter((item) => !item.includes('_deperated')) .forEach((item) => { (0, rimraf_1.rimrafSync)(item); }); } catch (error) { (0, log_1.default)(`🚥 api 生成失败: ${error}`); } } const isOnlyGenTypeScriptType = this.config.isOnlyGenTypeScriptType; const isGenJavaScript = this.config.isGenJavaScript; const reactQueryMode = this.config.reactQueryMode; const reactQueryFileName = (0, config_2.displayReactQueryFileName)(reactQueryMode); if (!isOnlyGenTypeScriptType) { const prettierError = []; // 生成 service controller 文件 this.getServiceTPConfigs().forEach((tp) => { var _a, _b; const { list } = tp, restTp = tslib_1.__rest(tp, ["list"]); const payload = Object.assign({ namespace: this.config.namespace, requestOptionsType: this.config.requestOptionsType, requestImportStatement: this.config.requestImportStatement, interfaceFileName: config_2.interfaceFileName, list }, restTp); const hookCustomTemplateService = (_b = (_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customTemplates) === null || _b === void 0 ? void 0 : _b[config_2.TypescriptFileType.serviceController]; if (hookCustomTemplateService) { payload.list = list.map((item) => { return { customTemplate: true, data: hookCustomTemplateService(item, payload), }; }); } const hasError = this.genFileFromTemplate(isGenJavaScript ? (0, util_2.getFinalFileName)(`${tp.className}.js`) : (0, util_2.getFinalFileName)(`${tp.className}.ts`), config_2.TypescriptFileType.serviceController, payload); prettierError.push(hasError); if (this.config.isGenReactQuery) { this.genFileFromTemplate(isGenJavaScript ? (0, util_2.getFinalFileName)(`${tp.className}.${reactQueryFileName}.js`) : (0, util_2.getFinalFileName)(`${tp.className}.${reactQueryFileName}.ts`), config_2.TypescriptFileType.reactQuery, Object.assign({ namespace: this.config.namespace, requestOptionsType: this.config.requestOptionsType, requestImportStatement: this.config.requestImportStatement, interfaceFileName: config_2.interfaceFileName, reactQueryModePackageName: (0, config_1.displayReactQueryMode)(reactQueryMode) }, tp)); } }); if (prettierError.includes(true)) { (0, log_1.default)('🚥 格式化失败,请检查 service controller 文件内可能存在的语法错误'); } } // 处理重复的 typeName this.interfaceTPConfigs = this.getInterfaceTPConfigs(); (0, util_2.handleDuplicateTypeNames)(this.interfaceTPConfigs); // 生成 ts 类型声明 if (!isGenJavaScript) { this.genFileFromTemplate(`${config_2.interfaceFileName}.ts`, config_2.TypescriptFileType.interface, { nullable: this.config.nullable, list: this.interfaceTPConfigs, }); } // 生成枚举翻译 const enums = (0, lodash_1.filter)(this.interfaceTPConfigs, (item) => item.isEnum); if (!isGenJavaScript && !isOnlyGenTypeScriptType && !(0, lodash_1.isEmpty)(enums)) { const hookCustomTemplateService = (_b = (_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customTemplates) === null || _b === void 0 ? void 0 : _b[config_2.TypescriptFileType.displayEnumLabel]; this.genFileFromTemplate(`${config_2.displayEnumLabelFileName}.ts`, config_2.TypescriptFileType.displayEnumLabel, { customTemplate: !!hookCustomTemplateService, list: hookCustomTemplateService ? hookCustomTemplateService(enums, this.config) : enums, namespace: this.config.namespace, interfaceFileName: config_2.interfaceFileName, }); } const displayTypeLabels = (0, lodash_1.filter)(this.interfaceTPConfigs, (item) => !item.isEnum); // 生成 type 翻译 if (!isGenJavaScript && !isOnlyGenTypeScriptType && this.config.isDisplayTypeLabel && !(0, lodash_1.isEmpty)(displayTypeLabels)) { const hookCustomTemplateService = (_d = (_c = this.config.hook) === null || _c === void 0 ? void 0 : _c.customTemplates) === null || _d === void 0 ? void 0 : _d[config_2.TypescriptFileType.displayTypeLabel]; this.genFileFromTemplate(`${config_2.displayTypeLabelFileName}.ts`, config_2.TypescriptFileType.displayTypeLabel, { customTemplate: !!hookCustomTemplateService, list: hookCustomTemplateService ? hookCustomTemplateService(enums, this.config) : displayTypeLabels, namespace: this.config.namespace, interfaceFileName: config_2.interfaceFileName, }); } if (!isOnlyGenTypeScriptType && this.config.isGenJsonSchemas && !(0, lodash_1.isEmpty)(this.schemaList)) { // 处理重复的 schemaName (0, util_2.handleDuplicateTypeNames)(this.schemaList); // 生成 schema 文件 this.genFileFromTemplate(isGenJavaScript ? `${config_2.schemaFileName}.js` : `${config_2.schemaFileName}.ts`, config_2.TypescriptFileType.schema, { list: this.schemaList, }); } // 生成 service index 文件 this.genFileFromTemplate(isGenJavaScript ? `${config_2.serviceEntryFileName}.js` : `${config_2.serviceEntryFileName}.ts`, config_2.TypescriptFileType.serviceIndex, { list: this.classNameList, namespace: this.config.namespace, interfaceFileName: config_2.interfaceFileName, genType: isGenJavaScript ? config_2.LangType.js : config_2.LangType.ts, isGenJsonSchemas: !isOnlyGenTypeScriptType && this.config.isGenJsonSchemas && !(0, lodash_1.isEmpty)(this.schemaList), schemaFileName: config_2.schemaFileName, isDisplayEnumLabel: !isOnlyGenTypeScriptType && !(0, lodash_1.isEmpty)(enums), displayEnumLabelFileName: config_2.displayEnumLabelFileName, isGenReactQuery: this.config.isGenReactQuery, reactQueryFileName, isDisplayTypeLabel: !isOnlyGenTypeScriptType && this.config.isDisplayTypeLabel && !(0, lodash_1.isEmpty)(displayTypeLabels), displayTypeLabelFileName: config_2.displayTypeLabelFileName, }); // 打印日志 (0, log_1.default)('✅ 成功生成 api 文件目录-> ', this.config.serversPath); } getInterfaceTPConfigs() { var _a, _b, _c; const schemas = (_a = this.openAPIData.components) === null || _a === void 0 ? void 0 : _a.schemas; const lastTypes = this.interfaceTPConfigs; const includeTags = ((_b = this.config) === null || _b === void 0 ? void 0 : _b.includeTags) || []; const includePaths = ((_c = this.config) === null || _c === void 0 ? void 0 : _c.includePaths) || []; // 强行替换掉请求参数params的类型,生成方法对应的 xxxxParams 类型 (0, lodash_1.keys)(this.openAPIData.paths).forEach((pathKey) => { const pathItem = this.openAPIData.paths[pathKey]; (0, lodash_1.forEach)(config_2.methods, (method) => { var _a, _b, _c, _d; const operationObject = pathItem[method]; const hookCustomFileNames = ((_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customFileNames) || util_2.getDefaultFileTag; if (!operationObject) { return; } const tags = hookCustomFileNames(operationObject, pathKey, method); if ((0, lodash_1.isEmpty)(includeTags) || (!(0, lodash_1.isEmpty)(includeTags) && (0, lodash_1.isEmpty)(tags)) || (0, lodash_1.isEmpty)(includePaths)) { return; } const flag = this.validateRegexp((0, lodash_1.filter)(tags, (tag) => !!tag), includeTags); const pathFlag = this.validateRegexp(pathKey, includePaths); if (!flag || !pathFlag) { return; } // 筛选出 pathItem 包含的 $ref 对应的schema (0, util_2.markAllowedSchema)(JSON.stringify(pathItem), this.openAPIData); operationObject.parameters = (_b = operationObject.parameters) === null || _b === void 0 ? void 0 : _b.filter((item) => { const parameter = this.resolveParameterRef(item); return (parameter === null || parameter === void 0 ? void 0 : parameter.in) !== `${config_2.parametersInsEnum.header}`; }); const props = []; (_c = operationObject.parameters) === null || _c === void 0 ? void 0 : _c.forEach((param) => { var _a; const parameter = this.resolveParameterRef(param); if (parameter) { props.push({ name: parameter.name, desc: ((_a = parameter.description) !== null && _a !== void 0 ? _a : '').replace(config_2.lineBreakReg, ''), required: parameter.required || false, type: this.getType(parameter.schema), }); } }); // parameters may be in path (_d = pathItem.parameters) === null || _d === void 0 ? void 0 : _d.forEach((param) => { var _a; const parameter = this.resolveParameterRef(param); if (parameter) { props.push({ name: parameter.name, desc: ((_a = parameter.description) !== null && _a !== void 0 ? _a : '').replace(config_2.lineBreakReg, ''), required: parameter.required, type: this.getType(parameter.schema), }); } }); const typeName = this.getFunctionParamsTypeName(Object.assign(Object.assign({}, operationObject), { method, path: pathKey })); if (props.length > 0 && typeName) { lastTypes.push({ typeName, type: 'Record<string, unknown>', props: [props], isEnum: false, }); } }); }); (0, lodash_1.keys)(schemas).forEach((schemaKey) => { var _a; const schema = schemas[schemaKey]; // 判断哪些 schema 需要添加进 type, schemas 渲染数组 if (!(schema === null || schema === void 0 ? void 0 : schema.isAllowed)) { return; } const result = this.resolveObject(schema); const getDefinesType = () => { if (result === null || result === void 0 ? void 0 : result.type) { return schema.type === 'object' ? config_1.SchemaObjectType.object : config_2.numberEnum.includes(result.type) ? config_1.SchemaObjectType.number : result.type; } return 'Record<string, unknown>'; }; // 解析 props 属性中的枚举 if ((0, lodash_1.isArray)(result.props) && result.props.length > 0) { (0, lodash_1.forEach)(result.props[0], (item) => { if (item.enum) { const enumObj = this.resolveEnumObject(item); lastTypes.push({ typeName: `${(0, lodash_1.upperFirst)(item.name)}Enum`, type: enumObj.type, props: [], isEnum: enumObj.isEnum, displayLabelFuncName: (0, util_1.camelCase)(`display-${item.name}-Enum`), enumLabelType: enumObj.enumLabelType, description: enumObj.description, }); } }); } const isEnum = result.isEnum; const typeName = (0, util_2.resolveTypeName)(schemaKey); if (typeName) { lastTypes.push({ typeName, type: getDefinesType(), props: (result.props || []), isEnum, displayLabelFuncName: isEnum ? (0, util_1.camelCase)(`display-${typeName}-Enum`) : '', enumLabelType: isEnum ? result.enumLabelType : '', description: result.description, }); } if (this.config.isGenJsonSchemas) { this.schemaList.push({ typeName: `$${(0, lodash_1.lowerFirst)((0, util_2.resolveTypeName)(schemaKey))}`, type: JSON.stringify((0, patchSchema_1.patchSchema)(schema, (_a = this.openAPIData.components) === null || _a === void 0 ? void 0 : _a.schemas)), }); } }); return lastTypes === null || lastTypes === void 0 ? void 0 : lastTypes.sort((a, b) => a.typeName.localeCompare(b.typeName)); // typeName排序 } getServiceTPConfigs() { return (0, lodash_1.keys)(this.apiData) .map((tag, index) => { var _a, _b; // functionName tag 级别防重 const tmpFunctionRD = {}; const genParams = this.apiData[tag] .filter((api) => // 暂不支持变量, path 需要普通前缀请使用例如: apiPrefix: "`api`", path 需要变量前缀请使用例如: apiPrefix: "api" !api.path.includes('${')) .map((api) => { var _a, _b, _c, _d, _e, _f; const newApi = api; try { const params = this.getParamsTP(newApi.parameters, newApi.path) || {}; const body = this.getBodyTP(newApi.requestBody, this.config.namespace); const bodyWithoutNamespace = this.getBodyTP(newApi.requestBody); const response = this.getResponseTP(newApi.responses); const file = this.getFileTP(newApi.requestBody); let formData = false; if (((_a = body === null || body === void 0 ? void 0 : body.mediaType) === null || _a === void 0 ? void 0 : _a.includes('form-data')) || file) { formData = true; } let functionName = this.getFunctionName(newApi); if (functionName && tmpFunctionRD[functionName]) { functionName = `${functionName}_${(tmpFunctionRD[functionName] += 1)}`; } else if (functionName) { tmpFunctionRD[functionName] = 1; } if (body === null || body === void 0 ? void 0 : body.isAnonymous) { const bodyName = (0, lodash_1.upperFirst)(`${functionName}Body`); this.interfaceTPConfigs.push({ typeName: bodyName, type: bodyWithoutNamespace === null || bodyWithoutNamespace === void 0 ? void 0 : bodyWithoutNamespace.type, isEnum: false, props: [], }); body.type = `${this.config.namespace}.${bodyName}`; } if (response === null || response === void 0 ? void 0 : response.isAnonymous) { const responseName = (0, lodash_1.upperFirst)(`${functionName}Response`); this.interfaceTPConfigs.push({ typeName: responseName, type: response === null || response === void 0 ? void 0 : response.type, isEnum: false, props: [], }); response.type = `${this.config.namespace}.${responseName}`; } const responsesType = this.getResponsesType(newApi.responses, functionName); // 如果有多个响应类型,生成对应的类型定义 if (responsesType) { this.interfaceTPConfigs.push({ typeName: (0, lodash_1.upperFirst)(`${functionName}Responses`), type: responsesType, isEnum: false, props: [], }); } let formattedPath = newApi.path.replace(/:([^/]*)|{([^}]*)}/gi, (_, str, str2) => `$\{${str || str2}}`); // 为 path 中的 params 添加 alias const escapedPathParams = (0, lodash_1.map)(params.path, (item, index) => (Object.assign(Object.assign({}, item), { alias: `param${index}` }))); if (escapedPathParams.length) { escapedPathParams.forEach((param) => { formattedPath = formattedPath.replace(`$\{${param.name}}`, `$\{${param.alias}}`); }); } const finalParams = escapedPathParams && escapedPathParams.length ? Object.assign(Object.assign({}, params), { path: escapedPathParams }) : params; // 处理 query 中的复杂对象 if (finalParams === null || finalParams === void 0 ? void 0 : finalParams.query) { finalParams.query = finalParams.query.map((item) => (Object.assign(Object.assign({}, item), { isComplexType: item.isObject }))); } // 处理 api path 前缀 const getPrefixPath = () => { if (!this.config.apiPrefix) { return formattedPath; } // 静态 apiPrefix const prefix = (0, lodash_1.isFunction)(this.config.apiPrefix) ? `${this.config.apiPrefix({ path: formattedPath, method: newApi.method, namespace: tag, functionName, })}`.trim() : this.config.apiPrefix.trim(); if (!prefix) { return formattedPath; } if (prefix.startsWith("'") || prefix.startsWith('"') || prefix.startsWith('`')) { const finalPrefix = prefix.slice(1, prefix.length - 1); const firstPath = formattedPath.split('/')[1]; if (firstPath === finalPrefix || `/${firstPath}` === finalPrefix) { return formattedPath; } return `${finalPrefix}${formattedPath}`; } // prefix 变量 return `$\{${prefix}}${formattedPath}`; }; return Object.assign(Object.assign({}, newApi), { functionName: this.config.isCamelCase ? (0, util_1.camelCase)(functionName) : functionName, typeName: this.getFunctionParamsTypeName(newApi), path: getPrefixPath(), pathInComment: formattedPath.replace(/\*/g, '&#42;'), apifoxRunLink: newApi === null || newApi === void 0 ? void 0 : newApi['x-run-in-apifox'], hasPathVariables: formattedPath.includes('{'), hasApiPrefix: !!this.config.apiPrefix, method: newApi.method, // 如果 functionName 和 summary 相同,则不显示 summary desc: functionName === newApi.summary ? (newApi.description || '').replace(config_2.lineBreakReg, '') : [ newApi.summary, newApi.description, ((_c = (_b = newApi.responses) === null || _b === void 0 ? void 0 : _b.default) === null || _c === void 0 ? void 0 : _c.description) ? `返回值: ${((_d = newApi.responses) === null || _d === void 0 ? void 0 : _d.default).description}` : '', ] .filter((s) => s) .join(' ') .replace(config_2.lineBreakReg, ''), hasHeader: !!(params === null || params === void 0 ? void 0 : params.header) || !!(body === null || body === void 0 ? void 0 : body.mediaType), params: finalParams, hasParams: Boolean((0, lodash_1.keys)(finalParams).length), options: ((_f = (_e = this.config.hook) === null || _e === void 0 ? void 0 : _e.customOptionsDefaultValue) === null || _f === void 0 ? void 0 : _f.call(_e, newApi)) || {}, body, file, hasFormData: formData, response }); } catch (error) { console.error('[GenSDK] gen service param error:', error); throw error; } }) // 排序下,防止git乱 .sort((a, b) => a.path.localeCompare(b.path)); const fileName = (0, util_2.replaceDot)(tag) || `api${index}`; let className = fileName; if ((_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customClassName) { className = this.config.hook.customClassName(tag); } if (genParams.length) { this.classNameList.push({ fileName: className, controllerName: className, }); } return { genType: this.config.isGenJavaScript ? config_2.LangType.js : config_2.LangType.ts, className, instanceName: `${(_b = fileName[0]) === null || _b === void 0 ? void 0 : _b.toLowerCase()}${fileName.slice(1)}`, list: genParams, }; }) .filter((item) => { var _a; return !!((_a = item === null || item === void 0 ? void 0 : item.list) === null || _a === void 0 ? void 0 : _a.length); }); } genFileFromTemplate(fileName, type, params) { try { const template = this.getTemplate(type); // 设置输出不转义 const env = nunjucks_1.default.configure({ autoescape: false, }); env.addFilter('capitalizeFirst', util_2.capitalizeFirstLetter); const destPath = (0, path_1.join)(this.config.serversPath, fileName); const destCode = nunjucks_1.default.renderString(template, Object.assign({ disableTypeCheck: false }, params)); let mergerProps = {}; if ((0, fs_1.existsSync)(destPath)) { mergerProps = { srcPath: destPath, }; } else { mergerProps = { source: '', }; } if (this.config.full) { return (0, file_1.writeFile)(this.config.serversPath, fileName, destCode); } const merger = new merge_1.Merger(mergerProps); return (0, file_1.writeFile)(this.config.serversPath, fileName, merger.merge({ source: destCode, })); } catch (error) { console.error('[GenSDK] file gen fail:', fileName, 'type:', type); throw error; } } getTemplate(type) { return (0, fs_1.readFileSync)((0, path_1.join)(this.config.templatesFolder, `${type}.njk`), 'utf8'); } // 生成方法名 functionName getFunctionName(data) { // 获取路径相同部分 const pathBasePrefix = (0, util_2.getBasePrefix)((0, lodash_1.keys)(this.openAPIData.paths)); return this.config.hook && this.config.hook.customFunctionName ? this.config.hook.customFunctionName(data, pathBasePrefix) : (0, util_1.camelCase)(`${(0, util_2.genDefaultFunctionName)(data.path, pathBasePrefix)}-using-${data.method}`); // return this.config.hook && this.config.hook.customFunctionName // ? this.config.hook.customFunctionName(data) // : data.operationId // ? resolveFunctionName(stripDot(data.operationId), data.method) // : data.method + genDefaultFunctionName(data.path, pathBasePrefix); } getType(schemaObject, namespace) { var _a, _b; const customTypeHookFunc = (_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customType; const schemas = (_b = this.openAPIData.components) === null || _b === void 0 ? void 0 : _b.schemas; if (customTypeHookFunc) { const type = customTypeHookFunc({ schemaObject, namespace, schemas, originGetType: util_2.getDefaultType, }); if (typeof type === 'string') { return type; } } return (0, util_2.getDefaultType)(schemaObject, namespace, schemas); } getFunctionParamsTypeName(data) { var _a, _b, _c; const namespace = this.config.namespace ? `${this.config.namespace}.` : ''; const typeName = ((_c = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.hook) === null || _b === void 0 ? void 0 : _b.customTypeName) === null || _c === void 0 ? void 0 : _c.call(_b, data)) || this.getFunctionName(data); return (0, lodash_1.upperFirst)((0, util_2.resolveTypeName)(`${namespace}${typeName !== null && typeName !== void 0 ? typeName : data.operationId}Params`)); } getBodyTP(requestBody, namespace) { var _a; const reqBody = this.resolveRefObject(requestBody); if ((0, lodash_1.isEmpty)(reqBody)) { return null; } const reqContent = reqBody.content; if (!(0, lodash_1.isObject)(reqContent)) { return null; } let mediaType = (0, lodash_1.keys)(reqContent)[0]; const schema = ((_a = reqContent[mediaType]) === null || _a === void 0 ? void 0 : _a.schema) || config_2.DEFAULT_SCHEMA; if (mediaType === '*/*') { mediaType = ''; } // 如果 requestBody 有 required 属性,则正常展示;如果没有,默认非必填 const required = typeof (requestBody === null || requestBody === void 0 ? void 0 : requestBody.required) === 'boolean' ? requestBody.required : false; const bodySchema = { mediaType, required, type: this.getType(schema, namespace), isAnonymous: false, }; // 匿名 body 场景 if (!(0, util_2.isReferenceObject)(schema)) { bodySchema.isAnonymous = true; } return bodySchema; } getFileTP(requestBody) { var _a; const reqBody = this.resolveRefObject(requestBody); if ((_a = reqBody === null || reqBody === void 0 ? void 0 : reqBody.content) === null || _a === void 0 ? void 0 : _a['multipart/form-data']) { const ret = this.resolveFileTP(reqBody.content['multipart/form-data'].schema); return ret.length > 0 ? ret : null; } return null; } resolveFileTP(obj) { var _a; let ret = []; const resolved = this.resolveObject(obj); const props = (((_a = resolved.props) === null || _a === void 0 ? void 0 : _a.length) > 0 && resolved.props[0].filter((p) => p.format === 'binary' || p.format === 'base64' || (0, util_2.isBinaryArraySchemaObject)(p))) || []; if (props.length > 0) { ret = props.map((p) => { // 这里 p.type 是自定义type, 注意别混淆 return { title: p.name, multiple: p.type === `${config_1.SchemaObjectType.array}` || p.type === `${config_1.SchemaObjectType.stringArray}`, }; }); } if (resolved.type) { ret = [...ret, ...this.resolveFileTP(resolved.type)]; } return ret; } getResponseTP(responses = {}) { var _a; const { components } = this.openAPIData; const response = responses && this.resolveRefObject(responses['200'] || responses['201'] || responses.default); const defaultResponse = { mediaType: '*/*', type: 'unknown', isAnonymous: false, }; if (!response) { return defaultResponse; } const resContent = response.content; const resContentMediaTypes = (0, lodash_1.keys)(resContent); const mediaType = resContentMediaTypes.includes('application/json') ? 'application/json' : resContentMediaTypes[0]; // 优先使用 application/json if (!(0, lodash_1.isObject)(resContent) || !mediaType) { return defaultResponse; } let schema = (resContent[mediaType].schema || config_2.DEFAULT_SCHEMA); const responseSchema = { mediaType, type: 'unknown', isAnonymous: false, }; if ((0, util_2.isReferenceObject)(schema)) { const refName = (0, util_2.getLastRefName)(schema.$ref); const childrenSchema = components.schemas[refName]; if ((0, util_2.isNonArraySchemaObject)(childrenSchema) && this.config.dataFields) { schema = (((_a = this.config.dataFields .map((field) => childrenSchema.properties[field]) .filter(Boolean)) === null || _a === void 0 ? void 0 : _a[0]) || resContent[mediaType].schema || config_2.DEFAULT_SCHEMA); } responseSchema.type = this.getType(schema, this.config.namespace); return responseSchema; } if ((0, util_2.isSchemaObject)(schema)) { (0, lodash_1.keys)(schema.properties).map((fieldName) => { var _a, _b; schema.properties[fieldName]['required'] = (_b = (_a = schema.required) === null || _a === void 0 ? void 0 : _a.includes(fieldName)) !== null && _b !== void 0 ? _b : false; }); responseSchema.isAnonymous = true; } responseSchema.type = this.getType(schema, this.config.namespace); return responseSchema; } /** * 生成多状态码响应类型定义 * 将 OpenAPI 的 responses 对象转换为 TypeScript 类型定义 * 例如:{ 200: ResponseType, 400: unknown, 404: unknown } * * @param responses OpenAPI 响应对象 * @param functionName 函数名称,用于生成主响应类型名称 * @returns 多状态码响应类型定义字符串,如果没有响应则返回 null */ getResponsesType(responses = {}, functionName) { if ((0, lodash_1.isEmpty)(responses) || ~(0, lodash_1.findIndex)(this.interfaceTPConfigs, (item) => item.typeName === (0, lodash_1.upperFirst)(`${functionName}Responses`))) { return null; } const { components } = this.openAPIData; // 生成主响应类型名称 const mainResponseTypeName = (0, lodash_1.upperFirst)(`${functionName}Response`); const responseEntries = this.parseResponseEntries(responses, components); const responseTypes = responseEntries.map(({ statusCode, type, description = '' }) => { var _a; // 检查是否已存在对应的主响应类型,如果存在则复用,避免重复定义 const existType = this.interfaceTPConfigs.find((item) => item.typeName === mainResponseTypeName); const lastType = existType ? mainResponseTypeName : type; // 格式化描述文本,让描述支持换行 const formattedDescription = config_2.lineBreakReg.test(description) ? (_a = description.split('\n')) === null || _a === void 0 ? void 0 : _a.join('\n * ') : description; // 生成带注释的类型定义 return formattedDescription ? ` /**\n * ${formattedDescription}\n */\n ${statusCode}: ${lastType};` : ` ${statusCode}: ${lastType};`; }); // 返回完整的对象类型定义 return `{\n${responseTypes.join('\n')}\n}`; } /** * 解析响应条目,提取每个状态码对应的类型和描述信息 * * @param responses OpenAPI 响应对象 * @param components OpenAPI 组件对象,用于解析引用类型 * @returns 响应条目数组,包含状态码、类型和描述 */ parseResponseEntries(responses, components) { return (0, lodash_1.keys)(responses).map((statusCode) => { const response = this.resolveRefObject(responses[statusCode]); if (!response) { return { statusCode, type: 'unknown', description: '' }; } const responseType = this.getResponseTypeFromContent(response, components); const description = response.description || ''; return { statusCode, type: responseType, description }; }); } /** * 从响应内容中提取 TypeScript 类型 * 处理不同的媒体类型和 schema 类型 * * @param response 响应对象 * @param components OpenAPI 组件对象 * @returns TypeScript 类型字符串 */ getResponseTypeFromContent(response, components) { var _a; if (!response.content) { return 'unknown'; } const resContent = response.content; const resContentMediaTypes = (0, lodash_1.keys)(resContent); const mediaType = resContentMediaTypes.includes('application/json') ? 'application/json' : resContentMediaTypes[0]; if (!(0, lodash_1.isObject)(resContent) || !mediaType) { return 'unknown'; } let schema = (resContent[mediaType].schema || config_2.DEFAULT_SCHEMA); if ((0, util_2.isReferenceObject)(schema)) { const refName = (0, util_2.getLastRefName)(schema.$ref); const childrenSchema = components.schemas[refName]; // 如果配置了 dataFields,尝试从指定字段提取类型 if ((0, util_2.isNonArraySchemaObject)(childrenSchema) && this.config.dataFields) { schema = (((_a = this.config.dataFields .map((field) => childrenSchema.properties[field]) .filter(Boolean)) === null || _a === void 0 ? void 0 : _a[0]) || resContent[mediaType].schema || config_2.DEFAULT_SCHEMA); } return this.getType(schema); } else if ((0, util_2.isSchemaObject)(schema)) { // 设置属性的 required 状态 (0, lodash_1.keys)(schema.properties).map((fieldName) => { var _a, _b; schema.properties[fieldName]['required'] = (_b = (_a = schema.required) === null || _a === void 0 ? void 0 : _a.includes(fieldName)) !== null && _b !== void 0 ? _b : false; }); return this.getType(schema); } else { return this.getType(schema); } } getParamsTP(parameters = [], path = null) { const templateParams = {}; if (parameters === null || parameters === void 0 ? void 0 : parameters.length) { (0, lodash_1.forEach)(config_2.parametersIn, (source) => { const params = parameters .map((p) => this.resolveRefObject(p)) .filter((p) => p.in === source) .map((p) => { var _a, _b, _c, _d, _e; const isDirectObject = (((_a = p.schema) === null || _a === void 0 ? void 0 : _a.type) === 'object' || p.type) === 'object'; const refName = (0, util_2.getLastRefName)(((_b = p.schema) === null || _b === void 0 ? void 0 : _b.$ref) || p.$ref); const deRefObj = (0, lodash_1.entries)((_c = this.openAPIData.components) === null || _c === void 0 ? void 0 : _c.schemas).find(([k]) => k === refName) || []; const isRefObject = ((_d = deRefObj[1]) === null || _d === void 0 ? void 0 : _d.type) === 'object' && !(0, lodash_1.isEmpty)((_e = deRefObj[1]) === null || _e === void 0 ? void 0 : _e.properties); return Object.assign(Object.assign({}, p), { isObject: isDirectObject || isRefObject, type: this.getType(p.schema || config_2.DEFAULT_SCHEMA, this.config.namespace) }); }); if (params.length) { templateParams[source] = params; } }); } if (path && path.length > 0) { const regex = /\{(\w+)\}/g; templateParams.path = templateParams.path || []; let match = null; while ((match = regex.exec(path))) { if (!templateParams.path.some((p) => p.name === match[1])) { templateParams.path.push(Object.assign(Object.assign({}, config_2.DEFAULT_PATH_PARAM), { name: match[1] })); } } // 如果 path 没有内容,则将删除 path 参数,避免影响后续的 hasParams 判断 if (!templateParams.path.length) delete templateParams.path; } return templateParams; } resolveObject(schemaObject) { // 不使用 schemaObject: ISchemaObject = {} schemaObject = schemaObject !== null && schemaObject !== void 0 ? schemaObject : {}; // 引用类型 if ((0, util_2.isReferenceObject)(schemaObject)) { return this.resolveRefObject(schemaObject); } // 枚举类型 if (schemaObject.enum) { return this.resolveEnumObject(schemaObject); } // 继承类型 if (schemaObject.allOf && schemaObject.allOf.length) { return this.resolveAllOfObject(schemaObject); } // 对象类型 if (schemaObject.properties) { return this.resolveProperties(schemaObject); } // 数组类型 if ((0, util_2.isArraySchemaObject)(schemaObject)) { return this.resolveArray(schemaObject); } return schemaObject; } resolveArray(schemaObject) { var _a; if ((0, util_2.isReferenceObject)(schemaObject.items)) { const refName = (0, util_2.getRefName)(schemaObject.items); return { type: `${refName}[]`, }; } else if ((_a = schemaObject.items) === null || _a === void 0 ? void 0 : _a.enum) { return { type: this.getType(schemaObject, this.config.namespace), }; } // 这里需要解析出具体属性,但由于 parser 层还不确定,所以暂时先返回 unknown[] return { type: 'unknown[]' }; } resolveProperties(schemaObject) { return { props: [this.getProps(schemaObject)], }; } resolveEnumObject(schemaObject) { var _a; const enumArray = schemaObject.enum; let enumStr = ''; let enumLabelTypeStr = ''; if (config_2.numberEnum.includes(schemaObject.type) || (0, util_2.isAllNumber)(enumArray)) { if (this.config.isSupportParseEnumDesc && schemaObject.description) { const enumMap = (0, util_2.parseDescriptionEnum)(schemaObject.description); enumStr = `{${(0, lodash_1.map)(enumArray, (value) => { const enumLabel = enumMap.get(Number(value)); return `${enumLabel}=${Number(value)}`; }).join(',')}}`; } else { enumStr = `{${(0, lodash_1.map)(enumArray, (value) => `"NUMBER_${value}"=${Number(value)}`).join(',')}}`; } } else if ((0, util_2.isAllNumeric)(enumArray)) { enumStr = `{${(0, lodash_1.map)(enumArray, (value) => `"STRING_NUMBER_${value}"="${value}"`).join(',')}}`; } else { enumStr = `{${(0, lodash_1.map)(enumArray, (value) => `"${value}"="${value}"`).join(',')}}`; } // 翻译枚举 if (schemaObject['x-enum-varnames'] && schemaObject['x-enum-comments']) { enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value, index) => { const enumKey = schemaObject['x-enum-varnames'][index]; return `${value}:"${schemaObject['x-enum-comments'][enumKey]}"`; }).join(',')}}`; } else if ((_a = schemaObject === null || schemaObject === void 0 ? void 0 : schemaObject['x-apifox']) === null || _a === void 0 ? void 0 : _a['enumDescriptions']) { enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => { const enumLabel = schemaObject['x-apifox']['enumDescriptions'][value]; return `${value}:"${enumLabel}"`; }).join(',')}}`; } else if (schemaObject === null || schemaObject === void 0 ? void 0 : schemaObject['x-apifox-enum']) { enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => { var _a; const enumLabel = (_a = (0, lodash_1.find)(schemaObject['x-apifox-enum'], (item) => item.value === value)) === null || _a === void 0 ? void 0 : _a.description; return `${value}:"${enumLabel}"`; }).join(',')}}`; } else { if (config_2.numberEnum.includes(schemaObject.type) || (0, util_2.isAllNumber)(enumArray)) { if (this.config.isSupportParseEnumDesc && schemaObject.description) { const enumMap = (0, util_2.parseDescriptionEnum)(schemaObject.description); enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => { const enumLabel = enumMap.get(Number(value)); return `${Number(value)}:"${enumLabel}"`; }).join(',')}}`; } else { enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => `${Number(value)}:"NUMBER_${value}"`).join(',')}}`; } } else if ((0, util_2.isAllNumeric)(enumArray)) { enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => `"${value}":"STRING_NUMBER_${value}"`).join(',')}}`; } else { enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => `"${value}":"${value}"`).join(','