@alova/wormhole
Version:
More modern openAPI generating solution for alova.js
372 lines (371 loc) • 12.4 kB
JavaScript
;
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'}`;
}