Skip to content
This repository has been archived by the owner on Aug 4, 2023. It is now read-only.

Commit

Permalink
Merge pull request #360 from SandyChapman/fix_response_piping
Browse files Browse the repository at this point in the history
Allow streaming data to the res object and still perform validation.
  • Loading branch information
whitlockjc committed Mar 21, 2016
2 parents 310d1de + 1182c44 commit 17e77be
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 7 deletions.
41 changes: 34 additions & 7 deletions middleware/swagger-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,24 +175,52 @@ var wrapEnd = function (req, res, next) {
var vPath = _.cloneDeep(req.swagger.operationPath);
var swaggerVersion = req.swagger.swaggerVersion;

var writtenData = [];

var originalWrite = res.write;
res.write = function(data) {
writtenData.push(data);
// Don't call the originalWrite. We want to validate the data before writing
// it to our response.
};

res.end = function (data, encoding) {
var schema = operation;
var val = data;
var val;
if(data)
{
if(data instanceof Buffer) {
writtenData.push(data);
val = Buffer.concat(writtenData);
}
else if(data instanceof String) {
writtenData.push(new Buffer(data));
val = Buffer.concat(writtenData);
}
else {
val = data;
}
}
else if(writtenData) {
val = Buffer.concat(writtenData);
}

var responseCode;

// Replace 'res.end' with the original
// Replace 'res.end' and 'res.write' with the originals
res.write = originalWrite;
res.end = originalEnd;

debug(' Response validation:');

// If the data is a buffer, convert it to a string so we can parse it prior to validation
if (val instanceof Buffer) {
val = data.toString(encoding);
val = val.toString(encoding);
}

// Express removes the Content-Type header from 204/304 responses which makes response validation impossible
if (_.isUndefined(res.getHeader('content-type')) && [204, 304].indexOf(res.statusCode) > -1) {
sendData(swaggerVersion, res, data, encoding, true);
sendData(swaggerVersion, res, val, encoding, true);
return; // do NOT call next() here, doing so would execute remaining middleware chain twice
}

Expand Down Expand Up @@ -249,14 +277,13 @@ var wrapEnd = function (req, res, next) {
debug(' Response ' + (swaggerVersion === '1.2' ? 'message' : 'code') + ': ' + responseCode);

if (_.isUndefined(schema)) {
sendData(swaggerVersion, res, data, encoding, true);
sendData(swaggerVersion, res, val, encoding, true);
} else {
validateValue(req, schema, vPath, val, function (err) {
if (err) {
throw err;
}

sendData(swaggerVersion, res, data, encoding, false);
sendData(swaggerVersion, res, val, encoding, true);
});
}
} catch (err) {
Expand Down
60 changes: 60 additions & 0 deletions test/1.2/test-middleware-swagger-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var assert = require('assert');
var async = require('async');
var helpers = require('../helpers');
var request = require('supertest');
var stream = require('stream');

var petJson = _.cloneDeep(require('../../samples/1.2/pet.json'));
var rlJson = _.cloneDeep(require('../../samples/1.2/resource-listing.json'));
Expand All @@ -47,6 +48,11 @@ var samplePet = {
name: 'Test Pet'
};

var sampleInvalidPet = {
identifier: 1,
name: 'Test Pet'
};

describe('Swagger Validator Middleware v1.2', function () {
describe('request validation', function () {
it('should not validate request when there are no operations', function (done) {
Expand Down Expand Up @@ -1108,6 +1114,60 @@ describe('Swagger Validator Middleware v1.2', function () {
], done));
});
});

it('should validate a valid piped response', function (done) {
var cPetJson = _.cloneDeep(petJson);

cPetJson.apis[0].operations[0].nickname = 'Pets_getPetById';

helpers.createServer([rlJson, [cPetJson, storeJson, userJson]], {
swaggerRouterOptions: {
controllers: {
'Pets_getPetById': function (req, res) {
var s = new stream.Readable();
s.push(new Buffer(JSON.stringify(samplePet)));
s.push(null);
s.pipe(res);
}
}
},
swaggerValidatorOptions: {
validateResponse: true
}
}, function (app) {
request(app)
.get('/api/pet/1')
.expect(200)
.end(helpers.expectContent(samplePet, done));
});
});

it('should validate an invalid piped response', function (done) {
var cPetJson = _.cloneDeep(petJson);

cPetJson.apis[0].operations[0].nickname = 'Pets_getPetById';

helpers.createServer([rlJson, [cPetJson, storeJson, userJson]], {
swaggerRouterOptions: {
controllers: {
'Pets_getPetById': function (req, res) {
var s = new stream.Readable();
s.push(new Buffer(JSON.stringify(sampleInvalidPet)));
s.push(null);
s.pipe(res);
}
}
},
swaggerValidatorOptions: {
validateResponse: true
}
}, function (app) {
request(app)
.get('/api/pet/1')
.expect(500)
.end(helpers.expectContent('Response validation failed: failed schema validation', done));
});
});
});

describe('issues', function () {
Expand Down
64 changes: 64 additions & 0 deletions test/2.0/test-middleware-swagger-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var assert = require('assert');
var async = require('async');
var helpers = require('../helpers');
var request = require('supertest');
var stream = require('stream');

var petStoreJson = _.cloneDeep(require('../../samples/2.0/petstore.json'));

Expand All @@ -44,6 +45,11 @@ var samplePet = {
name: 'Test Pet'
};

var sampleInvalidPet = {
identifier: 1,
name: 'Test Pet'
};

describe('Swagger Validator Middleware v2.0', function () {
describe('request validation', function () {
it('should not validate request when there are no operations', function (done) {
Expand Down Expand Up @@ -1057,6 +1063,64 @@ describe('Swagger Validator Middleware v2.0', function () {
], done));
});
});



it('should validate a valid piped response', function (done) {
var cPetStoreJson = _.cloneDeep(petStoreJson);

cPetStoreJson.paths['/pets/{id}'].get['x-swagger-router-controller'] = 'Pets';
cPetStoreJson.paths['/pets/{id}'].get.operationId = 'getPetById';

helpers.createServer([cPetStoreJson], {
swaggerRouterOptions: {
controllers: {
'Pets_getPetById': function (req, res) {
var s = new stream.Readable();
s.push(new Buffer(JSON.stringify(samplePet)));
s.push(null);
s.pipe(res);
}
}
},
swaggerValidatorOptions: {
validateResponse: true
}
}, function (app) {
request(app)
.get('/api/pets/1')
.expect(200)
.end(helpers.expectContent(samplePet, done));
});
});

it('should validate an invalid piped response', function (done) {
var cPetStoreJson = _.cloneDeep(petStoreJson);

cPetStoreJson.paths['/pets/{id}'].get['x-swagger-router-controller'] = 'Pets';
cPetStoreJson.paths['/pets/{id}'].get.operationId = 'getPetById';

helpers.createServer([cPetStoreJson], {
swaggerRouterOptions: {
controllers: {
'Pets_getPetById': function (req, res) {
var s = new stream.Readable();
s.push(new Buffer(JSON.stringify(sampleInvalidPet)));
s.push(null);
s.pipe(res);
}
}
},
swaggerValidatorOptions: {
validateResponse: true
}
}, function (app) {
request(app)
.get('/api/pets/1')
.expect(500)
.end(helpers.expectContent('Response validation failed: failed schema validation', done));
});
});
});

describe('issues', function () {
Expand Down

0 comments on commit 17e77be

Please sign in to comment.