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(` ${this.apiDoc.info?.title || 'no name set'} - SwaggerUI
`); res.end(); }; } public getSlashRedocMiddleware() { return (req: plugins.smartexpress.Request, res: plugins.smartexpress.Response) => { res.setHeader('content-type', 'text/html'); res.write(` ${this.apiDoc.info?.title || 'no name set'} - Redoc `); 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(); }; } }