Skip to content

Commit

Permalink
Feature/#22 open api 3 spec (#63)
Browse files Browse the repository at this point in the history
* Including swagger2openapi package

* predefinedSpec with OpenAPIV3 interface

* Ignoring test open api v3 spec files

* Creating openapi util

* Generating open api v3 spec file + converting pre-serve (not enabled)

* Adding back eslint and typescript local validations

* Installing missing ajv peer dependency

* Splitting api-docs and api-spec routes into v2, v3 and default

* Testing with updated ajv unmet peer dependency

* Back to previous ajv peer dependency version

* Reverting conditions for better coverage

* Removing old comment about basic server

* Fixing same spec v3 generated every time

* .gitignore wildcard for test_spec*.json and api-spec*.json

* Using top spec declaration instead of argument for middlewares

* Documenting version params for middlewares

* Checking res.headersSent in basic and advanced examples

* Bumps swagger2openapi to 6.2.3

* Bumps swagger2openapi to 7.0.0

* Removing unnecessary ajv dependency
  • Loading branch information
maxiejbe authored Sep 8, 2020
1 parent 1b8755b commit 742c8d6
Show file tree
Hide file tree
Showing 10 changed files with 629 additions and 74 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ node_modules
build
.idea
*.iml
test_spec.json
api-spec.json
test_spec*.json
api-spec*.json
11 changes: 8 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
*/

import { Express } from 'express';
import { OpenAPIV2 } from 'openapi-types';
import { OpenAPIV2,OpenAPIV3 } from 'openapi-types';

/** re-export for ease of use for the end user */
export {
OpenAPIV2, //
OpenAPIV2,
OpenAPIV3
};

/** Options for `handleResponses` */
Expand All @@ -28,7 +29,9 @@ export interface HandleResponsesOptions {
specOutputPath?: string;

/** either the Swagger specification or a function with one argument, which returns the spec */
predefinedSpec?: object | OpenAPIV2.Document | ((spec: OpenAPIV2.Document) => OpenAPIV2.Document);
predefinedSpec?: object | OpenAPIV2.Document | OpenAPIV3.Document |
((spec: OpenAPIV2.Document) => OpenAPIV2.Document) |
((spec: OpenAPIV3.Document) => OpenAPIV3.Document);

/** how often to write the openAPI specification to file */
writeIntervalMs?: number;
Expand Down Expand Up @@ -91,4 +94,6 @@ export function init(

export const getSpec: () => object | OpenAPIV2.Document;

export const getSpecV3: (callback: (err: object | string, specV3: object | OpenAPIV3.Document) => void) => void

export const setPackageInfoPath: (pkgInfoPath: string) => void;
110 changes: 88 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ const fs = require('fs');
const path = require('path');
const swaggerUi = require('swagger-ui-express');
const utils = require('./lib/utils');

const { generateMongooseModelsSpec } = require('./lib/mongoose');
const { generateTagsSpec, matchingTags } = require('./lib/tags');
const { convertOpenApiVersionToV3, getSpecByVersion, versions } = require('./lib/openapi');
const processors = require('./lib/processors');
const listEndpoints = require('express-list-endpoints');

Expand Down Expand Up @@ -97,11 +99,56 @@ function updateSpecFromPackage() {
}

/**
* @description serve the openAPI docs with swagger at a specified path / url
* @description Builds api spec middleware
*
* @returns Middleware
*/
function apiSpecMiddleware(version) {
return (req, res) => {
getSpecByVersion(spec, version, (err, openApiSpec) => {
if (!err) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(openApiSpec, null, 2));
}
});
};
}

/**
* @description Builds swagger serve middleware
* @param version Available open api versions: 'v2' (default if empty) or 'v3'.
* @returns Middleware
*/
function swaggerServeMiddleware(version) {
return (req, res) => {
getSpecByVersion(spec, version, (err, openApiSpec) => {
if (!err) {
res.setHeader('Content-Type', 'text/html');
swaggerUi.setup(openApiSpec)(req, res);
}
});
};
}

/**
* @description Applies spec middlewares
* @param version Available open api versions: 'v2' (default if empty) or 'v3'.
*/
function applySpecMiddlewares(version = '') {

const apiSpecBasePath = packageInfo.baseUrlPath.concat('/api-spec');
const baseSwaggerServePath = packageInfo.baseUrlPath.concat('/' + swaggerUiServePath);

app.use(apiSpecBasePath.concat('/' + version), apiSpecMiddleware(version));
app.use(baseSwaggerServePath.concat('/' + version), swaggerUi.serve, swaggerServeMiddleware(version));
}

