Skip to content

Commit

Permalink
Merge pull request #31 from NodeRedis/fix-errors
Browse files Browse the repository at this point in the history
Fix stack traces
  • Loading branch information
BridgeAR authored Mar 11, 2017
2 parents cd8566a + 67b02ed commit f9f720b
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 21 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ logs
coverage
node_modules
.idea
.vscode
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ test
.travis.yml
.gitignore
*.log
.vscode
.codeclimate.yml
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ var myParser = new Parser(options);

### Error classes

All errors returned by the parser are of the class `ReplyError` that is a sub class of `RedisError`.
Both types are exported by the parser.
* `RedisError` sub class of Error
* `ReplyError` sub class of RedisError
* `ParserError` sub class of RedisError

All Redis errors will be returned as `ReplyErrors` while a parser error is returned as `ParserError`.
All error classes are exported by the parser.

### Example

Expand Down
11 changes: 11 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## v.2.5.0 - 11 Mar, 2017

Features

- Added a `ParserError` class to differentiate them to ReplyErrors. The class is also exported

Bugfixes

- All errors now show their error message again next to the error name in the stack trace
- ParserErrors now show the offset and buffer attributes while being logged

## v.2.4.1 - 05 Feb, 2017

Bugfixes
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
module.exports = require('./lib/parser')
module.exports.ReplyError = require('./lib/replyError')
module.exports.RedisError = require('./lib/redisError')
module.exports.ParserError = require('./lib/redisError')
9 changes: 5 additions & 4 deletions lib/hiredis.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@

var hiredis = require('hiredis')
var ReplyError = require('../lib/replyError')
var ParserError = require('../lib/parserError')

/**
* Parse data
* @param parser
* @returns {*}
*/
function parseData (parser) {
function parseData (parser, data) {
try {
return parser.reader.get()
} catch (err) {
// Protocol errors land here
// Reset the parser. Otherwise new commands can't be processed properly
parser.reader = new hiredis.Reader(parser.options)
parser.returnFatalError(new ReplyError(err.message))
parser.returnFatalError(new ParserError(err.message, JSON.stringify(data), -1))
}
}

Expand All @@ -37,15 +38,15 @@ function HiredisReplyParser (options) {

HiredisReplyParser.prototype.execute = function (data) {
this.reader.feed(data)
var reply = parseData(this)
var reply = parseData(this, data)

while (reply !== undefined) {
if (reply && reply.name === 'Error') {
this.returnError(new ReplyError(reply.message))
} else {
this.returnReply(reply)
}
reply = parseData(this)
reply = parseData(this, data)
}
}

Expand Down
10 changes: 6 additions & 4 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
var StringDecoder = require('string_decoder').StringDecoder
var decoder = new StringDecoder()
var ReplyError = require('./replyError')
var ParserError = require('./parserError')
var bufferPool = new Buffer(32 * 1024)
var bufferOffset = 0
var interval = null
Expand Down Expand Up @@ -290,10 +291,11 @@ function parseType (parser, type) {
case 45: // -
return parseError(parser)
default:
var err = new ReplyError('Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte', 20)
err.offset = parser.offset
err.buffer = JSON.stringify(parser.buffer)
return handleError(parser, err)
return handleError(parser, new ParserError(
'Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte',
JSON.stringify(parser.buffer),
parser.offset
))
}
}

Expand Down
25 changes: 25 additions & 0 deletions lib/parserError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict'

var util = require('util')
var assert = require('assert')
var RedisError = require('./redisError')
var ADD_STACKTRACE = false

function ParserError (message, buffer, offset) {
assert(buffer)
assert.strictEqual(typeof offset, 'number')
RedisError.call(this, message, ADD_STACKTRACE)
this.offset = offset
this.buffer = buffer
Error.captureStackTrace(this, ParserError)
}

util.inherits(ParserError, RedisError)

Object.defineProperty(ParserError.prototype, 'name', {
value: 'ParserError',
configurable: true,
writable: true
})

module.exports = ParserError
9 changes: 6 additions & 3 deletions lib/redisError.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@

var util = require('util')

function RedisError (message) {
Error.call(this, message)
Error.captureStackTrace(this, this.constructor)
function RedisError (message, stack) {
Object.defineProperty(this, 'message', {
value: message || '',
configurable: true,
writable: true
})
if (stack || stack === undefined) {
Error.captureStackTrace(this, RedisError)
}
}

util.inherits(RedisError, Error)

Object.defineProperty(RedisError.prototype, 'name', {
value: 'RedisError',
configurable: true,
writable: true
})

Expand Down
13 changes: 8 additions & 5 deletions lib/replyError.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@

var util = require('util')
var RedisError = require('./redisError')
var ADD_STACKTRACE = false

function ReplyError (message, newLimit) {
var limit = Error.stackTraceLimit
Error.stackTraceLimit = newLimit || 2
RedisError.call(this, message)
Error.stackTraceLimit = limit
function ReplyError (message) {
var tmp = Error.stackTraceLimit
Error.stackTraceLimit = 2
RedisError.call(this, message, ADD_STACKTRACE)
Error.captureStackTrace(this, ReplyError)
Error.stackTraceLimit = tmp
}

util.inherits(ReplyError, RedisError)

Object.defineProperty(ReplyError.prototype, 'name', {
value: 'ReplyError',
configurable: true,
writable: true
})

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"test": "npm run coverage",
"benchmark": "node ./benchmark",
"posttest": "standard && npm run coverage:check",
"lint": "standard --fix",
"posttest": "npm run lint && npm run coverage:check",
"coverage": "node ./node_modules/istanbul/lib/cli.js cover --preserve-comments ./node_modules/mocha/bin/_mocha -- -R spec",
"coverage:check": "node ./node_modules/istanbul/lib/cli.js check-coverage --branch 100 --statement 100"
},
Expand Down
22 changes: 22 additions & 0 deletions test/errors.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict'

/* eslint-env mocha */

var assert = require('assert')
var ReplyError = require('../lib/replyError')
var ParserError = require('../lib/parserError')
var RedisError = require('../lib/redisError')

describe('errors', function () {
it('errors should have a stack trace with error message', function () {
var err1 = new RedisError('test')
var err2 = new ReplyError('test')
var err3 = new ParserError('test', new Buffer(''), 0)
assert(err1.stack)
assert(err2.stack)
assert(err3.stack)
assert(/RedisError: test/.test(err1.stack))
assert(/ReplyError: test/.test(err2.stack))
assert(/ParserError: test/.test(err3.stack))
})
})
10 changes: 8 additions & 2 deletions test/parsers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
/* eslint-env mocha */

var assert = require('assert')
var util = require('util')
var JavascriptParser = require('../')
var HiredisParser = require('../lib/hiredis')
var ReplyError = JavascriptParser.ReplyError
var ParserError = JavascriptParser.ParserError
var RedisError = JavascriptParser.RedisError
var parsers = [HiredisParser, JavascriptParser]

Expand Down Expand Up @@ -318,10 +320,14 @@ describe('parsers', function () {
Abc.prototype.checkReply = function (err) {
assert.strictEqual(typeof this.log, 'function')
assert.strictEqual(err.message, 'Protocol error, got "a" as reply type byte')
assert.strictEqual(err.name, 'ReplyError')
assert.strictEqual(err.name, 'ParserError')
assert(err instanceof RedisError)
assert(err instanceof ReplyError)
assert(err instanceof ParserError)
assert(err instanceof Error)
assert(err.offset)
assert(err.buffer)
assert(/\[97,42,49,13,42,49,13,36,49,96,122,97,115,100,13,10,97]/.test(err.buffer))
assert(/ParserError: Protocol error, got "a" as reply type byte/.test(util.inspect(err)))
replyCount++
}
Abc.prototype.log = console.log
Expand Down

0 comments on commit f9f720b

Please sign in to comment.