diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index c8ef3ca..0000000 --- a/.jshintrc +++ /dev/null @@ -1,59 +0,0 @@ -{ - "predef": [ ] - , "bitwise": false - , "camelcase": false - , "curly": false - , "eqeqeq": false - , "forin": false - , "immed": false - , "latedef": false - , "noarg": true - , "noempty": true - , "nonew": true - , "plusplus": false - , "quotmark": true - , "regexp": false - , "undef": true - , "unused": true - , "strict": false - , "trailing": true - , "maxlen": 120 - , "asi": true - , "boss": true - , "debug": true - , "eqnull": true - , "esnext": true - , "evil": true - , "expr": true - , "funcscope": false - , "globalstrict": false - , "iterator": false - , "lastsemic": true - , "laxbreak": true - , "laxcomma": true - , "loopfunc": true - , "multistr": false - , "onecase": false - , "proto": false - , "regexdash": false - , "scripturl": true - , "smarttabs": false - , "shadow": false - , "sub": true - , "supernew": false - , "validthis": true - , "browser": true - , "couch": false - , "devel": false - , "dojo": false - , "mootools": false - , "node": true - , "nonstandard": true - , "prototypejs": false - , "rhino": false - , "worker": true - , "wsh": false - , "nomen": false - , "onevar": false - , "passfail": false -} \ No newline at end of file diff --git a/README.md b/README.md index fa5c903..1cae67e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ The `options` object is optional and is passed on to hyperquest. One option is o The callback is called with up to 3 arguments. If there is an error there will only be an error argument in the first position, otherwise it will be `null`. The second argument will contain the deserialised object obtained from the server and the third argument will be the response object itself if you need to fetch headers or other metadata. +**Returns** the underlying hyperquest stream for this request. Can be safely ignored in most circumstances. + ### jsonist.post(url, data, [ options, ] callback) Send a POST request to `url`, writing JSON serialised data to the request, and return the callback with an error or JSON deserialised data (if any). @@ -71,18 +73,24 @@ The `data` parameter can also be a readable stream that will get `.pipe()`'d to The `options` object is optional and is passed on to hyperquest. +**Returns** the underlying hyperquest stream for this request. Can be safely ignored in most circumstances. + ### jsonist.put(url, data, [ options, ] callback) Same as `jsonist.post()` but for when that extra character is too much to type or you have to use someone's overloaded API. `'method'` is set to `'PUT'`. *Note: in each of the requests you can provide an optional `'hyperquest'` parameter in your options if you want to really customise the http chain (see [this](https://github.com/hyperquest))* +**Returns** the underlying hyperquest stream for this request. Can be safely ignored in most circumstances. + ### jsonist.delete(url, [ options, ] callback) Send a DELETE request to `url` and return the callback with an error or JSON deserialised data. Otherwise works the same as GET. +**Returns** the underlying hyperquest stream for this request. Can be safely ignored in most circumstances. + ## Error handling and bad JSON responses Server errors (i.e. response codes >= 300) are handled as standard responses. You can get the status code from the response object which is the third argument to the standard callback if you need to handle error responses in a different way. diff --git a/jsonist.js b/jsonist.js index cbc9eaf..5173a1d 100644 --- a/jsonist.js +++ b/jsonist.js @@ -1,49 +1,49 @@ -var url = require('url') - , hyperquest = require('hyperquest') - , bl = require('bl') - , stringify = require('json-stringify-safe') - , xtend = require('xtend') - +const url = require('url') +const hyperquest = require('hyperquest') +const bl = require('bl') +const stringify = require('json-stringify-safe') function HttpError (message) { - SyntaxError.call(this, message) - Error.captureStackTrace(this, arguments.callee) + SyntaxError.call(this, message) + Error.captureStackTrace(this, arguments.callee) } HttpError.prototype = Object.create(SyntaxError.prototype) HttpError.prototype.constructor = HttpError - function collector (uri, options, callback) { - var request = makeRequest(uri, options) - , redirect = null - , redirectCount = 0 + let request = makeRequest(uri, options) + let redirect = null + let redirectCount = 0 return handle(request) function handle (request) { if (options.followRedirects) { - request.on('response', function (response) { + request.on('response', (response) => { redirect = isRedirect(request.request, response) && response.headers.location }) } - request.pipe(bl(function (err, data) { + request.pipe(bl((err, data) => { if (redirect) { - if (++redirectCount >= (typeof options.followRedirects == 'number' ? options.followRedirects : 10)) + if (++redirectCount >= (typeof options.followRedirects === 'number' ? options.followRedirects : 10)) { return callback(new Error('Response was redirected too many times (' + redirectCount + ')')) + } request = makeRequest(url.resolve(uri, redirect), options) redirect = null return handle(request) } - if (err) + if (err) { return callback(err) + } - if (!data.length) + if (!data.length) { return callback(null, null, request.response) + } - var ret, msg + let ret, msg try { ret = JSON.parse(data.toString()) @@ -55,7 +55,7 @@ function collector (uri, options, callback) { err.data = data err.response = request.response - return callback(err); + return callback(err) } callback(null, ret, request.response) @@ -65,63 +65,59 @@ function collector (uri, options, callback) { } } - function makeMethod (method, data) { function handler (uri, options, callback) { - if (typeof options == 'function') { + let defaultOptions = { method, headers: {} } + if (typeof options === 'object') { + options = Object.assign(defaultOptions, options) + } else { callback = options - options = {} - } else - options = xtend(options, {}) - - if (typeof options.method != 'string') - options.method = method - - if (typeof options.headers != 'object') - options.headers = {} + options = defaultOptions + } - if (data && typeof options.headers['content-type'] != 'string') + if (data && typeof options.headers['content-type'] !== 'string') { options.headers['content-type'] = 'application/json' - - if (typeof options.headers['accept'] != 'string') + } + if (typeof options.headers['accept'] !== 'string') { options.headers['accept'] = 'application/json' + } return collector(uri, options, callback) } function dataHandler (uri, data, options, callback) { - var request = handler(uri, options, callback) - if (typeof data.pipe == 'function') + let request = handler(uri, options, callback) + + if (typeof data.pipe === 'function') { data.pipe(request) - else + } else { request.end(stringify(data)) + } + return request } return data ? dataHandler : handler } - function makeRequest (uri, options) { return (options.hyperquest || hyperquest)(uri, options) } - function isRedirect (request, response) { return request.method === 'GET' && - response.headers.location && - ( response.statusCode === 301 - || response.statusCode === 302 - || response.statusCode === 307 - || response.statusCode === 308 - ) + response.headers.location && + (response.statusCode === 301 || + response.statusCode === 302 || + response.statusCode === 307 || + response.statusCode === 308 + ) } - -module.exports.get = makeMethod('GET' , false) -module.exports.post = makeMethod('POST' , true) -module.exports.put = makeMethod('PUT' , true) -module.exports.delete = function deleteHandler (uri, options, callback) { +module.exports.get = makeMethod('GET', false) +module.exports.post = makeMethod('POST', true) +module.exports.put = makeMethod('PUT', true) +module.exports.delete = function deleteHandler (uri, options, callback) { // behaves half-way between a data posting request and a GET // since https://github.com/substack/hyperquest/commit/9b130e101 return makeMethod('DELETE', false)(uri, options, callback).end() diff --git a/package.json b/package.json index 77e689b..cb8133e 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "url": "https://github.com/rvagg/jsonist/issues" }, "dependencies": { - "bl": "~1.2.0", - "hyperquest": "~2.1.2", + "bl": "~3.0.0", + "hyperquest": "~2.1.3", "json-stringify-safe": "~5.0.1", "xtend": "~4.0.1" }, @@ -31,6 +31,6 @@ "devDependencies": { "after": "~0.8.2", "faucet": "~0.0.1", - "tape": "~4.6.3" + "tape": "~4.10.2" } } diff --git a/test.js b/test.js index 55f67e5..4fa189c 100644 --- a/test.js +++ b/test.js @@ -1,22 +1,20 @@ -const test = require('tape') - , http = require('http') - , fs = require('fs') - , bl = require('bl') - , xtend = require('xtend') - , EE = require('events').EventEmitter - , jsonist = require('./') - , stringify = require('json-stringify-safe') - , after = require('after') - +const test = require('tape') +const http = require('http') +const fs = require('fs') +const bl = require('bl') +const xtend = require('xtend') +const EE = require('events').EventEmitter +const jsonist = require('./') +const stringify = require('json-stringify-safe') +const after = require('after') function testServer (serverResponse, statusCode) { - var ee = new EE() - , server = http.createServer(handler) + const ee = new EE() + const server = http.createServer(handler) function handler (req, res) { req.pipe(bl(function (err, data) { - if (err) - return ee.emit('error', err) + if (err) { return ee.emit('error', err) } ee.emit('request', req, data.toString()) @@ -24,7 +22,7 @@ function testServer (serverResponse, statusCode) { })) res.writeHead( - typeof statusCode == 'number' ? statusCode : 200 + typeof statusCode === 'number' ? statusCode : 200 , { 'content-type': 'application/json' } ) res.end(serverResponse || '') @@ -39,12 +37,11 @@ function testServer (serverResponse, statusCode) { return ee } - -'get delete'.split(' ').forEach(function (type) { - test(type + ' fetch json doc', function (t) { +'get delete'.split(' ').forEach((type) => { + test(type + ' fetch json doc', (t) => { t.plan(7) - var testDoc = { a: 'test', doc: true, arr: [ { of: 'things' } ] } + let testDoc = { a: 'test', doc: true, arr: [ { of: 'things' } ] } testServer(stringify(testDoc)) .on('ready', function (url) { @@ -53,7 +50,7 @@ function testServer (serverResponse, statusCode) { t.deepEqual(data, testDoc, 'got correct doc') t.ok(response, 'got response object') t.equal( - response && response.headers && response.headers['content-type'] + response && response.headers && response.headers['content-type'] , 'application/json', 'verified response object by content-type header' ) }) @@ -65,7 +62,7 @@ function testServer (serverResponse, statusCode) { .on('close', t.ok.bind(t, true, 'ended')) }) - test(type + ' fetch non-json doc', function (t) { + test(type + ' fetch non-json doc', (t) => { t.plan(7) testServer('this is not json') @@ -86,7 +83,7 @@ function testServer (serverResponse, statusCode) { }) 'post put'.split(' ').forEach(function (type) { - test(type + ' json, no response', function (t) { + test(type + ' json, no response', (t) => { t.plan(9) var testDoc = { a: 'test2', doc: true, arr: [ { of: 'things' } ] } @@ -98,7 +95,7 @@ function testServer (serverResponse, statusCode) { t.notOk(data, 'no data') t.ok(response, 'got response object') t.equal( - response && response.headers && response.headers['content-type'] + response && response.headers && response.headers['content-type'] , 'application/json', 'verified response object by content-type header' ) }) @@ -112,11 +109,11 @@ function testServer (serverResponse, statusCode) { .on('close', t.ok.bind(t, true, 'ended')) }) - test(type + ' json, with response', function (t) { + test(type + ' json, with response', (t) => { t.plan(9) - var sendDoc = { a: 'test2', doc: true, arr: [ { of: 'things' } ] } - , recvDoc = { recv: 'this', obj: true } + let sendDoc = { a: 'test2', doc: true, arr: [ { of: 'things' } ] } + let recvDoc = { recv: 'this', obj: true } testServer(stringify(recvDoc)) .on('ready', function (url) { @@ -125,7 +122,7 @@ function testServer (serverResponse, statusCode) { t.deepEqual(data, recvDoc) t.ok(response, 'got response object') t.equal( - response && response.headers && response.headers['content-type'] + response && response.headers && response.headers['content-type'] , 'application/json', 'verified response object by content-type header' ) }) @@ -139,16 +136,16 @@ function testServer (serverResponse, statusCode) { .on('close', t.ok.bind(t, true, 'ended')) }) - test(type + ' data with no pipe function treated as data', function (t) { + test(type + ' data with no pipe function treated as data', (t) => { t.plan(9) - var sendDoc = { - a : 'test2' - , doc : true - , arr : [ { of: 'things' } ] - , pipe : 'this is a string and not a function' - } - , recvDoc = { recv: 'this', obj: true } + let sendDoc = { + a: 'test2', + doc: true, + arr: [ { of: 'things' } ], + pipe: 'this is a string and not a function' + } + let recvDoc = { recv: 'this', obj: true } testServer(stringify(recvDoc)) .on('ready', function (url) { @@ -157,7 +154,7 @@ function testServer (serverResponse, statusCode) { t.deepEqual(data, recvDoc) t.ok(response, 'got response object') t.equal( - response && response.headers && response.headers['content-type'] + response && response.headers && response.headers['content-type'] , 'application/json', 'verified response object by content-type header' ) }) @@ -171,21 +168,21 @@ function testServer (serverResponse, statusCode) { .on('close', t.ok.bind(t, true, 'ended')) }) - test(type + ' data with pipe function will data.pipe(req)', function (t) { + test(type + ' data with pipe function will data.pipe(req)', (t) => { t.plan(10) - var sendDoc = { - a : 'test2' - , doc : true - , arr : [ { of: 'things' } ] - } - , stream = { - pipe: function (req) { - t.ok(req, 'request should be set') - req.end(stringify(sendDoc)) - } - } - , recvDoc = { recv: 'this', obj: true } + let sendDoc = { + a: 'test2', + doc: true, + arr: [ { of: 'things' } ] + } + let stream = { + pipe: function (req) { + t.ok(req, 'request should be set') + req.end(stringify(sendDoc)) + } + } + let recvDoc = { recv: 'this', obj: true } testServer(stringify(recvDoc)) .on('ready', function (url) { @@ -194,7 +191,7 @@ function testServer (serverResponse, statusCode) { t.deepEqual(data, recvDoc) t.ok(response, 'got response object') t.equal( - response && response.headers && response.headers['content-type'] + response && response.headers && response.headers['content-type'] , 'application/json', 'verified response object by content-type header' ) }) @@ -208,13 +205,13 @@ function testServer (serverResponse, statusCode) { .on('close', t.ok.bind(t, true, 'ended')) }) - test(type + ' data with pipe function and real stream works', function (t) { + test(type + ' data with pipe function and real stream works', (t) => { t.plan(9) - var file = __dirname + '/package.json' - , content = JSON.parse(fs.readFileSync(file)) - , stream = fs.createReadStream(file) - , recvDoc = { recv: 'this', obj: true } + let file = `${__dirname}/package.json` + let content = JSON.parse(fs.readFileSync(file)) + let stream = fs.createReadStream(file) + let recvDoc = { recv: 'this', obj: true } testServer(stringify(recvDoc)) .on('ready', function (url) { @@ -223,7 +220,7 @@ function testServer (serverResponse, statusCode) { t.deepEqual(data, recvDoc) t.ok(response, 'got response object') t.equal( - response && response.headers && response.headers['content-type'] + response && response.headers && response.headers['content-type'] , 'application/json', 'verified response object by content-type header' ) }) @@ -238,26 +235,25 @@ function testServer (serverResponse, statusCode) { }) }) - -test('follow redirect', function (t) { +test('follow redirect', (t) => { t.plan(7) - var expectedResponse = { ok: 'foobar!' } - , server = http.createServer(function (req, res) { - if (req.url == '/') { // 2 requests come in here - t.ok('got /') - res.writeHead(302, { 'location': '/foobar' }) - return res.end() - } - // one comes in here - t.equal(req.url, '/foobar', 'got /foobar') - res.writeHead(200, { 'content-type': 'application/json' }) - res.end(stringify(expectedResponse)) - }) + let expectedResponse = { ok: 'foobar!' } + let server = http.createServer(function (req, res) { + if (req.url === '/') { // 2 requests come in here + t.ok('got /') + res.writeHead(302, { 'location': '/foobar' }) + return res.end() + } + // one comes in here + t.equal(req.url, '/foobar', 'got /foobar') + res.writeHead(200, { 'content-type': 'application/json' }) + res.end(stringify(expectedResponse)) + }) server.listen(function () { - var port = server.address().port - , done = after(2, function () { server.close() }) + let port = server.address().port + let done = after(2, function () { server.close() }) jsonist.get('http://localhost:' + port, function (err, data) { // don't follow redirect, don't get data @@ -274,27 +270,26 @@ test('follow redirect', function (t) { }) }) - -test('follow redirect limit', function (t) { +test('follow redirect limit', (t) => { t.plan(6 + 10 + 5 + 10) - var expectedResponse = { ok: 'foobar!' } - , server = http.createServer(function (req, res) { - var m = +req.url.match(/^\/(\d+)/)[1] - if (m < 20) { // 2 requests come in here - t.ok('got /') - res.writeHead(302, { 'location': '/' + (m + 1) }) - return res.end() - } - // one comes in here - t.equal(req.url, '/20', 'got /20') - res.writeHead(200, { 'content-type': 'application/json' }) - res.end(stringify(expectedResponse)) - }) + let expectedResponse = { ok: 'foobar!' } + let server = http.createServer(function (req, res) { + let m = +req.url.match(/^\/(\d+)/)[1] + if (m < 20) { // 2 requests come in here + t.ok('got /') + res.writeHead(302, { 'location': '/' + (m + 1) }) + return res.end() + } + // one comes in here + t.equal(req.url, '/20', 'got /20') + res.writeHead(200, { 'content-type': 'application/json' }) + res.end(stringify(expectedResponse)) + }) server.listen(function () { - var port = server.address().port - , done = after(3, function () { server.close() }) + let port = server.address().port + let done = after(3, function () { server.close() }) jsonist.get('http://localhost:' + port + '/1', { followRedirects: true }, function (err, data) { t.ok(err, 'got error') @@ -316,8 +311,7 @@ test('follow redirect limit', function (t) { }) }) - -test('server error, non-JSON', function (t) { +test('server error, non-JSON', (t) => { t.plan(7) var responseText = 'there was an error' @@ -338,8 +332,7 @@ test('server error, non-JSON', function (t) { .on('close', t.ok.bind(t, true, 'ended')) }) - -test('server error, with-JSON', function (t) { +test('server error, with-JSON', (t) => { t.plan(8) var responseDoc = 'there was an error' @@ -352,7 +345,7 @@ test('server error, with-JSON', function (t) { t.ok(response, 'got response object') t.equal(response.statusCode, 501, 'got correct status code') t.equal( - response && response.headers && response.headers['content-type'] + response && response.headers && response.headers['content-type'] , 'application/json', 'verified response object by content-type header' ) })