2021-11-19 19:05:52 +00:00
|
|
|
import path from 'path';
|
|
|
|
import * as plugins from './smartswagger.plugins';
|
|
|
|
|
|
|
|
interface RedocProps {
|
2021-11-21 00:14:53 +00:00
|
|
|
'x-tagGroups'?: {name: string, tags: string[]}[];
|
2021-11-19 19:05:52 +00:00
|
|
|
}
|
|
|
|
type IExtendedApiDoc = plugins.openapiTypes.OpenAPIV3.Document & RedocProps;
|
|
|
|
|
|
|
|
export class Smartswagger {
|
|
|
|
// STATIC
|
|
|
|
/**
|
2021-11-21 00:14:53 +00:00
|
|
|
* creates a new Smartswagger instance from an external document
|
2021-11-19 19:05:52 +00:00
|
|
|
* @param urlArg a url arg that contains an original swagger.json in the response
|
2021-11-20 21:38:37 +00:00
|
|
|
* @returns an instance of
|
2021-11-19 19:05:52 +00:00
|
|
|
*/
|
|
|
|
public static async createFromUrl(urlArg: string) {
|
|
|
|
const jsonResponse = await plugins.nodeFetch(urlArg, {
|
|
|
|
headers: {
|
|
|
|
accept: 'application/json',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const apiDoc = await jsonResponse.json();
|
|
|
|
const newSMartswaggerInstance = new Smartswagger(apiDoc);
|
|
|
|
return newSMartswaggerInstance;
|
|
|
|
}
|
|
|
|
|
2021-11-21 00:14:53 +00:00
|
|
|
public static async createNew(titleArg: string = 'an OpenApiDoc') {
|
|
|
|
const newSMartswaggerInstance = new Smartswagger({
|
|
|
|
openapi: '3.0.0',
|
|
|
|
paths: {},
|
|
|
|
info: {
|
|
|
|
title: titleArg,
|
|
|
|
version: '1.0.0'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return newSMartswaggerInstance;
|
|
|
|
}
|
|
|
|
|
2021-11-19 19:05:52 +00:00
|
|
|
// INSTANCE
|
|
|
|
/**
|
|
|
|
* the basic info of the api doc
|
|
|
|
*/
|
|
|
|
public baseInfo: plugins.openapiTypes.OpenAPIV3.InfoObject;
|
|
|
|
|
|
|
|
public apiDoc: IExtendedApiDoc;
|
|
|
|
|
|
|
|
constructor(apiDocArg: IExtendedApiDoc) {
|
|
|
|
this.apiDoc = apiDocArg;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dereferences the document at hand
|
|
|
|
*/
|
|
|
|
public async deref() {
|
2021-11-20 21:38:37 +00:00
|
|
|
this.apiDoc = (await plugins.swaggerParser.dereference(this.apiDoc)) as IExtendedApiDoc;
|
2021-11-19 19:05:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async addServer(serverArg: plugins.openapiTypes.OpenAPIV3.ServerObject) {
|
|
|
|
await this.deref();
|
|
|
|
this.apiDoc.servers = this.apiDoc.servers || [];
|
|
|
|
this.apiDoc.servers.push(serverArg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* merge a document from url
|
|
|
|
* @param documentToMergeArg
|
|
|
|
* @param basePathArg
|
|
|
|
*/
|
2021-11-21 00:14:53 +00:00
|
|
|
public async mergeDocument(documentToMergeArg: IExtendedApiDoc, basePathArg: string = '', tagArg?: string) {
|
2021-11-19 19:05:52 +00:00
|
|
|
console.log(`merging document with name ${documentToMergeArg.info?.title}`);
|
|
|
|
await this.deref();
|
|
|
|
// lets get a dereferenced version of the document we want to merge
|
|
|
|
const documentToMerge = (await plugins.swaggerParser.dereference(
|
|
|
|
documentToMergeArg
|
|
|
|
)) as IExtendedApiDoc;
|
|
|
|
for (const path of Object.keys(documentToMerge.paths)) {
|
|
|
|
const pathToMerge = plugins.path.join(basePathArg, path);
|
|
|
|
this.apiDoc.paths = this.apiDoc.paths || {};
|
|
|
|
this.apiDoc.paths[pathToMerge] = documentToMerge.paths[path];
|
2021-11-21 00:14:53 +00:00
|
|
|
if (tagArg) {
|
|
|
|
if (this.apiDoc.paths[pathToMerge].post) {
|
|
|
|
this.apiDoc.paths[pathToMerge].post.tags = this.apiDoc.paths[pathToMerge].post.tags || [];
|
|
|
|
this.apiDoc.paths[pathToMerge].post.tags.push(tagArg);
|
|
|
|
}
|
|
|
|
if (this.apiDoc.paths[pathToMerge].get) {
|
|
|
|
this.apiDoc.paths[pathToMerge].get.tags = this.apiDoc.paths[pathToMerge].get.tags || [];
|
|
|
|
this.apiDoc.paths[pathToMerge].get.tags.push(tagArg);
|
|
|
|
}
|
|
|
|
if (this.apiDoc.paths[pathToMerge].put) {
|
|
|
|
this.apiDoc.paths[pathToMerge].put.tags = this.apiDoc.paths[pathToMerge].put.tags || [];
|
|
|
|
this.apiDoc.paths[pathToMerge].put.tags.push(tagArg);
|
|
|
|
|
|
|
|
}
|
|
|
|
if (this.apiDoc.paths[pathToMerge].delete) {
|
|
|
|
this.apiDoc.paths[pathToMerge].delete.tags = this.apiDoc.paths[pathToMerge].delete.tags || [];
|
|
|
|
this.apiDoc.paths[pathToMerge].delete.tags.push(tagArg);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2021-11-19 19:05:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// merge tag groups
|
|
|
|
this.apiDoc['x-tagGroups'] = this.apiDoc['x-tagGroups'] || [];
|
|
|
|
if (documentToMerge['x-tagGroups']) {
|
|
|
|
for (const xTagGroup of documentToMerge['x-tagGroups']) {
|
|
|
|
this.apiDoc['x-tagGroups'].push(xTagGroup);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
console.log('merged!');
|
2021-11-21 00:14:53 +00:00
|
|
|
|
|
|
|
// set custom tag arg.
|
|
|
|
if (tagArg) {
|
|
|
|
this.apiDoc['x-tagGroups'].push({name: tagArg, tags: [tagArg]});
|
|
|
|
}
|
2021-11-19 19:05:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* merges a document from url
|
|
|
|
*/
|
2021-11-21 00:14:53 +00:00
|
|
|
public async mergeDocumentFromUrl(documentUrlArg: string, basePathArg: string = '', tagArg?: string) {
|
2021-11-20 21:38:37 +00:00
|
|
|
console.log(`getting document at ${documentUrlArg} for merging...`);
|
2021-11-19 19:05:52 +00:00
|
|
|
const documentResponse = await plugins.nodeFetch(documentUrlArg, {
|
|
|
|
headers: {
|
|
|
|
'accept-encoding': 'application/json',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const documentString = await documentResponse.text();
|
|
|
|
const apiDoc: IExtendedApiDoc = JSON.parse(documentString);
|
|
|
|
console.log(`document successfully fetched!`);
|
2021-11-21 00:14:53 +00:00
|
|
|
await this.mergeDocument(apiDoc, basePathArg, tagArg);
|
2021-11-19 19:05:52 +00:00
|
|
|
}
|
|
|
|
|
2021-11-19 19:16:01 +00:00
|
|
|
/**
|
|
|
|
* merge multiple documents in parallel
|
|
|
|
* @param urlArrayArg
|
|
|
|
*/
|
2021-11-21 00:14:53 +00:00
|
|
|
public async mergeManyDocumentsFromUrl(urlArrayArg: { url: string; basePath?: string, tagArg?: string }[]) {
|
2021-11-19 19:16:01 +00:00
|
|
|
const promiseArray: Promise<void>[] = [];
|
|
|
|
for (const urlArg of urlArrayArg) {
|
2021-11-21 00:14:53 +00:00
|
|
|
promiseArray.push(this.mergeDocumentFromUrl(urlArg.url, urlArg.basePath, urlArg.tagArg));
|
2021-11-20 21:38:37 +00:00
|
|
|
}
|
2021-11-19 19:16:01 +00:00
|
|
|
await Promise.all(promiseArray);
|
|
|
|
}
|
|
|
|
|
2021-11-19 19:05:52 +00:00
|
|
|
/**
|
|
|
|
* merges a component to routes based on regex
|
|
|
|
*/
|
|
|
|
public mergeComponentToRoutes(
|
|
|
|
routeDescriptor: {
|
|
|
|
includeRegexpArray: RegExp[];
|
|
|
|
excludeRegexpArray: RegExp[];
|
|
|
|
},
|
|
|
|
componentArg: plugins.openapiTypes.OpenAPIV3.ComponentsObject
|
|
|
|
) {
|
|
|
|
for (const pathCandidateRoute of Object.keys(this.apiDoc.paths)) {
|
|
|
|
let included: boolean = false;
|
|
|
|
let excluded: boolean = false;
|
|
|
|
for (const regExp of routeDescriptor.includeRegexpArray) {
|
|
|
|
if (regExp.test(pathCandidateRoute)) {
|
|
|
|
included = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (included) {
|
|
|
|
for (const regExp of routeDescriptor.excludeRegexpArray) {
|
|
|
|
if (regExp.test(pathCandidateRoute)) {
|
|
|
|
excluded = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (included && !excluded) {
|
|
|
|
// lets do the actual component inclusion
|
|
|
|
const pathCandidate = this.apiDoc.paths[pathCandidateRoute];
|
|
|
|
const instrumentMethod = (methodArg: typeof pathCandidate.get) => {
|
|
|
|
if (!methodArg) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (componentArg.securitySchemes) {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private cacheResult: string = '';
|
|
|
|
private cacheCreationUnixTimestamp: number;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* returns an express middleware using 'swagger-ui-express'
|
|
|
|
*/
|
|
|
|
public getSlashApiUiMiddleware() {
|
|
|
|
return (req: plugins.smartexpress.Request, res: plugins.smartexpress.Response) => {
|
|
|
|
res.setHeader('content-type', 'text/html');
|
|
|
|
res.write(`
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>${this.apiDoc.info?.title || 'no name set'} - SwaggerUI</title>
|
|
|
|
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3.12.1/swagger-ui.css">
|
|
|
|
<style>
|
|
|
|
body {
|
|
|
|
margin: 0px;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="swagger-ui"></div>
|
|
|
|
</body>
|
|
|
|
<script src="https://unpkg.com/swagger-ui-dist@3.12.1/swagger-ui-standalone-preset.js"></script>
|
|
|
|
<script src="https://unpkg.com/swagger-ui-dist@3.12.1/swagger-ui-bundle.js"></script>
|
|
|
|
<script>
|
|
|
|
window.onload = function() {
|
|
|
|
// Build a system
|
|
|
|
const ui = SwaggerUIBundle({
|
|
|
|
url: "/apischema",
|
|
|
|
dom_id: '#swagger-ui',
|
|
|
|
deepLinking: true,
|
|
|
|
presets: [
|
|
|
|
SwaggerUIBundle.presets.apis,
|
|
|
|
SwaggerUIStandalonePreset
|
|
|
|
],
|
|
|
|
plugins: [
|
|
|
|
SwaggerUIBundle.plugins.DownloadUrl
|
|
|
|
],
|
|
|
|
layout: "StandaloneLayout",
|
|
|
|
})
|
|
|
|
window.ui = ui
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
</html>
|
|
|
|
`);
|
|
|
|
res.end();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public getSlashRedocMiddleware() {
|
|
|
|
return (req: plugins.smartexpress.Request, res: plugins.smartexpress.Response) => {
|
|
|
|
res.setHeader('content-type', 'text/html');
|
|
|
|
res.write(`
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>${this.apiDoc.info?.title || 'no name set'} - Redoc </title>
|
|
|
|
<!-- needed for adaptive design -->
|
|
|
|
<meta charset="utf-8"/>
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
|
|
|
|
|
|
|
<!--
|
|
|
|
Redoc doesn't change outer page styles
|
|
|
|
-->
|
|
|
|
<style>
|
|
|
|
body {
|
|
|
|
margin: 0;
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<redoc spec-url='/apischema'></redoc>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
`);
|
|
|
|
res.end();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public getSlashApiSchemaMiddleware() {
|
|
|
|
return async (req: plugins.smartexpress.Request, res: plugins.smartexpress.Response) => {
|
|
|
|
res.header('content-type', 'application/json');
|
|
|
|
res.write(JSON.stringify(this.apiDoc));
|
|
|
|
res.end();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|