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
JavaScript
"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;