{"action":"created","comment":{"url":"https://api.github.com/repos/grpc/grpc-node/pulls/comments/170136977","pull_request_review_id":98779820,"id":170136977,"diff_hunk":"@@ -0,0 +1,1343 @@\n+/**\n+ * @license\n+ * Copyright 2018 gRPC authors.\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at\n+ *\n+ *     http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ *\n+ */\n+\n+/**\n+ * Client Interceptors\n+ *\n+ * This module describes the interceptor framework for clients.\n+ * An interceptor is a function which takes an options object and a nextCall\n+ * function and returns an InterceptingCall:\n+ *\n+ * ```\n+ * var interceptor = function(options, nextCall) {\n+ *   return new InterceptingCall(nextCall(options));\n+ * }\n+ * ```\n+ *\n+ * The interceptor function must return an InterceptingCall object. Returning\n+ * `new InterceptingCall(nextCall(options))` will satisfy the contract (but\n+ * provide no interceptor functionality). `nextCall` is a function which will\n+ * generate the next interceptor in the chain.\n+ *\n+ * To implement interceptor functionality, create a requester and pass it to\n+ * the InterceptingCall constructor:\n+ *\n+ * `return new InterceptingCall(nextCall(options), requester);`\n+ *\n+ * A requester is a POJO with zero or more of the following methods:\n+ *\n+ * `start(metadata, listener, next)`\n+ * * To continue, call next(metadata, listener). Listeners are described\n+ * * below.\n+ *\n+ * `sendMessage(message, next)`\n+ * * To continue, call next(message).\n+ *\n+ * `halfClose(next)`\n+ * * To continue, call next().\n+ *\n+ * `cancel(message, next)`\n+ * * To continue, call next().\n+ *\n+ * A listener is a POJO with one or more of the following methods:\n+ *\n+ * `onReceiveMetadata(metadata, next)`\n+ * * To continue, call next(metadata)\n+ *\n+ * `onReceiveMessage(message, next)`\n+ * * To continue, call next(message)\n+ *\n+ * `onReceiveStatus(status, next)`\n+ * * To continue, call next(status)\n+ *\n+ * A listener is provided by the requester's `start` method. The provided\n+ * listener implements all the inbound interceptor methods, which can be called\n+ * to short-circuit the gRPC call.\n+ *\n+ * Three usage patterns are supported for listeners:\n+ * 1) Pass the listener along without modification: `next(metadata, listener)`.\n+ *   In this case the interceptor declines to intercept any inbound operations.\n+ * 2) Create a new listener with one or more inbound interceptor methods and\n+ *   pass it to `next`. In this case the interceptor will fire on the inbound\n+ *   operations implemented in the new listener.\n+ * 3) Make direct inbound calls to the provided listener's methods. This\n+ *   short-circuits the interceptor stack.\n+ *\n+ * Do not modify the listener passed in. Either pass it along unmodified,\n+ * ignore it, or call methods on it to short-circuit the call.\n+ *\n+ * To intercept errors, implement the `onReceiveStatus` method and test for\n+ * `status.code !== grpc.status.OK`.\n+ *\n+ * To intercept trailers, examine `status.metadata` in the `onReceiveStatus`\n+ * method.\n+ *\n+ * This is a trivial implementation of all interceptor methods:\n+ * var interceptor = function(options, nextCall) {\n+ *   return new InterceptingCall(nextCall(options), {\n+ *     start: function(metadata, listener, next) {\n+ *       next(metadata, {\n+ *         onReceiveMetadata: function (metadata, next) {\n+ *           next(metadata);\n+ *         },\n+ *         onReceiveMessage: function (message, next) {\n+ *           next(message);\n+ *         },\n+ *         onReceiveStatus: function (status, next) {\n+ *           next(status);\n+ *         },\n+ *       });\n+ *     },\n+ *     sendMessage: function(message, next) {\n+ *       next(message);\n+ *     },\n+ *     halfClose: function(next) {\n+ *       next();\n+ *     },\n+ *     cancel: function(message, next) {\n+ *       next();\n+ *     }\n+ *   });\n+ * };\n+ *\n+ * This is an interceptor with a single method:\n+ * var interceptor = function(options, nextCall) {\n+ *   return new InterceptingCall(nextCall(options), {\n+ *     sendMessage: function(message, next) {\n+ *       next(message);\n+ *     }\n+ *   });\n+ * };\n+ *\n+ * Builders are provided for convenience: StatusBuilder, ListenerBuilder,\n+ * and RequesterBuilder\n+ *\n+ * gRPC client operations use this mapping to interceptor methods:\n+ *\n+ * grpc.opType.SEND_INITIAL_METADATA -> start\n+ * grpc.opType.SEND_MESSAGE -> sendMessage\n+ * grpc.opType.SEND_CLOSE_FROM_CLIENT -> halfClose\n+ * grpc.opType.RECV_INITIAL_METADATA -> onReceiveMetadata\n+ * grpc.opType.RECV_MESSAGE -> onReceiveMessage\n+ * grpc.opType.RECV_STATUS_ON_CLIENT -> onReceiveStatus\n+ *\n+ * @module\n+ */\n+\n+'use strict';\n+\n+var _ = require('lodash');\n+var grpc = require('./grpc_extension');\n+var Metadata = require('./metadata');\n+var constants = require('./constants');\n+var common = require('./common');\n+var methodTypes = constants.methodTypes;\n+var EventEmitter = require('events').EventEmitter;\n+\n+/**\n+ * A custom error thrown when interceptor configuration fails.\n+ * @param {string} message The error message\n+ * @param {object=} extra\n+ * @constructor\n+ */\n+var InterceptorConfigurationError =\n+  function InterceptorConfigurationError(message, extra) {\n+    Error.captureStackTrace(this, this.constructor);\n+    this.name = this.constructor.name;\n+    this.message = message;\n+    this.extra = extra;\n+  };\n+\n+require('util').inherits(InterceptorConfigurationError, Error);\n+\n+/**\n+ * A builder for gRPC status objects.\n+ * @constructor\n+ */\n+function StatusBuilder() {\n+  this.code = null;\n+  this.details = null;\n+  this.metadata = null;\n+}\n+\n+/**\n+ * Adds a status code to the builder.\n+ * @param {number} code The status code.\n+ * @return {StatusBuilder}\n+ */\n+StatusBuilder.prototype.withCode = function(code) {\n+  this.code = code;\n+  return this;\n+};\n+\n+/**\n+ * Adds details to the builder.\n+ * @param {string} details A status message.\n+ * @return {StatusBuilder}\n+ */\n+StatusBuilder.prototype.withDetails = function(details) {\n+  this.details = details;\n+  return this;\n+};\n+\n+/**\n+ * Adds metadata to the builder.\n+ * @param {Metadata} metadata The gRPC status metadata.\n+ * @return {StatusBuilder}\n+ */\n+StatusBuilder.prototype.withMetadata = function(metadata) {\n+  this.metadata = metadata;\n+  return this;\n+};\n+\n+/**\n+ * Builds the status object.\n+ * @return {grpc~StatusObject} A gRPC status.\n+ */\n+StatusBuilder.prototype.build = function() {\n+  var status = {};\n+  if (this.code !== undefined) {\n+    status.code = this.code;\n+  }\n+  if (this.details) {\n+    status.details = this.details;\n+  }\n+  if (this.metadata) {\n+    status.metadata = this.metadata;\n+  }\n+  return status;\n+};\n+\n+/**\n+ * A builder for listener interceptors.\n+ * @constructor\n+ */\n+function ListenerBuilder() {\n+  this.metadata = null;\n+  this.message = null;\n+  this.status = null;\n+}\n+\n+/**\n+ * Adds an onReceiveMetadata method to the builder.\n+ * @param {MetadataListener} on_receive_metadata A listener method for\n+ * receiving metadata.\n+ * @return {ListenerBuilder}\n+ */\n+ListenerBuilder.prototype.withOnReceiveMetadata =\n+  function(on_receive_metadata) {\n+    this.metadata = on_receive_metadata;\n+    return this;\n+  };\n+\n+/**\n+ * Adds an onReceiveMessage method to the builder.\n+ * @param {MessageListener} on_receive_message A listener method for receiving\n+ * messages.\n+ * @return {ListenerBuilder}\n+ */\n+ListenerBuilder.prototype.withOnReceiveMessage = function(on_receive_message) {\n+  this.message = on_receive_message;\n+  return this;\n+};\n+\n+/**\n+ * Adds an onReceiveStatus method to the builder.\n+ * @param {StatusListener} on_receive_status A listener method for receiving\n+ * status.\n+ * @return {ListenerBuilder}\n+ */\n+ListenerBuilder.prototype.withOnReceiveStatus = function(on_receive_status) {\n+  this.status = on_receive_status;\n+  return this;\n+};\n+\n+/**\n+ * Builds the call listener.\n+ * @return {grpc~Listener}\n+ */\n+ListenerBuilder.prototype.build = function() {\n+  var self = this;\n+  var listener = {};\n+  listener.onReceiveMetadata = self.metadata;\n+  listener.onReceiveMessage = self.message;\n+  listener.onReceiveStatus = self.status;\n+  return listener;\n+};\n+\n+/**\n+ * A builder for the outbound methods of an interceptor.\n+ * @constructor\n+ */\n+function RequesterBuilder() {\n+  this.start = null;\n+  this.message = null;\n+  this.half_close = null;\n+  this.cancel = null;\n+}\n+\n+/**\n+ * Add a metadata requester to the builder.\n+ * @param {MetadataRequester} start A requester method for handling metadata.\n+ * @return {RequesterBuilder}\n+ */\n+RequesterBuilder.prototype.withStart = function(start) {\n+  this.start = start;\n+  return this;\n+};\n+\n+/**\n+ * Add a message requester to the builder.\n+ * @param {MessageRequester} send_message A requester method for handling\n+ * messages.\n+ * @return {RequesterBuilder}\n+ */\n+RequesterBuilder.prototype.withSendMessage = function(send_message) {\n+  this.message = send_message;\n+  return this;\n+};\n+\n+/**\n+ * Add a close requester to the builder.\n+ * @param {CloseRequester} half_close A requester method for handling client\n+ * close.\n+ * @return {RequesterBuilder}\n+ */\n+RequesterBuilder.prototype.withHalfClose = function(half_close) {\n+  this.half_close = half_close;\n+  return this;\n+};\n+\n+/**\n+ * Add a cancel requester to the builder.\n+ * @param {CancelRequester} cancel A requester method for handling `cancel`\n+ * @return {RequesterBuilder}\n+ */\n+RequesterBuilder.prototype.withCancel = function(cancel) {\n+  this.cancel = cancel;\n+  return this;\n+};\n+\n+/**\n+ * Builds the requester's interceptor methods.\n+ * @return {grpc~Requester}\n+ */\n+RequesterBuilder.prototype.build = function() {\n+  var requester = {};\n+  requester.start = this.start;\n+  requester.sendMessage = this.message;\n+  requester.halfClose = this.half_close;\n+  requester.cancel = this.cancel;\n+  return requester;\n+};\n+\n+/**\n+ * Transforms a list of interceptor providers into interceptors.\n+ * @param {InterceptorProvider[]} providers\n+ * @param {grpc~MethodDefinition} method_definition\n+ * @return {null|Interceptor[]}\n+ */\n+var resolveInterceptorProviders = function(providers, method_definition) {\n+  if (!_.isArray(providers)) {\n+    return null;\n+  }\n+  var interceptors = [];\n+  for (var i = 0; i < providers.length; i++) {\n+    var provider = providers[i];\n+    var interceptor = provider(method_definition);\n+    if (interceptor) {\n+      interceptors.push(interceptor);\n+    }\n+  }\n+  return interceptors;\n+};\n+\n+/**\n+ * Resolves interceptor options at call invocation time\n+ * @param {grpc.Client~CallOptions} options The call options passed to a gRPC\n+ * call.\n+ * @param {Interceptor[]} [options.interceptors]\n+ * @param {InterceptorProvider[]} [options.interceptor_providers]\n+ * @param {grpc~MethodDefinition} method_definition\n+ * @return {null|function[]}\n+ */\n+var resolveInterceptorOptions = function(options, method_definition) {\n+  var provided = resolveInterceptorProviders(options.interceptor_providers,\n+    method_definition);\n+  if (_.isArray(options.interceptors) && _.isArray(provided)) {\n+    throw new InterceptorConfigurationError(\n+      'Both interceptors and interceptor_providers were passed as options ' +\n+      'to the call invocation. Only one of these is allowed.');\n+  }\n+  if (_.isArray(options.interceptors)) {\n+    return options.interceptors;\n+  }\n+  if (_.isArray(provided)) {\n+    return provided;\n+  }\n+  return null;\n+};\n+\n+/**\n+ * A chainable gRPC call proxy which will delegate to an optional requester\n+ * object. By default, interceptor methods will chain to next_call. If a\n+ * requester is provided which implements an interceptor method, that\n+ * requester method will be executed as part of the chain.\n+ * @param {InterceptingCall|null} next_call The next call in the chain\n+ * @param {grpc~Requester=} requester Interceptor methods to handle request\n+ * operations.\n+ * @constructor\n+ */\n+function InterceptingCall(next_call, requester) {\n+  this.next_call = next_call;\n+  this.requester = requester;\n+}\n+\n+/**\n+ * Get the next method in the chain or a no-op function if we are at the end\n+ * of the chain\n+ * @param {string} method_name\n+ * @return {function} The next method in the chain\n+ * @private\n+ */\n+InterceptingCall.prototype._getNextCall = function(method_name) {\n+  return this.next_call ?\n+    this.next_call[method_name].bind(this.next_call) :\n+    function(){};\n+};\n+\n+/**\n+ * Call the next method in the chain. This will either be on the next\n+ * InterceptingCall (next_call), or the requester if the requester\n+ * implements the method.\n+ * @param {string} method_name The name of the interceptor method\n+ * @param {array=} args Payload arguments for the operation\n+ * @param {function=} next The next InterceptingCall's method\n+ * @return {null}\n+ * @private\n+ */\n+InterceptingCall.prototype._callNext = function(method_name, args, next) {\n+  var args_array = args || [];\n+  var next_call = next ? next : this._getNextCall(method_name);\n+  if (this.requester && this.requester[method_name]) {\n+    // Avoid using expensive `apply` calls\n+    var num_args = args_array.length;\n+    switch (num_args) {\n+      case 0:\n+        return this.requester[method_name](next_call);\n+      case 1:\n+        return this.requester[method_name](args_array[0], next_call);\n+      case 2:\n+        return this.requester[method_name](args_array[0], args_array[1],\n+                                           next_call);\n+    }\n+  } else {\n+    return next_call(args_array[0], args_array[1]);\n+  }\n+};\n+\n+/**\n+ * Starts a call through the outbound interceptor chain and adds an element to\n+ * the reciprocal inbound listener chain.\n+ * @param {grpc.Metadata} metadata The outgoing metadata.\n+ * @param {grpc~Listener} listener An intercepting listener for inbound\n+ * operations.\n+ */\n+InterceptingCall.prototype.start = function(metadata, listener) {\n+  var self = this;\n+\n+  // If the listener provided is an InterceptingListener, use it. Otherwise, we\n+  // must be at the end of the listener chain, and any listener operations\n+  // should be terminated in an EndListener.\n+  var next_listener = _getInterceptingListener(listener, new EndListener());\n+\n+  // Build the next method in the interceptor chain\n+  var next = function(metadata, current_listener) {\n+    // If there is a next call in the chain, run it. Otherwise do nothing.\n+    if (self.next_call) {\n+      // Wire together any listener provided with the next listener\n+      var listener = _getInterceptingListener(current_listener, next_listener);\n+      self.next_call.start(metadata, listener);\n+    }\n+  };\n+  this._callNext('start', [metadata, next_listener], next);\n+};\n+\n+/**\n+ * Pass a message through the interceptor chain.\n+ * @param {jspb.Message} message\n+ */\n+InterceptingCall.prototype.sendMessage = function(message) {\n+  this._callNext('sendMessage', [message]);\n+};\n+\n+/**\n+ * Run a close operation through the interceptor chain\n+ */\n+InterceptingCall.prototype.halfClose = function() {\n+  this._callNext('halfClose');\n+};\n+\n+/**\n+ * Run a cancel operation through the interceptor chain\n+ */\n+InterceptingCall.prototype.cancel = function() {\n+  this._callNext('cancel');\n+};\n+\n+/**\n+ * Run a cancelWithStatus operation through the interceptor chain.\n+ * @param {grpc~StatusObject} status\n+ * @param {string} message\n+ */\n+InterceptingCall.prototype.cancelWithStatus = function(status, message) {\n+  this._callNext('cancelWithStatus', [status, message]);\n+};\n+\n+/**\n+ * Pass a getPeer call down to the base gRPC call (should not be intercepted)\n+ * @return {object}\n+ */\n+InterceptingCall.prototype.getPeer = function() {\n+  return this._callNext('getPeer');\n+};\n+\n+/**\n+ * For streaming calls, we need to transparently pass the stream's context\n+ * through the interceptor chain. Passes the context between InterceptingCalls\n+ * but hides it from any requester implementations.\n+ * @param {object} context Carries objects needed for streaming operations.\n+ * @param {jspb.Message} message The message to send.\n+ */\n+InterceptingCall.prototype.sendMessageWithContext = function(context, message) {\n+  var next = this.next_call ?\n+    this.next_call.sendMessageWithContext.bind(this.next_call, context) :\n+    context;\n+  this._callNext('sendMessage', [message], next);\n+};\n+\n+/**\n+ * For receiving streaming messages, we need to seed the base interceptor with\n+ * the streaming context to create a RECV_MESSAGE batch.\n+ * @param {object} context Carries objects needed for streaming operations\n+ */\n+InterceptingCall.prototype.recvMessageWithContext = function(context) {\n+  this._callNext('recvMessageWithContext', [context]);\n+};\n+\n+/**\n+ * A chain-able listener object which will delegate to a custom listener when\n+ * appropriate.\n+ * @param {InterceptingListener|null} next_listener The next\n+ * InterceptingListener in the chain\n+ * @param {grpc~Listener=} delegate A custom listener object which may implement\n+ * specific operations\n+ * @constructor\n+ */\n+function InterceptingListener(next_listener, delegate) {\n+  this.delegate = delegate || {};\n+  this.next_listener = next_listener;\n+}\n+\n+/**\n+ * Get the next method in the chain or a no-op function if we are at the end\n+ * of the chain.\n+ * @param {string} method_name The name of the listener method.\n+ * @return {function} The next method in the chain\n+ * @private\n+ */\n+InterceptingListener.prototype._getNextListener = function(method_name) {\n+  return this.next_listener ?\n+    this.next_listener[method_name].bind(this.next_listener) :\n+    function(){};\n+};\n+\n+/**\n+ * Call the next method in the chain. This will either be on the next\n+ * InterceptingListener (next_listener), or the requester if the requester\n+ * implements the method.\n+ * @param {string} method_name The name of the interceptor method\n+ * @param {array=} args Payload arguments for the operation\n+ * @param {function=} next The next InterceptingListener's method\n+ * @return {null}\n+ * @private\n+ */\n+InterceptingListener.prototype._callNext = function(method_name, args, next) {\n+  var args_array = args || [];\n+  var next_listener = next ? next : this._getNextListener(method_name);\n+  if (this.delegate && this.delegate[method_name]) {\n+    // Avoid using expensive `apply` calls\n+    var num_args = args_array.length;\n+    switch (num_args) {\n+      case 0:\n+        return this.delegate[method_name](next_listener);\n+      case 1:\n+        return this.delegate[method_name](args_array[0], next_listener);\n+      case 2:\n+        return this.delegate[method_name](args_array[0], args_array[1],\n+                                          next_listener);\n+    }\n+  } else {\n+    return next_listener(args_array[0], args_array[1]);\n+  }\n+};\n+/**\n+ * Inbound metadata receiver.\n+ * @param {Metadata} metadata\n+ */\n+InterceptingListener.prototype.onReceiveMetadata = function(metadata) {\n+  this._callNext('onReceiveMetadata', [metadata]);\n+};\n+\n+/**\n+ * Inbound message receiver.\n+ * @param {jspb.Message} message\n+ */\n+InterceptingListener.prototype.onReceiveMessage = function(message) {\n+  this._callNext('onReceiveMessage', [message]);\n+};\n+\n+/**\n+ * When intercepting streaming message, we need to pass the streaming context\n+ * transparently along the chain. Hides the context from the delegate listener\n+ * methods.\n+ * @param {object} context Carries objects needed for streaming operations.\n+ * @param {jspb.Message} message The message received.\n+ */\n+InterceptingListener.prototype.recvMessageWithContext = function(context,\n+                                                                 message) {\n+  var fallback = this.next_listener.recvMessageWithContext;\n+  var next_method = this.next_listener ?\n+    fallback.bind(this.next_listener, context) :\n+    context;\n+  if (this.delegate.onReceiveMessage) {\n+    this.delegate.onReceiveMessage(message, next_method, context);\n+  } else {\n+    next_method(message);\n+  }\n+};\n+\n+/**\n+ * Inbound status receiver.\n+ * @param {grpc~StatusObject} status\n+ */\n+InterceptingListener.prototype.onReceiveStatus = function(status) {\n+  this._callNext('onReceiveStatus', [status]);\n+};\n+\n+/**\n+ * A dead-end listener used to terminate a call chain. Used when an interceptor\n+ * creates a branch chain, when the branch returns the listener chain will\n+ * terminate here.\n+ * @constructor\n+ */\n+function EndListener() {}\n+EndListener.prototype.onReceiveMetadata = function(){};\n+EndListener.prototype.onReceiveMessage = function(){};\n+EndListener.prototype.onReceiveStatus = function(){};\n+EndListener.prototype.recvMessageWithContext = function(){};\n+\n+/**\n+ * Produces a callback triggered by streaming response messages.\n+ * @private\n+ * @param {EventEmitter} emitter\n+ * @param {grpc.internal~Call} call\n+ * @param {grpc~Listener} listener\n+ * @param {grpc~deserialize} deserialize\n+ * @return {Function}\n+ */\n+function _getStreamReadCallback(emitter, call, listener, deserialize) {\n+  return function (err, response) {\n+    if (err) {\n+      // Something has gone wrong. Stop reading and wait for status\n+      emitter.finished = true;\n+      emitter._readsDone();\n+      return;\n+    }\n+    var data = response.read;\n+    var deserialized;\n+    try {\n+      deserialized = deserialize(data);\n+    } catch (e) {\n+      emitter._readsDone({\n+        code: constants.status.INTERNAL,\n+        details: 'Failed to parse server response'\n+      });\n+      return;\n+    }\n+    if (data === null) {\n+      emitter._readsDone();\n+      return;\n+    }\n+    var context = {\n+      call: call,\n+      listener: listener\n+    };\n+    listener.recvMessageWithContext(context, deserialized);\n+  };\n+}\n+\n+/**\n+ * Produces an interceptor which will start gRPC batches for unary calls.\n+ * @private\n+ * @param {grpc~MethodDefinition} method_definition\n+ * @param {grpc.Channel} channel\n+ * @param {EventEmitter} emitter\n+ * @param {function} callback\n+ * @return {Interceptor}\n+ */\n+function _getUnaryInterceptor(method_definition, channel, emitter, callback) {\n+  var serialize = method_definition.requestSerialize;\n+  var deserialize = method_definition.responseDeserialize;\n+  return function (options) {\n+    var call = common.getCall(channel, method_definition.path, options);\n+    var first_listener;\n+    return new InterceptingCall(null, {\n+      start: function (metadata, listener) {\n+        var batch = {\n+          [grpc.opType.SEND_INITIAL_METADATA]:\n+            metadata._getCoreRepresentation(),\n+        };\n+        first_listener = listener;\n+        call.startBatch(batch, function () {});\n+      },\n+      sendMessage: function(message) {\n+        var batch = {\n+          [grpc.opType.SEND_MESSAGE]: serialize(message),\n+        };\n+        call.startBatch(batch, function () {});","path":"packages/grpc-native-core/src/client_interceptors.js","position":723,"original_position":723,"commit_id":"2064175555ef299bf94537d2ae4b931e78141b31","original_commit_id":"2064175555ef299bf94537d2ae4b931e78141b31","user":{"login":"murgatroid99","id":961599,"avatar_url":"https://avatars3.githubusercontent.com/u/961599?v=4","gravatar_id":"","url":"https://api.github.com/users/murgatroid99","html_url":"https://github.com/murgatroid99","followers_url":"https://api.github.com/users/murgatroid99/followers","following_url":"https://api.github.com/users/murgatroid99/following{/other_user}","gists_url":"https://api.github.com/users/murgatroid99/gists{/gist_id}","starred_url":"https://api.github.com/users/murgatroid99/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/murgatroid99/subscriptions","organizations_url":"https://api.github.com/users/murgatroid99/orgs","repos_url":"https://api.github.com/users/murgatroid99/repos","events_url":"https://api.github.com/users/murgatroid99/events{/privacy}","received_events_url":"https://api.github.com/users/murgatroid99/received_events","type":"User","site_admin":false},"body":"I believe that what you are doing is correct. I think that every instance of multiple operations in a batch where the single callback actually matters includes a `status` operation. And I'm pretty sure that the status operation is always guaranteed to complete eventually. And I'm also pretty sure that if there is an error with another operation, it is always because of some circumstance that would cause a non-OK status. So in the end, you will want to surface the status.\r\n\r\nOne thing you need to be careful of here to avoid spurious errors is that you have to start the `SEND_MESSAGE` batch before you start the `SEND_CLOSE_FROM_CLIENT` batch, because the latter indicates that no more messages will be sent and the write will fail if you do them in the wrong order. I haven't seen yet if you are ensuring that ordering elsewhere.","created_at":"2018-02-23T00:36:53Z","updated_at":"2018-02-23T00:36:53Z","html_url":"https://github.com/grpc/grpc-node/pull/59#discussion_r170136977","pull_request_url":"https://api.github.com/repos/grpc/grpc-node/pulls/59","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/grpc/grpc-node/pulls/comments/170136977"},"html":{"href":"https://github.com/grpc/grpc-node/pull/59#discussion_r170136977"},"pull_request":{"href":"https://api.github.com/repos/grpc/grpc-node/pulls/59"}},"in_reply_to_id":170114933},"pull_request":{"url":"https://api.github.com/repos/grpc/grpc-node/pulls/59","id":145266651,"html_url":"https://github.com/grpc/grpc-node/pull/59","diff_url":"https://github.com/grpc/grpc-node/pull/59.diff","patch_url":"https://github.com/grpc/grpc-node/pull/59.patch","issue_url":"https://api.github.com/repos/grpc/grpc-node/issues/59","number":59,"state":"open","locked":false,"title":"Implement client interceptors for grpc-native-core","user":{"login":"drobertduke","id":905655,"avatar_url":"https://avatars0.githubusercontent.com/u/905655?v=4","gravatar_id":"","url":"https://api.github.com/users/drobertduke","html_url":"https://github.com/drobertduke","followers_url":"https://api.github.com/users/drobertduke/followers","following_url":"https://api.github.com/users/drobertduke/following{/other_user}","gists_url":"https://api.github.com/users/drobertduke/gists{/gist_id}","starred_url":"https://api.github.com/users/drobertduke/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drobertduke/subscriptions","organizations_url":"https://api.github.com/users/drobertduke/orgs","repos_url":"https://api.github.com/users/drobertduke/repos","events_url":"https://api.github.com/users/drobertduke/events{/privacy}","received_events_url":"https://api.github.com/users/drobertduke/received_events","type":"User","site_admin":false},"body":"A NodeJS implementation of client-side interceptors, as described in\r\nthe proposal: https://github.com/grpc/proposal/pull/14","created_at":"2017-10-06T23:56:03Z","updated_at":"2018-02-23T00:36:53Z","closed_at":null,"merged_at":null,"merge_commit_sha":"125a6f2d95c4c1b172466b62854d722c00337655","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/grpc/grpc-node/pulls/59/commits","review_comments_url":"https://api.github.com/repos/grpc/grpc-node/pulls/59/comments","review_comment_url":"https://api.github.com/repos/grpc/grpc-node/pulls/comments{/number}","comments_url":"https://api.github.com/repos/grpc/grpc-node/issues/59/comments","statuses_url":"https://api.github.com/repos/grpc/grpc-node/statuses/2064175555ef299bf94537d2ae4b931e78141b31","head":{"label":"drobertduke:master","ref":"master","sha":"2064175555ef299bf94537d2ae4b931e78141b31","user":{"login":"drobertduke","id":905655,"avatar_url":"https://avatars0.githubusercontent.com/u/905655?v=4","gravatar_id":"","url":"https://api.github.com/users/drobertduke","html_url":"https://github.com/drobertduke","followers_url":"https://api.github.com/users/drobertduke/followers","following_url":"https://api.github.com/users/drobertduke/following{/other_user}","gists_url":"https://api.github.com/users/drobertduke/gists{/gist_id}","starred_url":"https://api.github.com/users/drobertduke/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drobertduke/subscriptions","organizations_url":"https://api.github.com/users/drobertduke/orgs","repos_url":"https://api.github.com/users/drobertduke/repos","events_url":"https://api.github.com/users/drobertduke/events{/privacy}","received_events_url":"https://api.github.com/users/drobertduke/received_events","type":"User","site_admin":false},"repo":{"id":106032746,"name":"grpc-node","full_name":"drobertduke/grpc-node","owner":{"login":"drobertduke","id":905655,"avatar_url":"https://avatars0.githubusercontent.com/u/905655?v=4","gravatar_id":"","url":"https://api.github.com/users/drobertduke","html_url":"https://github.com/drobertduke","followers_url":"https://api.github.com/users/drobertduke/followers","following_url":"https://api.github.com/users/drobertduke/following{/other_user}","gists_url":"https://api.github.com/users/drobertduke/gists{/gist_id}","starred_url":"https://api.github.com/users/drobertduke/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drobertduke/subscriptions","organizations_url":"https://api.github.com/users/drobertduke/orgs","repos_url":"https://api.github.com/users/drobertduke/repos","events_url":"https://api.github.com/users/drobertduke/events{/privacy}","received_events_url":"https://api.github.com/users/drobertduke/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/drobertduke/grpc-node","description":"gRPC for Node.js","fork":true,"url":"https://api.github.com/repos/drobertduke/grpc-node","forks_url":"https://api.github.com/repos/drobertduke/grpc-node/forks","keys_url":"https://api.github.com/repos/drobertduke/grpc-node/keys{/key_id}","collaborators_url":"https://api.github.com/repos/drobertduke/grpc-node/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/drobertduke/grpc-node/teams","hooks_url":"https://api.github.com/repos/drobertduke/grpc-node/hooks","issue_events_url":"https://api.github.com/repos/drobertduke/grpc-node/issues/events{/number}","events_url":"https://api.github.com/repos/drobertduke/grpc-node/events","assignees_url":"https://api.github.com/repos/drobertduke/grpc-node/assignees{/user}","branches_url":"https://api.github.com/repos/drobertduke/grpc-node/branches{/branch}","tags_url":"https://api.github.com/repos/drobertduke/grpc-node/tags","blobs_url":"https://api.github.com/repos/drobertduke/grpc-node/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/drobertduke/grpc-node/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/drobertduke/grpc-node/git/refs{/sha}","trees_url":"https://api.github.com/repos/drobertduke/grpc-node/git/trees{/sha}","statuses_url":"https://api.github.com/repos/drobertduke/grpc-node/statuses/{sha}","languages_url":"https://api.github.com/repos/drobertduke/grpc-node/languages","stargazers_url":"https://api.github.com/repos/drobertduke/grpc-node/stargazers","contributors_url":"https://api.github.com/repos/drobertduke/grpc-node/contributors","subscribers_url":"https://api.github.com/repos/drobertduke/grpc-node/subscribers","subscription_url":"https://api.github.com/repos/drobertduke/grpc-node/subscription","commits_url":"https://api.github.com/repos/drobertduke/grpc-node/commits{/sha}","git_commits_url":"https://api.github.com/repos/drobertduke/grpc-node/git/commits{/sha}","comments_url":"https://api.github.com/repos/drobertduke/grpc-node/comments{/number}","issue_comment_url":"https://api.github.com/repos/drobertduke/grpc-node/issues/comments{/number}","contents_url":"https://api.github.com/repos/drobertduke/grpc-node/contents/{+path}","compare_url":"https://api.github.com/repos/drobertduke/grpc-node/compare/{base}...{head}","merges_url":"https://api.github.com/repos/drobertduke/grpc-node/merges","archive_url":"https://api.github.com/repos/drobertduke/grpc-node/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/drobertduke/grpc-node/downloads","issues_url":"https://api.github.com/repos/drobertduke/grpc-node/issues{/number}","pulls_url":"https://api.github.com/repos/drobertduke/grpc-node/pulls{/number}","milestones_url":"https://api.github.com/repos/drobertduke/grpc-node/milestones{/number}","notifications_url":"https://api.github.com/repos/drobertduke/grpc-node/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/drobertduke/grpc-node/labels{/name}","releases_url":"https://api.github.com/repos/drobertduke/grpc-node/releases{/id}","deployments_url":"https://api.github.com/repos/drobertduke/grpc-node/deployments","created_at":"2017-10-06T17:36:42Z","updated_at":"2017-10-06T17:36:44Z","pushed_at":"2018-02-22T22:16:37Z","git_url":"git://github.com/drobertduke/grpc-node.git","ssh_url":"git@github.com:drobertduke/grpc-node.git","clone_url":"https://github.com/drobertduke/grpc-node.git","svn_url":"https://github.com/drobertduke/grpc-node","homepage":"https://grpc.io","size":1571,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"grpc:master","ref":"master","sha":"044102d9d2a1601a689f2128d1fc8f055f0f2978","user":{"login":"grpc","id":7802525,"avatar_url":"https://avatars1.githubusercontent.com/u/7802525?v=4","gravatar_id":"","url":"https://api.github.com/users/grpc","html_url":"https://github.com/grpc","followers_url":"https://api.github.com/users/grpc/followers","following_url":"https://api.github.com/users/grpc/following{/other_user}","gists_url":"https://api.github.com/users/grpc/gists{/gist_id}","starred_url":"https://api.github.com/users/grpc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/grpc/subscriptions","organizations_url":"https://api.github.com/users/grpc/orgs","repos_url":"https://api.github.com/users/grpc/repos","events_url":"https://api.github.com/users/grpc/events{/privacy}","received_events_url":"https://api.github.com/users/grpc/received_events","type":"Organization","site_admin":false},"repo":{"id":99148158,"name":"grpc-node","full_name":"grpc/grpc-node","owner":{"login":"grpc","id":7802525,"avatar_url":"https://avatars1.githubusercontent.com/u/7802525?v=4","gravatar_id":"","url":"https://api.github.com/users/grpc","html_url":"https://github.com/grpc","followers_url":"https://api.github.com/users/grpc/followers","following_url":"https://api.github.com/users/grpc/following{/other_user}","gists_url":"https://api.github.com/users/grpc/gists{/gist_id}","starred_url":"https://api.github.com/users/grpc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/grpc/subscriptions","organizations_url":"https://api.github.com/users/grpc/orgs","repos_url":"https://api.github.com/users/grpc/repos","events_url":"https://api.github.com/users/grpc/events{/privacy}","received_events_url":"https://api.github.com/users/grpc/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/grpc/grpc-node","description":"gRPC for Node.js","fork":false,"url":"https://api.github.com/repos/grpc/grpc-node","forks_url":"https://api.github.com/repos/grpc/grpc-node/forks","keys_url":"https://api.github.com/repos/grpc/grpc-node/keys{/key_id}","collaborators_url":"https://api.github.com/repos/grpc/grpc-node/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/grpc/grpc-node/teams","hooks_url":"https://api.github.com/repos/grpc/grpc-node/hooks","issue_events_url":"https://api.github.com/repos/grpc/grpc-node/issues/events{/number}","events_url":"https://api.github.com/repos/grpc/grpc-node/events","assignees_url":"https://api.github.com/repos/grpc/grpc-node/assignees{/user}","branches_url":"https://api.github.com/repos/grpc/grpc-node/branches{/branch}","tags_url":"https://api.github.com/repos/grpc/grpc-node/tags","blobs_url":"https://api.github.com/repos/grpc/grpc-node/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/grpc/grpc-node/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/grpc/grpc-node/git/refs{/sha}","trees_url":"https://api.github.com/repos/grpc/grpc-node/git/trees{/sha}","statuses_url":"https://api.github.com/repos/grpc/grpc-node/statuses/{sha}","languages_url":"https://api.github.com/repos/grpc/grpc-node/languages","stargazers_url":"https://api.github.com/repos/grpc/grpc-node/stargazers","contributors_url":"https://api.github.com/repos/grpc/grpc-node/contributors","subscribers_url":"https://api.github.com/repos/grpc/grpc-node/subscribers","subscription_url":"https://api.github.com/repos/grpc/grpc-node/subscription","commits_url":"https://api.github.com/repos/grpc/grpc-node/commits{/sha}","git_commits_url":"https://api.github.com/repos/grpc/grpc-node/git/commits{/sha}","comments_url":"https://api.github.com/repos/grpc/grpc-node/comments{/number}","issue_comment_url":"https://api.github.com/repos/grpc/grpc-node/issues/comments{/number}","contents_url":"https://api.github.com/repos/grpc/grpc-node/contents/{+path}","compare_url":"https://api.github.com/repos/grpc/grpc-node/compare/{base}...{head}","merges_url":"https://api.github.com/repos/grpc/grpc-node/merges","archive_url":"https://api.github.com/repos/grpc/grpc-node/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/grpc/grpc-node/downloads","issues_url":"https://api.github.com/repos/grpc/grpc-node/issues{/number}","pulls_url":"https://api.github.com/repos/grpc/grpc-node/pulls{/number}","milestones_url":"https://api.github.com/repos/grpc/grpc-node/milestones{/number}","notifications_url":"https://api.github.com/repos/grpc/grpc-node/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/grpc/grpc-node/labels{/name}","releases_url":"https://api.github.com/repos/grpc/grpc-node/releases{/id}","deployments_url":"https://api.github.com/repos/grpc/grpc-node/deployments","created_at":"2017-08-02T18:20:33Z","updated_at":"2018-02-22T10:19:51Z","pushed_at":"2018-02-22T22:16:38Z","git_url":"git://github.com/grpc/grpc-node.git","ssh_url":"git@github.com:grpc/grpc-node.git","clone_url":"https://github.com/grpc/grpc-node.git","svn_url":"https://github.com/grpc/grpc-node","homepage":"https://grpc.io","size":1562,"stargazers_count":257,"watchers_count":257,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":30,"mirror_url":null,"archived":false,"open_issues_count":60,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0"},"forks":30,"open_issues":60,"watchers":257,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/grpc/grpc-node/pulls/59"},"html":{"href":"https://github.com/grpc/grpc-node/pull/59"},"issue":{"href":"https://api.github.com/repos/grpc/grpc-node/issues/59"},"comments":{"href":"https://api.github.com/repos/grpc/grpc-node/issues/59/comments"},"review_comments":{"href":"https://api.github.com/repos/grpc/grpc-node/pulls/59/comments"},"review_comment":{"href":"https://api.github.com/repos/grpc/grpc-node/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/grpc/grpc-node/pulls/59/commits"},"statuses":{"href":"https://api.github.com/repos/grpc/grpc-node/statuses/2064175555ef299bf94537d2ae4b931e78141b31"}},"author_association":"NONE"}}
{"actor":{"display_login":"murgatroid99"}}