12 Commits

Author SHA1 Message Date
d540431158 1.1.2 2021-11-22 10:44:38 +01:00
4c68daf54c fix(core): update 2021-11-22 10:44:38 +01:00
ee6f7626a0 1.1.1 2021-11-22 10:24:09 +01:00
692be04ed4 fix(core): update 2021-11-22 10:24:09 +01:00
762305f29c 1.1.0 2021-11-21 01:14:53 +01:00
c83f0ccd35 feat(tagging for redoc): add tagging for redoc 2021-11-21 01:14:53 +01:00
d60ff7efdf 1.0.8 2021-11-20 22:38:37 +01:00
f29623c084 fix(core): update 2021-11-20 22:38:37 +01:00
d152920692 1.0.7 2021-11-20 22:28:08 +01:00
5628ce6328 fix(core): update 2021-11-20 22:28:08 +01:00
a24e7c6f73 1.0.6 2021-11-20 20:32:16 +01:00
8d9d3f92db fix(tests): add more meaningful tests 2021-11-20 20:32:16 +01:00
8 changed files with 194 additions and 58 deletions

View File

@@ -1,3 +0,0 @@
# smartswagger
a library for dealing with swagger schemas.

69
package-lock.json generated
View File

@@ -1,18 +1,19 @@
{ {
"name": "@pushrocks/smartswagger", "name": "@pushrocks/smartswagger",
"version": "1.0.5", "version": "1.1.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@pushrocks/smartswagger", "name": "@pushrocks/smartswagger",
"version": "1.0.5", "version": "1.1.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "^10.0.3", "@apidevtools/swagger-parser": "^10.0.3",
"@pushrocks/smartpromise": "^3.1.6", "@pushrocks/smartpromise": "^3.1.6",
"@types/node-fetch": "^2.5.12", "@types/node-fetch": "^2.5.12",
"@types/swagger-ui-express": "^4.1.3", "@types/swagger-ui-express": "^4.1.3",
"matcher": "^4.0.0",
"node-fetch": "^2.6.6", "node-fetch": "^2.6.6",
"openapi-types": "^9.3.1" "openapi-types": "^9.3.1"
}, },
@@ -2467,6 +2468,32 @@
"matcher": "^3.0.0" "matcher": "^3.0.0"
} }
}, },
"node_modules/@pushrocks/smartmatch/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://verdaccio.lossless.one/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@pushrocks/smartmatch/node_modules/matcher": {
"version": "3.0.0",
"resolved": "https://verdaccio.lossless.one/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"dev": true,
"license": "MIT",
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@pushrocks/smartmime": { "node_modules/@pushrocks/smartmime": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmartmime/-/smartmime-1.0.5.tgz", "resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmartmime/-/smartmime-1.0.5.tgz",
@@ -9540,23 +9567,24 @@
} }
}, },
"node_modules/matcher": { "node_modules/matcher": {
"version": "3.0.0", "version": "4.0.0",
"resolved": "https://verdaccio.lossless.one/matcher/-/matcher-3.0.0.tgz", "resolved": "https://verdaccio.lossless.one/matcher/-/matcher-4.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", "integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"escape-string-regexp": "^4.0.0" "escape-string-regexp": "^4.0.0"
}, },
"engines": { "engines": {
"node": ">=10" "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/matcher/node_modules/escape-string-regexp": { "node_modules/matcher/node_modules/escape-string-regexp": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://verdaccio.lossless.one/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://verdaccio.lossless.one/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@@ -17387,6 +17415,23 @@
"dev": true, "dev": true,
"requires": { "requires": {
"matcher": "^3.0.0" "matcher": "^3.0.0"
},
"dependencies": {
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://verdaccio.lossless.one/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true
},
"matcher": {
"version": "3.0.0",
"resolved": "https://verdaccio.lossless.one/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"dev": true,
"requires": {
"escape-string-regexp": "^4.0.0"
}
}
} }
}, },
"@pushrocks/smartmime": { "@pushrocks/smartmime": {
@@ -22758,10 +22803,9 @@
} }
}, },
"matcher": { "matcher": {
"version": "3.0.0", "version": "4.0.0",
"resolved": "https://verdaccio.lossless.one/matcher/-/matcher-3.0.0.tgz", "resolved": "https://verdaccio.lossless.one/matcher/-/matcher-4.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", "integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==",
"dev": true,
"requires": { "requires": {
"escape-string-regexp": "^4.0.0" "escape-string-regexp": "^4.0.0"
}, },
@@ -22769,8 +22813,7 @@
"escape-string-regexp": { "escape-string-regexp": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://verdaccio.lossless.one/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://verdaccio.lossless.one/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
"dev": true
} }
} }
}, },

