Skip to content

Commit c37e4b4

Browse files
manorlhmanorllkobikkibertoad
authored
support multiple instances of the validator for multiple yaml files (#145)
Co-authored-by: manorlahagani <[email protected]> Co-authored-by: Kobi Carmeli <[email protected]> Co-authored-by: Igor Savin <[email protected]>
1 parent 46eb330 commit c37e4b4

9 files changed

Lines changed: 667 additions & 81 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
### Improvements
44
- Update dependencies to address security vulnerabilities
55

6+
# 3.2.0 - 26 November, 2020
7+
8+
### Features
9+
- Add getNewMiddleware which allow crating multiple-instances of the middleware #145
10+
611
# 3.1.1 - 2 September, 2020
712

813
### Fixes

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ There are no code changes in `[email protected]` compared to `e
2828
- [Express](#express)
2929
- [Koa](#koa)
3030
- [Fastify](#fastify)
31+
- [Multiple-instances](#multiple-instances)
3132
- [Important Notes](#important-notes)
3233
- [Schema Objects](#schema-objects)
3334
- [Multipart/form-data (files)](#multipartform-data-files)
@@ -206,6 +207,21 @@ async function getApp() {
206207
}
207208
```
208209

210+
### multiple-instances
211+
```js
212+
const inputValidation = require('../../src/middleware');
213+
const validatorA = inputValidation.getNewMiddleware('test/pet-store-swaggerA.yaml', {framework: 'express'});
214+
const validatorB = inputValidation.getNewMiddleware('test/pet-store-swaggerB.yaml', {framework: 'express'});
215+
216+
app.get('/pets', validatorA.validate, (req, res, next) => {
217+
return res.json({ result: 'OK' });
218+
});
219+
220+
app.post('/pets', validatorB.validate, (req, res, next) => {
221+
return res.json({ result: 'OK' });
222+
});
223+
224+
```
209225
## Important Notes
210226

211227
### Schema Objects

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"author": "Idan Tovi",
6363
"license": "Apache-2.0",
6464
"dependencies": {
65+
"auto-bind": "^4.0.0",
6566
"api-schema-builder": "^2.0.4",
6667
"memoizee": "^0.4.14"
6768
},

src/middleware.js

Lines changed: 87 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,113 @@
11
'use strict';
2-
2+
const autoBind = require('auto-bind');
33
const SchemaEndpointResolver = require('./utils/schemaEndpointResolver');
4-
54
const InputValidationError = require('./inputValidationError'),
65
apiSchemaBuilder = require('api-schema-builder');
76
const allowedFrameworks = ['express', 'koa', 'fastify'];
87

9-
let schemas = {};
10-
let middlewareOptions;
11-
let framework;
12-
let schemaEndpointResolver;
13-
let validationMiddleware;
14-
15-
function init(swaggerPath, options) {
16-
_baseInit(swaggerPath, options);
17-
// build schema for requests only
18-
const schemaBuilderOptions = Object.assign({}, options, { buildRequests: true, buildResponses: false });
19-
schemas = apiSchemaBuilder.buildSchemaSync(swaggerPath, schemaBuilderOptions);
20-
}
8+
class Middleware {
9+
constructor(swaggerPath, options) {
10+
this.schemas = {};
11+
this.middlewareOptions = undefined;
12+
this.framework = undefined;
13+
this.schemaEndpointResolver = undefined;
14+
this.validationMiddleware = undefined;
15+
this.InputValidationError = InputValidationError;
16+
if (swaggerPath){
17+
this.init(swaggerPath, options);
18+
}
19+
autoBind(this);
20+
}
2121

22-
async function initAsync(swaggerPath, options){
23-
_baseInit(swaggerPath, options);
24-
// build schema for requests only
25-
const schemaBuilderOptions = Object.assign({}, options, { buildRequests: true, buildResponses: false });
26-
schemas = await apiSchemaBuilder.buildSchema(swaggerPath, schemaBuilderOptions);
27-
}
22+
async initAsync(swaggerPath, options) {
23+
this._baseInit(options);
24+
// build schema for requests only
25+
const schemaBuilderOptions = Object.assign({}, options, { buildRequests: true, buildResponses: false });
26+
this.schemas = await apiSchemaBuilder.buildSchema(swaggerPath, schemaBuilderOptions);
27+
}
2828

29-
function validate(...args) {
30-
return validationMiddleware(...args);
31-
}
29+
init (swaggerPath, options) {
30+
this._baseInit(options);
31+
// build schema for requests only
32+
const schemaBuilderOptions = Object.assign({}, options, { buildRequests: true, buildResponses: false });
33+
this.schemas = apiSchemaBuilder.buildSchemaSync(swaggerPath, schemaBuilderOptions);
34+
}
3235

33-
function _baseInit(swaggerPath, options) {
34-
middlewareOptions = options || {};
35-
const frameworkToLoad = allowedFrameworks.find((frameworkName) => {
36-
return middlewareOptions.framework === frameworkName;
37-
});
36+
getNewMiddleware(swaggerPath, options) {
37+
return new Middleware(swaggerPath, options);
38+
}
3839

39-
framework = frameworkToLoad ? require(`./frameworks/${frameworkToLoad}`) : require('./frameworks/express');
40-
validationMiddleware = framework.getValidator(_validateRequest);
41-
schemaEndpointResolver = new SchemaEndpointResolver();
42-
}
40+
validate(...args) {
41+
return this.validationMiddleware(...args);
42+
}
4343

44-
function _getContentType(headers) {
45-
// This is to filter out things like charset
46-
const contentType = headers['content-type'];
47-
return contentType && contentType.split(';')[0].trim();
48-
}
44+
_baseInit (options) {
45+
this.middlewareOptions = options || {};
46+
const frameworkToLoad = allowedFrameworks.find((frameworkName) => {
47+
return this.middlewareOptions.framework === frameworkName;
48+
});
4949

50-
function _validateRequest(requestOptions) {
51-
const paramValidationErrors = _validateParams(requestOptions);
52-
const bodyValidationErrors = _validateBody(requestOptions);
50+
this.framework = frameworkToLoad ? require(`./frameworks/${frameworkToLoad}`) : require('./frameworks/express');
51+
this.validationMiddleware = this.framework.getValidator((requestOptions) => this._validateRequest(requestOptions));
52+
this.schemaEndpointResolver = new SchemaEndpointResolver();
53+
}
5354

54-
const errors = paramValidationErrors.concat(bodyValidationErrors);
55+
_getContentType (headers) {
56+
// This is to filter out things like charset
57+
const contentType = headers['content-type'];
58+
return contentType && contentType.split(';')[0].trim();
59+
}
5560

56-
if (errors.length) {
57-
let error;
61+
_validateRequest (requestOptions) {
62+
const paramValidationErrors = this._validateParams(requestOptions);
63+
const bodyValidationErrors = this._validateBody(requestOptions);
5864

59-
if (middlewareOptions.errorFormatter) {
60-
error = middlewareOptions.errorFormatter(errors, middlewareOptions);
61-
} else {
62-
error = new InputValidationError(errors,
63-
{
64-
beautifyErrors: middlewareOptions.beautifyErrors,
65-
firstError: middlewareOptions.firstError
66-
});
67-
}
65+
const errors = paramValidationErrors.concat(bodyValidationErrors);
6866

69-
return error;
70-
}
71-
}
67+
if (errors.length) {
68+
let error;
7269

73-
function _validateBody(requestOptions) {
74-
const { body, path } = requestOptions;
75-
const method = requestOptions.method.toLowerCase();
76-
const contentType = _getContentType(requestOptions.headers);
77-
const methodSchema = schemaEndpointResolver.getMethodSchema(schemas, path, method) || {};
70+
if (this.middlewareOptions.errorFormatter) {
71+
error = this.middlewareOptions.errorFormatter(errors, this.middlewareOptions);
72+
} else {
73+
error = new InputValidationError(errors,
74+
{
75+
beautifyErrors: this.middlewareOptions.beautifyErrors,
76+
firstError: this.middlewareOptions.firstError
77+
});
78+
}
7879

79-
if (methodSchema.body) {
80-
const validator = methodSchema.body[contentType] || methodSchema.body;
81-
if (!validator.validate(body)) {
82-
return validator.errors || [];
80+
return error;
8381
}
8482
}
8583

86-
return [];
87-
}
88-
89-
function _validateParams(requestOptions) {
90-
const { headers, params: pathParams, query, files, path } = requestOptions;
91-
const method = requestOptions.method.toLowerCase();
84+
_validateBody(requestOptions) {
85+
const { body, path } = requestOptions;
86+
const method = requestOptions.method.toLowerCase();
87+
const contentType = this._getContentType(requestOptions.headers);
88+
const methodSchema = this.schemaEndpointResolver.getMethodSchema(this.schemas, path, method) || {};
89+
90+
if (methodSchema.body) {
91+
const validator = methodSchema.body[contentType] || methodSchema.body;
92+
if (!validator.validate(body)) {
93+
return validator.errors || [];
94+
}
95+
}
9296

93-
const methodSchema = schemaEndpointResolver.getMethodSchema(schemas, path, method);
94-
if (methodSchema && methodSchema.parameters && !methodSchema.parameters.validate({ query: query, headers: headers, path: pathParams, files: files })) {
95-
return methodSchema.parameters.errors || [];
97+
return [];
9698
}
9799

98-
return [];
100+
_validateParams (requestOptions) {
101+
const { headers, params: pathParams, query, files, path } = requestOptions;
102+
const method = requestOptions.method.toLowerCase();
103+
104+
const methodSchema = this.schemaEndpointResolver.getMethodSchema(this.schemas, path, method);
105+
if (methodSchema && methodSchema.parameters && !methodSchema.parameters.validate({ query: query, headers: headers, path: pathParams, files: files })) {
106+
return methodSchema.parameters.errors || [];
107+
}
108+
109+
return [];
110+
}
99111
}
100112

101-
module.exports = {
102-
init,
103-
initAsync,
104-
validate,
105-
InputValidationError
106-
};
113+
module.exports = new Middleware();

test/openapi3/openapi3-test.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
const chai = require('chai'),
44
expect = chai.expect,
55
chaiSinon = require('sinon-chai'),
6-
request = require('supertest');
6+
request = require('supertest'),
7+
inputValidation = require('../../src/middleware');
78
chai.use(chaiSinon);
9+
810
const inputValidationOptions = function () {
911
return {
1012
formats: [
@@ -432,4 +434,45 @@ describe('input-validation middleware tests', function () {
432434
});
433435
});
434436
});
437+
describe('support multi instances', function (){
438+
before(function (){
439+
app = require('./test-server-validator-instance')(inputValidationOptions());
440+
});
441+
442+
describe('when running server with two seperated yaml one with get and other with post - should make validator per each yaml', function (){
443+
it('verify two instances has two different schemas', function (){
444+
const inputValidationWithGet = inputValidation.getNewMiddleware(`${__dirname}/pets-instance1.yaml`);
445+
const inputValidationWithPost = inputValidation.getNewMiddleware(`${__dirname}/pets-instance2.yaml`);
446+
expect(JSON.stringify(inputValidationWithGet.schemas)).eql('{"/pets":{"get":{}}}');
447+
expect(JSON.stringify(inputValidationWithPost.schemas)).eql('{"/pets":{"post":{"body":{"errors":null,"application/json":{"errors":null},"application/x-www-form-urlencoded":{"errors":null}},"parameters":{"errors":null}}}}');
448+
});
449+
it('get pets from pets-instance1.yaml', function (done){
450+
request(app)
451+
.get('/pets')
452+
.expect(200, function (err, res) {
453+
if (err) {
454+
throw err;
455+
}
456+
expect(res.body.result).to.equal('OK');
457+
done();
458+
});
459+
});
460+
461+
it('post pets from pets-instance2.yaml', function (done){
462+
request(app)
463+
.post('/pets')
464+
.set('public-key', '1.0')
465+
.send({
466+
bark: 'hav hav'
467+
})
468+
.expect(200, function (err, res) {
469+
if (err) {
470+
throw err;
471+
}
472+
expect(res.body.result).to.equal('OK');
473+
done();
474+
});
475+
});
476+
});
477+
});
435478
});

0 commit comments

Comments
 (0)