docusaurus-plugin-openapi-docs
Version:
OpenAPI plugin for Docusaurus.
172 lines (154 loc) • 5 kB
text/typescript
/* ============================================================================
* Copyright (c) Palo Alto Networks
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* ========================================================================== */
import $RefParser from "@apidevtools/json-schema-ref-parser";
import { bundle, Config } from "@redocly/openapi-core";
import type { Source, Document } from "@redocly/openapi-core";
import { ResolvedConfig } from "@redocly/openapi-core/lib/config";
import chalk from "chalk";
// @ts-ignore
import { convertObj } from "swagger2openapi";
import { OpenApiObject } from "../types";
import { OpenAPIParser } from "./services/OpenAPIParser";
function serializer(replacer: any, cycleReplacer: any) {
var stack: any = [],
keys: any = [];
if (cycleReplacer === undefined)
cycleReplacer = function (key: any, value: any) {
if (stack[0] === value) return "circular()";
return value.title ? `circular(${value.title})` : "circular()";
};
return function (key: any, value: any) {
// Resolve discriminator ref pointers
if (value?.discriminator !== undefined) {
const parser = new OpenAPIParser(stack[0]);
if (
value.discriminator.mapping &&
typeof value.discriminator.mapping === "object"
) {
for (let [k, v] of Object.entries(value.discriminator.mapping)) {
const discriminator = k as string;
if (typeof v === "string" && v.charAt(0) === "#") {
const ref = v as string;
const resolvedRef = parser.byRef(ref);
value.discriminator.mapping[discriminator] = resolvedRef;
}
}
}
}
if (stack.length > 0) {
// @ts-ignore
var thisPos = stack.indexOf(this);
// @ts-ignore
~thisPos ? stack.splice(thisPos + 1) : stack.push(this);
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key);
// @ts-ignore
if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value);
} else stack.push(value);
// @ts-ignore
return replacer === undefined ? value : replacer.call(this, key, value);
};
}
export function convertSwagger2OpenAPI(spec: object) {
console.warn(
"[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0"
);
return new Promise((resolve, reject) =>
convertObj(
spec,
{
patch: true,
warnOnly: true,
text: "{}",
anchors: true,
resolveInternal: true,
},
(err: any, res: any) => {
// TODO: log any warnings
if (err) {
return reject(err);
}
resolve(res && res.openapi);
}
)
);
}
async function resolveJsonRefs(specUrlOrObject: object | string) {
try {
let schema = await $RefParser.dereference(specUrlOrObject, {
continueOnError: true,
resolve: {
file: true,
external: true,
http: {
timeout: 15000, // 15 sec timeout
},
},
dereference: {
circular: true,
},
});
return schema as OpenApiObject;
} catch (err: any) {
let errorMsg = "";
if (err.errors[0] !== undefined) {
const error = err.errors[0];
errorMsg = `Error: [${error.message}] with footprint [${error.footprint}]`;
} else {
errorMsg = err;
}
console.error(chalk.yellow(errorMsg));
return;
}
}
export async function loadAndResolveSpec(specUrlOrObject: object | string) {
const config = new Config({} as ResolvedConfig);
const bundleOpts = {
config,
base: process.cwd(),
} as any;
if (typeof specUrlOrObject === "object" && specUrlOrObject !== null) {
bundleOpts["doc"] = {
source: { absoluteRef: "" } as Source,
parsed: specUrlOrObject,
} as Document;
} else {
bundleOpts["ref"] = specUrlOrObject;
}
// Force dereference ?
// bundleOpts["dereference"] = true;
const {
bundle: { parsed },
} = await bundle(bundleOpts);
//Pre-processing before resolving JSON refs
if (parsed.components) {
for (let [component, type] of Object.entries(parsed.components) as any) {
if (component === "schemas") {
for (let [schemaKey, schemaValue] of Object.entries(type) as any) {
const title: string | undefined = schemaValue["title"];
if (!title) {
schemaValue.title = schemaKey;
}
}
}
}
}
const resolved = await resolveJsonRefs(parsed);
// Force serialization and replace circular $ref pointers
// @ts-ignore
const serialized = JSON.stringify(resolved, serializer());
let decycled;
try {
decycled = JSON.parse(serialized);
} catch (err: any) {
console.error(chalk.yellow(err));
}
return decycled !== undefined && typeof decycled === "object"
? decycled.swagger !== undefined
? convertSwagger2OpenAPI(decycled)
: decycled
: resolved;
}