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

578 lines (577 loc) 24.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveArray = resolveArray; exports.resolveProperties = resolveProperties; exports.resolveEnumObject = resolveEnumObject; exports.resolveAllOfObject = resolveAllOfObject; exports.getProps = getProps; exports.resolveParameterRef = resolveParameterRef; exports.resolveRefObject = resolveRefObject; exports.getResponsesType = getResponsesType; exports.parseResponseEntries = parseResponseEntries; exports.getResponseTypeFromContent = getResponseTypeFromContent; exports.getParamsTP = getParamsTP; exports.analyzeTypeReferences = analyzeTypeReferences; exports.getModuleImports = getModuleImports; exports.moveCommonTypeDependenciesToCommon = moveCommonTypeDependenciesToCommon; exports.resolveFileTP = resolveFileTP; const lodash_1 = require("lodash"); const config_1 = require("../config"); const config_2 = require("./config"); const util_1 = require("./util"); /** * 解析数组类型 */ function resolveArray(params) { var _a; const { schemaObject, namespace, getType } = params; if ((0, util_1.isReferenceObject)(schemaObject.items)) { const refName = (0, util_1.getRefName)(schemaObject.items); return { type: `${refName}[]`, }; } else if ((_a = schemaObject.items) === null || _a === void 0 ? void 0 : _a.enum) { return { type: getType(schemaObject, namespace), }; } return { type: 'unknown[]' }; } /** * 解析属性 */ function resolveProperties(params) { const { schemaObject, getProps } = params; return { props: [getProps(schemaObject)], }; } /** * 解析枚举对象 */ function resolveEnumObject(params) { var _a; const { schemaObject, config } = params; const enumArray = schemaObject.enum; let enumStr = ''; let enumLabelTypeStr = ''; // 获取实际的类型(处理 OpenAPI 3.1 的 type 数组情况) const getActualType = (type) => { if (Array.isArray(type)) { // 如果是数组,返回第一个非 null 类型 return type.find((t) => t !== 'null') || 'string'; } return type; }; const actualType = getActualType(schemaObject.type); if (config_2.numberEnum.includes(actualType) || (0, util_1.isAllNumber)(enumArray)) { if (config.isSupportParseEnumDesc && schemaObject.description) { const enumMap = (0, util_1.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_1.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(actualType) || (0, util_1.isAllNumber)(enumArray)) { if ((config.isSupportParseEnumDesc || config.supportParseEnumDescByReg) && schemaObject.description) { const enumMap = config.isSupportParseEnumDesc ? (0, util_1.parseDescriptionEnum)(schemaObject.description) : (0, util_1.parseDescriptionEnumByReg)(schemaObject.description, config.supportParseEnumDescByReg); 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) >= 0 ? Number(value) : `"${value}"`}:"NUMBER_${value}"`).join(',')}}`; } } else if ((0, util_1.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(',')}}`; } } // 格式化描述文本,让描述支持换行 const formattedDescription = schemaObject.description && config_2.lineBreakReg.test(schemaObject.description) ? '\n * ' + schemaObject.description.split('\n').join('\n * ') + '\n' : schemaObject.description; return { isEnum: true, type: Array.isArray(enumArray) ? enumStr : 'string', enumLabelType: enumLabelTypeStr, description: formattedDescription, }; } /** * 解析 allOf 对象 */ function resolveAllOfObject(params) { const { schemaObject, getType, getProps } = params; const props = (0, lodash_1.map)(schemaObject.allOf, (item) => { return (0, util_1.isReferenceObject)(item) ? [Object.assign(Object.assign({}, item), { type: getType(item) })] : getProps(item); }); if (schemaObject.properties) { const extProps = getProps(schemaObject); return { props: [...props, extProps] }; } return { props }; } /** * 获取 TS 类型的属性列表 */ function getProps(params) { var _a; const { schemaObject, getType, openAPIData } = params; 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) => { var _a, _b; 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, ''); // 获取描述信息,如果是 $ref 引用,尝试获取引用对象的描述 let desc = [schema.title, schema.description] .filter((item) => item) .join(' '); // 格式化描述文本,让描述支持换行 desc = config_2.lineBreakReg.test(desc) ? '\n * ' + desc.split('\n').join('\n * ') + '\n' : desc; // 如果是 $ref 引用,尝试获取引用对象的描述 if ((0, util_1.isReferenceObject)(schema) && openAPIData) { const refName = (0, util_1.getLastRefName)(schema.$ref); const refSchema = (_b = (_a = openAPIData.components) === null || _a === void 0 ? void 0 : _a.schemas) === null || _b === void 0 ? void 0 : _b[refName]; if (refSchema) { let refDesc = [refSchema.title, refSchema.description] .filter((item) => item) .join(' '); // 格式化描述文本,让描述支持换行 refDesc = config_2.lineBreakReg.test(refDesc) ? '\n * ' + refDesc.split('\n').join('\n * ') + '\n' : refDesc; if (refDesc) { desc = desc + refDesc; } } } // 复用 schema 部分字段 return Object.assign(Object.assign({}, schema), { name: propKey, type: getType(schema), desc: desc, // 如果没有 required 信息,默认全部是非必填 required: requiredPropKeys ? requiredPropKeys.some((key) => key === propKey) : false }); }); } /** * 解析参数引用 */ function resolveParameterRef(params) { var _a, _b; const { param, openAPIData } = params; if (!(0, util_1.isReferenceObject)(param)) { return param; } // 解析 $ref 引用,从 components.parameters 中获取实际定义 const refName = (0, util_1.getLastRefName)(param.$ref); const parameter = ((_b = (_a = openAPIData.components) === null || _a === void 0 ? void 0 : _a.parameters) === null || _b === void 0 ? void 0 : _b[refName]) || null; return parameter; } /** * 解析引用对象 */ function resolveRefObject(params) { const { refObject, openAPIData, resolveRefObjectFunc } = params; if (!(0, util_1.isReferenceObject)(refObject)) { return refObject; } const refPaths = refObject.$ref.split('/'); if (refPaths[0] === '#') { const schema = (0, util_1.resolveRefs)(openAPIData, refPaths.slice(1)); if (!schema) { throw new Error(`[GenSDK] Data Error! Notfoud: ${refObject.$ref}`); } const resolvedSchema = resolveRefObjectFunc({ refObject: schema, openAPIData, resolveRefObjectFunc, }); let resolvedType; if ((0, util_1.isReferenceObject)(schema)) { const refResolved = resolveRefObjectFunc({ refObject: schema, openAPIData, resolveRefObjectFunc, }); resolvedType = refResolved === null || refResolved === void 0 ? void 0 : refResolved.type; } else { const schemaObj = schema; // 处理 OpenAPI 3.1 的 type 数组情况 if (Array.isArray(schemaObj.type)) { // 如果是数组,使用第一个非 null 类型 resolvedType = schemaObj.type.find((t) => t !== 'null') || 'string'; } else { resolvedType = schemaObj.type; } } const finalSchema = schema; return Object.assign(Object.assign({}, (resolvedSchema || {})), { type: resolvedType || finalSchema.type }); } return refObject; } /** * 生成多状态码响应类型定义 */ function getResponsesType(params) { const { responses, functionName, interfaceTPConfigs, components, getResponseTypeFromContentFunc, resolveRefObjectFunc, } = params; if (Object.keys(responses || {}).length === 0 || interfaceTPConfigs.find((item) => item.typeName === (0, lodash_1.upperFirst)(`${functionName}Responses`))) { return null; } // 生成主响应类型名称 const mainResponseTypeName = (0, lodash_1.upperFirst)(`${functionName}Response`); const responseEntries = parseResponseEntries({ responses, components, getResponseTypeFromContentFunc, resolveRefObjectFunc, }); const responseTypes = responseEntries.map(({ statusCode, type, description = '' }) => { var _a; // 检查是否已存在对应的主响应类型,如果存在则复用,避免重复定义 const existType = 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}`; } /** * 解析响应条目 */ function parseResponseEntries(params) { const { responses, components, getResponseTypeFromContentFunc, resolveRefObjectFunc, } = params; return (0, lodash_1.keys)(responses).map((statusCode) => { const response = resolveRefObjectFunc(responses[statusCode]); if (!response) { return { statusCode, type: 'unknown', description: '' }; } const responseType = getResponseTypeFromContentFunc({ response, components, }); const description = response.description || ''; return { statusCode, type: responseType, description }; }); } /** * 从响应内容中提取 TypeScript 类型 */ function getResponseTypeFromContent(params) { var _a; const { response, components, config, getType } = params; if (!response.content) { return 'unknown'; } const resContent = response.content; const resContentMediaTypes = (0, lodash_1.keys)(resContent); // 检测二进制流媒体类型 const binaryMediaTypes = (0, util_1.getBinaryMediaTypes)(config.binaryMediaTypes); const binaryMediaType = resContentMediaTypes.find((mediaType) => (0, util_1.isBinaryMediaType)(mediaType, binaryMediaTypes)); const mediaType = resContentMediaTypes.includes('application/json') ? 'application/json' : binaryMediaType || resContentMediaTypes[0]; if (!(0, lodash_1.isObject)(resContent) || !mediaType) { return 'unknown'; } // 如果是二进制媒体类型,直接返回二进制类型 if ((0, util_1.isBinaryMediaType)(mediaType, binaryMediaTypes)) { return (0, util_1.getBinaryResponseType)(); } let schema = (resContent[mediaType].schema || config_2.DEFAULT_SCHEMA); if ((0, util_1.isReferenceObject)(schema)) { const refName = (0, util_1.getLastRefName)(schema.$ref); const childrenSchema = components.schemas[refName]; // 如果配置了 dataFields,尝试从指定字段提取类型 if ((0, util_1.isNonArraySchemaObject)(childrenSchema) && config.dataFields) { schema = (((_a = 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 getType(schema); } else if ((0, util_1.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 getType(schema); } else { return getType(schema); } } /** * 获取参数模板 */ function getParamsTP(params) { const { parameters = [], path = null, namespace, openAPIData, getType, resolveParameterRefFunc, } = params; const templateParams = {}; if (parameters === null || parameters === void 0 ? void 0 : parameters.length) { (0, lodash_1.forEach)(config_2.parametersIn, (source) => { const paramsList = parameters .map((p) => resolveParameterRefFunc({ param: p, openAPIData })) .filter((p) => p !== null && p.in === source) .map((p) => { var _a, _b, _c; const isDirectObject = (((_a = p.schema) === null || _a === void 0 ? void 0 : _a.type) === 'object' || p.type) === 'object'; const refName = (0, util_1.getLastRefName)(((_b = p.schema) === null || _b === void 0 ? void 0 : _b.$ref) || p.$ref); const schemas = ((_c = openAPIData.components) === null || _c === void 0 ? void 0 : _c.schemas) || {}; const deRefObjEntry = Object.entries(schemas).find(([k]) => k === refName); const deRefSchema = deRefObjEntry === null || deRefObjEntry === void 0 ? void 0 : deRefObjEntry[1]; const isRefObject = deRefSchema !== undefined && (deRefSchema === null || deRefSchema === void 0 ? void 0 : deRefSchema.type) === 'object' && Object.keys((deRefSchema === null || deRefSchema === void 0 ? void 0 : deRefSchema.properties) || {}).length > 0; return Object.assign(Object.assign({}, p), { isObject: isDirectObject || isRefObject, type: getType(p.schema || config_2.DEFAULT_SCHEMA, namespace) }); }); if (paramsList.length) { templateParams[source] = paramsList; } }); } 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; } /** * 分析类型定义中使用的类型名称 */ function analyzeTypeReferences(types) { const references = new Set(); types.forEach((typeItem) => { // 分析 type 字段 if (typeof typeItem.type === 'string') { // 匹配类型引用,例如: Category, Tag[], _CInputDto, Category | null // 支持以下划线开头的类型名 const typeMatches = typeItem.type.match(/\b_*[A-Z][a-zA-Z0-9_]*\b/g); if (typeMatches) { typeMatches.forEach((match) => references.add(match)); } } // 分析 props if (typeItem.props && typeItem.props.length > 0) { typeItem.props.forEach((propGroup) => { propGroup.forEach((prop) => { if (prop.type) { // 匹配类型引用,支持以下划线开头的类型名 const propTypeMatches = prop.type.match(/\b_*[A-Z][a-zA-Z0-9_]*\b/g); if (propTypeMatches) { propTypeMatches.forEach((match) => references.add(match)); } } }); }); } }); return references; } /** * 获取模块需要导入的类型 */ function getModuleImports(params) { const { moduleTypes, commonTypes, enumTypes } = params; const references = analyzeTypeReferences(moduleTypes); // 获取公共类型名称集合 const commonTypeNames = new Set(commonTypes.map((t) => t.typeName)); // 获取枚举类型名称集合(包括 IEnumName) const enumTypeNames = new Set(); enumTypes.forEach((t) => { enumTypeNames.add(t.typeName); if (t.isEnum) { enumTypeNames.add(`I${t.typeName}`); } }); // 筛选出实际需要导入的类型 const commonImports = Array.from(references).filter((ref) => commonTypeNames.has(ref)); const enumImports = Array.from(references).filter((ref) => enumTypeNames.has(ref)); return { commonImports, enumImports }; } /** * 将公共类型依赖的类型从模块类型移到公共类型 * 同时检测跨模块引用,将被多个模块使用的类型移到公共类型 * @param moduleTypes 模块类型 * @param commonTypes 公共类型 */ function moveCommonTypeDependenciesToCommon(params) { const { moduleTypes, commonTypes } = params; let moved = true; while (moved) { moved = false; // 1. 处理公共类型的依赖 const commonTypeRefs = analyzeTypeReferences(commonTypes); moduleTypes.forEach((types) => { const toMove = []; types.forEach((typeItem) => { if (commonTypeRefs.has(typeItem.typeName)) { toMove.push(typeItem); moved = true; } }); toMove.forEach((typeItem) => { const index = types.indexOf(typeItem); if (index > -1) { types.splice(index, 1); commonTypes.push(typeItem); } }); }); // 2. 检测跨模块引用:如果一个类型被其他模块引用,移到 common // 首先建立类型名称到定义模块的映射 const typeDefModule = new Map(); // typeName -> moduleName (where it's defined) moduleTypes.forEach((types, moduleName) => { types.forEach((typeItem) => { typeDefModule.set(typeItem.typeName, moduleName); }); }); // 收集每个模块引用了哪些定义在其他模块的类型 const crossModuleRefs = new Map(); // typeName -> Set<moduleName that references it> moduleTypes.forEach((types, moduleName) => { const referencedTypes = analyzeTypeReferences(types); referencedTypes.forEach((typeName) => { const defModule = typeDefModule.get(typeName); // 如果这个类型定义在其他模块,记录跨模块引用 if (defModule && defModule !== moduleName) { if (!crossModuleRefs.has(typeName)) { crossModuleRefs.set(typeName, new Set()); } crossModuleRefs.get(typeName).add(moduleName); } }); }); // 找出被其他模块引用的类型(包括被一个或多个其他模块引用) const typesToMove = new Set(); crossModuleRefs.forEach((referencingModules, typeName) => { if (referencingModules.size > 0) { typesToMove.add(typeName); } }); // 将这些类型移到 common if (typesToMove.size > 0) { moduleTypes.forEach((types) => { const toMove = []; types.forEach((typeItem) => { if (typesToMove.has(typeItem.typeName)) { toMove.push(typeItem); moved = true; } }); toMove.forEach((typeItem) => { const index = types.indexOf(typeItem); if (index > -1) { types.splice(index, 1); commonTypes.push(typeItem); } }); }); } } } /** * 解析文件模板 */ function resolveFileTP(params) { var _a; const { obj, resolveObjectFunc } = params; let ret = []; const resolved = resolveObjectFunc(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_1.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, ...resolveFileTP({ obj: resolved.type, resolveObjectFunc, }), ]; } return ret; }