nuxt-openapi-docs-module
Version:
A module for Nuxt.js to generate pages from OpenAPI specifications
194 lines (193 loc) • 6.51 kB
JavaScript
import { kebabCase } from "scule";
import { FileHandler } from "./FileHandler.js";
import { MarkdownRenderer } from "./MarkdownRenderer.js";
import { ReferenceResolver } from "./ReferenceResolver.js";
import { basename, extname } from "path";
export default class OpenApiProcessor {
workDir;
fileName = "";
spec = {};
markdownRenderer;
referenceResolver;
fileHandler = new FileHandler();
locales = { "en": "English" };
localesReload = false;
constructor(workDir) {
this.workDir = workDir;
this.markdownRenderer = new MarkdownRenderer();
this.referenceResolver = new ReferenceResolver(workDir, this.fileHandler);
}
load(fileName) {
this.spec = this.fileHandler.loadYamlFile(this.workDir, fileName);
this.fileName = kebabCase(basename(fileName, extname(fileName)));
this.spec = this.processSpec(this.spec);
this.spec = this.replaceMarkdown(this.spec);
this.processLocales();
this.processTags();
}
processLocales() {
this.localesReload = !!this.spec?.info?.["x-locales-reload"];
this.locales = { en: "English", ...this.spec?.info?.["x-locales"] };
}
processTags() {
if (!this.spec.tags) {
this.spec.tags = [];
}
this.spec.tags = this.spec.tags.reduce((acc, tag) => {
acc[tag.name.toString().toLowerCase()] = tag;
return acc;
}, {});
}
processSpec(obj) {
if (Array.isArray(obj)) {
return obj.map((val) => val);
} else if (typeof obj === "object" && obj !== null) {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (key === "$ref" && typeof value === "string") {
if (!value.startsWith("#")) {
return obj = this.referenceResolver.resolveRef(value);
}
return obj = value;
} else {
acc[key] = this.processSpec(value);
}
return acc;
}, {});
}
return obj;
}
getFilename() {
return this.fileName;
}
getDoc() {
return this.spec;
}
getLocalesReload() {
return this.localesReload;
}
getLocales() {
return this.locales;
}
getServers() {
return this.spec.servers ?? [];
}
getPaths() {
return JSON.parse(JSON.stringify({
...this.processOpenApiPaths(this.spec.webhooks ?? {}, "webhooks"),
...this.processCustomPaths(this.spec["x-custom-path"] ?? {}),
...this.processOpenApiPaths(this.spec.paths)
}));
}
processCustomPaths(custom) {
const pathsByTags = {};
if (Object.keys(custom).length) {
pathsByTags["custom"] = {
name: custom.name ?? "Custom",
description: custom.description ?? "",
isOpen: custom["x-tag-expanded"] ?? true,
items: []
};
for (const i in this.locales) {
if (custom[`x-description-${i}`]) {
pathsByTags["custom"][`x-description-${i}`] = custom[`x-description-${i}`];
}
if (custom[`x-name-${i}`]) {
pathsByTags["custom"][`x-name-${i}`] = custom[`x-name-${i}`];
}
}
for (const path in custom.paths) {
const item = {
name: custom.paths[path].title ?? path,
path,
type: "custom",
description: custom.paths[path].description ?? "",
icon: custom.paths[path]["x-icon"] ?? null
};
for (const i in this.locales) {
if (custom.paths[path][`x-description-${i}`]) {
item[`x-description-${i}`] = custom.paths[path][`x-description-${i}`];
}
if (custom.paths[path][`x-title-${i}`]) {
item[`x-name-${i}`] = custom.paths[path][`x-title-${i}`];
}
}
pathsByTags["custom"].items.push(item);
}
}
return pathsByTags;
}
processOpenApiPaths(paths, type = "other") {
const pathsByTags = {};
for (const url in paths) {
let routePath = url;
if (routePath.startsWith("/")) routePath = routePath.substring(1);
if (routePath.endsWith("/")) routePath = routePath.substring(-1);
routePath = routePath.replace(/[/\\.?+=&{}]/gumi, "_").replace(/__+/, "_");
for (const method in paths[url]) {
const openapi_item = paths[url][method];
if (!openapi_item.tags) openapi_item.tags = [type];
openapi_item.tags.forEach((tag) => {
if (method === "parameters") return;
if (method === "servers") return;
if (!pathsByTags[tag]) {
const tagInfo = this.spec.tags[tag.toString().toLowerCase()] ?? {};
const item2 = {
name: tagInfo.name ?? tag,
description: this.markdownRenderer.render(tagInfo.description ?? ""),
isOpen: tagInfo["x-tag-expanded"] ?? true,
items: []
};
for (const locale in this.locales) {
if (tagInfo[`x-description-${locale}`]) {
item2[`x-description-${locale}`] = this.markdownRenderer.render(tagInfo[`x-description-${locale}`]);
}
if (tagInfo[`x-name-${locale}`]) {
item2[`x-name-${locale}`] = tagInfo[`x-name-${locale}`];
}
}
pathsByTags[tag] = item2;
}
const item = {
name: url,
path: routePath,
type: method,
icon: openapi_item["x-icon"] ?? null,
description: openapi_item.summary ?? null
};
if (openapi_item.deprecated) {
item.deprecated = openapi_item.deprecated;
}
for (const i in this.locales) {
if (openapi_item[`x-summary-${i}`]) {
item[`x-description-${i}`] = openapi_item[`x-summary-${i}`];
}
}
pathsByTags[tag].items.push(item);
});
}
}
return pathsByTags;
}
replaceMarkdown(obj) {
if (typeof obj === "string") {
if (obj.match(/\[.*?\]\(.*?\)|^>/) || obj.match(/```|\*\*|:--|<a |## |------|`..?`/)) {
return this.markdownRenderer.render(obj);
} else {
return this.markdownRenderer.sanitizeText(obj);
}
} else if (Array.isArray(obj)) {
return obj.map((val) => this.replaceMarkdown(val));
} else if (typeof obj === "object" && obj !== null) {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (key === "$ref" && typeof value === "string") {
return value;
} else {
acc[key] = this.replaceMarkdown(value);
}
return acc;
}, {});
} else {
return obj;
}
}
}