197 lines
6.9 KiB
JavaScript
197 lines
6.9 KiB
JavaScript
import * as yaml from "js-yaml";
|
|
export class OpenAPISpec {
|
|
constructor(document) {
|
|
Object.defineProperty(this, "document", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: document
|
|
});
|
|
}
|
|
get baseUrl() {
|
|
return this.document.servers ? this.document.servers[0].url : undefined;
|
|
}
|
|
getPathsStrict() {
|
|
if (!this.document.paths) {
|
|
throw new Error("No paths found in spec");
|
|
}
|
|
return this.document.paths;
|
|
}
|
|
getParametersStrict() {
|
|
if (!this.document.components?.parameters) {
|
|
throw new Error("No parameters found in spec");
|
|
}
|
|
return this.document.components.parameters;
|
|
}
|
|
getSchemasStrict() {
|
|
if (!this.document.components?.schemas) {
|
|
throw new Error("No schemas found in spec.");
|
|
}
|
|
return this.document.components.schemas;
|
|
}
|
|
getRequestBodiesStrict() {
|
|
if (!this.document.components?.requestBodies) {
|
|
throw new Error("No request body found in spec.");
|
|
}
|
|
return this.document.components.requestBodies;
|
|
}
|
|
getPathStrict(path) {
|
|
const pathItem = this.getPathsStrict()[path];
|
|
if (pathItem === undefined) {
|
|
throw new Error(`No path found for "${path}".`);
|
|
}
|
|
return pathItem;
|
|
}
|
|
getReferencedParameter(ref) {
|
|
const refComponents = ref.$ref.split("/");
|
|
const refName = refComponents[refComponents.length - 1];
|
|
if (this.getParametersStrict()[refName] === undefined) {
|
|
throw new Error(`No parameter found for "${refName}".`);
|
|
}
|
|
return this.getParametersStrict()[refName];
|
|
}
|
|
getRootReferencedParameter(ref) {
|
|
let parameter = this.getReferencedParameter(ref);
|
|
while (parameter.$ref !== undefined) {
|
|
parameter = this.getReferencedParameter(parameter);
|
|
}
|
|
return parameter;
|
|
}
|
|
getReferencedSchema(ref) {
|
|
const refComponents = ref.$ref.split("/");
|
|
const refName = refComponents[refComponents.length - 1];
|
|
const schema = this.getSchemasStrict()[refName];
|
|
if (schema === undefined) {
|
|
throw new Error(`No schema found for "${refName}".`);
|
|
}
|
|
return schema;
|
|
}
|
|
getSchema(schema) {
|
|
if (schema.$ref !== undefined) {
|
|
return this.getReferencedSchema(schema);
|
|
}
|
|
return schema;
|
|
}
|
|
getRootReferencedSchema(ref) {
|
|
let schema = this.getReferencedSchema(ref);
|
|
while (schema.$ref !== undefined) {
|
|
schema = this.getReferencedSchema(schema);
|
|
}
|
|
return schema;
|
|
}
|
|
getReferencedRequestBody(ref) {
|
|
const refComponents = ref.$ref.split("/");
|
|
const refName = refComponents[refComponents.length - 1];
|
|
const requestBodies = this.getRequestBodiesStrict();
|
|
if (requestBodies[refName] === undefined) {
|
|
throw new Error(`No request body found for "${refName}"`);
|
|
}
|
|
return requestBodies[refName];
|
|
}
|
|
getRootReferencedRequestBody(ref) {
|
|
let requestBody = this.getReferencedRequestBody(ref);
|
|
while (requestBody.$ref !== undefined) {
|
|
requestBody = this.getReferencedRequestBody(requestBody);
|
|
}
|
|
return requestBody;
|
|
}
|
|
getMethodsForPath(path) {
|
|
const pathItem = this.getPathStrict(path);
|
|
// This is an enum in the underlying package.
|
|
// Werestate here to allow "import type" above and not cause warnings in certain envs.
|
|
const possibleMethods = [
|
|
"get",
|
|
"put",
|
|
"post",
|
|
"delete",
|
|
"options",
|
|
"head",
|
|
"patch",
|
|
"trace",
|
|
];
|
|
return possibleMethods.filter((possibleMethod) => pathItem[possibleMethod] !== undefined);
|
|
}
|
|
getParametersForPath(path) {
|
|
const pathItem = this.getPathStrict(path);
|
|
if (pathItem.parameters === undefined) {
|
|
return [];
|
|
}
|
|
return pathItem.parameters.map((parameter) => {
|
|
if (parameter.$ref !== undefined) {
|
|
return this.getRootReferencedParameter(parameter);
|
|
}
|
|
return parameter;
|
|
});
|
|
}
|
|
getOperation(path, method) {
|
|
const pathItem = this.getPathStrict(path);
|
|
if (pathItem[method] === undefined) {
|
|
throw new Error(`No ${method} method found for "path".`);
|
|
}
|
|
return pathItem[method];
|
|
}
|
|
getParametersForOperation(operation) {
|
|
if (operation.parameters === undefined) {
|
|
return [];
|
|
}
|
|
return operation.parameters.map((parameter) => {
|
|
if (parameter.$ref !== undefined) {
|
|
return this.getRootReferencedParameter(parameter);
|
|
}
|
|
return parameter;
|
|
});
|
|
}
|
|
getRequestBodyForOperation(operation) {
|
|
const { requestBody } = operation;
|
|
if (requestBody?.$ref !== undefined) {
|
|
return this.getRootReferencedRequestBody(requestBody);
|
|
}
|
|
return requestBody;
|
|
}
|
|
static getCleanedOperationId(operation, path, method) {
|
|
let { operationId } = operation;
|
|
if (operationId === undefined) {
|
|
const updatedPath = path.replaceAll(/[^a-zA-Z0-9]/, "_");
|
|
operationId = `${updatedPath.startsWith("/") ? updatedPath.slice(1) : updatedPath}_${method}`;
|
|
}
|
|
return operationId
|
|
.replaceAll("-", "_")
|
|
.replaceAll(".", "_")
|
|
.replaceAll("/", "_");
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
static alertUnsupportedSpec(document) {
|
|
const warningMessage = "This may result in degraded performance. Convert your OpenAPI spec to 3.1.0 for better support.";
|
|
const swaggerVersion = document.swagger;
|
|
const openAPIVersion = document.openapi;
|
|
if (openAPIVersion !== undefined && openAPIVersion !== "3.1.0") {
|
|
console.warn(`Attempting to load an OpenAPI ${openAPIVersion} spec. ${warningMessage}`);
|
|
}
|
|
else if (swaggerVersion !== undefined) {
|
|
console.warn(`Attempting to load a Swagger ${swaggerVersion} spec. ${warningMessage}`);
|
|
}
|
|
else {
|
|
throw new Error(`Attempting to load an unsupported spec:\n\n${JSON.stringify(document, null, 2)}.`);
|
|
}
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
static fromObject(document) {
|
|
OpenAPISpec.alertUnsupportedSpec(document);
|
|
return new OpenAPISpec(document);
|
|
}
|
|
static fromString(rawString) {
|
|
let document;
|
|
try {
|
|
document = JSON.parse(rawString);
|
|
}
|
|
catch (e) {
|
|
document = yaml.load(rawString);
|
|
}
|
|
return OpenAPISpec.fromObject(document);
|
|
}
|
|
static async fromURL(url) {
|
|
const response = await fetch(url);
|
|
const rawDocument = await response.text();
|
|
return OpenAPISpec.fromString(rawDocument);
|
|
}
|
|
}
|