/**
* @description Prepares spec to be served
*
* @returns void
*/
function serveApiDocs() {
function prepareSpec() {
spec = { swagger: '2.0', paths: {} };

const endpoints = listEndpoints(app);
Expand Down Expand Up @@ -141,15 +188,22 @@ function serveApiDocs() {
spec.definitions = mongooseModelsSpecs || {};
updateSpecFromPackage();
spec = patchSpec(predefinedSpec);
app.use(packageInfo.baseUrlPath + '/api-spec', (req, res, next) => {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(patchSpec(predefinedSpec), null, 2));
next();
});
app.use(packageInfo.baseUrlPath + '/' + swaggerUiServePath, swaggerUi.serve, (req, res) => {
res.setHeader('Content-Type', 'text/html');
swaggerUi.setup(patchSpec(predefinedSpec))(req, res);
});
}

/**
* @description serve the openAPI docs with swagger at a specified path / url
*
* @returns void
*/
function serveApiDocs() {
prepareSpec();

applySpecMiddlewares(versions.OPEN_API_V2);

applySpecMiddlewares(versions.OPEN_API_V3);

// Base path middleware should be applied after specific versions
applySpecMiddlewares();
}

/**
Expand Down Expand Up @@ -275,7 +329,7 @@ function handleResponses(expressApp,

updateDefinitionsSpec(options.mongooseModels);
updateTagsSpec(options.tags || options.mongooseModels);

/** middleware to handle RESPONSES */
app.use((req, res, next) => {
try {
Expand All @@ -289,17 +343,21 @@ function handleResponses(expressApp,
firstResponseProcessing = false;
lastRecordTime = ts;

fs.writeFile(specOutputPath, JSON.stringify(spec, null, 2), 'utf8', err => {
const fullPath = path.resolve(specOutputPath);

if (err) {
/**
* TODO - this is broken - the error will be caught and ignored in the catch below.
* See https://github.com/mpashkovskiy/express-oas-generator/pull/39#discussion_r340026645
*/
throw new Error(`Cannot store the specification into ${fullPath} because of ${err.message}`);
fs.writeFileSync(specOutputPath, JSON.stringify(spec, null, 2), 'utf8');

convertOpenApiVersionToV3(spec, (err, specV3) => {
if (!err) {
const parsedSpecOutputPath = path.parse(specOutputPath);
const {name, ext} = parsedSpecOutputPath;
parsedSpecOutputPath.base = name.concat('_').concat(versions.OPEN_API_V3).concat(ext);

const v3Path = path.format(parsedSpecOutputPath);

fs.writeFileSync(v3Path, JSON.stringify(specV3), 'utf8');
}
});
/** TODO - Log that open api v3 could not be generated */
});

}
} catch (e) {
/** TODO - shouldn't we do something here? */
Expand Down Expand Up @@ -381,6 +439,13 @@ const getSpec = () => {
return patchSpec(predefinedSpec);
};

/**
* @type { typeof import('./index').getSpecV3 }
*/
const getSpecV3 = callback => {
convertOpenApiVersionToV3(getSpec(), callback);
};

/**
* @type { typeof import('./index').setPackageInfoPath }
*
Expand All @@ -395,5 +460,6 @@ module.exports = {
handleRequests,
init,
getSpec,
getSpecV3,
setPackageInfoPath
};
25 changes: 25 additions & 0 deletions lib/openapi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const openApiVersionConverter = require('swagger2openapi');

module.exports.versions = {
OPEN_API_V2:'v2',
OPEN_API_V3:'v3'
};

module.exports.getSpecByVersion = (specV2, version, callback) => {
const defaultSpec = (specV2, callback) => callback(null, specV2);
const availableSpecs = {
[this.versions.OPEN_API_V2]: defaultSpec,
[this.versions.OPEN_API_V3]: this.convertOpenApiVersionToV3,
};
const specByVersion = availableSpecs[version] || defaultSpec;

return specByVersion(specV2, callback);
};

module.exports.convertOpenApiVersionToV3 = (specV2, callback) => {
const options = {patch: true, warnOnly: true};

openApiVersionConverter.convertObj(specV2, options, function(err, results) {
callback(err, results && results.openapi);
});
};
Loading

0 comments on commit 742c8d6

Please sign in to comment.