Skip to content

Commit

Permalink
Merge branch 'release/v1.10'
Browse files Browse the repository at this point in the history
  • Loading branch information
rudijs committed Nov 6, 2014
2 parents 3cca1d9 + 8bdd360 commit 2beb005
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 60 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
# Changelog
All notable changes to this project will be documented in this file.

## 1.1.0 - 2014-11-06

### Added
- Validate accept header for all requests.
- Validate content-type header for POST, PUT, PATCH only.
- Readme updates.
- Dev dependency updates.

### Deprecated
- Nothing.

### Removed
- Nothing.

### Fixed
- Nothing.

## 1.0.0 - 2014-10-26

### Added
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ KoaJS Validate JSON-API Request Headers Middleware

## Overview

KoaJS middleware to validate required Request headers for [JSON API](http://jsonapi.org/format/) spec.
KoaJS middleware to validate required HTTP request headers for [JSON API](http://jsonapi.org/format/) spec.

Content-type: application/vnd.api+json
This middleware will validate *all* requests have this header set:

Accept: application/vnd.api+json

This middleware will validate POST, PUT and PATCH requests have this header set:

Content-type: application/vnd.api+json

Validation failure will return HTTP `400 Bad Request` with the response text of a collection of objects keyed by "errors" (pretty printed here):

{
Expand Down
32 changes: 18 additions & 14 deletions lib/koa-jsonapi-headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,8 @@ module.exports = function koaJsonApiHeaders(options) {

function validateJsonApiHeaders(ctx) {

// Content-type: application/vnd.api+json
if (!ctx.header['content-type'] || !/application\/vnd\.api\+json/.test(ctx.header['content-type'])) {
ctx.throw(400, {
message: {
errors: [
{
code: 'invalid_request',
title: 'API requires header "Content-type application/vnd.api+json" for exchanging data.'
}
]
}
});
}

// Accept: application/vnd.api+json
// All requests must have json-api HTTP accept header
if (!ctx.header.accept || !/application\/vnd\.api\+json/.test(ctx.header.accept)) {
ctx.throw(400, {
message: {
Expand All @@ -40,6 +27,23 @@ module.exports = function koaJsonApiHeaders(options) {
});
}

// Content-type: application/vnd.api+json
// POST PUT and PATCH must have json-api HTTP content-type header
if (/^(POST|PUT|PATCH)$/.test(ctx.method)) {
if (!ctx.header['content-type'] || !/application\/vnd\.api\+json/.test(ctx.header['content-type'])) {
ctx.throw(400, {
message: {
errors: [
{
code: 'invalid_request',
title: 'API requires header "Content-type application/vnd.api+json" for exchanging data.'
}
]
}
});
}
}

}

return function *jsonApiHeaders(next) {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "koa-jsonapi-headers",
"version": "1.0.0",
"version": "1.1.0",
"description": "KoaJS Validate JSON-API Request Headers Middleware",
"repository": {
"type": "git",
Expand Down Expand Up @@ -28,7 +28,7 @@
"chai": "^1.9.2",
"coveralls": "^2.11.2",
"istanbul-harmony": "^0.3.0",
"jshint": "^2.5.6",
"jshint": "^2.5.8",
"koa": "^0.13.0",
"mocha": "^2.0.1",
"supertest": "^0.14.0"
Expand Down
199 changes: 157 additions & 42 deletions test/koa-jsonapi-headers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('JSON API Headers Middleware', function () {

describe('reject', function () {

it('missing Content-type header', function (done) {
beforeEach(function (done) {

app.use(function *catchJsonApiErrors(next) {
try {
Expand All @@ -36,8 +36,15 @@ describe('JSON API Headers Middleware', function () {
// default route
app.use(function *route1(next) {
yield next;
this.body = 'OK';
});

done();

});

it('missing Accept header', function (done) {

request(app.listen())
.get('/')
.expect(400)
Expand All @@ -57,37 +64,19 @@ describe('JSON API Headers Middleware', function () {
assert.isArray(jsonResponse.errors, 'Top level response property should be an Array');

jsonResponse.errors[0].code.should.equal('invalid_request');
jsonResponse.errors[0].title.should.equal('API requires header "Content-type application/vnd.api+json" for exchanging data.');
jsonResponse.errors[0].title.should.equal('API requires header "Accept application/vnd.api+json" for exchanging data.');

done();
});

});

it('missing Accept header', function (done) {

app.use(function *catchJsonApiErrors(next) {
try {
yield next;
}
catch (err) {

// Response properties
this.status = err.status || 500;
this.body = err.message;
}
});

app.use(koaJsonApiHeaders());

// default route
app.use(function *route1(next) {
yield next;
});
it('POST missing Content-type header', function (done) {

request(app.listen())
.get('/')
.set('Content-type', 'application/vnd.api+json')
.post('/')
.set('Accept', 'application/vnd.api+json')
.send({})
.expect(400)
.end(function (err, res) {
if (err) {
Expand All @@ -105,43 +94,167 @@ describe('JSON API Headers Middleware', function () {
assert.isArray(jsonResponse.errors, 'Top level response property should be an Array');

jsonResponse.errors[0].code.should.equal('invalid_request');
jsonResponse.errors[0].title.should.equal('API requires header "Accept application/vnd.api+json" for exchanging data.');
jsonResponse.errors[0].title.should.equal('API requires header "Content-type application/vnd.api+json" for exchanging data.');

done();
});

});

});
it('PUT missing Content-type header', function (done) {

describe('accept', function () {
request(app.listen())
.put('/')
.set('Accept', 'application/vnd.api+json')
.send({})
.expect(400)
.end(function (err, res) {
if (err) {
should.not.exist(err);
return done(err);
}

it('valid headers', function (done) {
// Test: response is a collection of objects keyed by "errors"
res.text.should.match(/{"errors":\[{.*}/);

app.use(koaJsonApiHeaders());
// Parse response test
var jsonResponse = JSON.parse(res.text);

// default route
app.use(function *route1(next) {
this.body = 'Corret headers found';
yield next;
});
should.exist(jsonResponse.errors);
assert.isArray(jsonResponse.errors, 'Top level response property should be an Array');

jsonResponse.errors[0].code.should.equal('invalid_request');
jsonResponse.errors[0].title.should.equal('API requires header "Content-type application/vnd.api+json" for exchanging data.');

done();
});

});

it('PATCH missing Content-type header', function (done) {

request(app.listen())
.get('/')
.set('Content-type', 'application/vnd.api+json')
.patch('/')
.set('Accept', 'application/vnd.api+json')
.expect(200)
.send({})
.expect(400)
.end(function (err, res) {
if (err) {
should.not.exist(err);
return done(err);
}
res.text.should.equal('Corret headers found');

// Test: response is a collection of objects keyed by "errors"
res.text.should.match(/{"errors":\[{.*}/);

// Parse response test
var jsonResponse = JSON.parse(res.text);

should.exist(jsonResponse.errors);
assert.isArray(jsonResponse.errors, 'Top level response property should be an Array');

jsonResponse.errors[0].code.should.equal('invalid_request');
jsonResponse.errors[0].title.should.equal('API requires header "Content-type application/vnd.api+json" for exchanging data.');

done();
});

});

});

describe('accept', function () {

describe('Valid Headers', function () {

beforeEach(function(done) {
app.use(koaJsonApiHeaders());

// default route
app.use(function *route1(next) {
yield next;
this.body = 'Correct headers found';
});

done();
});

it('GET valid headers', function (done) {
request(app.listen())
.get('/')
.set('Accept', 'application/vnd.api+json')
.expect(200)
.end(function (err, res) {
if (err) {
should.not.exist(err);
return done(err);
}
res.text.should.equal('Correct headers found');
done();
});

});

it('POST valid headers', function (done) {

request(app.listen())
.post('/')
.set('Accept', 'application/vnd.api+json')
.set('Content-type', 'application/vnd.api+json')
.send('name=test')
.expect(200)
.end(function (err, res) {
if (err) {
should.not.exist(err);
return done(err);
}
res.text.should.equal('Correct headers found');
done();
});

});

it('PUT valid headers', function (done) {

request(app.listen())
.put('/')
.set('Accept', 'application/vnd.api+json')
.set('Content-type', 'application/vnd.api+json')
.send('name=test')
.expect(200)
.end(function (err, res) {
if (err) {
should.not.exist(err);
return done(err);
}
res.text.should.equal('Correct headers found');
done();
});

});

it('PATCH valid headers', function (done) {

request(app.listen())
.patch('/')
.set('Accept', 'application/vnd.api+json')
.set('Content-type', 'application/vnd.api+json')
.send('name=test')
.expect(200)
.end(function (err, res) {
if (err) {
should.not.exist(err);
return done(err);
}
res.text.should.equal('Correct headers found');
done();
});

});


});

it('should permit exclusions via URL query', function (done) {

app.use(koaJsonApiHeaders());
Expand All @@ -168,10 +281,12 @@ describe('JSON API Headers Middleware', function () {

it('should permit exclusions via regex list', function (done) {

app.use(koaJsonApiHeaders({excludeList: [
'resource\/path',
'excluded\/endpoint\\?id'
]}));
app.use(koaJsonApiHeaders({
excludeList: [
'resource\/path',
'excluded\/endpoint\\?id'
]
}));

// default route
app.use(function *route1(next) {
Expand Down

0 comments on commit 2beb005

Please sign in to comment.