Skip to content
Snippets Groups Projects
Commit 1fd052f4 authored by Jan Tattermusch's avatar Jan Tattermusch
Browse files

Merge pull request #5288 from murgatroid99/node_proto_options

Add options to Node library to modify ProtoBuf behavior
parents e404de63 5acbb9c1
No related branches found
No related tags found
No related merge requests found
...@@ -56,17 +56,18 @@ var grpc = require('./src/grpc_extension'); ...@@ -56,17 +56,18 @@ var grpc = require('./src/grpc_extension');
/** /**
* Load a gRPC object from an existing ProtoBuf.Reflect object. * Load a gRPC object from an existing ProtoBuf.Reflect object.
* @param {ProtoBuf.Reflect.Namespace} value The ProtoBuf object to load. * @param {ProtoBuf.Reflect.Namespace} value The ProtoBuf object to load.
* @param {Object=} options Options to apply to the loaded object
* @return {Object<string, *>} The resulting gRPC object * @return {Object<string, *>} The resulting gRPC object
*/ */
exports.loadObject = function loadObject(value) { exports.loadObject = function loadObject(value, options) {
var result = {}; var result = {};
if (value.className === 'Namespace') { if (value.className === 'Namespace') {
_.each(value.children, function(child) { _.each(value.children, function(child) {
result[child.name] = loadObject(child); result[child.name] = loadObject(child, options);
}); });
return result; return result;
} else if (value.className === 'Service') { } else if (value.className === 'Service') {
return client.makeProtobufClientConstructor(value); return client.makeProtobufClientConstructor(value, options);
} else if (value.className === 'Message' || value.className === 'Enum') { } else if (value.className === 'Message' || value.className === 'Enum') {
return value.build(); return value.build();
} else { } else {
...@@ -77,28 +78,45 @@ exports.loadObject = function loadObject(value) { ...@@ -77,28 +78,45 @@ exports.loadObject = function loadObject(value) {
var loadObject = exports.loadObject; var loadObject = exports.loadObject;
/** /**
* Load a gRPC object from a .proto file. * Load a gRPC object from a .proto file. The options object can provide the
* @param {string} filename The file to load * following options:
* - convertFieldsToCamelCase: Loads this file with that option on protobuf.js
* set as specified. See
* https://github.com/dcodeIO/protobuf.js/wiki/Advanced-options for details
* - binaryAsBase64: deserialize bytes values as base64 strings instead of
* Buffers. Defaults to false
* - longsAsStrings: deserialize long values as strings instead of objects.
* Defaults to true
* @param {string|{root: string, file: string}} filename The file to load
* @param {string=} format The file format to expect. Must be either 'proto' or * @param {string=} format The file format to expect. Must be either 'proto' or
* 'json'. Defaults to 'proto' * 'json'. Defaults to 'proto'
* @param {Object=} options Options to apply to the loaded file
* @return {Object<string, *>} The resulting gRPC object * @return {Object<string, *>} The resulting gRPC object
*/ */
exports.load = function load(filename, format) { exports.load = function load(filename, format, options) {
if (!format) { if (!format) {
format = 'proto'; format = 'proto';
} }
var convertFieldsToCamelCaseOriginal = ProtoBuf.convertFieldsToCamelCase;
if(options && options.hasOwnProperty('convertFieldsToCamelCase')) {
ProtoBuf.convertFieldsToCamelCase = options.convertFieldsToCamelCase;
}
var builder; var builder;
switch(format) { try {
case 'proto': switch(format) {
builder = ProtoBuf.loadProtoFile(filename); case 'proto':
break; builder = ProtoBuf.loadProtoFile(filename);
case 'json': break;
builder = ProtoBuf.loadJsonFile(filename); case 'json':
break; builder = ProtoBuf.loadJsonFile(filename);
default: break;
throw new Error('Unrecognized format "' + format + '"'); default:
throw new Error('Unrecognized format "' + format + '"');
}
} finally {
ProtoBuf.convertFieldsToCamelCase = convertFieldsToCamelCaseOriginal;
} }
return loadObject(builder.ns); return loadObject(builder.ns, options);
}; };
/** /**
......
...@@ -698,13 +698,16 @@ exports.waitForClientReady = function(client, deadline, callback) { ...@@ -698,13 +698,16 @@ exports.waitForClientReady = function(client, deadline, callback) {
* Creates a constructor for clients for the given service * Creates a constructor for clients for the given service
* @param {ProtoBuf.Reflect.Service} service The service to generate a client * @param {ProtoBuf.Reflect.Service} service The service to generate a client
* for * for
* @param {Object=} options Options to apply to the client
* @return {function(string, Object)} New client constructor * @return {function(string, Object)} New client constructor
*/ */
exports.makeProtobufClientConstructor = function(service) { exports.makeProtobufClientConstructor = function(service, options) {
var method_attrs = common.getProtobufServiceAttrs(service, service.name); var method_attrs = common.getProtobufServiceAttrs(service, service.name,
options);
var Client = exports.makeClientConstructor( var Client = exports.makeClientConstructor(
method_attrs, common.fullyQualifiedName(service)); method_attrs, common.fullyQualifiedName(service));
Client.service = service; Client.service = service;
Client.service.grpc_options = options;
return Client; return Client;
}; };
......
...@@ -44,9 +44,20 @@ var _ = require('lodash'); ...@@ -44,9 +44,20 @@ var _ = require('lodash');
/** /**
* Get a function that deserializes a specific type of protobuf. * Get a function that deserializes a specific type of protobuf.
* @param {function()} cls The constructor of the message type to deserialize * @param {function()} cls The constructor of the message type to deserialize
* @param {bool=} binaryAsBase64 Deserialize bytes fields as base64 strings
* instead of Buffers. Defaults to false
* @param {bool=} longsAsStrings Deserialize long values as strings instead of
* objects. Defaults to true
* @return {function(Buffer):cls} The deserialization function * @return {function(Buffer):cls} The deserialization function
*/ */
exports.deserializeCls = function deserializeCls(cls) { exports.deserializeCls = function deserializeCls(cls, binaryAsBase64,
longsAsStrings) {
if (binaryAsBase64 === undefined || binaryAsBase64 === null) {
binaryAsBase64 = false;
}
if (longsAsStrings === undefined || longsAsStrings === null) {
longsAsStrings = true;
}
/** /**
* Deserialize a buffer to a message object * Deserialize a buffer to a message object
* @param {Buffer} arg_buf The buffer to deserialize * @param {Buffer} arg_buf The buffer to deserialize
...@@ -55,7 +66,7 @@ exports.deserializeCls = function deserializeCls(cls) { ...@@ -55,7 +66,7 @@ exports.deserializeCls = function deserializeCls(cls) {
return function deserialize(arg_buf) { return function deserialize(arg_buf) {
// Convert to a native object with binary fields as Buffers (first argument) // Convert to a native object with binary fields as Buffers (first argument)
// and longs as strings (second argument) // and longs as strings (second argument)
return cls.decode(arg_buf).toRaw(false, true); return cls.decode(arg_buf).toRaw(binaryAsBase64, longsAsStrings);
}; };
}; };
...@@ -119,19 +130,28 @@ exports.wrapIgnoreNull = function wrapIgnoreNull(func) { ...@@ -119,19 +130,28 @@ exports.wrapIgnoreNull = function wrapIgnoreNull(func) {
/** /**
* Return a map from method names to method attributes for the service. * Return a map from method names to method attributes for the service.
* @param {ProtoBuf.Reflect.Service} service The service to get attributes for * @param {ProtoBuf.Reflect.Service} service The service to get attributes for
* @param {Object=} options Options to apply to these attributes
* @return {Object} The attributes map * @return {Object} The attributes map
*/ */
exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service) { exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service,
options) {
var prefix = '/' + fullyQualifiedName(service) + '/'; var prefix = '/' + fullyQualifiedName(service) + '/';
var binaryAsBase64, longsAsStrings;
if (options) {
binaryAsBase64 = options.binaryAsBase64;
longsAsStrings = options.longsAsStrings;
}
return _.object(_.map(service.children, function(method) { return _.object(_.map(service.children, function(method) {
return [_.camelCase(method.name), { return [_.camelCase(method.name), {
path: prefix + method.name, path: prefix + method.name,
requestStream: method.requestStream, requestStream: method.requestStream,
responseStream: method.responseStream, responseStream: method.responseStream,
requestSerialize: serializeCls(method.resolvedRequestType.build()), requestSerialize: serializeCls(method.resolvedRequestType.build()),
requestDeserialize: deserializeCls(method.resolvedRequestType.build()), requestDeserialize: deserializeCls(method.resolvedRequestType.build(),
binaryAsBase64, longsAsStrings),
responseSerialize: serializeCls(method.resolvedResponseType.build()), responseSerialize: serializeCls(method.resolvedResponseType.build()),
responseDeserialize: deserializeCls(method.resolvedResponseType.build()) responseDeserialize: deserializeCls(method.resolvedResponseType.build(),
binaryAsBase64, longsAsStrings)
}]; }];
})); }));
}; };
...@@ -737,7 +737,12 @@ Server.prototype.addService = function(service, implementation) { ...@@ -737,7 +737,12 @@ Server.prototype.addService = function(service, implementation) {
* method implementation for the provided service. * method implementation for the provided service.
*/ */
Server.prototype.addProtoService = function(service, implementation) { Server.prototype.addProtoService = function(service, implementation) {
this.addService(common.getProtobufServiceAttrs(service), implementation); var options;
if (service.grpc_options) {
options = service.grpc_options;
}
this.addService(common.getProtobufServiceAttrs(service, options),
implementation);
}; };
/** /**
......
/* /*
* *
* Copyright 2015, Google Inc. * Copyright 2015-2016, Google Inc.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
...@@ -42,7 +42,7 @@ var ProtoBuf = require('protobufjs'); ...@@ -42,7 +42,7 @@ var ProtoBuf = require('protobufjs');
var messages_proto = ProtoBuf.loadProtoFile( var messages_proto = ProtoBuf.loadProtoFile(
__dirname + '/test_messages.proto').build(); __dirname + '/test_messages.proto').build();
describe('Proto message serialize and deserialize', function() { describe('Proto message long int serialize and deserialize', function() {
var longSerialize = common.serializeCls(messages_proto.LongValues); var longSerialize = common.serializeCls(messages_proto.LongValues);
var longDeserialize = common.deserializeCls(messages_proto.LongValues); var longDeserialize = common.deserializeCls(messages_proto.LongValues);
var pos_value = '314159265358979'; var pos_value = '314159265358979';
...@@ -87,4 +87,52 @@ describe('Proto message serialize and deserialize', function() { ...@@ -87,4 +87,52 @@ describe('Proto message serialize and deserialize', function() {
assert.strictEqual(longDeserialize(serialized).sfixed_64.toString(), assert.strictEqual(longDeserialize(serialized).sfixed_64.toString(),
neg_value); neg_value);
}); });
it('should deserialize as a number with the right option set', function() {
var longNumDeserialize = common.deserializeCls(messages_proto.LongValues,
false, false);
var serialized = longSerialize({int_64: pos_value});
assert.strictEqual(typeof longDeserialize(serialized).int_64, 'string');
/* With the longsAsStrings option disabled, long values are represented as
* objects with 3 keys: low, high, and unsigned */
assert.strictEqual(typeof longNumDeserialize(serialized).int_64, 'object');
});
});
describe('Proto message bytes serialize and deserialize', function() {
var sequenceSerialize = common.serializeCls(messages_proto.SequenceValues);
var sequenceDeserialize = common.deserializeCls(
messages_proto.SequenceValues);
var sequenceBase64Deserialize = common.deserializeCls(
messages_proto.SequenceValues, true);
var buffer_val = new Buffer([0x69, 0xb7]);
var base64_val = 'abc=';
it('should preserve a buffer', function() {
var serialized = sequenceSerialize({bytes_field: buffer_val});
var deserialized = sequenceDeserialize(serialized);
assert.strictEqual(deserialized.bytes_field.compare(buffer_val), 0);
});
it('should accept base64 encoded strings', function() {
var serialized = sequenceSerialize({bytes_field: base64_val});
var deserialized = sequenceDeserialize(serialized);
assert.strictEqual(deserialized.bytes_field.compare(buffer_val), 0);
});
it('should output base64 encoded strings with an option set', function() {
var serialized = sequenceSerialize({bytes_field: base64_val});
var deserialized = sequenceBase64Deserialize(serialized);
assert.strictEqual(deserialized.bytes_field, base64_val);
});
/* The next two tests are specific tests to verify that issue
* https://github.com/grpc/grpc/issues/5174 has been fixed. They are skipped
* because they will not pass until a protobuf.js release has been published
* with a fix for https://github.com/dcodeIO/protobuf.js/issues/390 */
it.skip('should serialize a repeated field as packed by default', function() {
var expected_serialize = new Buffer([0x12, 0x01, 0x01, 0x0a]);
var serialized = sequenceSerialize({repeated_field: [10]});
assert.strictEqual(expected_serialize.compare(serialized), 0);
});
it.skip('should deserialize packed or unpacked repeated', function() {
var serialized = new Buffer([0x12, 0x01, 0x01, 0x0a]);
assert.doesNotThrow(function() {
sequenceDeserialize(serialized);
});
});
}); });
...@@ -36,3 +36,8 @@ message LongValues { ...@@ -36,3 +36,8 @@ message LongValues {
fixed64 fixed_64 = 4; fixed64 fixed_64 = 4;
sfixed64 sfixed_64 = 5; sfixed64 sfixed_64 = 5;
} }
message SequenceValues {
bytes bytes_field = 1;
repeated int32 repeated_field = 2;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment