Skip to content

Commit 595d3e8

Browse files
author
Ruben Bridgewater
committed
Print warnings for explicitly requested parsers that do not exist
Remove obsolete variable in the js parser by using already existing ones Add stringNumbers option
1 parent 56bf882 commit 595d3e8

File tree

7 files changed

+130
-46
lines changed

7 files changed

+130
-46
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ npm install redis-parser
1919
## Usage
2020

2121
```js
22+
var Parser = require('redis-parser');
23+
2224
new Parser(options);
2325
```
2426

@@ -28,7 +30,8 @@ new Parser(options);
2830
* `returnError`: *function*; mandatory
2931
* `returnFatalError`: *function*; optional, defaults to the returnError function
3032
* `returnBuffers`: *boolean*; optional, defaults to false
31-
* `name`: *javascript|hiredis*; optional, defaults to hiredis and falls back to the js parser if not available
33+
* `name`: *javascript|hiredis*; optional, defaults to hiredis and falls back to the js parser if not available or if the stringNumbers option is choosen
34+
* `stringNumbers`: *boolean*; optional, defaults to false. This is only available for the javascript parser at the moment!
3235

3336
### Example
3437

@@ -64,7 +67,9 @@ Library.prototype.streamHandler = function () {
6467
```
6568
You do not have to use the returnFatalError function. Fatal errors will be returned in the normal error function in that case.
6669

67-
And if you want to return buffers instead of strings, you can do this by adding the returnBuffers option.
70+
And if you want to return buffers instead of strings, you can do this by adding the `returnBuffers` option.
71+
72+
If you handle big numbers, you should pass the `stringNumbers` option. That case numbers above 2^53 can be handled properly without reduced precision.
6873

6974
```js
7075
// Same functions as in the first example
@@ -76,6 +81,8 @@ var parser = new Parser({
7681
returnError: function(err) {
7782
lib.returnError(err);
7883
},
84+
name: 'javascript', // Use the Javascript parser
85+
stringNumbers: true, // Return all numbers as string instead of a js number
7986
returnBuffers: true // All strings are returned as buffer e.g. <Buffer 48 65 6c 6c 6f>
8087
});
8188

changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## v.1.2.0 - 27 Mar, 2016
2+
3+
Features
4+
5+
- Added `stringNumbers` option to make sure all numbers are returned as string instead of a js number for precision
6+
- The parser is from now on going to print warnings if a parser is explicitly requested that does not exist and gracefully chooses the JS parser
17

28
## v.1.1.0 - 26 Jan, 2016
39

lib/hiredis.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22

33
var hiredis = require('hiredis');
44

5-
function HiredisReplyParser(returnBuffers) {
5+
function HiredisReplyParser(options) {
66
this.name = 'hiredis';
7-
this.returnBuffers = returnBuffers;
8-
this.reader = new hiredis.Reader({
9-
return_buffers: returnBuffers
10-
});
7+
this.options = options;
8+
this.reader = new hiredis.Reader(options);
119
}
1210

1311
HiredisReplyParser.prototype.parseData = function () {
@@ -16,11 +14,8 @@ HiredisReplyParser.prototype.parseData = function () {
1614
} catch (err) {
1715
// Protocol errors land here
1816
// Reset the parser. Otherwise new commands can't be processed properly
19-
this.reader = new hiredis.Reader({
20-
return_buffers: this.returnBuffers
21-
});
17+
this.reader = new hiredis.Reader(this.options);
2218
this.returnFatalError(err);
23-
return void 0;
2419
}
2520
};
2621

lib/javascript.js

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
'use strict';
22

3-
var util = require('util');
4-
5-
function JavascriptReplyParser(return_buffers) {
3+
function JavascriptReplyParser(options) {
64
this.name = 'javascript';
75
this.buffer = new Buffer(0);
86
this.offset = 0;
@@ -12,23 +10,29 @@ function JavascriptReplyParser(return_buffers) {
1210
this.type = 0;
1311
this.protocolError = false;
1412
this.offsetCache = 0;
15-
if (return_buffers) {
13+
// If returnBuffers is active, all return values are returned as buffers besides numbers and errors
14+
if (options.return_buffers) {
1615
this.handleReply = function (start, end) {
1716
return this.buffer.slice(start, end);
1817
};
18+
} else {
19+
this.handleReply = function (start, end) {
20+
return this.buffer.toString('utf-8', start, end);
21+
};
22+
}
23+
// If stringNumbers is activated the parser always returns numbers as string
24+
// This is important for big numbers (number > Math.pow(2, 53)) as js numbers are 64bit floating point numbers with reduced precision
25+
if (options.string_numbers) {
26+
this.handleNumbers = function (start, end) {
27+
return this.buffer.toString('ascii', start, end);
28+
};
29+
} else {
30+
this.handleNumbers = function (start, end) {
31+
return +this.buffer.toString('ascii', start, end);
32+
};
1933
}
2034
}
2135

22-
JavascriptReplyParser.prototype.handleReply = function (start, end) {
23-
return this.buffer.toString('utf-8', start, end);
24-
};
25-
26-
function IncompleteReadBuffer(message) {
27-
this.name = 'IncompleteReadBuffer';
28-
this.message = message;
29-
}
30-
util.inherits(IncompleteReadBuffer, Error);
31-
3236
JavascriptReplyParser.prototype.parseResult = function (type) {
3337
var start = 0,
3438
end = 0,
@@ -48,7 +52,7 @@ JavascriptReplyParser.prototype.parseResult = function (type) {
4852
this.chunksSize = this.buffers[0].length;
4953
// Include the packetHeader delimiter
5054
this.bigStrSize = packetHeader + 2;
51-
throw new IncompleteReadBuffer('Wait for more data.');
55+
throw new Error('Wait for more data.');
5256
}
5357
// Set the offset to after the delimiter
5458
this.offset = end + 2;
@@ -60,7 +64,7 @@ JavascriptReplyParser.prototype.parseResult = function (type) {
6064
// Include the delimiter
6165
this.offset = end + 2;
6266
// Return the coerced numeric value
63-
return +this.buffer.toString('ascii', start, end);
67+
return this.handleNumbers(start, end);
6468
} else if (type === 43) { // +
6569
end = this.packetEndOffset();
6670
start = this.offset;
@@ -74,7 +78,7 @@ JavascriptReplyParser.prototype.parseResult = function (type) {
7478
reply = [];
7579
for (var i = 0; i < packetHeader; i++) {
7680
if (this.offset >= this.buffer.length) {
77-
throw new IncompleteReadBuffer('Wait for more data.');
81+
throw new Error('Wait for more data.');
7882
}
7983
reply.push(this.parseResult(this.buffer[this.offset++]));
8084
}
@@ -84,8 +88,6 @@ JavascriptReplyParser.prototype.parseResult = function (type) {
8488
start = this.offset;
8589
this.offset = end + 2;
8690
return new Error(this.buffer.toString('utf-8', start, end));
87-
} else {
88-
return void 0;
8991
}
9092
};
9193

@@ -107,7 +109,6 @@ JavascriptReplyParser.prototype.execute = function (buffer) {
107109
this.buffer = Buffer.concat([this.buffer.slice(this.offset), buffer]);
108110
}
109111
this.offset = 0;
110-
this.protocolError = true;
111112
this.run();
112113
};
113114

@@ -118,8 +119,8 @@ JavascriptReplyParser.prototype.tryParsing = function () {
118119
// Catch the error (not enough data), rewind if it's an array,
119120
// and wait for the next packet to appear
120121
this.offset = this.offsetCache;
121-
this.protocolError = false;
122-
return void 0;
122+
// Indicate that there's no protocol error by resetting the type too
123+
this.type = undefined;
123124
}
124125
};
125126

@@ -139,7 +140,7 @@ JavascriptReplyParser.prototype.run = function () {
139140
this.type = this.buffer[this.offset++];
140141
reply = this.tryParsing();
141142
}
142-
if (this.type !== undefined && this.protocolError === true) {
143+
if (this.type !== undefined) {
143144
// Reset the buffer so the parser can handle following commands properly
144145
this.buffer = new Buffer(0);
145146
this.returnFatalError(new Error('Protocol error, got ' + JSON.stringify(String.fromCharCode(this.type)) + ' as reply type byte'));
@@ -162,7 +163,7 @@ JavascriptReplyParser.prototype.packetEndOffset = function () {
162163
offset++;
163164

164165
if (offset >= len) {
165-
throw new IncompleteReadBuffer('Did not see LF after NL reading multi bulk count (' + offset + ' => ' + this.buffer.length + ', ' + this.offset + ')');
166+
throw new Error('Did not see LF after NL reading multi bulk count (' + offset + ' => ' + this.buffer.length + ', ' + this.offset + ')');
166167
}
167168
}
168169
return offset;

lib/parser.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ try {
1010
} catch (err) { /* ignore errors */ }
1111

1212
function Parser (options) {
13-
var parser;
13+
var parser, msg;
1414

1515
if (
1616
!options ||
@@ -21,18 +21,34 @@ function Parser (options) {
2121
}
2222

2323
/* istanbul ignore if: hiredis should always be installed while testing */
24-
if (options.name === 'hiredis' && !parsers.hiredis) {
25-
console.warn('<< WARNING >> You explicitly required the hiredis parser but hiredis is not installed. The js parser is going to be returned instead.');
24+
if (options.name === 'hiredis') {
25+
if (!parsers.hiredis) {
26+
msg = 'You explicitly required the hiredis parser but hiredis is not installed. The JS parser is going to be returned instead.';
27+
} else if (options.stringNumbers) {
28+
msg = 'You explicitly required the hiredis parser in combination with the stringNumbers option. Only the JS parser can handle that and is choosen instead.';
29+
}
30+
} else if (options.name && !parsers[options.name]) {
31+
msg = 'The requested parser "' + options.name + '" is unkown and the JS parser is choosen instead.';
32+
}
33+
34+
if (msg) {
35+
console.warn(new Error(msg).stack.replace('Error: ', 'Warning: '));
36+
options.name = 'javascript';
2637
}
2738

2839
options.name = options.name || 'hiredis';
2940
options.name = options.name.toLowerCase();
30-
options.returnBuffers = options.returnBuffers || false;
3141

32-
if (options.name === 'javascript' || !parsers.hiredis) {
33-
parser = new parsers.javascript(options.returnBuffers);
42+
var innerOptions = {
43+
// The hiredis parser expects underscores
44+
return_buffers: options.returnBuffers || false,
45+
string_numbers: options.stringNumbers || false
46+
};
47+
48+
if (options.name === 'javascript' || !parsers.hiredis || options.stringNumbers) {
49+
parser = new parsers.javascript(innerOptions);
3450
} else {
35-
parser = new parsers.hiredis(options.returnBuffers);
51+
parser = new parsers.hiredis(innerOptions);
3652
}
3753

3854
parser.returnError = options.returnError;

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@
2828
"node": ">=0.10.0"
2929
},
3030
"devDependencies": {
31-
"jshint": "^2.8.0",
32-
"mocha": "^2.3.2",
31+
"codeclimate-test-reporter": "^0.1.1",
32+
"intercept-stdout": "^0.1.2",
3333
"istanbul": "^0.4.0",
34-
"codeclimate-test-reporter": "^0.1.1"
34+
"jshint": "^2.8.0",
35+
"mocha": "^2.3.2"
3536
},
3637
"optionalDependency": {
3738
"hiredis": "^0.4.1"

test/parsers.spec.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
var intercept = require('intercept-stdout');
34
var assert = require('assert');
45
var Parser = require('../');
56
var parsers = ['javascript', 'hiredis'];
@@ -28,10 +29,44 @@ describe('parsers', function () {
2829
returnReply: returnReply,
2930
returnBuffers: true
3031
});
31-
}, 'Please provide all return functions while initiating the parser');
32+
}, function (err) {
33+
assert.strictEqual(err.message, 'Please provide all return functions while initiating the parser');
34+
return true;
35+
});
3236

3337
});
3438

39+
it('unknown parser', function () {
40+
var str = '';
41+
var unhookIntercept = intercept(function (data) {
42+
str += data;
43+
return '';
44+
});
45+
new Parser({
46+
returnReply: returnReply,
47+
returnError: returnError,
48+
name: 'something_unknown'
49+
});
50+
unhookIntercept();
51+
assert(/^Warning: The requested parser "something_unknown" is unkown and the JS parser is choosen instead\.\n +at new Parser/.test(str), str);
52+
});
53+
54+
it('hiredis and stringNumbers', function () {
55+
var str = '';
56+
var unhookIntercept = intercept(function (data) {
57+
str += data;
58+
return '';
59+
});
60+
new Parser({
61+
returnReply: returnReply,
62+
returnError: returnError,
63+
name: 'hiredis',
64+
stringNumbers: true
65+
});
66+
unhookIntercept();
67+
assert(/^Warning: You explicitly required the hiredis parser in combination with the stringNumbers option. .+.\.\n +at new Parser/.test(str), str);
68+
});
69+
3570
});
3671

3772
parsers.forEach(function (parserName) {
@@ -401,6 +436,29 @@ describe('parsers', function () {
401436
parser.execute(new Buffer('\n'));
402437
assert.strictEqual(replyCount, 3);
403438
});
439+
440+
it('return numbers as strings', function () {
441+
var replyCount = 0;
442+
var entries = ['123', '590295810358705700002', '-99999999999999999'];
443+
function checkReply(reply) {
444+
assert.strictEqual(typeof reply, 'string');
445+
assert.strictEqual(reply, entries[replyCount]);
446+
replyCount++;
447+
}
448+
var unhookIntercept = intercept(function () {
449+
return '';
450+
});
451+
var parser = new Parser({
452+
returnReply: checkReply,
453+
returnError: returnError,
454+
returnFatalError: returnFatalError,
455+
name: parserName,
456+
stringNumbers: true
457+
});
458+
unhookIntercept();
459+
parser.execute(new Buffer(':123\r\n:590295810358705700002\r\n:-99999999999999999\r\n'));
460+
assert.strictEqual(replyCount, 3);
461+
});
404462
});
405463
});
406464
});

0 commit comments

Comments
 (0)