aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Alexander Polcyn <apolcyn@google.com>2017-09-07 19:04:59 -0700
committerGravatar Alexander Polcyn <apolcyn@google.com>2017-09-18 13:58:53 -0700
commita594ba0bd7be974cff1a8d1eeb3b21eb049e507e (patch)
tree08b6ddce596637c06a7a77c83b82e938009e64e3
parentcbe93839937e4e6272407ab1b7ae7ef848b64894 (diff)
add a client side utility for extracting a google rpc
status from a grpc::Status
-rw-r--r--grpc.gemspec1
-rw-r--r--src/ruby/lib/grpc/google_rpc_status_utils.rb28
-rw-r--r--src/ruby/spec/google_rpc_status_utils_spec.rb223
-rw-r--r--templates/grpc.gemspec.template1
4 files changed, 253 insertions, 0 deletions
diff --git a/grpc.gemspec b/grpc.gemspec
index 2d0f9fd450..96323aed10 100644
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
s.add_dependency 'google-protobuf', '~> 3.1'
s.add_dependency 'googleauth', '~> 0.5.1'
+ s.add_dependency 'googleapis-common-protos-types', '~> 1.0.0'
s.add_development_dependency 'bundler', '~> 1.9'
s.add_development_dependency 'facter', '~> 2.4'
diff --git a/src/ruby/lib/grpc/google_rpc_status_utils.rb b/src/ruby/lib/grpc/google_rpc_status_utils.rb
new file mode 100644
index 0000000000..fdadd6b76e
--- /dev/null
+++ b/src/ruby/lib/grpc/google_rpc_status_utils.rb
@@ -0,0 +1,28 @@
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require_relative './grpc'
+require 'google/rpc/status_pb'
+
+# GRPC contains the General RPC module.
+module GRPC
+ # GoogleRpcStatusUtils provides utilities to convert between a
+ # GRPC::Core::Status and a deserialized Google::Rpc::Status proto
+ class GoogleRpcStatusUtils
+ def self.extract_google_rpc_status(status)
+ fail ArgumentError, 'bad type' unless status.is_a? Struct::Status
+ Google::Rpc::Status.decode(status.metadata['grpc-status-details-bin'])
+ end
+ end
+end
diff --git a/src/ruby/spec/google_rpc_status_utils_spec.rb b/src/ruby/spec/google_rpc_status_utils_spec.rb
new file mode 100644
index 0000000000..fe221c30dd
--- /dev/null
+++ b/src/ruby/spec/google_rpc_status_utils_spec.rb
@@ -0,0 +1,223 @@
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'grpc'
+require_relative '../lib/grpc/google_rpc_status_utils'
+require_relative '../pb/src/proto/grpc/testing/messages_pb'
+require_relative '../pb/src/proto/grpc/testing/messages_pb'
+require 'google/protobuf/well_known_types'
+
+include GRPC::Core
+
+describe 'conversion from a status struct to a google protobuf status' do
+ it 'fails if the input is not a status struct' do
+ begin
+ GRPC::GoogleRpcStatusUtils.extract_google_rpc_status('string')
+ rescue => e
+ exception = e
+ end
+ expect(exception.is_a?(ArgumentError)).to be true
+ expect(exception.message.include?('bad type')).to be true
+ end
+
+ it 'fails with some error if the header key is missing' do
+ status = Struct::Status.new(1, 'details', key: 'val')
+ expect(status.metadata.nil?).to be false
+ expect do
+ GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status)
+ end.to raise_error(StandardError)
+ end
+
+ it 'fails with some error if the header key fails to deserialize' do
+ status = Struct::Status.new(1, 'details',
+ 'grpc-status-details-bin' => 'string_val')
+ expect do
+ GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status)
+ end.to raise_error(StandardError)
+ end
+
+ it 'silently ignores erroneous mismatch between messages in '\
+ 'status struct and protobuf status' do
+ proto = Google::Rpc::Status.new(code: 1, message: 'proto message')
+ encoded_proto = Google::Rpc::Status.encode(proto)
+ status = Struct::Status.new(1, 'struct message',
+ 'grpc-status-details-bin' => encoded_proto)
+ rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status)
+ expect(rpc_status).to eq(proto)
+ end
+
+ it 'silently ignores erroneous mismatch between codes in status struct '\
+ 'and protobuf status' do
+ proto = Google::Rpc::Status.new(code: 1, message: 'matching message')
+ encoded_proto = Google::Rpc::Status.encode(proto)
+ status = Struct::Status.new(2, 'matching message',
+ 'grpc-status-details-bin' => encoded_proto)
+ rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status)
+ expect(rpc_status).to eq(proto)
+ end
+
+ it 'can succesfully convert a status struct into a google protobuf status '\
+ 'when there are no rpcstatus details' do
+ proto = Google::Rpc::Status.new(code: 1, message: 'matching message')
+ encoded_proto = Google::Rpc::Status.encode(proto)
+ status = Struct::Status.new(1, 'matching message',
+ 'grpc-status-details-bin' => encoded_proto)
+ out = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status)
+ expect(out.code).to eq(1)
+ expect(out.message).to eq('matching message')
+ expect(out.details).to eq([])
+ end
+
+ it 'can succesfully convert a status struct into a google protobuf '\
+ 'status when there are multiple rpcstatus details' do
+ simple_request_any = Google::Protobuf::Any.new
+ simple_request = Grpc::Testing::SimpleRequest.new(
+ payload: Grpc::Testing::Payload.new(body: 'request'))
+ simple_request_any.pack(simple_request)
+ simple_response_any = Google::Protobuf::Any.new
+ simple_response = Grpc::Testing::SimpleResponse.new(
+ payload: Grpc::Testing::Payload.new(body: 'response'))
+ simple_response_any.pack(simple_response)
+ payload_any = Google::Protobuf::Any.new
+ payload = Grpc::Testing::Payload.new(body: 'payload')
+ payload_any.pack(payload)
+ proto = Google::Rpc::Status.new(code: 1,
+ message: 'matching message',
+ details: [
+ simple_request_any,
+ simple_response_any,
+ payload_any
+ ])
+ encoded_proto = Google::Rpc::Status.encode(proto)
+ status = Struct::Status.new(1, 'matching message',
+ 'grpc-status-details-bin' => encoded_proto)
+ out = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(status)
+ expect(out.code).to eq(1)
+ expect(out.message).to eq('matching message')
+ expect(out.details[0].unpack(
+ Grpc::Testing::SimpleRequest)).to eq(simple_request)
+ expect(out.details[1].unpack(
+ Grpc::Testing::SimpleResponse)).to eq(simple_response)
+ expect(out.details[2].unpack(
+ Grpc::Testing::Payload)).to eq(payload)
+ end
+end
+
+# Test message
+class EchoMsg
+ def self.marshal(_o)
+ ''
+ end
+
+ def self.unmarshal(_o)
+ EchoMsg.new
+ end
+end
+
+# A test service that fills in the "reserved" grpc-status-details-bin trailer,
+# for client-side testing of GoogleRpcStatus protobuf extraction from trailers.
+class GoogleRpcStatusTestService
+ include GRPC::GenericService
+ rpc :an_rpc, EchoMsg, EchoMsg
+
+ def initialize(encoded_rpc_status)
+ @encoded_rpc_status = encoded_rpc_status
+ end
+
+ def an_rpc(_, _)
+ # TODO: create a server-side utility API for sending a google rpc status.
+ # Applications are not expected to set the grpc-status-details-bin
+ # ("grpc"-fixed and reserved for library use) manually.
+ # Doing so here is only for testing of the client-side api for extracting
+ # a google rpc status, which is useful
+ # when the interacting with a server that does fill in this trailer.
+ fail GRPC::Unknown.new('test message',
+ 'grpc-status-details-bin' => @encoded_rpc_status)
+ end
+end
+
+GoogleRpcStatusTestStub = GoogleRpcStatusTestService.rpc_stub_class
+
+describe 'receving a google rpc status from a remote endpoint' do
+ def start_server(encoded_rpc_status)
+ @srv = GRPC::RpcServer.new(pool_size: 1)
+ @server_port = @srv.add_http2_port('localhost:0',
+ :this_port_is_insecure)
+ @srv.handle(GoogleRpcStatusTestService.new(encoded_rpc_status))
+ @server_thd = Thread.new { @srv.run }
+ @srv.wait_till_running
+ end
+
+ def stop_server
+ expect(@srv.stopped?).to be(false)
+ @srv.stop
+ @server_thd.join
+ expect(@srv.stopped?).to be(true)
+ end
+
+ before(:each) do
+ simple_request_any = Google::Protobuf::Any.new
+ simple_request = Grpc::Testing::SimpleRequest.new(
+ payload: Grpc::Testing::Payload.new(body: 'request'))
+ simple_request_any.pack(simple_request)
+ simple_response_any = Google::Protobuf::Any.new
+ simple_response = Grpc::Testing::SimpleResponse.new(
+ payload: Grpc::Testing::Payload.new(body: 'response'))
+ simple_response_any.pack(simple_response)
+ payload_any = Google::Protobuf::Any.new
+ payload = Grpc::Testing::Payload.new(body: 'payload')
+ payload_any.pack(payload)
+ @expected_proto = Google::Rpc::Status.new(
+ code: StatusCodes::UNKNOWN,
+ message: 'test message',
+ details: [simple_request_any, simple_response_any, payload_any])
+ start_server(Google::Rpc::Status.encode(@expected_proto))
+ end
+
+ after(:each) do
+ stop_server
+ end
+
+ it 'should receive be able to extract a google rpc status from the '\
+ 'status struct taken from a BadStatus exception' do
+ stub = GoogleRpcStatusTestStub.new("localhost:#{@server_port}",
+ :this_channel_is_insecure)
+ begin
+ stub.an_rpc(EchoMsg.new)
+ rescue GRPC::BadStatus => e
+ rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(
+ e.to_status)
+ end
+ expect(rpc_status).to eq(@expected_proto)
+ end
+
+ it 'should receive be able to extract a google rpc status from the '\
+ 'status struct taken from the op view of a call' do
+ stub = GoogleRpcStatusTestStub.new("localhost:#{@server_port}",
+ :this_channel_is_insecure)
+ op = stub.an_rpc(EchoMsg.new, return_op: true)
+ begin
+ op.execute
+ rescue GRPC::BadStatus => e
+ status_from_exception = e.to_status
+ end
+ rpc_status = GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(
+ op.status)
+ expect(rpc_status).to eq(@expected_proto)
+ # "to_status" on the bad status should give the same result
+ # as "status" on the "op view".
+ expect(GRPC::GoogleRpcStatusUtils.extract_google_rpc_status(
+ status_from_exception)).to eq(rpc_status)
+ end
+end
diff --git a/templates/grpc.gemspec.template b/templates/grpc.gemspec.template
index e62e5b2721..215d5f9df9 100644
--- a/templates/grpc.gemspec.template
+++ b/templates/grpc.gemspec.template
@@ -31,6 +31,7 @@
s.add_dependency 'google-protobuf', '~> 3.1'
s.add_dependency 'googleauth', '~> 0.5.1'
+ s.add_dependency 'googleapis-common-protos-types', '~> 1.0.0'
s.add_development_dependency 'bundler', '~> 1.9'
s.add_development_dependency 'facter', '~> 2.4'