amqplib
Version:
An AMQP 0-9-1 (e.g., RabbitMQ) library and client.
250 lines (225 loc) • 6.05 kB
JavaScript
const { data: arb, transform, repeat, label, sequence, asGenerator, sized, recursive, choice, Undefined } = require('claire');
const defs = require('../../lib/defs');
// These aren't exported in claire/index. so I could have to reproduce them I guess.
function choose(a, b) {
return Math.random() * (b - a) + a;
}
function chooseInt(a, b) {
return Math.floor(choose(a, b));
}
function rangeInt(name, a, b) {
return label(name, asGenerator((_) => chooseInt(a, b)));
}
function toFloat32(i) {
const b = Buffer.alloc(4);
b.writeFloatBE(i, 0);
return b.readFloatBE(0);
}
function floatChooser(maxExp) {
return () => {
let n = Number.NaN;
while (Number.isNaN(n)) {
const mantissa = Math.random() * 2 - 1;
const exponent = chooseInt(0, maxExp);
n = mantissa ** exponent;
}
return n;
};
}
function explicitType(t, underlying) {
return label(
t,
transform((n) => ({ '!': t, value: n }), underlying),
);
}
// FIXME null, byte array, others?
const Octet = rangeInt('octet', 0, 255);
const ShortStr = label('shortstr', transform((s) => s.substr(0, 255), arb.Str));
const LongStr = label('longstr', transform((bytes) => Buffer.from(bytes), repeat(Octet)));
const UShort = rangeInt('short-uint', 0, 0xffff);
const ULong = rangeInt('long-uint', 0, 0xffffffff);
const ULongLong = rangeInt('longlong-uint', 0, Number.MAX_SAFE_INTEGER);
const Short = rangeInt('short-int', -0x8000, 0x7fff);
const Long = rangeInt('long-int', -0x80000000, 0x7fffffff);
const LongLong = rangeInt('longlong-int', Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
const Bit = label('bit', arb.Bool);
const Double = label('double', asGenerator(floatChooser(308)));
const Float = label('float', transform(toFloat32, floatChooser(38)));
const Timestamp = label('timestamp', transform((n) => ({ '!': 'timestamp', value: n }), ULongLong));
const Decimal = label('decimal', transform((args) => ({ '!': 'decimal', value: { places: args[1], digits: args[0] } }), sequence(arb.UInt, Octet)));
const UnsignedByte = label('unsignedbyte', transform((n) => ({ '!': 'unsignedbyte', value: n }), Octet));
const UnsignedShort = label('unsignedshort', transform((n) => ({ '!': 'unsignedshort', value: n }), UShort));
const UnsignedInt = label('unsignedint', transform((n) => ({ '!': 'unsignedint', value: n }), ULong));
const Byte = rangeInt('byte', -128, 127);
// Explicitly typed values
const ExByte = explicitType('byte', Byte);
const ExInt8 = explicitType('int8', Byte);
const ExShort = explicitType('short', Short);
const ExInt16 = explicitType('int16', Short);
const ExInt = explicitType('int', Long);
const ExInt32 = explicitType('int32', Long);
const ExLong = explicitType('long', LongLong);
const ExInt64 = explicitType('int64', LongLong);
const FieldArray = label('field-array',
recursive(() =>
arb.Array(
arb.Null,
LongStr,
ShortStr,
Octet,
UShort,
ULong,
ULongLong,
Byte,
Short,
Long,
LongLong,
ExByte,
ExInt8,
ExShort,
ExInt16,
ExInt,
ExInt32,
ExLong,
ExInt64,
Bit,
Float,
Double,
FieldTable,
FieldArray,
),
),
);
const FieldTable = label('field-table',
recursive(() =>
sized(
() => 5,
arb.Object(
arb.Null,
LongStr,
ShortStr,
Octet,
UShort,
ULong,
ULongLong,
Byte,
Short,
Long,
LongLong,
ExByte,
ExInt8,
ExShort,
ExInt16,
ExInt,
ExInt32,
ExLong,
ExInt64,
Bit,
Float,
Double,
FieldArray,
FieldTable,
),
),
),
);
// For methods and properties (as opposed to field table values) it's
// easier just to accept and produce numbers for timestamps.
const ArgTimestamp = label('timestamp', ULongLong);
// These are the domains used in method arguments
const ARG_TYPES = {
octet: Octet,
shortstr: ShortStr,
longstr: LongStr,
short: UShort,
long: ULong,
longlong: ULongLong,
bit: Bit,
table: FieldTable,
timestamp: ArgTimestamp,
};
function argtype(thing) {
if (thing.default === undefined) {
return ARG_TYPES[thing.type];
} else {
return choice(ARG_TYPES[thing.type], Undefined);
}
}
function zipObject(vals, names) {
const obj = {};
vals.forEach((v, i) => {
obj[names[i]] = v;
});
return obj;
}
function name(arg) {
return arg.name;
}
function method(info) {
const domain = sequence.apply(null, info.args.map(argtype));
const names = info.args.map(name);
return label(
info.name,
transform((fieldVals) => ({ id: info.id, fields: zipObject(fieldVals, names) }), domain),
);
}
function properties(info) {
const types = info.args.map(argtype);
types.unshift(ULongLong); // size
const domain = sequence.apply(null, types);
const names = info.args.map(name);
return label(info.name,
transform((fieldVals) => ({ id: info.id, size: fieldVals[0], fields: zipObject(fieldVals.slice(1), names) }), domain),
);
}
const methods = [];
const propertieses = [];
for (const k in defs) {
if (k.substr(0, 10) === 'methodInfo') {
methods.push(method(defs[k]));
methods[defs[k].name] = method(defs[k]);
} else if (k.substr(0, 14) === 'propertiesInfo') {
propertieses.push(properties(defs[k]));
propertieses[defs[k].name] = properties(defs[k]);
}
}
const OPEN_OPTS = {
// start-ok
clientProperties: {},
mechanism: 'PLAIN',
response: Buffer.from(['', 'guest', 'guest'].join(String.fromCharCode(0))),
locale: 'en_US',
// tune-ok
channelMax: 0,
frameMax: 0,
heartbeat: 0,
// open
virtualHost: '/',
capabilities: '',
insist: 0,
};
module.exports = {
Octet,
ShortStr,
LongStr,
UShort,
ULong,
ULongLong,
Short,
Long,
LongLong,
Bit,
Double,
Float,
Timestamp,
Decimal,
UnsignedByte,
UnsignedShort,
UnsignedInt,
FieldArray,
FieldTable,
methods,
properties: propertieses,
rangeInt,
OPEN_OPTS,
};