Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[api][flags] Started working on the `Parser#flag` specification #3

  • Loading branch information...
commit 34318b1b82472cbad743d002484c372a6ba885f1 1 parent a16f240
@3rd-Eden authored
Showing with 116 additions and 35 deletions.
  1. +18 −2 README.md
  2. +34 −8 index.js
  3. +64 −25 test/api.test.js
View
20 README.md
@@ -42,12 +42,21 @@ The `--save` flag tells NPM to automatically add the package to your
### API
+All the examples assume the following boot strapped code:
+
```js
var Parser = require('memcached-stream');
```
-Adding the parser to a stream, in this example we assume that `stream` is a
-valid TCP connection that already had `setEncoding('utf-8')` applied to it.
+And we assume that the `stream` variable is a valid TCP connection that already
+had `setEncoding('utf-8')` applied to it so it will not terminate UTF-8 chars.
+
+#### Initializing the parser
+
+The parser inherits from the Node.js Stream interface. This allows you to easily
+attach the parser to a connection by using the `Stream#pipe` method which is
+available on every Stream interface in node. The best thing about using the pipe
+method is that it takes care of all the flow control for us.
```js
var parser = new Parser();
@@ -55,6 +64,8 @@ var parser = new Parser();
stream.pipe(parser);
```
+#### Listening for results
+
Now that we attached the parser to the stream we can start receiving responses
from the memcached protocol enabled server:
@@ -68,6 +79,8 @@ parser.on('error:response', function error(err) {
});
```
+#### Unknown server responses
+
In addition to these events, we also have an `error` event that gets emitted
when we receive an unknown response. When this happens the parser is destroyed
immediately as we have no idea in what state our parser is in.
@@ -78,8 +91,11 @@ parser.on('error', function (err) {
});
```
+#### Finishing it up
+
Once you are done with parsing you can terminate it by calling:
+
```js
parser.end();
```
View
42 index.js
@@ -33,6 +33,13 @@ function Parser(options) {
// Parser related properties.
this.queue = ''; // This is where the server responses are parsed from
this.expecting = 0; // How much bytes we expect to receive before parse a result
+
+ // Flags support
+ this.flags = Object.create(null);
+
+ if ('flags' in options) Object.keys(options.flags).forEach(function setFlag(flag) {
+ this.flag(flag, options.flags[flag]);
+ }, this);
}
/**
@@ -81,6 +88,19 @@ Parser.responses = Object.create(null);
});
/**
+ * Register a new VALUE parser when the response has the given integer.
+ *
+ * @param {String|Number} int unsigned 16/32bit integer
+ * @param {Function} parser function that parses the result set
+ */
+Parser.prototype.flag = function flag(int, parser) {
+ if (+int > 4294967295 || +int < 0) throw new Error('Integer must be a 16/32bit unsigned integer');
+ if ('function' !== typeof parser) throw new Error('The parser should be a function');
+
+ this.flags[int.toString()] = parser;
+};
+
+/**
* Receives data from a Stream, we assume that the stream has
* `setEncoding('utf8')` so we are sure that we receive strings as arguments and
* don't have to worry about receiving a half UTF-8 character.
@@ -104,6 +124,7 @@ Parser.prototype.write = function write(data) {
this.expected = 0;
this.parse();
}
+
return true;
};
@@ -115,12 +136,13 @@ Parser.prototype.write = function write(data) {
Parser.prototype.parse = function parse() {
var data = this.queue
, bytesRemaining = Buffer.byteLength(data)
- , charCode // Stores the current cursor position
- , rn // Found a \r\n
- , msg // Stores the response
- , err // Stores the potential error message
- , length; // Stores the total message length which is added to the
- // cursor and subtracted from the bytesRemaining
+ , parsers = this.flags // Custom VALUE parsers
+ , charCode // Stores the current cursor position
+ , rn // Found a \r\n
+ , msg // Stores the response
+ , err // Stores the potential error message
+ , length; // Stores the total message length which is added to the
+ // cursor and subtracted from the bytesRemaining
for (var i = 0, l = data.length; i < l;) {
charCode = data.charCodeAt(i);
@@ -379,12 +401,16 @@ Parser.prototype.parse = function parse() {
// decisions should be made by the client, not the parser.
// @TODO we might not even need to change it to a string, as we are only
// doing this atm so we can add the value's length to the cursor (i).
- value = value.toString();
+ msg = value.toString();
+
+ // Check if we have a custom parser for the given flag
+ if (flags in parsers) value = parsers[flags](msg, value);
+ else value = msg;
this.emit('response', 'VALUE', value, flags, cas, key);
// + value length & closing \r\n
- i += value.length + 2;
+ i += msg.length + 2;
bytesRemaining -= bytes + 2;
} else {
// VERSION:
View
89 test/api.test.js
@@ -8,16 +8,20 @@ describe('memcached-stream', function () {
chai.Assertion.includeStack = true;
var net = require('net')
- , fuzzy = require('./fuzzer')
, stream = require('../index')
, Parser = stream.Parser;
- it('exposes the parser', function () {
+ it('exposes the .Parser', function () {
expect(stream.Parser).to.be.a('function');
});
+ it('exposes a .createStream', function () {
+ expect(stream.createStream).to.be.a('function');
+ expect(stream.createStream()).to.be.instanceOf(Parser);
+ });
+
describe('Parser', function () {
- describe('@constructor', function () {
+ describe('@constructing', function () {
it('constructs without any errors', function () {
var memcached = new Parser();
});
@@ -27,6 +31,33 @@ describe('memcached-stream', function () {
expect(memcached.readable).to.equal(false);
});
+
+ it('iterates over the flags Object', function () {
+ var memcached = new Parser({ flags: {
+ '109': function () {}
+ , '110': function () {}
+ }});
+
+ expect(Object.keys(memcached.flags).length).to.equal(2);
+ });
+ });
+
+ describe("#flag", function () {
+ it('only accepts unsigned integers', function () {
+ var memcached = new Parser();
+
+ expect(memcached.flag.bind(memcached, -1)).to.throw(/unsigned/);
+ expect(memcached.flag.bind(memcached, '-1')).to.throw(/unsigned/);
+ expect(memcached.flag.bind(memcached, 4294967296)).to.throw(/unsigned/);
+ expect(memcached.flag.bind(memcached, 2)).to.not.throw(/unsigned/);
+ });
+
+ it('only only accepts functions as parsers', function () {
+ var memcached = new Parser();
+
+ expect(memcached.flag.bind(memcached, 1, 1)).to.throw(/function/);
+ expect(memcached.flag.bind(memcached, 1, function () {})).not.to.throw(/function/);
+ });
});
describe('#write', function () {
@@ -37,38 +68,46 @@ describe('memcached-stream', function () {
});
});
- describe('#pipe', function () {
- return;
+ describe('[parser internals]', function () {
+ it('parses VALUE flag responses with the a flag parser', function (done) {
+ var memcached = new Parser();
- it('pipes to a net.Connection', function (done) {
- var server = fuzzy.createServer({ 'responses': 100, 'write stdout': false })
- , memcached = new Parser()
- , port = portnumbers
- , responses = 0;
+ memcached.flag(1, function number(value) {
+ return +value;
+ });
- this.timeout(20E3);
+ memcached.on('response', function (command, value) {
+ expect(command).to.equal('VALUE');
+ expect(value).to.be.a('number');
+ expect(value).to.equal(1);
- memcached.on('error', function () {
- ++responses;
+ done();
});
- memcached.on('response', function () {
- ++responses;
- });
+ memcached.write('VALUE f 1 1\r\n1\r\n');
+ });
- server.listen(port, function (err) {
- if (err) return done(err);
+ it('parses VALUE flag response with the correct parser', function (done) {
+ var memcached = new Parser();
- var connection = net.connect(port);
- connection.pipe(memcached);
+ memcached.flag(1, function number(value) {
+ return +value;
+ });
- connection.once('close', function () {
- server.close();
+ memcached.flag(2, function number(value) {
+ return JSON.parse(value);
+ });
- expect(responses).to.equal(100);
- done();
- });
+ memcached.on('response', function (command, value) {
+ expect(command).to.equal('VALUE');
+ expect(value).to.be.a('object');
+ expect(value.foo).to.equal('bar');
+ expect(value.bar).to.equal(121313);
+
+ done();
});
+
+ memcached.write('VALUE f 2 26\r\n{"foo":"bar","bar":121313}\r\n');
});
});
});
Please sign in to comment.
Something went wrong with that request. Please try again.