Skip to content

Commit

Permalink
add option to install synchronously
Browse files Browse the repository at this point in the history
  • Loading branch information
Carmine DiMascio committed Nov 30, 2019
1 parent fc27c2c commit 8753b9e
Show file tree
Hide file tree
Showing 8 changed files with 447 additions and 182 deletions.
370 changes: 221 additions & 149 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion example/app.js
Expand Up @@ -31,7 +31,7 @@ new OpenApiValidator({
// },
})
.install(app)
.then(app => {
.then(() => {
// 3. Add routes
app.get('/v1/pets', function(req, res, next) {
res.json(pets);
Expand Down
76 changes: 76 additions & 0 deletions example/test.js
@@ -0,0 +1,76 @@
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const logger = require('morgan');
const http = require('http');
const app = express();

// 1. Import the express-openapi-validator library
const OpenApiValidator = require('express-openapi-validator').OpenApiValidator;

// 2. Set up body parsers for the request body types you expect
// Must be specified prior to endpoints in 5.
app.use(bodyParser.json());
app.use(bodyParser.text());
app.use(bodyParser.urlencoded({ extended: false }));

app.use(logger('dev'));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// 3. (optionally) Serve the OpenAPI spec
const spec = path.join(__dirname, 'example.yaml');
app.use('/spec', express.static(spec));

// 4. Install the OpenApiValidator onto your express app
new OpenApiValidator({
apiSpec: './example.yaml',
validateResponses: true, // <-- to validate responses
// securityHandlers: { ... }, // <-- if using security
// unknownFormats: ['my-format'] // <-- to provide custom formats
})
.install(app)
.then(app => {
// 5. Define routes using Express
app.get('/v1/pets', function(req, res, next) {
res.json([{ id: 1, name: 'max' }, { id: 2, name: 'mini' }]);
});

app.post('/v1/pets', function(req, res, next) {
res.json({ name: 'sparky' });
});

app.get('/v1/pets/:id', function(req, res, next) {
res.json({ id: req.params.id, name: 'sparky' });
});

// 5a. Define route(s) to upload file(s)
app.post('/v1/pets/:id/photos', function(req, res, next) {
// files are found in req.files
// non-file multipart params can be found as such: req.body['my-param']

res.json({
files_metadata: req.files.map(f => ({
originalname: f.originalname,
encoding: f.encoding,
mimetype: f.mimetype,
// Buffer of file conents
buffer: f.buffer,
})),
});
});

// 6. Create an Express error handler
app.use((err, req, res, next) => {
// 7. Customize errors
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
});
});

const server = http.createServer(app);
server.listen(3000);
console.log('Listening on port 3000');
33 changes: 32 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "express-openapi-validator",
"version": "2.18.0",
"version": "3.0.0",
"description": "Automatically validate API requests and responses with OpenAPI 3 and Express.",
"main": "dist/index.js",
"scripts": {
Expand Down Expand Up @@ -35,6 +35,9 @@
"ono": "^5.0.1",
"path-to-regexp": "^6.0.0"
},
"optionalDependencies": {
"deasync": "^0.1.16"
},
"devDependencies": {
"@types/ajv": "^1.0.0",
"@types/cookie-parser": "^1.4.1",
Expand Down
23 changes: 23 additions & 0 deletions src/framework/openapi.spec.loader.ts
Expand Up @@ -35,6 +35,29 @@ export class OpenApiSpecLoader {
return this.discoverRoutes();
}

public loadSync(): Spec {
const discoverRoutesSync = () => {
let savedError,
savedResult: Spec,
done = false;
const discoverRoutes = require('util').callbackify(
this.discoverRoutes.bind(this),
);
// const discoverRoutes: any = this.discoverRoutes.bind(this);
discoverRoutes((error, result) => {
savedError = error;
savedResult = result;
done = true;
});

require('deasync').loopWhile(() => !done);

if (savedError) throw savedError;
return savedResult;
};
return discoverRoutesSync();
}

private async discoverRoutes(): Promise<DiscoveredRoutes> {
const routes: RouteMetadata[] = [];
const toExpressParams = this.toExpressParams;
Expand Down
65 changes: 35 additions & 30 deletions src/index.ts
Expand Up @@ -15,7 +15,6 @@ import {

export class OpenApiValidator {
private readonly options: OpenApiValidatorOpts;
// private readonly context: OpenApiContext;

constructor(options: OpenApiValidatorOpts) {
this.validateOptions(options);
Expand All @@ -40,51 +39,57 @@ export class OpenApiValidator {
this.options = options;
}

public installSync(app: Application): void {
const spec = new OpenApiSpecLoader({
apiDoc: this.options.apiSpec,
}).loadSync();
this.installMiddleware(app, spec);
}

public async install(app: Application): Promise<void>;
public install(
app: Application,
callback: (error: Error, app: Application) => void,
): void;
public install(app: Application): Promise<Application>;
public install(
app: Application,
callback?: (error: Error, app: Application) => void,
): Promise<Application> | void {
const useCallback = callback && typeof callback === 'function';
): Promise<void> | void {
const p = new OpenApiSpecLoader({
apiDoc: this.options.apiSpec,
})
.load()
.then(spec => {
const context = new OpenApiContext(spec, this.options.ignorePaths);

this.installPathParams(app, context);
this.installMetadataMiddleware(app, context);
this.installMultipartMiddleware(app, context);

const components = context.apiDoc.components;
if (components && components.securitySchemes) {
this.installSecurityMiddleware(app, context);
}

if (this.options.validateRequests) {
this.installRequestValidationMiddleware(app, context);
}

if (this.options.validateResponses) {
this.installResponseValidationMiddleware(app, context);
}
if (useCallback) {
callback(undefined, app);
}
return app;
});
.then(spec => this.installMiddleware(app, spec));

const useCallback = callback && typeof callback === 'function';
if (useCallback) {
p.catch(e => {
callback(e, undefined);
});
return;
} else {
return p;
}
}

private installMiddleware(app: Application, spec: Spec): void {
const context = new OpenApiContext(spec, this.options.ignorePaths);

this.installPathParams(app, context);
this.installMetadataMiddleware(app, context);
this.installMultipartMiddleware(app, context);

const components = context.apiDoc.components;
if (components && components.securitySchemes) {
this.installSecurityMiddleware(app, context);
}

if (this.options.validateRequests) {
this.installRequestValidationMiddleware(app, context);
}

if (this.options.validateResponses) {
this.installResponseValidationMiddleware(app, context);
}
return p;
}

private installPathParams(app: Application, context: OpenApiContext): void {
Expand Down
55 changes: 55 additions & 0 deletions test/sync.install.spec.ts
@@ -0,0 +1,55 @@
import * as path from 'path';
import * as express from 'express';
import { expect } from 'chai';
import * as request from 'supertest';
import * as http from 'http';
import { OpenApiValidator } from '../dist';

const packageJson = require('../package.json');

describe(packageJson.name, () => {
let app = null;

before(() => {
try {
// Set up the express app
const apiSpec = path.join('test', 'resources', 'openapi.yaml');
app = express();
new OpenApiValidator({
apiSpec,
}).installSync(app);
app.get('/v1/pets', (req, res) => res.json({ name: 'max' }));
app.use((err, req, res, next) => {
// format error
// console.error(err, err.stack);
res.status(err.status).json({
message: err.message,
errors: err.errors,
});
});

const server = http.createServer(app);
server.listen(3000);
console.log('Listening on port 3000');
} catch (e) {
console.error(e);
}
});

after(() => {
process.exit();
});

it('should validate /v1/pets and return 400', async () =>
request(app)
.get(`/v1/pets`)
.expect(400)
.then(r => {
console.log(r.body.errors[0].message);
expect(r.body).has.property('errors');
expect(r.body.errors[0].message).to.equal(
"should have required property 'limit'",
);
// expect(r.body.name).to.be.equal(true);
}));
});

0 comments on commit 8753b9e

Please sign in to comment.