@alova/wormhole
Version:
More modern openAPI generating solution for alova.js
161 lines (160 loc) • 5.53 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.openApiHelper = exports.OpenApiHelper = exports.supportedApiMethods = void 0;
const type_1 = require("../../type");
const utils_1 = require("../../utils");
/**
* 支持的API方法列表
* @see https://github.com/alovajs/alova/blob/main/packages/alova/typings/index.d.ts#L640
*/
exports.supportedApiMethods = [
type_1.HttpMethod.GET,
type_1.HttpMethod.PUT,
type_1.HttpMethod.POST,
type_1.HttpMethod.DELETE,
type_1.HttpMethod.PATCH,
type_1.HttpMethod.HEAD,
type_1.HttpMethod.OPTIONS,
];
class OpenApiHelper {
constructor() {
this.usedRefsCache = new Set();
}
load(document) {
this.document = document;
// reset cache when document changes
this.usedRefsCache.clear();
return this;
}
static load(document) {
const ins = new OpenApiHelper();
return ins.load(document);
}
getApiMethods() {
const paths = this.document.paths || [];
const apiMethods = [];
for (const [url, pathInfo] of Object.entries(paths)) {
if (!pathInfo) {
continue;
}
for (const [method, operationObject] of Object.entries(pathInfo)) {
if (!exports.supportedApiMethods.includes(method)) {
continue;
}
if (typeof operationObject === 'string' || Array.isArray(operationObject)) {
continue;
}
apiMethods.push({
url,
method,
operationObject,
});
}
}
return apiMethods;
}
/**
* 将 ApiMethods 数组写回到 openapi document 的 paths 中。
* 仅更新传入的 url+method 对应的 operationObject,保留其它未涉及的内容。
*/
saveApiMethods(apiMethods) {
this.document.paths = {};
// 构建 url -> { method -> operationObject } 映射,后写入覆盖前者
const grouped = {};
for (const item of apiMethods || []) {
if (!item || !item.url || !item.method || !item.operationObject) {
continue;
}
const method = String(item.method).toLowerCase();
if (!exports.supportedApiMethods.includes(method)) {
// 跳过不支持的 http 方法
continue;
}
if (!grouped[item.url]) {
grouped[item.url] = {};
}
grouped[item.url][method] = item.operationObject;
}
// 将分组后的方法写回到 document.paths
for (const [url, methodsMap] of Object.entries(grouped)) {
const pathInfo = (this.document.paths?.[url] || {});
for (const [method, operationObject] of Object.entries(methodsMap)) {
pathInfo[method] = operationObject;
}
if (this.document.paths) {
this.document.paths[url] = pathInfo;
}
}
// 更新后清除引用缓存,确保后续计算的使用引用为最新
this.usedRefsCache.clear();
return this;
}
/**
* 判断某个引用是否在文档中被使用
* @param ref ReferenceObject 或其 `$ref` 路径字符串
*/
isReferenceUsed(ref) {
const refPath = typeof ref === 'string' ? ref : ref.$ref;
const usedSet = this.getUsedReferenceSet();
return usedSet.has(refPath);
}
/**
* 预计算并返回已使用的 `$ref` 集合,仅从 paths 可达
*/
getUsedReferenceSet() {
if (this.usedRefsCache.size > 0) {
return this.usedRefsCache;
}
const used = this.usedRefsCache;
const visitedRefs = new Set();
const stack = [];
const roots = [];
if (this.document.paths) {
roots.push(this.document.paths);
}
stack.push(...roots);
while (stack.length) {
const node = stack.pop();
if (!node || typeof node !== 'object') {
continue;
}
if ((0, utils_1.isReferenceObject)(node)) {
const currentRef = node.$ref;
used.add(currentRef);
if (!visitedRefs.has(currentRef)) {
visitedRefs.add(currentRef);
try {
const target = (0, utils_1.findBy$ref)(currentRef, this.document);
stack.push(target);
}
catch {
// ignore invalid refs
}
}
continue;
}
if (Array.isArray(node)) {
for (const item of node) {
stack.push(item);
}
continue;
}
for (const key of Object.keys(node)) {
stack.push(node[key]);
}
}
return used;
}
/**
* 过滤掉未使用的引用,返回仍被使用的引用列表
*/
filterUsedReferences(refs) {
const usedSet = this.getUsedReferenceSet();
return (refs || []).filter((ref) => {
const refPath = typeof ref === 'string' ? ref : ref.$ref;
return usedSet.has(refPath);
});
}
}
exports.OpenApiHelper = OpenApiHelper;
exports.openApiHelper = new OpenApiHelper();