Skip to content

Commit ddeec69

Browse files
avalanche123Natalie
and
Natalie
authored
feat: error metadata handling (#33)
* Serialize & deserialize gRPC errors * add metadata support and tests * add unit tests for error metadata Co-authored-by: Natalie <[email protected]>
1 parent 22b65d1 commit ddeec69

File tree

6 files changed

+50
-23
lines changed

6 files changed

+50
-23
lines changed

lib/grpc_web/client/client_executor.rb

+15-9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class << self
1414

1515
GRPC_STATUS_HEADER = 'grpc-status'
1616
GRPC_MESSAGE_HEADER = 'grpc-message'
17+
GRPC_HEADERS = %W[x-grpc-web #{GRPC_STATUS_HEADER} #{GRPC_MESSAGE_HEADER}].freeze
1718

1819
def request(uri, rpc_desc, params = {})
1920
req_proto = rpc_desc.input.new(params)
@@ -82,32 +83,37 @@ def parse_headers(header_str)
8283
end
8384

8485
def raise_on_response_errors(resp, headers, error_unpacking_frames)
86+
metadata = headers.reject { |key, _| GRPC_HEADERS.include?(key) }
8587
status_str = headers[GRPC_STATUS_HEADER]
8688
status_code = status_str.to_i if status_str && status_str == status_str.to_i.to_s
8789

8890
# see https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
8991
if status_code && status_code != 0
90-
raise ::GRPC::BadStatus.new_status_exception(status_code, headers[GRPC_MESSAGE_HEADER])
92+
raise ::GRPC::BadStatus.new_status_exception(
93+
status_code,
94+
headers[GRPC_MESSAGE_HEADER],
95+
metadata,
96+
)
9197
end
9298

9399
case resp
94100
when Net::HTTPBadRequest # 400
95-
raise ::GRPC::Internal, resp.message
101+
raise ::GRPC::Internal.new(resp.message, metadata)
96102
when Net::HTTPUnauthorized # 401
97-
raise ::GRPC::Unauthenticated, resp.message
103+
raise ::GRPC::Unauthenticated.new(resp.message, metadata)
98104
when Net::HTTPForbidden # 403
99-
raise ::GRPC::PermissionDenied, resp.message
105+
raise ::GRPC::PermissionDenied.new(resp.message, metadata)
100106
when Net::HTTPNotFound # 404
101-
raise ::GRPC::Unimplemented, resp.message
107+
raise ::GRPC::Unimplemented.new(resp.message, metadata)
102108
when Net::HTTPTooManyRequests, # 429
103109
Net::HTTPBadGateway, # 502
104110
Net::HTTPServiceUnavailable, # 503
105111
Net::HTTPGatewayTimeOut # 504
106-
raise ::GRPC::Unavailable, resp.message
112+
raise ::GRPC::Unavailable.new(resp.message, metadata)
107113
else
108-
raise ::GRPC::Unknown, resp.message unless resp.is_a?(Net::HTTPSuccess) # 200
109-
raise ::GRPC::Internal, resp.message if error_unpacking_frames
110-
raise ::GRPC::Unknown, resp.message if status_code.nil?
114+
raise ::GRPC::Unknown.new(resp.message, metadata) unless resp.is_a?(Net::HTTPSuccess) # 200
115+
raise ::GRPC::Internal.new(resp.message, metadata) if error_unpacking_frames
116+
raise ::GRPC::Unknown.new(resp.message, metadata) if status_code.nil?
111117
end
112118
end
113119
end

lib/grpc_web/server/message_serialization.rb

+11-5
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def serialize_success_response(response)
5959
def serialize_error_response(response)
6060
ex = response.body
6161
if ex.is_a?(::GRPC::BadStatus)
62-
header_str = generate_headers(ex.code, ex.details)
62+
header_str = generate_headers(ex.code, ex.details, ex.metadata)
6363
else
6464
header_str = generate_headers(
6565
::GRPC::Core::StatusCodes::UNKNOWN,
@@ -72,13 +72,19 @@ def serialize_error_response(response)
7272

7373
# If needed, trailers can be appended to the response as a 2nd
7474
# base64 encoded string with independent framing.
75-
def generate_headers(status, message)
76-
[
75+
def generate_headers(status, message, metadata = {})
76+
headers = [
7777
"grpc-status:#{status}",
7878
"grpc-message:#{message}",
7979
'x-grpc-web:1',
80-
nil, # for trailing newline
81-
].join("\r\n")
80+
]
81+
metadata.each do |name, value|
82+
next if %w[grpc-status grpc-message x-grpc-web].include?(name)
83+
84+
headers << "#{name}:#{value}"
85+
end
86+
headers << nil # for trailing newline
87+
headers.join("\r\n")
8288
end
8389
end
8490
end

spec/integration/envoy_server_ruby_client_spec.rb

+8-2
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,19 @@
3333
let(:service) do
3434
Class.new(TestHelloService) do
3535
def say_hello(_request, _metadata = nil)
36-
raise ::GRPC::InvalidArgument, 'Test message'
36+
raise ::GRPC::InvalidArgument.new(
37+
'Test message',
38+
'metadata' => 'more info',
39+
'envoy' => 'more info',
40+
)
3741
end
3842
end
3943
end
4044

4145
it 'raises an error' do
42-
expect { subject }.to raise_error(GRPC::InvalidArgument, '3:Test message')
46+
expect { subject }.to raise_error(GRPC::InvalidArgument, '3:Test message') do |e|
47+
expect(e.metadata).to eq('metadata' => 'more info', 'envoy' => 'more info')
48+
end
4349
end
4450
end
4551

spec/integration/ruby_server_ruby_client_spec.rb

+8-2
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,19 @@
3838
let(:service) do
3939
Class.new(TestHelloService) do
4040
def say_hello(_request, _metadata = nil)
41-
raise ::GRPC::InvalidArgument, 'Test message'
41+
raise ::GRPC::InvalidArgument.new(
42+
'Test message',
43+
'metadata' => 'more info',
44+
'envoy' => 'more info',
45+
)
4246
end
4347
end
4448
end
4549

4650
it 'raises an error' do
47-
expect { subject }.to raise_error(GRPC::InvalidArgument, '3:Test message')
51+
expect { subject }.to raise_error(GRPC::InvalidArgument, '3:Test message') do |e|
52+
expect(e.metadata).to eq('metadata' => 'more info', 'envoy' => 'more info')
53+
end
4854
end
4955
end
5056

spec/unit/grpc_web/client/client_executor_spec.rb

+6-3
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,16 @@
134134
{
135135
status: 200,
136136
body:
137-
"\x80\x00\x00\x008grpc-status:#{GRPC::Core::StatusCodes::INVALID_ARGUMENT}"\
138-
"\r\ngrpc-message:Test message\r\nx-grpc-web:1\r\n",
137+
"\x80\x00\x00\x00Wgrpc-status:#{GRPC::Core::StatusCodes::INVALID_ARGUMENT}"\
138+
"\r\ngrpc-message:Test message\r\nx-grpc-web:1"\
139+
"\r\nuser-role-id:123\r\nuser-id:456\r\n",
139140
}
140141
end
141142

142143
it 'raises an error' do
143-
expect { response }.to raise_error(GRPC::InvalidArgument)
144+
expect { response }.to raise_error(GRPC::InvalidArgument) do |exception|
145+
expect(exception.metadata).to eq('user-role-id' => '123', 'user-id' => '456')
146+
end
144147
end
145148
end
146149
end

spec/unit/grpc_web/server/message_serialization_spec.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,14 @@
115115
end
116116

117117
context 'when response is a GRPC::BadStatus' do
118-
let(:body) { ::GRPC::NotFound.new('Where am I?') }
118+
let(:body) { ::GRPC::NotFound.new('Where am I?', 'user-role-id' => '123') }
119119

120120
it_behaves_like 'generates a body without a payload frame'
121121

122122
it_behaves_like(
123123
'generates a body with a header frame',
124124
expected_header_frame_body:
125-
"grpc-status:5\r\ngrpc-message:Where am I?\r\nx-grpc-web:1\r\n",
125+
"grpc-status:5\r\ngrpc-message:Where am I?\r\nx-grpc-web:1\r\nuser-role-id:123\r\n",
126126
)
127127
end
128128
end

0 commit comments

Comments
 (0)