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

588 lines (587 loc) 24.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAxiosResponseType = exports.getBinaryResponseType = exports.isBinaryMediaType = exports.getBinaryMediaTypes = exports.getDefaultBinaryMediaTypes = exports.parseDescriptionEnumByReg = exports.parseDescriptionEnum = void 0; exports.toPlainPinyin = toPlainPinyin; exports.stripDot = stripDot; exports.resolveTypeName = resolveTypeName; exports.getRefName = getRefName; exports.getRefNameWithMapping = getRefNameWithMapping; exports.getLastRefName = getLastRefName; exports.getDefaultType = getDefaultType; exports.getDefaultFileTag = getDefaultFileTag; exports.handleDuplicateTypeNames = handleDuplicateTypeNames; exports.getBasePrefix = getBasePrefix; exports.genDefaultFunctionName = genDefaultFunctionName; exports.getFinalFileName = getFinalFileName; exports.replaceDot = replaceDot; exports.resolveFunctionName = resolveFunctionName; exports.markAllowedSchema = markAllowedSchema; exports.isReferenceObject = isReferenceObject; exports.isSchemaObject = isSchemaObject; exports.isNonArraySchemaObject = isNonArraySchemaObject; exports.isArraySchemaObject = isArraySchemaObject; exports.isBinaryArraySchemaObject = isBinaryArraySchemaObject; exports.resolveRefs = resolveRefs; exports.isAllNumeric = isAllNumeric; exports.isAllNumber = isAllNumber; exports.capitalizeFirstLetter = capitalizeFirstLetter; exports.escapeStringForJs = escapeStringForJs; const tslib_1 = require("tslib"); const lodash_1 = require("lodash"); const pinyin_pro_1 = require("pinyin-pro"); const reserved_words_1 = tslib_1.__importDefault(require("reserved-words")); const config_1 = require("../config"); const log_1 = tslib_1.__importDefault(require("../log")); const config_2 = require("./config"); /** * 汉字转连续无音调小写拼音(对齐原 tiny-pinyin 的 convertToPinyin(str, '', true)) */ function toPlainPinyin(str) { return (0, pinyin_pro_1.pinyin)(str, { toneType: 'none', type: 'string', separator: '' }); } function stripDot(str) { return str.replace(/[-_ .](\w)/g, (_all, letter) => letter.toUpperCase()); } // 兼容C#泛型的typeLastName取法 function getTypeLastName(typeName) { var _a, _b, _c, _d, _e; const tempTypeName = typeName || ''; const childrenTypeName = (_a = tempTypeName === null || tempTypeName === void 0 ? void 0 : tempTypeName.match(/\[\[.+\]\]/g)) === null || _a === void 0 ? void 0 : _a[0]; if (!childrenTypeName) { const publicKeyToken = ((_c = (_b = tempTypeName.split('PublicKeyToken=')) === null || _b === void 0 ? void 0 : _b[1]) !== null && _c !== void 0 ? _c : '').replace('null', ''); const firstTempTypeName = (_e = (_d = tempTypeName.split(',')) === null || _d === void 0 ? void 0 : _d[0]) !== null && _e !== void 0 ? _e : tempTypeName; let typeLastName = firstTempTypeName.split('/').pop().split('.').pop(); if (typeLastName.endsWith('[]')) { typeLastName = typeLastName.substring(0, typeLastName.length - 2) + 'Array'; } // 特殊处理C#默认系统类型,不追加publicKeyToken const isCsharpSystemType = firstTempTypeName.startsWith('System.'); if (!publicKeyToken || isCsharpSystemType) { return typeLastName; } return `${typeLastName}_${publicKeyToken}`; } const currentTypeName = getTypeLastName(tempTypeName.replace(childrenTypeName, '')); const childrenTypeNameLastName = getTypeLastName(childrenTypeName.substring(2, childrenTypeName.length - 2)); return `${currentTypeName}_${childrenTypeNameLastName}`; } // 类型声明过滤关键字 function resolveTypeName(typeName) { if (reserved_words_1.default.check(typeName)) { return `__openAPI__${typeName}`; } const typeLastName = getTypeLastName(typeName); const name = typeLastName .replace(/[-_ ](\w)/g, (_all, letter) => letter.toUpperCase()) .replace(/[^\w^\s^\u4e00-\u9fa5]/gi, ''); // 当model名称是number开头的时候,ts会报错。这种场景一般发生在后端定义的名称是中文 if (name === '_' || /^\d+$/.test(name)) { (0, log_1.default)('⚠️ models不能以number开头,原因可能是Model定义名称为中文, 建议联系后台修改'); return `Pinyin_${name}`; } if (!/[\u3220-\uFA29]/.test(name) && !/^\d$/.test(name)) { return (0, lodash_1.upperFirst)(name); } const noBlankName = name.replace(/ +/g, ''); return (0, lodash_1.upperFirst)(toPlainPinyin(noBlankName)); } function getRefName(refObject) { if (!isReferenceObject(refObject)) { return refObject; } return resolveTypeName(getLastRefName(refObject.$ref)); } /** * 获取引用类型名称,支持类型名称映射 * @param refObject 引用对象 * @param schemaKeyToTypeNameMap 从原始 schema key 到最终类型名称的映射(处理重名情况) * @returns 最终的类型名称 */ function getRefNameWithMapping(refObject, schemaKeyToTypeNameMap) { if (!isReferenceObject(refObject)) { return refObject; } const schemaKey = getLastRefName(refObject.$ref); // 如果存在映射,使用映射后的类型名称 if (schemaKeyToTypeNameMap === null || schemaKeyToTypeNameMap === void 0 ? void 0 : schemaKeyToTypeNameMap.has(schemaKey)) { return schemaKeyToTypeNameMap.get(schemaKey); } // 否则使用默认的类型名称解析 return resolveTypeName(schemaKey); } function getLastRefName(refPath = '') { const refPaths = refPath.split('/'); return refPaths.length > 0 ? decodeURIComponent(refPaths[refPaths.length - 1]) : ''; } function getDefaultType(schemaObject, namespace = '', schemas, schemaKeyToTypeNameMap) { var _a, _b; if ((0, lodash_1.isUndefined)(schemaObject) || (0, lodash_1.isNull)(schemaObject)) { return 'unknown'; } if (!(0, lodash_1.isObject)(schemaObject)) { return schemaObject; } if (isReferenceObject(schemaObject)) { // 使用映射获取正确的类型名称(处理重名情况) const refName = getRefNameWithMapping(schemaObject, schemaKeyToTypeNameMap); return [namespace, refName].filter((s) => s).join('.'); } let type = schemaObject === null || schemaObject === void 0 ? void 0 : schemaObject.type; const dateEnum = ['Date', 'date', 'dateTime', 'date-time', 'datetime']; const stringEnum = ['string', 'email', 'password', 'url', 'byte', 'binary']; // OpenAPI 3.1 支持 type 为数组,例如 ["string", "null"] if (Array.isArray(type)) { return type .map((t) => { // 为数组中的每个类型创建一个临时的 schemaObject const tempSchema = Object.assign(Object.assign({}, schemaObject), { type: t }); return getDefaultType(tempSchema, namespace, schemas); }) .join(' | '); } if (type === 'null') { return 'null'; } if (config_2.numberEnum.includes(schemaObject.format)) { type = 'number'; } if (schemaObject.enum) { type = 'enum'; } if (config_2.numberEnum.includes(type)) { return 'number'; } if (dateEnum.includes(type)) { return 'Date'; } if (stringEnum.includes(type)) { return 'string'; } if (type === 'boolean') { return 'boolean'; } if (type === 'array') { let items = schemaObject.items; if ('schema' in schemaObject) { items = schemaObject.schema.items; } if (Array.isArray(items)) { const arrayItemType = items .map((subType) => getDefaultType((subType.schema || subType), namespace, schemas, schemaKeyToTypeNameMap)) .toString(); return `[${arrayItemType}]`; } const arrayType = getDefaultType(items, namespace, schemas, schemaKeyToTypeNameMap); return arrayType.includes(' | ') ? `(${arrayType})[]` : `${arrayType}[]`; } if (type === 'enum') { return Array.isArray(schemaObject.enum) ? Array.from(new Set(schemaObject.enum.map((v) => typeof v === 'string' ? `"${v.replace(/"/g, '"')}"` : getDefaultType(v)))).join(' | ') : 'string'; } if (schemaObject.oneOf && schemaObject.oneOf.length) { return schemaObject.oneOf .map((item) => getDefaultType(item, namespace, schemas, schemaKeyToTypeNameMap)) .join(' | '); } if ((_a = schemaObject.anyOf) === null || _a === void 0 ? void 0 : _a.length) { return schemaObject.anyOf .map((item) => getDefaultType(item, namespace, schemas, schemaKeyToTypeNameMap)) .join(' | '); } if ((_b = schemaObject.allOf) === null || _b === void 0 ? void 0 : _b.length) { const allofList = schemaObject.allOf.map((item) => { var _a; if (isReferenceObject(item)) { // 不使用 getRefName 函数处理,无法通过 schemas[schemaKey] 获取到schema const schemaKey = getLastRefName(item.$ref); if ((_a = schemas === null || schemas === void 0 ? void 0 : schemas[schemaKey]) === null || _a === void 0 ? void 0 : _a.enum) { return getDefaultType(item, namespace, schemas, schemaKeyToTypeNameMap); } } return getDefaultType(item, namespace, schemas, schemaKeyToTypeNameMap); }); return `(${allofList.join(' & ')})`; } if (schemaObject.type === 'object' || schemaObject.properties) { if ((0, lodash_1.isObject)(schemaObject.additionalProperties)) { const type = getDefaultType(schemaObject.additionalProperties, namespace, schemas, schemaKeyToTypeNameMap); return `Record<string, ${type}>`; } if (!(0, lodash_1.keys)(schemaObject.properties).length) { return 'Record<string, unknown>'; } return `{ ${(0, lodash_1.keys)(schemaObject.properties) .map((key) => { var _a; let required = false; const property = (((_a = schemaObject.properties) === null || _a === void 0 ? void 0 : _a[key]) || {}); if ((0, lodash_1.isBoolean)(schemaObject.required) && schemaObject.required) { required = true; } if ((0, lodash_1.isArray)(schemaObject.required) && schemaObject.required.includes(key)) { required = true; } if (property.required) { required = true; } /** * 将类型属性变为字符串,兼容错误格式如: * 3d_tile(数字开头)等错误命名, * 在后面进行格式化的时候会将正确的字符串转换为正常形式, * 错误的继续保留字符串。 * */ return ` ${property.description ? `/** ${property.description} */\n` : ''}'${key}'${required ? '' : '?'}: ${getDefaultType(property, namespace, schemas, schemaKeyToTypeNameMap)}; `; }) .join('')}}`; } return 'unknown'; } function getDefaultFileTag(operationObject, apiPath) { let lastTags = []; if (operationObject['x-swagger-router-controller']) { lastTags = [operationObject['x-swagger-router-controller']]; } else if (!(0, lodash_1.isEmpty)(operationObject.tags)) { lastTags = operationObject.tags; } else if (operationObject.operationId) { lastTags = [operationObject.operationId]; } else { lastTags = [apiPath.replace('/', '').split('/')[1]]; } return lastTags; } function findDuplicateTypeNames(arr) { const counts = (0, lodash_1.countBy)(arr); const duplicates = (0, lodash_1.filter)((0, lodash_1.keys)(counts), (key) => counts[key] > 1); return duplicates; } function handleDuplicateTypeNames(interfaceTPConfigs) { // 创建从原始 schema key 到最终类型名称的映射 const schemaKeyToTypeNameMap = new Map(); const duplicateTypeNames = findDuplicateTypeNames((0, lodash_1.map)(interfaceTPConfigs, (item) => item.typeName)); if (!(0, lodash_1.isEmpty)(duplicateTypeNames)) { (0, lodash_1.forEach)(duplicateTypeNames, (typeName) => { const selectInterfaceTPConfigs = (0, lodash_1.filter)(interfaceTPConfigs, (interfaceTP) => interfaceTP.typeName === typeName); (0, lodash_1.forEach)(selectInterfaceTPConfigs, (interfaceTP, index) => { if (index >= 1) { interfaceTP.typeName = `${interfaceTP.typeName}${index + 1}`; if (interfaceTP.displayLabelFuncName) { interfaceTP.displayLabelFuncName = `${interfaceTP.displayLabelFuncName}${index + 1}`; } } }); }); } // 建立映射关系(在处理重名之后) (0, lodash_1.forEach)(interfaceTPConfigs, (interfaceTP) => { if (interfaceTP.originalSchemaKey) { schemaKeyToTypeNameMap.set(interfaceTP.originalSchemaKey, interfaceTP.typeName); } }); return schemaKeyToTypeNameMap; } // 检测所有path重复区域(prefix) function getBasePrefix(paths) { const arr = []; paths .map((item) => item.split('/')) .forEach((pathItem) => { pathItem.forEach((item, key) => { if (arr.length <= key) { arr[key] = []; } arr[key].push(item); }); }); const res = []; arr .map((item) => Array.from(new Set(item))) .every((item) => { const b = item.length === 1; if (b) { res.push(item); } return b; }); return `${res.join('/')}/`; } // 将地址path路径转为大驼峰 function genDefaultFunctionName(path, pathBasePrefix) { // 首字母转大写 function toUpperFirstLetter(text) { return text.charAt(0).toUpperCase() + text.slice(1); } return path === null || path === void 0 ? void 0 : path.replace(pathBasePrefix, '').split('/').map((str) => { /** * 兼容错误命名如 /user/:id/:name * 因为是typeName,所以直接进行转换 * */ let s = resolveTypeName(str); if (s.includes('-')) { s = s.replace(/(-\w)+/g, (_match, p1) => p1 === null || p1 === void 0 ? void 0 : p1.slice(1).toUpperCase()); } if (s.match(/^{.+}$/gim)) { return `By${toUpperFirstLetter(s.slice(1, s.length - 1))}`; } return toUpperFirstLetter(s); }).join(''); } function getFinalFileName(s) { // 支持下划线、中划线和空格分隔符,注意分隔符枚举值的顺序不能改变,否则正则匹配会报错 return s.replace(/[-_ ](\w)/g, (_all, letter) => letter.toUpperCase()); } function replaceDot(s) { return s .replace(/\./g, '_') .replace(/[-_ ](\w)/g, (_all, letter) => letter.toUpperCase()); } function resolveFunctionName(functionName, methodName) { // 类型声明过滤关键字 if (reserved_words_1.default.check(functionName)) { return `${functionName}Using${methodName.toUpperCase()}`; } return functionName; } // 标记引用的 $ref 对应的schema function markAllowedSchema(schemaStr, openAPIData) { const refs = (0, lodash_1.map)(schemaStr === null || schemaStr === void 0 ? void 0 : schemaStr.match(/"#\/components\/[^"]+"/g), (item) => item.slice(1, -1)); (0, lodash_1.forEach)(refs, (ref) => { // const schema = schemas?.[getLastRefName(ref)] as ICustomSchemaObject; const refPaths = ref.split('/'); const schema = resolveRefs(openAPIData, refPaths.slice(1)); if (schema && !schema.isAllowed) { schema.isAllowed = true; markAllowedSchema(JSON.stringify(schema), openAPIData); } }); } function isReferenceObject(schema) { return (schema === null || schema === void 0 ? void 0 : schema.$ref) !== undefined; } function isSchemaObject(schema) { return (schema === null || schema === void 0 ? void 0 : schema.properties) !== undefined; } function isNonArraySchemaObject(schema) { return ((schema === null || schema === void 0 ? void 0 : schema.type) === 'object' && (schema === null || schema === void 0 ? void 0 : schema.properties) !== undefined); } function isArraySchemaObject(schema) { return (((schema === null || schema === void 0 ? void 0 : schema.type) === config_1.SchemaObjectType.array || // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error (schema === null || schema === void 0 ? void 0 : schema.type) === config_1.SchemaObjectType.stringArray) && (schema === null || schema === void 0 ? void 0 : schema.items) !== undefined); } function isBinaryArraySchemaObject(schema) { var _a, _b; return (isArraySchemaObject(schema) && (((_a = schema.items) === null || _a === void 0 ? void 0 : _a.format) === 'binary' || ((_b = schema.items) === null || _b === void 0 ? void 0 : _b.format) === 'base64')); } function resolveRefs(obj, fields) { return fields.reduce((acc, field) => { if (!acc) return; const s = acc[decodeURIComponent(field)]; if (!s) return; return s; }, obj); } function isAllNumeric(arr) { return (0, lodash_1.every)(arr, (item) => (0, lodash_1.isString)(item) && /^-?[0-9]+$/.test(item)); } // 检查数组每一项是否都是数字 function isAllNumber(arr) { return (0, lodash_1.every)(arr, (item) => (0, lodash_1.isNumber)(item)); } function capitalizeFirstLetter(str) { return str.replace(/^[a-z]/, (match) => match.toUpperCase()); } /** * 转义字符串,用于 JavaScript 字符串字面量 * 将换行符、单引号等特殊字符转义为转义序列 */ function escapeStringForJs(str) { if (!str) return str; return str .replace(/\\/g, '\\\\') // 先转义反斜杠 .replace(/'/g, "\\'") // 转义单引号 .replace(/\n/g, '\\n') // 转义换行符 .replace(/\r/g, '\\r') // 转义回车符 .replace(/\t/g, '\\t'); // 转义制表符 } // 解析 description 中的枚举翻译 const parseDescriptionEnum = (description) => { const enumMap = new Map(); if (!description) return enumMap; // 首先处理可能的总体描述,例如 "系统用户角色:User=0,..." let descToProcess = description; const mainDescriptionMatch = description.match(/^([^:]+):(.*)/); if (mainDescriptionMatch) { // 如果有总体描述(如 "系统用户角色:"),只处理冒号后面的部分 descToProcess = mainDescriptionMatch[2]; } // 匹配形如 "User(普通用户)=0""User=0" 的模式 const enumPattern = /([^():,=]+)(?:\(([^)]+)\))?=(\d+)/g; let match; while ((match = enumPattern.exec(descToProcess)) !== null) { const name = match[1] ? match[1].trim() : ''; const valueStr = match[3] ? match[3].trim() : ''; if (valueStr && !isNaN(Number(valueStr))) { // 统一使用英文key(如User) enumMap.set(Number(valueStr), name); } } // 如果没有匹配到任何枚举,尝试使用简单的分割方法作为后备 if (enumMap.size === 0) { const pairs = descToProcess.split(','); pairs.forEach((pair) => { const parts = pair.split('='); if (parts.length === 2) { let label = parts[0].trim(); const value = parts[1].trim(); // 处理可能带有括号的情况 const bracketMatch = label.match(/([^(]+)\(([^)]+)\)/); if (bracketMatch) { // 只使用括号前的英文key label = bracketMatch[1].trim(); } if (label && value && !isNaN(Number(value))) { enumMap.set(Number(value), label); } } }); } return enumMap; }; exports.parseDescriptionEnum = parseDescriptionEnum; /** * 通过自定义正则表达式解析 description 中的枚举翻译 * @param description 描述文本 * @param regex 自定义正则表达式,用于匹配枚举项,例如:(\S+)\s*=\s*(\S+) * @returns Map<number, string> 枚举值到标签的映射 */ const parseDescriptionEnumByReg = (description, regex) => { const enumMap = new Map(); if (!description) return enumMap; // 将字符串正则转换为 RegExp 对象,确保有全局标志 let regExp; if (typeof regex === 'string') { const flagsMatch = regex.match(/\/([gimsuy]*)$/); if (flagsMatch) { const parts = regex.split('/'); const pattern = parts.slice(1, -1).join('/'); const flags = flagsMatch[1] || ''; regExp = new RegExp(pattern, flags.includes('g') ? flags : flags + 'g'); } else { regExp = new RegExp(regex, 'g'); } } else { const flags = regex.flags; regExp = flags.includes('g') ? regex : new RegExp(regex.source, flags + 'g'); } // 匹配所有结果,然后对每个匹配结果按 = 拆分 let match; while ((match = regExp.exec(description)) !== null) { const fullMatch = match[0].trim(); // 按 = 拆分,左边是标签,右边是值 const parts = fullMatch.split(/\s*=\s*/); if (parts.length >= 2) { const label = parts[0].trim(); const valueStr = parts[1].trim(); const numValue = Number(valueStr); if (label && valueStr && !isNaN(numValue)) { enumMap.set(numValue, label); } } } return enumMap; }; exports.parseDescriptionEnumByReg = parseDescriptionEnumByReg; /** * 获取默认的二进制媒体类型列表 */ const getDefaultBinaryMediaTypes = () => { return [ 'application/octet-stream', 'application/pdf', 'application/zip', 'application/x-zip-compressed', 'image/*', 'video/*', 'audio/*', ]; }; exports.getDefaultBinaryMediaTypes = getDefaultBinaryMediaTypes; /** * 获取二进制媒体类型列表 * 支持配置自定义二进制媒体类型 * @param customBinaryTypes 自定义二进制媒体类型列表 */ const getBinaryMediaTypes = (customBinaryTypes = []) => { const defaultBinaryTypes = (0, exports.getDefaultBinaryMediaTypes)(); return [...defaultBinaryTypes, ...customBinaryTypes]; }; exports.getBinaryMediaTypes = getBinaryMediaTypes; /** * 检测是否为二进制媒体类型 * @param mediaType 媒体类型 * @param binaryMediaTypes 二进制媒体类型列表 */ const isBinaryMediaType = (mediaType, binaryMediaTypes) => { return binaryMediaTypes.some((type) => { if (type.endsWith('/*')) { // 处理通配符类型,如 image/*, video/* const prefix = type.slice(0, -1); return mediaType.startsWith(prefix); } return mediaType === type; }); }; exports.isBinaryMediaType = isBinaryMediaType; /** * 获取二进制响应类型 * 默认返回 Blob,这是浏览器环境中最常用的二进制类型 */ const getBinaryResponseType = () => { return 'Blob'; }; exports.getBinaryResponseType = getBinaryResponseType; /** * 获取 axios responseType 配置 * 根据二进制响应类型返回对应的 responseType * @param binaryType 二进制类型 */ const getAxiosResponseType = (binaryType) => { switch (binaryType.toLowerCase()) { case 'blob': return 'blob'; case 'arraybuffer': return 'arraybuffer'; case 'uint8array': return 'arraybuffer'; // Uint8Array 需要从 ArrayBuffer 转换 case 'buffer': return 'arraybuffer'; // Node.js Buffer 需要从 ArrayBuffer 转换 default: return 'blob'; } }; exports.getAxiosResponseType = getAxiosResponseType;