232 lines
7.0 KiB
TypeScript
232 lines
7.0 KiB
TypeScript
|
import path from 'path';
|
||
|
import * as plugins from './smartswagger.plugins';
|
||
|
|
||
|
interface RedocProps {
|
||
|
'x-tagGroups': any;
|
||
|
}
|
||
|
type IExtendedApiDoc = plugins.openapiTypes.OpenAPIV3.Document & RedocProps;
|
||
|
|
||
|
export class Smartswagger {
|
||
|
// STATIC
|
||
|
/**
|
||
|
*
|
||
|
* @param urlArg a url arg that contains an original swagger.json in the response
|
||
|
* @returns an instance of
|
||
|
*/
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// 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() {
|
||
|
this.apiDoc = (await plugins.swaggerParser.dereference(
|
||
|
this.apiDoc
|
||
|
)) as IExtendedApiDoc;
|
||
|
}
|
||
|
|
||
|
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
|
||
|
*/
|
||
|
public async mergeDocument(
|
||
|
documentToMergeArg: IExtendedApiDoc,
|
||
|
basePathArg: string
|
||
|
) {
|
||
|
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];
|
||
|
}
|
||
|
|
||
|
// 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!');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* merges a document from url
|
||
|
*/
|
||
|
public async mergeDocumentFromUrl(documentUrlArg: string, basePathArg: string) {
|
||
|
console.log(`getting document at ${documentUrlArg} for merging...`)
|
||
|
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!`);
|
||
|
await this.mergeDocument(apiDoc, basePathArg);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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();
|
||
|
};
|
||
|
}
|
||
|
}
|