diff --git a/.gitignore b/.gitignore index 237427b..0f3aa78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ coverage.html node_modules/ npm-debug.log +coverage diff --git a/Makefile b/Makefile index fd7b3c8..070236c 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,20 @@ REPORTER = spec test: - @NODE_ENV=test ./node_modules/.bin/mocha -b --reporter $(REPORTER) --recursive + @NODE_ENV=test ./node_modules/.bin/mocha -b --reporter $(REPORTER) -lib-cov: - ./node_modules/jscoverage/bin/jscoverage lib lib-cov +lint: + ./node_modules/.bin/jshint ./test ./index.js -test-cov: lib-cov - @JSON_STATUS_COVERAGE=1 $(MAKE) test REPORTER=html-cov 1> coverage.html - rm -rf lib-cov +test-cov: + $(MAKE) lint + @NODE_ENV=test ./node_modules/.bin/istanbul cover \ +./node_modules/mocha/bin/_mocha -- -R spec -test-coveralls: lib-cov - $(MAKE) test REPORTER=spec +test-coveralls: echo TRAVIS_JOB_ID $(TRAVIS_JOB_ID) - @JSON_STATUS_COVERAGE=1 $(MAKE) test REPORTER=mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js || true - rm -rf lib-cov + $(MAKE) test + @NODE_ENV=test ./node_modules/.bin/istanbul cover \ +./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && \ +cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true .PHONY: test diff --git a/index.js b/index.js index 4ab79f4..83dfd82 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,127 @@ -var dir = './lib/'; -if (process.env.JSON_STATUS_COVERAGE){ - dir = './lib-cov/'; +var EventEmitter = require('events').EventEmitter; +var util = require('util'); + +var JsonResponder = function(req, res){ + this.req = req; + this.res = res; +}; + +JsonResponder.connectMiddleware = function(namespace, errorHandler){ + namespace = namespace || 'status'; + return function(req, res, next){ + res[namespace] = new JsonResponder(req, res); + if (errorHandler && (typeof errorHandler === 'function')){ + res[namespace].on('error', errorHandler); + } + next(); + }; +}; + +JsonResponder.prototype = Object.create(EventEmitter.prototype); + +var errors = { + 'badRequest' : {type : 400, message : 'Bad Request'}, + 'unauthenticated' : {type : 401, message : 'Unauthenticated'}, + 'forbidden' : {type : 403, message : 'Forbidden'}, + 'notFound' : {type : 404, message : 'Not Found'}, + 'methodNotAllowed' : {type : 405, message : 'Method Not Allowed'}, + 'notAcceptable' : {type : 406, message : 'Not Acceptable'}, + 'conflict' : {type : 409, message : 'Conflict'}, + 'gone' : {type : 410, message : 'Gone'}, + 'lengthRequired' : {type : 411, message : 'Length Required'}, + 'preconditionFailed' : {type : 412, message : 'Precondition Failed'}, + 'requestEntityTooLarge' : {type : 413, message : 'Request Entity Too Large'}, + 'requestUriTooLong' : {type : 414, message : 'Request URI Too Long'}, + 'unsupportedMediaType' : {type : 415, message : 'Unsupported Media Type'}, + 'unprocessableEntity' : {type : 422, message : 'Unprocessable Entity'}, + 'tooManyRequests' : {type : 429, message : 'Too Many Requests'}, + 'internalServerError' : {type : 500, message : 'Internal Server Error'}, + 'notImplemented' : {type : 501, message : 'Not Implemented'}, + 'badGateway' : {type : 502, message : 'Bad Gateway'}, + 'serviceUnavailable' : {type : 503, message : 'Service Unavailable'}, + 'gatewayTimeout' : {type : 504, message : 'Gateway Timeout'} +}; + +var makeErrorHandler = function(name, payload){ + JsonResponder.prototype[name] = function(detail){ + var obj = {"error" : payload}; + + detail = detail || {}; + // if detail is circular, just flatten it to a string. + try { + JSON.stringify(detail); // will throw if circular + } catch(ex) { + detail = util.inspect(detail); // stringifies. only goes one level deep. + } + + obj.error.detail = detail; + + this.res.setHeader('Content-Type', 'application/json; charset=utf-8'); + this.res.writeHead(payload.type); + var out = JSON.stringify(obj); + this.res.write(out); + this.res.end(); + this.emit("error", { req : this.req, res : this.res, type : payload.type, message : payload.message, detail : detail } ); + }; +}; + +/* set methods on the JsonResponder for every kind of error that could occur. */ +for(var errorName in errors){ + makeErrorHandler(errorName, errors[errorName]); } -module.exports = require(dir + 'JsonResponder'); + +JsonResponder.prototype.created = function(url, data){ + url = url || ''; + this.res.setHeader('Location', url); + if (data){ + this.res.setHeader('content-type', 'application/json'); + } + this.res.writeHead(201); + if (data) { + this.res.end(JSON.stringify(data)); + } else { + this.res.end(); + } +}; + +JsonResponder.prototype.accepted = function(){ + this.res.writeHead(202); + this.res.end(); +}; + +JsonResponder.prototype.noContent = function(){ + this.res.writeHead(204); + this.res.end(); +}; + +JsonResponder.prototype.resetContent = function(){ + this.res.writeHead(205); + this.res.end(); +}; + +JsonResponder.prototype.movedPermanently = function(url){ + url = url || ''; + this.res.setHeader('Location', url); + this.res.writeHead(301); + this.res.end(); +}; + +JsonResponder.prototype.found = function(url){ + url = url || ''; + this.res.setHeader('Location', url); + this.res.writeHead(302); + this.res.end(); +}; + +JsonResponder.prototype.redirect = function(url){ + this.found(url); +}; + +JsonResponder.prototype.OPTIONS = function(methods){ + this.res.setHeader('Allow', methods.join(",")); + this.res.writeHead(200); + return this.res.end(JSON.stringify({"allowed methods" : methods})); +}; + + +module.exports = JsonResponder; diff --git a/lib/JsonResponder.js b/lib/JsonResponder.js deleted file mode 100644 index 2b6556e..0000000 --- a/lib/JsonResponder.js +++ /dev/null @@ -1,125 +0,0 @@ -var EventEmitter = require('events').EventEmitter; -var util = require('util'); - -var JsonResponder = function(req, res){ - this.req = req; - this.res = res; -}; - -JsonResponder.connectMiddleware = function(namespace, errorHandler){ - namespace = namespace || 'status'; - return function(req, res, next){ - res[namespace] = new JsonResponder(req, res); - if (errorHandler && (typeof errorHandler === 'function')){ - res[namespace].on('error', errorHandler); - } - next(); - }; -}; - -JsonResponder.prototype = Object.create(EventEmitter.prototype); - -var errors = { - 'badRequest' : {type : 400, message : 'Bad Request'}, - 'unauthenticated' : {type : 401, message : 'Unauthenticated'}, - 'forbidden' : {type : 403, message : 'Forbidden'}, - 'notFound' : {type : 404, message : 'Not Found'}, - 'methodNotAllowed' : {type : 405, message : 'Method Not Allowed'}, - 'notAcceptable' : {type : 406, message : 'Not Acceptable'}, - 'conflict' : {type : 409, message : 'Conflict'}, - 'gone' : {type : 410, message : 'Gone'}, - 'lengthRequired' : {type : 411, message : 'Length Required'}, - 'preconditionFailed' : {type : 412, message : 'Precondition Failed'}, - 'requestEntityTooLarge' : {type : 413, message : 'Request Entity Too Large'}, - 'requestUriTooLong' : {type : 414, message : 'Request URI Too Long'}, - 'unsupportedMediaType' : {type : 415, message : 'Unsupported Media Type'}, - 'unprocessableEntity' : {type : 422, message : 'Unprocessable Entity'}, - 'tooManyRequests' : {type : 429, message : 'Too Many Requests'}, - 'internalServerError' : {type : 500, message : 'Internal Server Error'}, - 'notImplemented' : {type : 501, message : 'Not Implemented'}, - 'badGateway' : {type : 502, message : 'Bad Gateway'}, - 'serviceUnavailable' : {type : 503, message : 'Service Unavailable'}, - 'gatewayTimeout' : {type : 504, message : 'Gateway Timeout'} -}; - -var makeErrorHandler = function(name, payload){ - JsonResponder.prototype[name] = function(detail){ - var obj = {"error" : payload}; - - detail = detail || {}; - // if detail is circular, just flatten it to a string. - try { - JSON.stringify(detail); // will throw if circular - } catch(ex) { - detail = util.inspect(detail); // stringifies. only goes one level deep. - } - - obj.error.detail = detail; - - this.res.setHeader('Content-Type', 'application/json; charset=utf-8'); - this.res.writeHead(payload.type); - var out = JSON.stringify(obj); - this.res.write(out); - this.res.end(); - this.emit("error", { req : this.req, res : this.res, type : payload.type, message : payload.message, detail : detail } ); - }; -}; - -/* set methods on the JsonResponder for every kind of error that could occur. */ -for(var errorName in errors){ - makeErrorHandler(errorName, errors[errorName]); -} - -JsonResponder.prototype.created = function(url, data){ - url = url || ''; - this.res.setHeader('Location', url); - this.res.setHeader('content-type', 'application/json'); - this.res.writeHead(201); - if (data) { - this.res.end(JSON.stringify(data)); - } else { - this.res.end(); - } -}; - -JsonResponder.prototype.accepted = function(){ - this.res.writeHead(202); - this.res.end(); -}; - -JsonResponder.prototype.noContent = function(){ - this.res.writeHead(204); - this.res.end(); -}; - -JsonResponder.prototype.resetContent = function(){ - this.res.writeHead(205); - this.res.end(); -}; - -JsonResponder.prototype.movedPermanently = function(url){ - url = url || ''; - this.res.setHeader('Location', url); - this.res.writeHead(301); - this.res.end(); -}; - -JsonResponder.prototype.found = function(url){ - url = url || ''; - this.res.setHeader('Location', url); - this.res.writeHead(302); - this.res.end(); -}; - -JsonResponder.prototype.redirect = function(url){ - this.found(url); -}; - -JsonResponder.prototype.OPTIONS = function(methods){ - this.res.setHeader('Allow', methods.join(",")); - this.res.writeHead(200); - return this.res.end(JSON.stringify({"allowed methods" : methods})); -}; - - -module.exports = JsonResponder; diff --git a/package.json b/package.json index 22ff667..f75f971 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "http", "hypermedia" ], - "version": "0.2.4", + "version": "0.3.0", "bugs": { "url": "https://github.com/cainus/json-status/issues" }, @@ -23,12 +23,11 @@ "dependencies": { }, "devDependencies": { + "coveralls": "2.10.0", + "jshint": "2.4.4", + "istanbul": "0.2.7", "request": "2.19.0", "mocha-lcov-reporter": "0.0.1", - "jscoverage": "0.3.6", - "coveralls": "2.0.13", - "mockery": "1.1.2", - "hottap": "1.0.0", "mocha": "1.8.1", "connect": "~2.7.11", "should": "1.1.0" diff --git a/test/JsonResponder.js b/test/index.js similarity index 98% rename from test/JsonResponder.js rename to test/index.js index af4a8a0..61e418a 100644 --- a/test/JsonResponder.js +++ b/test/index.js @@ -195,6 +195,8 @@ describe("JsonStatus", function(){ var responder = new JsonStatus({}, fakeRes); responder.created("SOMEURL"); fakeRes.headers.Location.should.equal("SOMEURL"); + should.not.exist(fakeRes.headers['content-type']); + // no content-type when there's no body! fakeRes.status.should.equal(201); fakeRes.ended.should.equal(true); });