UNPKG

@alova/wormhole

Version:

More modern openAPI generating solution for alova.js

372 lines (371 loc) 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isReferenceObject = isReferenceObject; exports.isMaybeArraySchemaObject = isMaybeArraySchemaObject; exports.findBy$ref = findBy$ref; exports.parseReference = parseReference; exports.dereference = dereference; exports.nextReference = nextReference; exports.setComponentsBy$ref = setComponentsBy$ref; exports.get$refName = get$refName; exports.removeAll$ref = removeAll$ref; exports.isEqualObject = isEqualObject; exports.getNext$refKey = getNext$refKey; exports.mergeObject = mergeObject; exports.getResponseSuccessKey = getResponseSuccessKey; const lodash_1 = require("lodash"); const config_1 = require("../config"); const loader_1 = require("../core/loader"); const utils_1 = require("../utils"); /** * Determine whether it is a $ref object * @param obj Judgment object * @returns Whether it is a $ref object */ function isReferenceObject(obj) { return !!obj?.$ref; } function isBaseReferenceObject(obj) { return !!obj?._$ref; } function isMaybeArraySchemaObject(obj) { return !!obj?.items; } /** * * @param path $ref search path * @param openApi openApi document object * @param isDeep Whether to deep copy * @returns SchemaObject found */ function findBy$ref(path, openApi, isDeep = false) { const pathArr = path.split('/'); let find = { '#': openApi, }; pathArr.forEach((key) => { if (find) { find = find[key]; } }); if (!find) { const DEFAULT_CONFIG = (0, config_1.getGlobalConfig)(); throw new DEFAULT_CONFIG.Error(`cannot find $ref '${path}'`); } return (isDeep ? (0, lodash_1.cloneDeep)(find) : find); } function parseReference(obj, document, isDeep = false) { if (isReferenceObject(obj)) { return findBy$ref(obj.$ref, document, isDeep); } return obj; } function dereference(obj, document, isDeep = false) { if (isReferenceObject(obj)) { const result = findBy$ref(obj.$ref, document, isDeep); const description = obj.description || result.description; return { ...result, description, }; } return obj; } function nextReference(schema) { for (const keyStr of Object.keys(schema)) { const key = keyStr; if (schema[key] && typeof schema[key] === 'object' && isReferenceObject(schema[key])) { return schema[key]; } } return null; } /** * * @param path $ref path * @param data Reuse object * @param openApi Inserted openapi document object */ function setComponentsBy$ref(path, data, openApi) { const pathArr = path.split('/'); let find = { '#': openApi, }; pathArr.forEach((key, idx) => { if (idx + 1 === pathArr.length) { find[key] = data; return; } if (find[key]) { find = find[key]; } else { find = find[key] = {}; } }); } /** * * @param path $ref path * @param toUpperCase Whether the initial letter size * @returns Reference object name */ function get$refName(path, toUpperCase = true) { const pathArr = path.split('/'); const name = pathArr[pathArr.length - 1]; if (!toUpperCase) { return name; } return (0, utils_1.capitalizeFirstLetter)(name); } /** * * @param schemaOrigin Object with $ref * @param openApi openApi document object * @returns Removed $ref object */ function removeAll$ref(schemaOrigin, openApi, searchMap = new Map()) { const deepSchemaOrigin = (0, lodash_1.cloneDeep)(schemaOrigin); let schema; if (isReferenceObject(deepSchemaOrigin)) { if (searchMap.has(deepSchemaOrigin.$ref)) { return searchMap.get(deepSchemaOrigin.$ref); } schema = findBy$ref(deepSchemaOrigin.$ref, openApi, true); // Mark for easy restoration schema._$ref = deepSchemaOrigin.$ref; searchMap.set(deepSchemaOrigin.$ref, schema); } else { schema = deepSchemaOrigin; } for (const key of Object.keys(schema)) { if (schema[key] && typeof schema[key] === 'object') { schema[key] = removeAll$ref(schema[key], openApi, searchMap); } } return schema; } /** * * @param objValue object to be compared * @param srcValue source object * @param openApi openApi document object * @returns Are they equal? */ function isEqualObject(objValue, srcValue, openApi) { const visited = new WeakMap(); const ignoreKeyArr = ['_$ref']; function customizer(objValueOrigin, otherValueOrigin) { if (objValueOrigin === otherValueOrigin) { return true; } let objValue = objValueOrigin; let otherValue = otherValueOrigin; if (isReferenceObject(objValueOrigin)) { objValue = findBy$ref(objValueOrigin.$ref, openApi); } if (isReferenceObject(otherValueOrigin)) { otherValue = findBy$ref(otherValueOrigin.$ref, openApi); } // Ignore the effect of array order if ((0, lodash_1.isArray)(objValue) && (0, lodash_1.isArray)(otherValue)) { const sortObjValue = (0, lodash_1.sortBy)(objValue); const sortOtherValue = (0, lodash_1.sortBy)(otherValue); const keys = [...new Set([...Object.keys(sortObjValue), ...Object.keys(sortOtherValue)])].filter(key => !ignoreKeyArr.includes(key)); return keys.every(key => (0, lodash_1.isEqualWith)(sortObjValue[key], sortOtherValue[key], customizer)); } // If it is an object, compare recursively if ((0, lodash_1.isObject)(objValue) && (0, lodash_1.isObject)(otherValue)) { if (visited.has(objValue) && visited.get(objValue) === otherValue) { return true; } visited.set(objValue, otherValue); const keys = [...new Set([...Object.keys(objValue), ...Object.keys(otherValue)])].filter(key => !ignoreKeyArr.includes(key)); return keys.every(key => (0, lodash_1.isEqualWith)(objValue[key], otherValue[key], customizer)); } } return (0, lodash_1.isEqualWith)(objValue, srcValue, customizer); } /** * * @param path $ref path * @param map Existing $ref path * @returns Another version of the $ref path */ function getNext$refKey(path, map = []) { function getNameVersion(path) { const name = loader_1.standardLoader.transformRefName(path, { toUpperCase: false, }); const [, nameVersion = 0] = /(\d+)$/.exec(name) ?? []; return Number(nameVersion); } function getOnlyName(path) { const name = loader_1.standardLoader.transformRefName(path, { toUpperCase: false, }); return name.replace(/\d+$/, ''); } function getOnlyPath(path) { return path.split('/').slice(0, -1).join('/'); } const name = getOnlyName(path); const basePath = getOnlyPath(path); let nameVersion = getNameVersion(path); map.forEach(([key]) => { if (getOnlyName(key) === name && getOnlyPath(path) === basePath) { nameVersion = Math.max(nameVersion, getNameVersion(key)); } }); return `${basePath}/${name}${nameVersion + 1}`; } function isCircular(obj) { const seenObjects = new WeakSet(); function detect(obj) { if (obj && typeof obj === 'object') { if (seenObjects.has(obj)) { return true; } seenObjects.add(obj); for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { if (detect(obj[key])) { return true; } } } } return false; } return detect(obj); } function hasBaseReferenceObject(obj) { if (obj && typeof obj === 'object') { if (isBaseReferenceObject(obj)) { return true; } for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { if (hasBaseReferenceObject(obj[key])) { return true; } } } } return false; } function removeBaseReference(obj, openApi, map) { if (isBaseReferenceObject(obj)) { const refObj = { $ref: obj._$ref }; if (isEqualObject(obj, refObj, openApi)) { return refObj; } for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key) && hasBaseReferenceObject(obj[key])) { obj[key] = removeBaseReference(obj[key], openApi, map); } } const [path] = map.find(([, item]) => isEqualObject(item, obj, openApi)) ?? []; if (path) { return { $ref: path, }; } const nextPath = getNext$refKey(refObj.$ref, map); map.push([nextPath, obj]); setComponentsBy$ref(nextPath, obj, openApi); return { $ref: nextPath, }; } for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key) && hasBaseReferenceObject(obj[key])) { obj[key] = removeBaseReference(obj[key], openApi, map); } } return obj; } function unCircular(obj, openApi, map, objPath = '$', seen = new WeakMap()) { if (typeof obj !== 'object' || obj === null) { return obj; // 原始值直接返回 } if (seen.has(obj)) { return seen.get(obj); } const setSeen = (obj, refOjec) => { const oldRefObj = seen.get(obj) ?? refOjec; oldRefObj.$ref = refOjec.$ref; seen.set(obj, oldRefObj); }; const $ref = `#/components/schemas/${loader_1.standardLoader.transform(loader_1.standardLoader.transformRadomVariable(objPath), { style: 'camelCas', })}`; setSeen(obj, { $ref }); if (isBaseReferenceObject(obj)) { const refObj = { $ref: obj._$ref }; if (isEqualObject(obj, refObj, openApi)) { setSeen(obj, refObj); return refObj; } const nextPath = getNext$refKey(refObj.$ref, map); seen.set(obj, { $ref: nextPath }); map.push([nextPath, obj]); for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { obj[key] = unCircular(obj[key], openApi, map, `${objPath}.${key}`, seen); } } setComponentsBy$ref(nextPath, obj, openApi); return { $ref: nextPath, }; } map.push([$ref, obj]); for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { obj[key] = unCircular(obj[key], openApi, map, `${objPath}.${key}`, seen); } } setComponentsBy$ref($ref, obj, openApi); return obj; } /** * When merging openApi document objects, try to use srcValue as the standard. * @template T * @param objValue * @param srcValue * @param openApi * @returns T */ function mergeObject(objValue, srcValue, openApi, map = []) { function customizer(objValue, srcValue) { // If they are all arrays and the src value is an empty array, the src value will be returned directly. if ((0, lodash_1.isArray)(objValue) && (0, lodash_1.isArray)(srcValue) && !srcValue.length) { return srcValue; } if (isEqualObject(objValue, srcValue, openApi)) { return objValue; } // Handle circular references if (isCircular(srcValue)) { srcValue = unCircular(srcValue, openApi, map, loader_1.standardLoader.transformRadomVariable(JSON.stringify(objValue))); } // Is there also a $ref attribute? if (hasBaseReferenceObject(srcValue)) { return removeBaseReference(srcValue, openApi, map); } return srcValue; } return (0, lodash_1.mergeWith)((0, lodash_1.cloneDeep)(objValue), srcValue, customizer); } function getResponseSuccessKey(responsesObject) { if (!responsesObject) { return '200'; } const successKeys = Object.keys(responsesObject) .map(key => Number(key)) .filter(key => !Number.isNaN(key) && key >= 200 && key < 300) .sort((a, b) => a - b); return `${successKeys[0] ?? 'default'}`; }