diff options
author | Sree Kuchibhotla <sreek@google.com> | 2017-04-07 13:44:34 -0700 |
---|---|---|
committer | Sree Kuchibhotla <sreek@google.com> | 2017-04-07 13:44:34 -0700 |
commit | 7b9f97bfb88359d070f66c7f2cb132622046af5b (patch) | |
tree | 41fe278c1cb28a58bf417b3f645626118bd9cc2a /src/node | |
parent | e1bfff0bbedd5c0b973e5a08839a957183e006de (diff) | |
parent | dd550c7316069973c8729bfeca23d4f9a60fd2ca (diff) |
Merge branch 'master' into cq_create_api_changes
Diffstat (limited to 'src/node')
-rw-r--r-- | src/node/index.js | 115 | ||||
-rw-r--r-- | src/node/interop/interop_server.js | 2 | ||||
-rw-r--r-- | src/node/performance/benchmark_server.js | 2 | ||||
-rw-r--r-- | src/node/performance/worker.js | 4 | ||||
-rw-r--r-- | src/node/src/client.js | 22 | ||||
-rw-r--r-- | src/node/src/common.js | 117 | ||||
-rw-r--r-- | src/node/src/protobuf_js_5_common.js | 181 | ||||
-rw-r--r-- | src/node/src/protobuf_js_6_common.js | 170 | ||||
-rw-r--r-- | src/node/src/server.js | 22 | ||||
-rw-r--r-- | src/node/stress/metrics_server.js | 2 | ||||
-rw-r--r-- | src/node/test/common_test.js | 107 | ||||
-rw-r--r-- | src/node/test/credentials_test.js | 2 | ||||
-rw-r--r-- | src/node/test/surface_test.js | 115 | ||||
-rw-r--r-- | src/node/test/test_messages.proto | 17 |
14 files changed, 641 insertions, 237 deletions
diff --git a/src/node/index.js b/src/node/index.js index a294aad8ee..071bfd7927 100644 --- a/src/node/index.js +++ b/src/node/index.js @@ -52,42 +52,89 @@ var Metadata = require('./src/metadata.js'); var grpc = require('./src/grpc_extension'); +var protobuf_js_5_common = require('./src/protobuf_js_5_common'); +var protobuf_js_6_common = require('./src/protobuf_js_6_common'); + grpc.setDefaultRootsPem(fs.readFileSync(SSL_ROOTS_PATH, 'ascii')); /** - * Load a gRPC object from an existing ProtoBuf.Reflect object. - * @param {ProtoBuf.Reflect.Namespace} value The ProtoBuf object to load. - * @param {Object=} options Options to apply to the loaded object + * Load a ProtoBuf.js object as a gRPC object. The options object can provide + * the following options: + * - 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 + * - enumsAsStrings: deserialize enum values as strings instead of numbers. + * Defaults to true + * - deprecatedArgumentOrder: Use the beta method argument order for client + * methods, with optional arguments after the callback. Defaults to false. + * This option is only a temporary stopgap measure to smooth an API breakage. + * It is deprecated, and new code should not use it. + * - protobufjsVersion: Available values are 5, 6, and 'detect'. 5 and 6 + * respectively indicate that an object from the corresponding version of + * ProtoBuf.js is provided in the value argument. If the option is 'detect', + * gRPC will guess what the version is based on the structure of the value. + * Defaults to 'detect'. + * @param {Object} value The ProtoBuf.js reflection object to load + * @param {Object=} options Options to apply to the loaded file * @return {Object<string, *>} The resulting gRPC object */ exports.loadObject = function loadObject(value, options) { - var result = {}; - if (value.className === 'Namespace') { - _.each(value.children, function(child) { - result[child.name] = loadObject(child, options); - }); - return result; - } else if (value.className === 'Service') { - return client.makeProtobufClientConstructor(value, options); - } else if (value.className === 'Message' || value.className === 'Enum') { - return value.build(); + options = _.defaults(options, common.defaultGrpcOptions); + options = _.defaults(options, {'protobufjsVersion': 'detect'}); + var protobufjsVersion; + if (options.protobufjsVersion === 'detect') { + if (protobuf_js_6_common.isProbablyProtobufJs6(value)) { + protobufjsVersion = 6; + } else if (protobuf_js_5_common.isProbablyProtobufJs5(value)) { + protobufjsVersion = 5; + } else { + var error_message = 'Could not detect ProtoBuf.js version. Please ' + + 'specify the version number with the "protobufjs_version" option'; + throw new Error(error_message); + } } else { - return value; + protobufjsVersion = options.protobufjsVersion; + } + switch (protobufjsVersion) { + case 6: return protobuf_js_6_common.loadObject(value, options); + case 5: + var deprecation_message = 'Calling grpc.loadObject with an object ' + + 'generated by ProtoBuf.js 5 is deprecated. Please upgrade to ' + + 'ProtoBuf.js 6.'; + common.log(grpc.logVerbosity.INFO, deprecation_message); + return protobuf_js_5_common.loadObject(value, options); + default: + throw new Error('Unrecognized protobufjsVersion', protobufjsVersion); } }; var loadObject = exports.loadObject; +function applyProtoRoot(filename, root) { + if (_.isString(filename)) { + return filename; + } + filename.root = path.resolve(filename.root) + '/'; + root.resolvePath = function(originPath, importPath, alreadyNormalized) { + return ProtoBuf.util.path.resolve(filename.root, + importPath, + alreadyNormalized); + }; + return filename.file; +} + /** * Load a gRPC object from a .proto file. The options object can provide the * 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 + * - convertFieldsToCamelCase: Load this file with field names in camel case + * instead of their original case * - 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 + * - enumsAsStrings: deserialize enum values as strings instead of numbers. + * Defaults to true * - deprecatedArgumentOrder: Use the beta method argument order for client * methods, with optional arguments after the callback. Defaults to false. * This option is only a temporary stopgap measure to smooth an API breakage. @@ -99,29 +146,17 @@ var loadObject = exports.loadObject; * @return {Object<string, *>} The resulting gRPC object */ exports.load = function load(filename, format, options) { - if (!format) { - format = 'proto'; - } - var convertFieldsToCamelCaseOriginal = ProtoBuf.convertFieldsToCamelCase; - if(options && options.hasOwnProperty('convertFieldsToCamelCase')) { - ProtoBuf.convertFieldsToCamelCase = options.convertFieldsToCamelCase; - } - var builder; - try { - switch(format) { - case 'proto': - builder = ProtoBuf.loadProtoFile(filename); - break; - case 'json': - builder = ProtoBuf.loadJsonFile(filename); - break; - default: - throw new Error('Unrecognized format "' + format + '"'); - } - } finally { - ProtoBuf.convertFieldsToCamelCase = convertFieldsToCamelCaseOriginal; - } - return loadObject(builder.ns, options); + /* Note: format is currently unused, because the API for loading a proto + file or a JSON file is identical in Protobuf.js 6. In the future, there is + still the possibility of adding other formats that would be loaded + differently */ + options = _.defaults(options, common.defaultGrpcOptions); + options.protobufjs_version = 6; + var root = new ProtoBuf.Root(); + var parse_options = {keepCase: !options.convertFieldsToCamelCase}; + return loadObject(root.loadSync(applyProtoRoot(filename, root), + parse_options), + options); }; var log_template = _.template( diff --git a/src/node/interop/interop_server.js b/src/node/interop/interop_server.js index 05f52a1083..83b8a7c1ec 100644 --- a/src/node/interop/interop_server.js +++ b/src/node/interop/interop_server.js @@ -228,7 +228,7 @@ function getServer(port, tls) { server_creds = grpc.ServerCredentials.createInsecure(); } var server = new grpc.Server(options); - server.addProtoService(testProto.TestService.service, { + server.addService(testProto.TestService.service, { emptyCall: handleEmpty, unaryCall: handleUnary, streamingOutputCall: handleStreamingOutput, diff --git a/src/node/performance/benchmark_server.js b/src/node/performance/benchmark_server.js index 7158af775a..ea85029d98 100644 --- a/src/node/performance/benchmark_server.js +++ b/src/node/performance/benchmark_server.js @@ -140,7 +140,7 @@ function BenchmarkServer(host, port, tls, generic, response_size) { streamingCall: makeStreamingGenericCall(response_size) }); } else { - server.addProtoService(serviceProto.BenchmarkService.service, { + server.addService(serviceProto.BenchmarkService.service, { unaryCall: unaryCall, streamingCall: streamingCall }); diff --git a/src/node/performance/worker.js b/src/node/performance/worker.js index 030bf7d7ba..90a9b7d59c 100644 --- a/src/node/performance/worker.js +++ b/src/node/performance/worker.js @@ -44,8 +44,8 @@ var serviceProto = grpc.load({ function runServer(port, benchmark_impl) { var server_creds = grpc.ServerCredentials.createInsecure(); var server = new grpc.Server(); - server.addProtoService(serviceProto.WorkerService.service, - new WorkerServiceImpl(benchmark_impl, server)); + server.addService(serviceProto.WorkerService.service, + new WorkerServiceImpl(benchmark_impl, server)); var address = '0.0.0.0:' + port; server.bind(address, server_creds); server.start(); diff --git a/src/node/src/client.js b/src/node/src/client.js index 44081a3a6c..1aaf35c16c 100644 --- a/src/node/src/client.js +++ b/src/node/src/client.js @@ -780,6 +780,8 @@ exports.makeClientConstructor = function(methods, serviceName, _.assign(Client.prototype[name], attrs); }); + Client.service = methods; + return Client; }; @@ -823,26 +825,6 @@ exports.waitForClientReady = function(client, deadline, callback) { }; /** - * Creates a constructor for clients for the given service - * @param {ProtoBuf.Reflect.Service} service The service to generate a client - * for - * @param {Object=} options Options to apply to the client - * @return {function(string, Object)} New client constructor - */ -exports.makeProtobufClientConstructor = function(service, options) { - var method_attrs = common.getProtobufServiceAttrs(service, options); - if (!options) { - options = {deprecatedArgumentOrder: false}; - } - var Client = exports.makeClientConstructor( - method_attrs, common.fullyQualifiedName(service), - options); - Client.service = service; - Client.service.grpc_options = options; - return Client; -}; - -/** * Map of status code names to status codes */ exports.status = grpc.status; diff --git a/src/node/src/common.js b/src/node/src/common.js index a0fe4480ea..757969dbdd 100644 --- a/src/node/src/common.js +++ b/src/node/src/common.js @@ -42,74 +42,6 @@ var _ = require('lodash'); /** - * Get a function that deserializes a specific type of protobuf. - * @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 - */ -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 - * @param {Buffer} arg_buf The buffer to deserialize - * @return {cls} The resulting object - */ - return function deserialize(arg_buf) { - // Convert to a native object with binary fields as Buffers (first argument) - // and longs as strings (second argument) - return cls.decode(arg_buf).toRaw(binaryAsBase64, longsAsStrings); - }; -}; - -var deserializeCls = exports.deserializeCls; - -/** - * Get a function that serializes objects to a buffer by protobuf class. - * @param {function()} Cls The constructor of the message type to serialize - * @return {function(Cls):Buffer} The serialization function - */ -exports.serializeCls = function serializeCls(Cls) { - /** - * Serialize an object to a Buffer - * @param {Object} arg The object to serialize - * @return {Buffer} The serialized object - */ - return function serialize(arg) { - return new Buffer(new Cls(arg).encode().toBuffer()); - }; -}; - -var serializeCls = exports.serializeCls; - -/** - * Get the fully qualified (dotted) name of a ProtoBuf.Reflect value. - * @param {ProtoBuf.Reflect.Namespace} value The value to get the name of - * @return {string} The fully qualified name of the value - */ -exports.fullyQualifiedName = function fullyQualifiedName(value) { - if (value === null || value === undefined) { - return ''; - } - var name = value.name; - var parent_name = fullyQualifiedName(value.parent); - if (parent_name !== '') { - name = parent_name + '.' + name; - } - return name; -}; - -var fullyQualifiedName = exports.fullyQualifiedName; - -/** * Wrap a function to pass null-like values through without calling it. If no * function is given, just uses the identity; * @param {?function} func The function to wrap @@ -128,44 +60,6 @@ exports.wrapIgnoreNull = function wrapIgnoreNull(func) { }; /** - * Return a map from method names to method attributes for the service. - * @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 - */ -exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service, - options) { - var prefix = '/' + fullyQualifiedName(service) + '/'; - var binaryAsBase64, longsAsStrings; - if (options) { - binaryAsBase64 = options.binaryAsBase64; - longsAsStrings = options.longsAsStrings; - } - /* This slightly awkward construction is used to make sure we only use - lodash@3.10.1-compatible functions. A previous version used - _.fromPairs, which would be cleaner, but was introduced in lodash - version 4 */ - return _.zipObject(_.map(service.children, function(method) { - return _.camelCase(method.name); - }), _.map(service.children, function(method) { - return { - originalName: method.name, - path: prefix + method.name, - requestStream: method.requestStream, - responseStream: method.responseStream, - requestType: method.resolvedRequestType, - responseType: method.resolvedResponseType, - requestSerialize: serializeCls(method.resolvedRequestType.build()), - requestDeserialize: deserializeCls(method.resolvedRequestType.build(), - binaryAsBase64, longsAsStrings), - responseSerialize: serializeCls(method.resolvedResponseType.build()), - responseDeserialize: deserializeCls(method.resolvedResponseType.build(), - binaryAsBase64, longsAsStrings) - }; - })); -}; - -/** * The logger object for the gRPC module. Defaults to console. */ exports.logger = console; @@ -185,3 +79,14 @@ exports.log = function log(severity, message) { exports.logger.error(message); } }; + +/** + * Default options for loading proto files into gRPC + */ +exports.defaultGrpcOptions = { + convertFieldsToCamelCase: false, + binaryAsBase64: false, + longsAsStrings: true, + enumsAsStrings: true, + deprecatedArgumentOrder: false +}; diff --git a/src/node/src/protobuf_js_5_common.js b/src/node/src/protobuf_js_5_common.js new file mode 100644 index 0000000000..62cf2f4aca --- /dev/null +++ b/src/node/src/protobuf_js_5_common.js @@ -0,0 +1,181 @@ +/* + * + * Copyright 2017, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +var _ = require('lodash'); +var client = require('./client'); + +/** + * Get a function that deserializes a specific type of protobuf. + * @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 + */ +exports.deserializeCls = function deserializeCls(cls, binaryAsBase64, + longsAsStrings) { + /** + * Deserialize a buffer to a message object + * @param {Buffer} arg_buf The buffer to deserialize + * @return {cls} The resulting object + */ + return function deserialize(arg_buf) { + // Convert to a native object with binary fields as Buffers (first argument) + // and longs as strings (second argument) + return cls.decode(arg_buf).toRaw(binaryAsBase64, longsAsStrings); + }; +}; + +var deserializeCls = exports.deserializeCls; + +/** + * Get a function that serializes objects to a buffer by protobuf class. + * @param {function()} Cls The constructor of the message type to serialize + * @return {function(Cls):Buffer} The serialization function + */ +exports.serializeCls = function serializeCls(Cls) { + /** + * Serialize an object to a Buffer + * @param {Object} arg The object to serialize + * @return {Buffer} The serialized object + */ + return function serialize(arg) { + return new Buffer(new Cls(arg).encode().toBuffer()); + }; +}; + +var serializeCls = exports.serializeCls; + +/** + * Get the fully qualified (dotted) name of a ProtoBuf.Reflect value. + * @param {ProtoBuf.Reflect.Namespace} value The value to get the name of + * @return {string} The fully qualified name of the value + */ +exports.fullyQualifiedName = function fullyQualifiedName(value) { + if (value === null || value === undefined) { + return ''; + } + var name = value.name; + var parent_name = fullyQualifiedName(value.parent); + if (parent_name !== '') { + name = parent_name + '.' + name; + } + return name; +}; + +var fullyQualifiedName = exports.fullyQualifiedName; + +/** + * Return a map from method names to method attributes for the service. + * @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 + */ +exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service, + options) { + var prefix = '/' + fullyQualifiedName(service) + '/'; + var binaryAsBase64, longsAsStrings; + if (options) { + binaryAsBase64 = options.binaryAsBase64; + longsAsStrings = options.longsAsStrings; + } + /* This slightly awkward construction is used to make sure we only use + lodash@3.10.1-compatible functions. A previous version used + _.fromPairs, which would be cleaner, but was introduced in lodash + version 4 */ + return _.zipObject(_.map(service.children, function(method) { + return _.camelCase(method.name); + }), _.map(service.children, function(method) { + return { + originalName: method.name, + path: prefix + method.name, + requestStream: method.requestStream, + responseStream: method.responseStream, + requestType: method.resolvedRequestType, + responseType: method.resolvedResponseType, + requestSerialize: serializeCls(method.resolvedRequestType.build()), + requestDeserialize: deserializeCls(method.resolvedRequestType.build(), + binaryAsBase64, longsAsStrings), + responseSerialize: serializeCls(method.resolvedResponseType.build()), + responseDeserialize: deserializeCls(method.resolvedResponseType.build(), + binaryAsBase64, longsAsStrings) + }; + })); +}; + +var getProtobufServiceAttrs = exports.getProtobufServiceAttrs; + +/** + * Load a gRPC object from an existing ProtoBuf.Reflect object. + * @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 + */ +exports.loadObject = function loadObject(value, options) { + var result = {}; + if (!value) { + return value; + } + if (value.hasOwnProperty('ns')) { + return loadObject(value.ns, options); + } + if (value.className === 'Namespace') { + _.each(value.children, function(child) { + result[child.name] = loadObject(child, options); + }); + return result; + } else if (value.className === 'Service') { + return client.makeClientConstructor(getProtobufServiceAttrs(value, options), + options); + } else if (value.className === 'Message' || value.className === 'Enum') { + return value.build(); + } else { + return value; + } +}; + +/** + * The primary purpose of this method is to distinguish between reflection + * objects from different versions of ProtoBuf.js. This is just a heuristic, + * checking for properties that are (currently) specific to this version of + * ProtoBuf.js + * @param {Object} obj The object to check + * @return {boolean} Whether the object appears to be a Protobuf.js 5 + * ReflectionObject + */ +exports.isProbablyProtobufJs5 = function isProbablyProtobufJs5(obj) { + return _.isArray(obj.children) && (typeof obj.build === 'function'); +}; diff --git a/src/node/src/protobuf_js_6_common.js b/src/node/src/protobuf_js_6_common.js new file mode 100644 index 0000000000..00f11f2736 --- /dev/null +++ b/src/node/src/protobuf_js_6_common.js @@ -0,0 +1,170 @@ +/* + * + * Copyright 2017, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +var _ = require('lodash'); +var client = require('./client'); + +/** + * Get a function that deserializes a specific type of protobuf. + * @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 + */ +exports.deserializeCls = function deserializeCls(cls, options) { + var conversion_options = { + defaults: true, + bytes: options.binaryAsBase64 ? String : Buffer, + longs: options.longsAsStrings ? String : null, + enums: options.enumsAsStrings ? String : null, + oneofs: true + }; + /** + * Deserialize a buffer to a message object + * @param {Buffer} arg_buf The buffer to deserialize + * @return {cls} The resulting object + */ + return function deserialize(arg_buf) { + return cls.decode(arg_buf).toObject(conversion_options); + }; +}; + +var deserializeCls = exports.deserializeCls; + +/** + * Get a function that serializes objects to a buffer by protobuf class. + * @param {function()} Cls The constructor of the message type to serialize + * @return {function(Cls):Buffer} The serialization function + */ +exports.serializeCls = function serializeCls(cls) { + /** + * Serialize an object to a Buffer + * @param {Object} arg The object to serialize + * @return {Buffer} The serialized object + */ + return function serialize(arg) { + var message = cls.fromObject(arg); + return cls.encode(message).finish(); + }; +}; + +var serializeCls = exports.serializeCls; + +/** + * Get the fully qualified (dotted) name of a ProtoBuf.Reflect value. + * @param {ProtoBuf.ReflectionObject} value The value to get the name of + * @return {string} The fully qualified name of the value + */ +exports.fullyQualifiedName = function fullyQualifiedName(value) { + if (value === null || value === undefined) { + return ''; + } + var name = value.name; + var parent_fqn = fullyQualifiedName(value.parent); + if (parent_fqn !== '') { + name = parent_fqn + '.' + name; + } + return name; +}; + +var fullyQualifiedName = exports.fullyQualifiedName; + +/** + * Return a map from method names to method attributes for the service. + * @param {ProtoBuf.Service} service The service to get attributes for + * @param {Object=} options Options to apply to these attributes + * @return {Object} The attributes map + */ +exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service, + options) { + var prefix = '/' + fullyQualifiedName(service) + '/'; + service.resolveAll(); + return _.zipObject(_.map(service.methods, function(method) { + return _.camelCase(method.name); + }), _.map(service.methods, function(method) { + return { + originalName: method.name, + path: prefix + method.name, + requestStream: !!method.requestStream, + responseStream: !!method.responseStream, + requestType: method.resolvedRequestType, + responseType: method.resolvedResponseType, + requestSerialize: serializeCls(method.resolvedRequestType), + requestDeserialize: deserializeCls(method.resolvedRequestType, options), + responseSerialize: serializeCls(method.resolvedResponseType), + responseDeserialize: deserializeCls(method.resolvedResponseType, options) + }; + })); +}; + +var getProtobufServiceAttrs = exports.getProtobufServiceAttrs; + +exports.loadObject = function loadObject(value, options) { + var result = {}; + if (!value) { + return value; + } + if (value.hasOwnProperty('methods')) { + // It's a service object + var service_attrs = getProtobufServiceAttrs(value, options); + return client.makeClientConstructor(service_attrs); + } + + if (value.hasOwnProperty('nested')) { + // It's a namespace or root object + _.each(value.nested, function(nested, name) { + result[name] = loadObject(nested, options); + }); + return result; + } + + // Otherwise, it's not something we need to change + return value; +}; + +/** + * The primary purpose of this method is to distinguish between reflection + * objects from different versions of ProtoBuf.js. This is just a heuristic, + * checking for properties that are (currently) specific to this version of + * ProtoBuf.js + * @param {Object} obj The object to check + * @return {boolean} Whether the object appears to be a Protobuf.js 6 + * ReflectionObject + */ +exports.isProbablyProtobufJs6 = function isProbablyProtobufJs6(obj) { + return (typeof obj.root === 'object') && (typeof obj.resolve === 'function'); +}; diff --git a/src/node/src/server.js b/src/node/src/server.js index bdb4a56203..3450abed08 100644 --- a/src/node/src/server.js +++ b/src/node/src/server.js @@ -781,17 +781,31 @@ Server.prototype.addService = function(service, implementation) { /** * Add a proto service to the server, with a corresponding implementation + * @deprecated Use grpc.load and Server#addService instead * @param {Protobuf.Reflect.Service} service The proto service descriptor * @param {Object<String, function>} implementation Map of method names to * method implementation for the provided service. */ Server.prototype.addProtoService = function(service, implementation) { var options; - if (service.grpc_options) { - options = service.grpc_options; + var protobuf_js_5_common = require('./protobuf_js_5_common'); + var protobuf_js_6_common = require('./protobuf_js_6_common'); + common.log(grpc.logVerbosity.INFO, + 'Server#addProtoService is deprecated. Use addService instead'); + if (protobuf_js_5_common.isProbablyProtobufJs5(service)) { + options = _.defaults(service.grpc_options, common.defaultGrpcOptions); + this.addService( + protobuf_js_5_common.getProtobufServiceAttrs(service, options), + implementation); + } else if (protobuf_js_6_common.isProbablyProtobufJs6(service)) { + options = _.defaults(service.grpc_options, common.defaultGrpcOptions); + this.addService( + protobuf_js_6_common.getProtobufServiceAttrs(service, options), + implementation); + } else { + // We assume that this is a service attributes object + this.addService(service, implementation); } - this.addService(common.getProtobufServiceAttrs(service, options), - implementation); }; /** diff --git a/src/node/stress/metrics_server.js b/src/node/stress/metrics_server.js index 3ab4b4c82d..b3f939e8f3 100644 --- a/src/node/stress/metrics_server.js +++ b/src/node/stress/metrics_server.js @@ -63,7 +63,7 @@ function getAllGauges(call) { function MetricsServer(port) { var server = new grpc.Server(); - server.addProtoService(metrics.MetricsService.service, { + server.addService(metrics.MetricsService.service, { getGauge: _.bind(getGauge, this), getAllGauges: _.bind(getAllGauges, this) }); diff --git a/src/node/test/common_test.js b/src/node/test/common_test.js index c57b7388f6..e1ce864f97 100644 --- a/src/node/test/common_test.js +++ b/src/node/test/common_test.js @@ -34,17 +34,26 @@ 'use strict'; var assert = require('assert'); +var _ = require('lodash'); -var common = require('../src/common.js'); +var common = require('../src/common'); +var protobuf_js_6_common = require('../src/protobuf_js_6_common'); + +var serializeCls = protobuf_js_6_common.serializeCls; +var deserializeCls = protobuf_js_6_common.deserializeCls; var ProtoBuf = require('protobufjs'); -var messages_proto = ProtoBuf.loadProtoFile( - __dirname + '/test_messages.proto').build(); +var messages_proto = new ProtoBuf.Root(); +messages_proto = messages_proto.loadSync( + __dirname + '/test_messages.proto', {keepCase: true}).resolveAll(); + +var default_options = common.defaultGrpcOptions; describe('Proto message long int serialize and deserialize', function() { - var longSerialize = common.serializeCls(messages_proto.LongValues); - var longDeserialize = common.deserializeCls(messages_proto.LongValues); + var longSerialize = serializeCls(messages_proto.LongValues); + var longDeserialize = deserializeCls(messages_proto.LongValues, + default_options); var pos_value = '314159265358979'; var neg_value = '-27182818284590'; it('should preserve positive int64 values', function() { @@ -88,8 +97,9 @@ describe('Proto message long int serialize and deserialize', function() { neg_value); }); it('should deserialize as a number with the right option set', function() { - var longNumDeserialize = common.deserializeCls(messages_proto.LongValues, - false, false); + var num_options = _.defaults({longsAsStrings: false}, default_options); + var longNumDeserialize = deserializeCls(messages_proto.LongValues, + num_options); 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 @@ -98,11 +108,12 @@ describe('Proto message long int serialize and deserialize', function() { }); }); 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 sequenceSerialize = serializeCls(messages_proto.SequenceValues); + var sequenceDeserialize = deserializeCls( + messages_proto.SequenceValues, default_options); + var b64_options = _.defaults({binaryAsBase64: true}, default_options); + var sequenceBase64Deserialize = deserializeCls( + messages_proto.SequenceValues, b64_options); var buffer_val = new Buffer([0x69, 0xb7]); var base64_val = 'abc='; it('should preserve a buffer', function() { @@ -120,19 +131,73 @@ describe('Proto message bytes serialize and deserialize', function() { 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]); + it('should serialize a repeated field as packed by default', function() { + var expected_serialize = new Buffer([0x12, 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]); + it('should deserialize packed or unpacked repeated', function() { + var expectedDeserialize = { + bytes_field: new Buffer(''), + repeated_field: [10] + }; + var packedSerialized = new Buffer([0x12, 0x01, 0x0a]); + var unpackedSerialized = new Buffer([0x10, 0x0a]); + var packedDeserialized; + var unpackedDeserialized; assert.doesNotThrow(function() { - sequenceDeserialize(serialized); + packedDeserialized = sequenceDeserialize(packedSerialized); }); + assert.doesNotThrow(function() { + unpackedDeserialized = sequenceDeserialize(unpackedSerialized); + }); + assert.deepEqual(packedDeserialized, expectedDeserialize); + assert.deepEqual(unpackedDeserialized, expectedDeserialize); + }); +}); +describe('Proto message oneof serialize and deserialize', function() { + var oneofSerialize = serializeCls(messages_proto.OneOfValues); + var oneofDeserialize = deserializeCls( + messages_proto.OneOfValues, default_options); + it('Should have idempotent round trips', function() { + var test_message = {oneof_choice: 'int_choice', int_choice: 5}; + var serialized1 = oneofSerialize(test_message); + var deserialized1 = oneofDeserialize(serialized1); + assert.equal(deserialized1.int_choice, 5); + var serialized2 = oneofSerialize(deserialized1); + var deserialized2 = oneofDeserialize(serialized2); + assert.deepEqual(deserialized1, deserialized2); + }); + it('Should emit a property indicating which field was chosen', function() { + var test_message1 = {oneof_choice: 'int_choice', int_choice: 5}; + var serialized1 = oneofSerialize(test_message1); + var deserialized1 = oneofDeserialize(serialized1); + assert.equal(deserialized1.oneof_choice, 'int_choice'); + var test_message2 = {oneof_choice: 'string_choice', string_choice: 'abc'}; + var serialized2 = oneofSerialize(test_message2); + var deserialized2 = oneofDeserialize(serialized2); + assert.equal(deserialized2.oneof_choice, 'string_choice'); + }); +}); +describe('Proto message enum serialize and deserialize', function() { + var enumSerialize = serializeCls(messages_proto.EnumValues); + var enumDeserialize = deserializeCls( + messages_proto.EnumValues, default_options); + var enumIntOptions = _.defaults({enumsAsStrings: false}, default_options); + var enumIntDeserialize = deserializeCls( + messages_proto.EnumValues, enumIntOptions); + it('Should accept both names and numbers', function() { + var nameSerialized = enumSerialize({enum_value: 'ONE'}); + var numberSerialized = enumSerialize({enum_value: 1}); + assert.strictEqual(messages_proto.TestEnum.ONE, 1); + assert.deepEqual(enumDeserialize(nameSerialized), + enumDeserialize(numberSerialized)); + }); + it('Should deserialize as a string the enumsAsStrings option', function() { + var serialized = enumSerialize({enum_value: 'TWO'}); + var nameDeserialized = enumDeserialize(serialized); + var numberDeserialized = enumIntDeserialize(serialized); + assert.deepEqual(nameDeserialized, {enum_value: 'TWO'}); + assert.deepEqual(numberDeserialized, {enum_value: 2}); }); }); diff --git a/src/node/test/credentials_test.js b/src/node/test/credentials_test.js index 305843f665..b66b4bf5ea 100644 --- a/src/node/test/credentials_test.js +++ b/src/node/test/credentials_test.js @@ -228,7 +228,7 @@ describe('client credentials', function() { before(function() { var proto = grpc.load(__dirname + '/test_service.proto'); server = new grpc.Server(); - server.addProtoService(proto.TestService.service, { + server.addService(proto.TestService.service, { unary: function(call, cb) { call.sendMetadata(call.metadata); cb(null, {}); diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js index 1d739562a6..783028fa99 100644 --- a/src/node/test/surface_test.js +++ b/src/node/test/surface_test.js @@ -34,19 +34,22 @@ 'use strict'; var assert = require('assert'); +var _ = require('lodash'); var surface_client = require('../src/client.js'); +var common = require('../src/common'); var ProtoBuf = require('protobufjs'); var grpc = require('..'); -var math_proto = ProtoBuf.loadProtoFile(__dirname + - '/../../proto/math/math.proto'); +var math_proto = new ProtoBuf.Root(); +math_proto = math_proto.loadSync(__dirname + + '/../../proto/math/math.proto', {keepCase: true}); var mathService = math_proto.lookup('math.Math'); - -var _ = require('lodash'); +var mathServiceAttrs = grpc.loadObject( + mathService, common.defaultGrpcOptions).service; /** * This is used for testing functions with multiple asynchronous calls that @@ -87,11 +90,6 @@ describe('File loader', function() { grpc.load(__dirname + '/test_service.json', 'json'); }); }); - it('Should fail to load a file with an unknown format', function() { - assert.throws(function() { - grpc.load(__dirname + '/test_service.proto', 'fake_format'); - }); - }); }); describe('surface Server', function() { var server; @@ -132,15 +130,40 @@ describe('Server.prototype.addProtoService', function() { afterEach(function() { server.forceShutdown(); }); - it('Should succeed with a single service', function() { + it('Should succeed with a single proto service', function() { assert.doesNotThrow(function() { server.addProtoService(mathService, dummyImpls); }); }); + it('Should succeed with a single service attributes object', function() { + assert.doesNotThrow(function() { + server.addProtoService(mathServiceAttrs, dummyImpls); + }); + }); +}); +describe('Server.prototype.addService', function() { + var server; + var dummyImpls = { + 'div': function() {}, + 'divMany': function() {}, + 'fib': function() {}, + 'sum': function() {} + }; + beforeEach(function() { + server = new grpc.Server(); + }); + afterEach(function() { + server.forceShutdown(); + }); + it('Should succeed with a single service', function() { + assert.doesNotThrow(function() { + server.addService(mathServiceAttrs, dummyImpls); + }); + }); it('Should fail with conflicting method names', function() { - server.addProtoService(mathService, dummyImpls); + server.addService(mathServiceAttrs, dummyImpls); assert.throws(function() { - server.addProtoService(mathService, dummyImpls); + server.addService(mathServiceAttrs, dummyImpls); }); }); it('Should allow method names as originally written', function() { @@ -172,15 +195,15 @@ describe('Server.prototype.addProtoService', function() { it('Should fail if the server has been started', function() { server.start(); assert.throws(function() { - server.addProtoService(mathService, dummyImpls); + server.addService(mathServiceAttrs, dummyImpls); }); }); describe('Default handlers', function() { var client; beforeEach(function() { - server.addProtoService(mathService, {}); + server.addService(mathServiceAttrs, {}); var port = server.bind('localhost:0', server_insecure_creds); - var Client = surface_client.makeProtobufClientConstructor(mathService); + var Client = grpc.loadObject(mathService); client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); @@ -252,7 +275,7 @@ describe('waitForClientReady', function() { server = new grpc.Server(); port = server.bind('localhost:0', grpc.ServerCredentials.createInsecure()); server.start(); - Client = surface_client.makeProtobufClientConstructor(mathService); + Client = grpc.loadObject(mathService); }); beforeEach(function() { client = new Client('localhost:' + port, grpc.credentials.createInsecure()); @@ -309,16 +332,18 @@ describe('Echo service', function() { var server; var client; before(function() { - var test_proto = ProtoBuf.loadProtoFile(__dirname + '/echo_service.proto'); + var test_proto = new ProtoBuf.Root(); + test_proto = test_proto.loadSync(__dirname + '/echo_service.proto', + {keepCase: true}); var echo_service = test_proto.lookup('EchoService'); + var Client = grpc.loadObject(echo_service); server = new grpc.Server(); - server.addProtoService(echo_service, { + server.addService(Client.service, { echo: function(call, callback) { callback(null, call.request); } }); var port = server.bind('localhost:0', server_insecure_creds); - var Client = surface_client.makeProtobufClientConstructor(echo_service); client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); }); @@ -432,10 +457,13 @@ describe('Echo metadata', function() { var server; var metadata; before(function() { - var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto'); + var test_proto = new ProtoBuf.Root(); + test_proto = test_proto.loadSync(__dirname + '/test_service.proto', + {keepCase: true}); var test_service = test_proto.lookup('TestService'); + var Client = grpc.loadObject(test_service); server = new grpc.Server(); - server.addProtoService(test_service, { + server.addService(Client.service, { unary: function(call, cb) { call.sendMetadata(call.metadata); cb(null, {}); @@ -460,7 +488,6 @@ describe('Echo metadata', function() { } }); var port = server.bind('localhost:0', server_insecure_creds); - var Client = surface_client.makeProtobufClientConstructor(test_service); client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); metadata = new grpc.Metadata(); @@ -533,7 +560,9 @@ describe('Client malformed response handling', function() { var client; var badArg = new Buffer([0xFF]); before(function() { - var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto'); + var test_proto = new ProtoBuf.Root(); + test_proto = test_proto.loadSync(__dirname + '/test_service.proto', + {keepCase: true}); var test_service = test_proto.lookup('TestService'); var malformed_test_service = { unary: { @@ -591,7 +620,7 @@ describe('Client malformed response handling', function() { } }); var port = server.bind('localhost:0', server_insecure_creds); - var Client = surface_client.makeProtobufClientConstructor(test_service); + var Client = grpc.loadObject(test_service); client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); }); @@ -640,7 +669,9 @@ describe('Server serialization failure handling', function() { var client; var server; before(function() { - var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto'); + var test_proto = new ProtoBuf.Root(); + test_proto = test_proto.loadSync(__dirname + '/test_service.proto', + {keepCase: true}); var test_service = test_proto.lookup('TestService'); var malformed_test_service = { unary: { @@ -698,7 +729,7 @@ describe('Server serialization failure handling', function() { } }); var port = server.bind('localhost:0', server_insecure_creds); - var Client = surface_client.makeProtobufClientConstructor(test_service); + var Client = grpc.loadObject(test_service); client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); }); @@ -747,12 +778,15 @@ describe('Other conditions', function() { var server; var port; before(function() { - var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto'); + var test_proto = new ProtoBuf.Root(); + test_proto = test_proto.loadSync(__dirname + '/test_service.proto', + {keepCase: true}); test_service = test_proto.lookup('TestService'); + Client = grpc.loadObject(test_service); server = new grpc.Server(); var trailer_metadata = new grpc.Metadata(); trailer_metadata.add('trailer-present', 'yes'); - server.addProtoService(test_service, { + server.addService(Client.service, { unary: function(call, cb) { var req = call.request; if (req.error) { @@ -812,7 +846,6 @@ describe('Other conditions', function() { } }); port = server.bind('localhost:0', server_insecure_creds); - Client = surface_client.makeProtobufClientConstructor(test_service); client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); }); @@ -1093,17 +1126,19 @@ describe('Call propagation', function() { var client; var server; before(function() { - var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto'); + var test_proto = new ProtoBuf.Root(); + test_proto = test_proto.loadSync(__dirname + '/test_service.proto', + {keepCase: true}); test_service = test_proto.lookup('TestService'); server = new grpc.Server(); - server.addProtoService(test_service, { + Client = grpc.loadObject(test_service); + server.addService(Client.service, { unary: function(call) {}, clientStream: function(stream) {}, serverStream: function(stream) {}, bidiStream: function(stream) {} }); var port = server.bind('localhost:0', server_insecure_creds); - Client = surface_client.makeProtobufClientConstructor(test_service); client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); }); @@ -1138,7 +1173,7 @@ describe('Call propagation', function() { }); call.cancel(); }; - proxy.addProtoService(test_service, proxy_impl); + proxy.addService(Client.service, proxy_impl); var proxy_port = proxy.bind('localhost:0', server_insecure_creds); proxy.start(); var proxy_client = new Client('localhost:' + proxy_port, @@ -1160,7 +1195,7 @@ describe('Call propagation', function() { }); call.cancel(); }; - proxy.addProtoService(test_service, proxy_impl); + proxy.addService(Client.service, proxy_impl); var proxy_port = proxy.bind('localhost:0', server_insecure_creds); proxy.start(); var proxy_client = new Client('localhost:' + proxy_port, @@ -1180,7 +1215,7 @@ describe('Call propagation', function() { }); call.cancel(); }; - proxy.addProtoService(test_service, proxy_impl); + proxy.addService(Client.service, proxy_impl); var proxy_port = proxy.bind('localhost:0', server_insecure_creds); proxy.start(); var proxy_client = new Client('localhost:' + proxy_port, @@ -1204,7 +1239,7 @@ describe('Call propagation', function() { }); call.cancel(); }; - proxy.addProtoService(test_service, proxy_impl); + proxy.addService(Client.service, proxy_impl); var proxy_port = proxy.bind('localhost:0', server_insecure_creds); proxy.start(); var proxy_client = new Client('localhost:' + proxy_port, @@ -1235,7 +1270,7 @@ describe('Call propagation', function() { } }); }; - proxy.addProtoService(test_service, proxy_impl); + proxy.addService(Client.service, proxy_impl); var proxy_port = proxy.bind('localhost:0', server_insecure_creds); proxy.start(); var proxy_client = new Client('localhost:' + proxy_port, @@ -1259,7 +1294,7 @@ describe('Call propagation', function() { done(); }); }; - proxy.addProtoService(test_service, proxy_impl); + proxy.addService(Client.service, proxy_impl); var proxy_port = proxy.bind('localhost:0', server_insecure_creds); proxy.start(); var proxy_client = new Client('localhost:' + proxy_port, @@ -1279,14 +1314,14 @@ describe('Cancelling surface client', function() { var server; before(function() { server = new grpc.Server(); - server.addProtoService(mathService, { + server.addService(mathServiceAttrs, { 'div': function(stream) {}, 'divMany': function(stream) {}, 'fib': function(stream) {}, 'sum': function(stream) {} }); var port = server.bind('localhost:0', server_insecure_creds); - var Client = surface_client.makeProtobufClientConstructor(mathService); + var Client = surface_client.makeClientConstructor(mathServiceAttrs); client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); }); diff --git a/src/node/test/test_messages.proto b/src/node/test/test_messages.proto index a1a6a32833..ae70f6e152 100644 --- a/src/node/test/test_messages.proto +++ b/src/node/test/test_messages.proto @@ -41,3 +41,20 @@ message SequenceValues { bytes bytes_field = 1; repeated int32 repeated_field = 2; } + +message OneOfValues { + oneof oneof_choice { + int32 int_choice = 1; + string string_choice = 2; + } +} + +enum TestEnum { + ZERO = 0; + ONE = 1; + TWO = 2; +} + +message EnumValues { + TestEnum enum_value = 1; +}
\ No newline at end of file |