View File

@@ -1,6 +1,6 @@
{ {
"name": "@pushrocks/smartswagger", "name": "@pushrocks/smartswagger",
"version": "1.0.5", "version": "1.1.2",
"private": false, "private": false,
"description": "a swagger tookit for working with swagger files", "description": "a swagger tookit for working with swagger files",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
@@ -26,6 +26,7 @@
"@pushrocks/smartpromise": "^3.1.6", "@pushrocks/smartpromise": "^3.1.6",
"@types/node-fetch": "^2.5.12", "@types/node-fetch": "^2.5.12",
"@types/swagger-ui-express": "^4.1.3", "@types/swagger-ui-express": "^4.1.3",
"matcher": "^4.0.0",
"node-fetch": "^2.6.6", "node-fetch": "^2.6.6",
"openapi-types": "^9.3.1" "openapi-types": "^9.3.1"
}, },

View File

@@ -3,11 +3,12 @@ import * as smartexpress from '@pushrocks/smartexpress';
import * as smartswagger from '../ts'; import * as smartswagger from '../ts';
const run = async () => { const run = async () => {
const smartswaggerInstance = await smartswagger.Smartswagger.createFromUrl('https://my.sevdesk.de/OpenAPI/ReceiptAPI/openApi.json'); const smartswaggerInstance = await smartswagger.Smartswagger.createNew('A new OpenAPI Doc');
await smartswaggerInstance.mergeDocumentFromUrl('https://my.sevdesk.de/OpenAPI/ContactAPI/openApi.json', ''); await smartswaggerInstance.mergeDocumentFromUrl('https://my.sevdesk.de/OpenAPI/ReceiptAPI/openApi.json', '', 'Yes1');
await smartswaggerInstance.mergeDocumentFromUrl('https://my.sevdesk.de/OpenAPI/InvoiceAPI/openApi.json', ''); await smartswaggerInstance.mergeDocumentFromUrl('https://my.sevdesk.de/OpenAPI/ContactAPI/openApi.json', '', 'Yes2');
await smartswaggerInstance.mergeDocumentFromUrl('https://my.sevdesk.de/OpenAPI/OrderAPI/openApi.json', ''); await smartswaggerInstance.mergeDocumentFromUrl('https://my.sevdesk.de/OpenAPI/InvoiceAPI/openApi.json', '', 'Yes3');
await smartswaggerInstance.mergeDocumentFromUrl('https://my.sevdesk.de/OpenAPI/InventoryAPI/openApi.json', ''); await smartswaggerInstance.mergeDocumentFromUrl('https://my.sevdesk.de/OpenAPI/OrderAPI/openApi.json', '', 'Yes4');
await smartswaggerInstance.mergeDocumentFromUrl('https://my.sevdesk.de/OpenAPI/InventoryAPI/openApi.json', '', 'Yes5');
// express stuff // express stuff
const expressServer = new smartexpress.Server({ const expressServer = new smartexpress.Server({

View File

@@ -1,8 +1,68 @@
import { expect, tap } from '@pushrocks/tapbundle'; import { expect, tap } from '@pushrocks/tapbundle';
import * as smartswagger from '../ts/index'; import * as smartswagger from '../ts/index';
import * as smartexpress from '@pushrocks/smartexpress';
let testSmartswaggerInstance: smartswagger.Smartswagger;
let testExpressServer: smartexpress.Server;
tap.test('first test', async () => { tap.test('first test', async () => {
console.log(smartswagger); testSmartswaggerInstance = await smartswagger.Smartswagger.createFromUrl(
'https://my.sevdesk.de/OpenAPI/ReceiptAPI/openApi.json'
);
await testSmartswaggerInstance.mergeDocumentFromUrl(
'https://my.sevdesk.de/OpenAPI/ContactAPI/openApi.json',
''
);
await testSmartswaggerInstance.mergeDocumentFromUrl(
'https://my.sevdesk.de/OpenAPI/InvoiceAPI/openApi.json',
''
);
await testSmartswaggerInstance.mergeDocumentFromUrl(
'https://my.sevdesk.de/OpenAPI/OrderAPI/openApi.json',
''
);
await testSmartswaggerInstance.mergeDocumentFromUrl(
'https://my.sevdesk.de/OpenAPI/InventoryAPI/openApi.json',
''
);
await testSmartswaggerInstance.mergeComponentToRoutes({excludeGlobArray: [], includeGlobArray: []}, {
"securitySchemes": {
"token": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
})
// express stuff
testExpressServer = new smartexpress.Server({
cors: true,
});
testExpressServer.addRoute(
'/apiui',
new smartexpress.Handler('ALL', testSmartswaggerInstance.getSlashApiUiMiddleware())
);
testExpressServer.addRoute(
'/apiredoc',
new smartexpress.Handler('ALL', testSmartswaggerInstance.getSlashRedocMiddleware())
);
testExpressServer.addRoute(
'/apischema',
new smartexpress.Handler('ALL', testSmartswaggerInstance.getSlashApiSchemaMiddleware())
);
await testExpressServer.start(3000);
});
tap.test('should run for a few seconds', async (toolsArg) => {
await toolsArg.delayFor(5000);
});
tap.test('should end smartswagger', async () => {
await testExpressServer.stop();
}); });
tap.start(); tap.start();

View File

@@ -2,14 +2,14 @@ import path from 'path';
import * as plugins from './smartswagger.plugins'; import * as plugins from './smartswagger.plugins';
interface RedocProps { interface RedocProps {
'x-tagGroups': any; 'x-tagGroups'?: {name: string, tags: string[]}[];
} }
type IExtendedApiDoc = plugins.openapiTypes.OpenAPIV3.Document & RedocProps; type IExtendedApiDoc = plugins.openapiTypes.OpenAPIV3.Document & RedocProps;
export class Smartswagger { export class Smartswagger {
// STATIC // STATIC
/** /**
* * creates a new Smartswagger instance from an external document
* @param urlArg a url arg that contains an original swagger.json in the response * @param urlArg a url arg that contains an original swagger.json in the response
* @returns an instance of * @returns an instance of
*/ */
@@ -24,6 +24,18 @@ export class Smartswagger {
return newSMartswaggerInstance; return newSMartswaggerInstance;
} }
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;
}
// INSTANCE // INSTANCE
/** /**
* the basic info of the api doc * the basic info of the api doc
@@ -40,9 +52,7 @@ export class Smartswagger {
* dereferences the document at hand * dereferences the document at hand
*/ */
public async deref() { public async deref() {
this.apiDoc = (await plugins.swaggerParser.dereference( this.apiDoc = (await plugins.swaggerParser.dereference(this.apiDoc)) as IExtendedApiDoc;
this.apiDoc
)) as IExtendedApiDoc;
} }
public async addServer(serverArg: plugins.openapiTypes.OpenAPIV3.ServerObject) { public async addServer(serverArg: plugins.openapiTypes.OpenAPIV3.ServerObject) {
@@ -56,10 +66,7 @@ export class Smartswagger {
* @param documentToMergeArg * @param documentToMergeArg
* @param basePathArg * @param basePathArg
*/ */
public async mergeDocument( public async mergeDocument(documentToMergeArg: IExtendedApiDoc, basePathArg: string = '', tagArg?: string) {
documentToMergeArg: IExtendedApiDoc,
basePathArg: string
) {
console.log(`merging document with name ${documentToMergeArg.info?.title}`); console.log(`merging document with name ${documentToMergeArg.info?.title}`);
await this.deref(); await this.deref();
// lets get a dereferenced version of the document we want to merge // lets get a dereferenced version of the document we want to merge
@@ -70,6 +77,26 @@ export class Smartswagger {
const pathToMerge = plugins.path.join(basePathArg, path); const pathToMerge = plugins.path.join(basePathArg, path);
this.apiDoc.paths = this.apiDoc.paths || {}; this.apiDoc.paths = this.apiDoc.paths || {};
this.apiDoc.paths[pathToMerge] = documentToMerge.paths[path]; this.apiDoc.paths[pathToMerge] = documentToMerge.paths[path];
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);
}
}
} }
// merge tag groups // merge tag groups
@@ -80,13 +107,18 @@ export class Smartswagger {
} }
} }
console.log('merged!'); console.log('merged!');
// set custom tag arg.
if (tagArg) {
this.apiDoc['x-tagGroups'].push({name: tagArg, tags: [tagArg]});
}
} }
/** /**
* merges a document from url * merges a document from url
*/ */
public async mergeDocumentFromUrl(documentUrlArg: string, basePathArg: string = '') { public async mergeDocumentFromUrl(documentUrlArg: string, basePathArg: string = '', tagArg?: string) {
console.log(`getting document at ${documentUrlArg} for merging...`) console.log(`getting document at ${documentUrlArg} for merging...`);
const documentResponse = await plugins.nodeFetch(documentUrlArg, { const documentResponse = await plugins.nodeFetch(documentUrlArg, {
headers: { headers: {
'accept-encoding': 'application/json', 'accept-encoding': 'application/json',
@@ -95,18 +127,18 @@ export class Smartswagger {
const documentString = await documentResponse.text(); const documentString = await documentResponse.text();
const apiDoc: IExtendedApiDoc = JSON.parse(documentString); const apiDoc: IExtendedApiDoc = JSON.parse(documentString);
console.log(`document successfully fetched!`); console.log(`document successfully fetched!`);
await this.mergeDocument(apiDoc, basePathArg); await this.mergeDocument(apiDoc, basePathArg, tagArg);
} }
/** /**
* merge multiple documents in parallel * merge multiple documents in parallel
* @param urlArrayArg * @param urlArrayArg
*/ */
public async mergeManyDocumentsFromUrl(urlArrayArg: {url: string, basePath: string}[]) { public async mergeManyDocumentsFromUrl(urlArrayArg: { url: string; basePath?: string, tagArg?: string }[]) {
const promiseArray: Promise<void>[] = []; const promiseArray: Promise<void>[] = [];
for (const urlArg of urlArrayArg) { for (const urlArg of urlArrayArg) {
promiseArray.push(this.mergeDocumentFromUrl(urlArg.url, urlArg.basePath)); promiseArray.push(this.mergeDocumentFromUrl(urlArg.url, urlArg.basePath, urlArg.tagArg));
}; }
await Promise.all(promiseArray); await Promise.all(promiseArray);
} }
@@ -115,23 +147,25 @@ export class Smartswagger {
*/ */
public mergeComponentToRoutes( public mergeComponentToRoutes(
routeDescriptor: { routeDescriptor: {
includeRegexpArray: RegExp[]; includeGlobArray: string[];
excludeRegexpArray: RegExp[]; excludeGlobArray: string[];
}, },
componentArg: plugins.openapiTypes.OpenAPIV3.ComponentsObject componentArg: plugins.openapiTypes.OpenAPIV3.ComponentsObject
) { ) {
for (const pathCandidateRoute of Object.keys(this.apiDoc.paths)) { for (const pathCandidateRoute of Object.keys(this.apiDoc.paths)) {
let included: boolean = false; let included: boolean = false;
let excluded: boolean = false; let excluded: boolean = false;
for (const regExp of routeDescriptor.includeRegexpArray) {
if (regExp.test(pathCandidateRoute)) { // We are using glob espressions here due to easier path expressions
for (const globExpression of routeDescriptor.includeGlobArray) {
if (plugins.matcher.isMatch(pathCandidateRoute, globExpression)) {
included = true; included = true;
break; break;
} }
} }
if (included) { if (included) {
for (const regExp of routeDescriptor.excludeRegexpArray) { for (const globExpression of routeDescriptor.excludeGlobArray) {
if (regExp.test(pathCandidateRoute)) { if (plugins.matcher.isMatch(pathCandidateRoute, globExpression)) {
excluded = true; excluded = true;
break; break;
} }
@@ -145,8 +179,16 @@ export class Smartswagger {
return; return;
} }
if (componentArg.securitySchemes) { if (componentArg.securitySchemes) {
methodArg.security = methodArg.security || [];
for (const securityScheme of Object.keys(componentArg.securitySchemes)) {
methodArg.security.push({ [securityScheme]: [] })
}
} }
}; };
instrumentMethod(pathCandidate.get);
instrumentMethod(pathCandidate.post);
instrumentMethod(pathCandidate.put);
instrumentMethod(pathCandidate.delete);
} }
} }
} }

View File

@@ -1,26 +1,18 @@
// node native // node native
import * as path from 'path'; import * as path from 'path';
export { export { path };
path
}
// @pushrocks scope // @pushrocks scope
import * as smartexpress from '@pushrocks/smartexpress'; import * as smartexpress from '@pushrocks/smartexpress';
import * as smartpromise from '@pushrocks/smartpromise'; import * as smartpromise from '@pushrocks/smartpromise';
export { export { smartexpress, smartpromise };
smartexpress,
smartpromise
}
// third party // third party
import * as openapiTypes from 'openapi-types'; import * as openapiTypes from 'openapi-types';
import swaggerParser from '@apidevtools/swagger-parser'; import swaggerParser from '@apidevtools/swagger-parser';
import matcher from 'matcher';
import nodeFetch from 'node-fetch'; import nodeFetch from 'node-fetch';
export { export { openapiTypes, matcher, nodeFetch, swaggerParser, };
openapiTypes,
swaggerParser,
nodeFetch
}