Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support giving explicit type with '!' #389

Merged
merged 1 commit into from
Nov 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ RABBITMQ_SRC_VERSION=rabbitmq_v3_2_1
JSON=amqp-rabbitmq-0.9.1.json
RABBITMQ_CODEGEN=https://raw.githubusercontent.com/rabbitmq/rabbitmq-codegen
AMQP_JSON=$(RABBITMQ_CODEGEN)/$(RABBITMQ_SRC_VERSION)/$(JSON)
NODEJS_VERSIONS='0.8' '0.9' '0.10' '0.11' '0.12' '1.6' '2.5' '3.3' '4.2' '5.5' '6.2'
NODEJS_VERSIONS='0.8' '0.9' '0.10' '0.11' '0.12' '1.6' '2.5' '3.3' '4.2' '5.5' '6.2' '8.9'

MOCHA=./node_modules/.bin/mocha
_MOCHA=./node_modules/.bin/_mocha
Expand Down
102 changes: 64 additions & 38 deletions lib/codec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//
//
//
//

Expand Down Expand Up @@ -110,6 +110,40 @@ function encodeFieldValue(buffer, value, offset) {
type = value['!'];
}

// If it's a JS number, we'll have to guess what type to encode it
// as.
if (type == 'number') {
// Making assumptions about the kind of number (floating point
// v integer, signed, unsigned, size) desired is dangerous in
// general; however, in practice RabbitMQ uses only
// longstrings and unsigned integers in its arguments, and
// other clients generally conflate number types anyway. So
// the only distinction we care about is floating point vs
// integers, preferring integers since those can be promoted
// if necessary. If floating point is required, we may as well
// use double precision.
if (isFloatingPoint(val)) {
type = 'double';
}
else { // only signed values are used in tables by
// RabbitMQ. It *used* to (< v3.3.0) treat the byte 'b'
// type as unsigned, but most clients (and the spec)
// think it's signed, and now RabbitMQ does too.
if (val < 128 && val >= -128) {
type = 'byte';
}
else if (val >= -0x8000 && val < 0x8000) {
type = 'short'
}
else if (val >= -0x80000000 && val < 0x80000000) {
type = 'int';
}
else {
type = 'long';
}
}
}

function tag(t) { buffer.write(t, offset); offset++; }

