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

951 lines (950 loc) 49.6 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; } const tagLowerCase = tag.toLowerCase(); 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(tagLowerCase, includeTags)) { return; } } if (priorityRule === config_1.PriorityRule.exclude) { if (this.validateRegexp(tagLowerCase, 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(tagLowerCase, includeTags); const inExcludeTags = !(0, lodash_1.isEmpty)(excludeTags) && this.validateRegexp(tagLowerCase, excludeTags); if (outIncludeTags || inExcludeTags) { return; } } const tagKey = this.config.isCamelCase ? (0, util_1.camelCase)((0, util_2.resolveTypeName)(tag)) : (0, util_2.resolveTypeName)(tag); if (!this.apiData[tagKey]) { this.apiData[tagKey] = []; } this.apiData[tagKey].push(Object.assign({ path: pathKey, method }, operationObject)); }); }); } } genFile() { 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)) { this.genFileFromTemplate(`${config_2.displayEnumLabelFileName}.ts`, config_2.TypescriptFileType.displayEnumLabel, { list: 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)) { this.genFileFromTemplate(`${config_2.displayTypeLabelFileName}.ts`, config_2.TypescriptFileType.displayTypeLabel, { list: 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)((0, lodash_1.map)(tags, (tag) => (tag === null || tag === void 0 ? void 0 : tag.toLowerCase) ? tag.toLowerCase() : undefined), (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) => (item === null || item === void 0 ? void 0 : item.in) !== `${config_2.parametersInsEnum.header}`); const props = []; (_c = operationObject.parameters) === null || _c === void 0 ? void 0 : _c.forEach((parameter) => { var _a; 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((parameter) => { var _a; 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, }); } }); } 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 : '', }); } if (this.config.isGenJsonSchemas) { this.schemaList.push({ typeName: `$${(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); 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: body === null || body === void 0 ? void 0 : body.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}`; } 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, util_2.resolveTypeName)(`${namespace}${typeName !== null && typeName !== void 0 ? typeName : data.operationId}Params`); } getBodyTP(requestBody) { 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, this.config.namespace), isAnonymous: false, }; // 具名 body 场景 if ((0, util_2.isReferenceObject)(schema)) { bodySchema.type = `${this.config.namespace}.${bodySchema.type}`; } else { 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.default || responses['200'] || responses['201']); 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.config.namespace}.${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; } 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(',')}}`; } } return { isEnum: true, type: Array.isArray(enumArray) ? enumStr : 'string', enumLabelType: enumLabelTypeStr, }; } resolveAllOfObject(schemaObject) { const props = (0, lodash_1.map)(schemaObject.allOf, (item) => { return (0, util_2.isReferenceObject)(item) ? [Object.assign(Object.assign({}, item), { type: this.getType(item) })] : this.getProps(item); }); if (schemaObject.properties) { const extProps = this.getProps(schemaObject); return { props: [...props, extProps] }; } return { props }; } // 获取 TS 类型的属性列表 getProps(schemaObject) { var _a; const requiredPropKeys = (_a = schemaObject === null || schemaObject === void 0 ? void 0 : schemaObject.required) !== null && _a !== void 0 ? _a : false; const properties = schemaObject.properties; return (0, lodash_1.keys)(properties).map((propKey) => { const schema = ((properties === null || properties === void 0 ? void 0 : properties[propKey]) || config_2.DEFAULT_SCHEMA); // 剔除属性键值中的特殊符号,因为函数入参变量存在特殊符号会导致解析文件失败 // eslint-disable-next-line no-useless-escape propKey = propKey.replace(/[\[|\]]/g, ''); // 复用 schema 部分字段 return Object.assign(Object.assign({}, schema), { name: propKey, type: this.getType(schema), desc: [schema.title, schema.description] .filter((item) => item) .join(' ') .replace(config_2.lineBreakReg, ''), // 如果没有 required 信息,默认全部是非必填 required: requiredPropKeys ? requiredPropKeys.some((key) => key === propKey) : false }); }); } resolveRefObject(refObject) { if (!(0, util_2.isReferenceObject)(refObject)) { return refObject; } const refPaths = refObject.$ref.split('/'); if (refPaths[0] === '#') { const schema = (0, util_2.resolveRefs)(this.openAPIData, refPaths.slice(1)); if (!schema) { throw new Error(`[GenSDK] Data Error! Notfoud: ${refObject.$ref}`); } return Object.assign(Object.assign({}, (this.resolveRefObject(schema) || {})), { type: (0, util_2.isReferenceObject)(schema) ? this.resolveRefObject(schema).type : schema.type }); } return refObject; } log(message) { if (this.config.enableLogging) { (0, log_1.default)(message); } } generateInfoLog() { var _a, _b, _c, _d, _e, _f, _g, _h, _j; this.log(`priorityRule: ${(_a = this.config) === null || _a === void 0 ? void 0 : _a.priorityRule}`); if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.includeTags) { this.log(`includeTags: ${(_c = this.config) === null || _c === void 0 ? void 0 : _c.includeTags.join(', ')}`); } if ((_d = this.config) === null || _d === void 0 ? void 0 : _d.excludeTags) { this.log(`excludeTags: ${(_e = this.config) === null || _e === void 0 ? void 0 : _e.excludeTags.join(', ')}`); } if ((_f = this.config) === null || _f === void 0 ? void 0 : _f.includePaths) { this.log(`includePaths: ${(_g = this.config) === null || _g === void 0 ? void 0 : _g.includePaths.join(', ')}`); } if ((_h = this.config) === null || _h === void 0 ? void 0 : _h.excludePaths) { this.log(`excludePaths: ${(_j = this.config) === null || _j === void 0 ? void 0 : _j.excludePaths.join(', ')}`); } } /** * 校验规则 * @param regexp 正则数组 * @param data 数据 */ validateRegexp(data, regexp) { // 确保 data 是数组 const dataArray = Array.isArray(data) ? data : [data]; this.log(`"Data Array:", ${dataArray.join(',')}`); this.log(`"Regexp Array:", ${regexp.join(',')}`); return dataArray.some((item) => { const result = regexp.some((reg) => this.matches(item, reg)); this.log(`"Item:", ${item}, "Matches:", ${result}`); return result; }); } /** * * @param item 数组数据 * @param reg 规则 */ // 提取匹配逻辑到单独的函数 matches(item, reg) { if (typeof reg === 'string') { return (0, minimatch_1.minimatch)(item, reg); } else if (reg instanceof RegExp) { reg.lastIndex = 0; // 重置正则表达式的 lastIndex 属性 return reg.test(item); } return false; // 对于其他类型,返回 false } } exports.default = ServiceGenerator;