switch (type) {
Expand Down Expand Up @@ -141,44 +175,36 @@ function encodeFieldValue(buffer, value, offset) {
tag('t');
buffer.writeUInt8((val) ? 1 : 0, offset); offset++;
break;
case 'number':
// Making assumptions about the kind of number (floating point
// v integer, signed, unsigned, size) desired is dangerous in
// general; however, in practice RabbitMQ uses only
// longstrings and unsigned integers in its arguments, and
// other clients generally conflate number types anyway. So
// the only distinction we care about is floating point vs
// integers, preferring integers since those can be promoted
// if necessary. If floating point is required, we may as well
// use double precision.
if (isFloatingPoint(val)) {
tag('d');
buffer.writeDoubleBE(val, offset);
offset += 8;
}
else { // only signed values are used in tables by
// RabbitMQ. It *used* to (< v3.3.0) treat the byte 'b'
// type as unsigned, but most clients (and the spec)
// think it's signed, and now RabbitMQ does too.
if (val < 128 && val >= -128) {
tag('b');
buffer.writeInt8(val, offset); offset++;
}
else if (val >= -0x8000 && val < 0x8000) { // short
tag('s');
buffer.writeInt16BE(val, offset); offset += 2;
}
else if (val >= -0x80000000 && val < 0x80000000) { // int
tag('I');
buffer.writeInt32BE(val, offset); offset += 4;
}
else { // long
tag('l');
ints.writeInt64BE(buffer, val, offset); offset += 8;
}
}
// These are the types that are either guessed above, or
// explicitly given using the {'!': type} notation.
case 'double':
case 'float64':
tag('d');
buffer.writeDoubleBE(val, offset);
offset += 8;
break;
case 'byte':
case 'int8':
tag('b');
buffer.writeInt8(val, offset); offset++;
break;
case 'short':
case 'int16':
tag('s');
buffer.writeInt16BE(val, offset); offset += 2;
break;
// Now for exotic types, those can only be denoted by using
case 'int':
case 'int32':
tag('I');
buffer.writeInt32BE(val, offset); offset += 4;
break;
case 'long':
case 'int64':
tag('l');
ints.writeInt64BE(buffer, val, offset); offset += 8;
break;

// Now for exotic types, those can _only_ be denoted by using
// `{'!': type, value: val}
case 'timestamp':
tag('T');
Expand Down
47 changes: 43 additions & 4 deletions test/codec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var testCases = [
['float value', {double: 0.5}, [6,100,111,117,98,108,101,100,63,224,0,0,0,0,0,0]],
['negative float value', {double: -0.5}, [6,100,111,117,98,108,101,100,191,224,0,0,0,0,0,0]],
// %% test some boundaries of precision?

// string
['string', {string: "boop"}, [6,115,116,114,105,110,103,83,0,0,0,4,98,111,111,112]],

Expand Down Expand Up @@ -64,7 +64,7 @@ function bufferToArray(b) {
return Array.prototype.slice.call(b);
}

suite("Explicit encodings", function() {
suite("Implicit encodings", function() {

testCases.forEach(function(tc) {
var name = tc[0], val = tc[1], expect = tc[2];
Expand All @@ -86,7 +86,7 @@ function roundtrip_table(t) {
var size = codec.encodeTable(buf, t, 0);
var decoded = codec.decodeFields(buf.slice(4, size)); // ignore the length-prefix
try {
assert.deepEqual(t, decoded);
assert.deepEqual(removeExplicitTypes(t), decoded);
}
catch (e) { return false; }
return true;
Expand Down Expand Up @@ -119,6 +119,45 @@ suite("Roundtrip values", function() {
});
});

// When encoding, you can supply explicitly-typed fields like `{'!':
// int32, 50}`. Most of these do not appear in the decoded values, so
// to compare like-to-like we have to remove them from the input.
function removeExplicitTypes(input) {
switch (typeof input) {
case 'object':
if (input == null) {
return null;
}
if (Array.isArray(input)) {
var newArr = [];
for (var i = 0; i < input.length; i++) {
newArr[i] = removeExplicitTypes(input[i]);
}
return newArr;
}
if (Buffer.isBuffer(input)) {
return input;
}
switch (input['!']) {
case 'timestamp':
case 'decimal':
case 'float':
return input;
case undefined:
var newObj = {}
for (var k in input) {
newObj[k] = removeExplicitTypes(input[k]);
}
return newObj;
default:
return input.value;
}

default:
return input;
}
}

// Asserts that the decoded fields are equal to the original fields,
// or equal to a default where absent in the original. The defaults
// depend on the type of method or properties.
Expand All @@ -145,7 +184,7 @@ function assertEqualModuloDefaults(original, decodedFields) {
decodedValue);
}
else {
assert.deepEqual(originalValue, decodedValue);
assert.deepEqual(removeExplicitTypes(originalValue), decodedValue);
}
}
catch (assertionErr) {
Expand Down
35 changes: 29 additions & 6 deletions test/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var Undefined = C.Undefined;

// Stub these out so we can use outside tests
// if (!suite) var suite = function() {}
// if (!test) var test = function() {}
// if (!test) var test = function() {}

// These aren't exported in claire/index. so I could have to reproduce
// them I guess.
Expand Down Expand Up @@ -53,6 +53,12 @@ function floatChooser(maxExp) {
}
}

function explicitType(t, underlying) {
return label(t, transform(function(n) {
return {'!': t, value: n};
}, underlying));
}

// FIXME null, byte array, others?

var Octet = rangeInt('octet', 0, 255);
Expand Down Expand Up @@ -85,12 +91,27 @@ var Decimal = label('decimal', transform(
return {'!': 'decimal', value: {places: args[1], digits: args[0]}};
}, sequence(arb.UInt, Octet)));

// Signed 8 bit int
var Byte = rangeInt('byte', -128, 127);

// Explicitly typed values
var ExByte = explicitType('byte', Byte);
var ExInt8 = explicitType('int8', Byte);
var ExShort = explicitType('short', Short);
var ExInt16 = explicitType('int16', Short);
var ExInt = explicitType('int', Long);
var ExInt32 = explicitType('int32', Long);
var ExLong = explicitType('long', LongLong);
var ExInt64 = explicitType('int64', LongLong);

var FieldArray = label('field-array', recursive(function() {
return arb.Array(
arb.Null,
LongStr, ShortStr, Octet,
UShort, ULong, ULongLong,
Short, Long, LongLong,
LongStr, ShortStr,
Octet, UShort, ULong, ULongLong,
Byte, Short, Long, LongLong,
ExByte, ExInt8, ExShort, ExInt16,
ExInt, ExInt32, ExLong, ExInt64,
Bit, Float, Double, FieldTable, FieldArray)
}));

Expand All @@ -100,7 +121,9 @@ var FieldTable = label('table', recursive(function() {
arb.Null,
LongStr, ShortStr, Octet,
UShort, ULong, ULongLong,
Short, Long, LongLong,
Byte, Short, Long, LongLong,
ExByte, ExInt8, ExShort, ExInt16,
ExInt, ExInt32, ExLong, ExInt64,
Bit, Float, Double, FieldArray, FieldTable))
}));

Expand Down Expand Up @@ -184,7 +207,7 @@ function method(info) {
function properties(info) {
var types = info.args.map(argtype);
types.unshift(ULongLong); // size
var domain = sequence.apply(null, types);
var domain = sequence.apply(null, types);
var names = info.args.map(name);
return label(info.name, transform(function(fieldVals) {
return {id: info.id,
Expand Down