twirp 1.7.2 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f17d89c29073da7ebffa780c9e8577e10f2537e28900344d8a055e098aa5f3d
4
- data.tar.gz: 2932fb1abec1db6b14c3b39e8b7d2f81e318c0bdc02639d83a99bdfacd26001f
3
+ metadata.gz: 2564512dae1e9dcb9345119a0e09ffa0a65bba37c3800a9c6ac6c77c900701cf
4
+ data.tar.gz: da232ae6c1526bb650fbc32b106191c533e8aa6c11b682d6d28044d612f0def8
5
5
  SHA512:
6
- metadata.gz: aca9edc089a15abfe4426880e8db8a8e2cac3f47ff056703e686dc14a3fe879a952a2b96c2de8bb06301b7406262601102c29fe48af6f9ead8775e9e2c76e35b
7
- data.tar.gz: 10fc31c496e2fa4020ff9a643e08aa9ba333b0baa2324d1a42444ca6916d672a449eeef7ab22e674de81cc7a3e906d82fc97736714dfab29a999bfb4dd191f75
6
+ metadata.gz: 41cbdba9e68278362f8674d6b28336c97a13da955535abbbc5b148f006cee1e1413f76df3daa9e93fb1c80297fcfa7d6c1828591a44680bcd4025ca340f0aacc
7
+ data.tar.gz: b381e1be9c43e0836bf47f60b98e14dc14cdf5ceb444597abd8cc7587e8162b4f7c8feff4f821e50cafa9a68e5db060359c474d8c2d30a5903a16d6e5db24454
data/lib/twirp/client.rb CHANGED
@@ -160,6 +160,31 @@ module Twirp
160
160
  body = Encoding.encode(input, rpcdef[:input_class], content_type)
161
161
 
162
162
  resp = self.class.make_http_request(@conn, @service_full_name, rpc_method, content_type, req_opts, body)
163
+
164
+ rpc_response_thennable(resp) do |resp|
165
+ rpc_response_to_clientresp(resp, content_type, rpcdef)
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ # Executes the post-processing on 'resp' in the provided block,
172
+ # either in a call to ".then { ... }" as a promise-like interface,
173
+ # or just execute it directly if the object doesn't support "then".
174
+ # On Ruby 2.6+ all objects have ".then", but they behave the same as
175
+ # a promise-like object, so this behaves identically.
176
+ #
177
+ # This allows extensions of Faraday that return promises to work
178
+ # natively with twirp-ruby. For normal Faraday, this is a noop.
179
+ def rpc_response_thennable(resp)
180
+ return yield resp unless resp.respond_to?(:then)
181
+
182
+ resp.then do |resp|
183
+ yield resp
184
+ end
185
+ end
186
+
187
+ def rpc_response_to_clientresp(resp, content_type, rpcdef)
163
188
  if resp.status != 200
164
189
  return ClientResp.new(nil, self.class.error_from_response(resp))
165
190
  end
@@ -36,6 +36,15 @@ module Twirp
36
36
 
37
37
  encoding = @strict ? Encoding::JSON_STRICT : Encoding::JSON
38
38
  resp = self.class.make_http_request(@conn, @service_full_name, rpc_method, encoding, req_opts, body)
39
+
40
+ rpc_response_thennable(resp) do |resp|
41
+ rpc_response_to_clientresp(resp)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def rpc_response_to_clientresp(resp)
39
48
  if resp.status != 200
40
49
  return ClientResp.new(nil, self.class.error_from_response(resp))
41
50
  end
data/lib/twirp/service.rb CHANGED
@@ -136,7 +136,9 @@ module Twirp
136
136
 
137
137
  input = nil
138
138
  begin
139
- input = Encoding.decode(rack_request.body.read, env[:input_class], content_type)
139
+ body_str = rack_request.body.read
140
+ rack_request.body.rewind # allow other middleware to read again (https://github.com/twitchtv/twirp-ruby/issues/50)
141
+ input = Encoding.decode(body_str, env[:input_class], content_type)
140
142
  rescue => e
141
143
  error_msg = "Invalid request body for rpc method #{method_name.inspect} with Content-Type=#{content_type}"
142
144
  if e.is_a?(Google::Protobuf::ParseError)
data/lib/twirp/version.rb CHANGED
@@ -12,5 +12,5 @@
12
12
  # permissions and limitations under the License.
13
13
 
14
14
  module Twirp
15
- VERSION = "1.7.2"
15
+ VERSION = "1.9.0"
16
16
  end
@@ -29,6 +29,30 @@ class ClientJSONTest < Minitest::Test
29
29
  assert_equal 3, resp.data["blah_resp"]
30
30
  end
31
31
 
32
+ def test_client_json_thennable
33
+ c = Twirp::ClientJSON.new(conn_stub_thennable("/my.pkg.Talking/Blah") {|req|
34
+ assert_equal "application/json", req.request_headers['Content-Type']
35
+ assert_equal '{"blah1":1,"blah2":2}', req.body # body is json
36
+
37
+ [200, {}, '{"blah_resp": 3}']
38
+ }, package: "my.pkg", service: "Talking")
39
+
40
+ resp_thennable = c.rpc :Blah, blah1: 1, blah2: 2
41
+ # the final `.then {}` call will yield a ClientResp
42
+ assert resp_thennable.is_a?(Thennable)
43
+ resp = resp_thennable.value
44
+ assert resp.is_a?(Twirp::ClientResp)
45
+
46
+ # the final Thennable will have come from one with a faraday response
47
+ assert resp_thennable.parent.is_a?(Thennable)
48
+ assert resp_thennable.parent.value.is_a?(Faraday::Response)
49
+
50
+ # the final ClientResp should look the same as when then isn't used
51
+ assert_nil resp.error
52
+ refute_nil resp.data
53
+ assert_equal 3, resp.data["blah_resp"]
54
+ end
55
+
32
56
  def test_client_json_strict_encoding
33
57
  c = Twirp::ClientJSON.new(conn_stub("/my.pkg.Talking/Blah") {|req|
34
58
  assert_equal "application/json; strict=true", req.request_headers['Content-Type']
@@ -77,4 +101,31 @@ class ClientJSONTest < Minitest::Test
77
101
  end
78
102
  end
79
103
 
104
+ # mock of a promise-like thennable, allowing a call to ".then" to get the real value
105
+ class Thennable
106
+ attr_reader :value, :parent
107
+
108
+ def initialize(value, parent = nil)
109
+ @value = value
110
+ @parent = parent
111
+ end
112
+
113
+ def then(&block)
114
+ # similar to a promise, but runs immediately
115
+ Thennable.new(block.call(@value), self)
116
+ end
117
+ end
118
+
119
+ module ThennableFaraday
120
+ def post(*)
121
+ Thennable.new(super)
122
+ end
123
+ end
124
+
125
+ def conn_stub_thennable(path, &block)
126
+ s = conn_stub(path, &block)
127
+ s.extend(ThennableFaraday)
128
+ s
129
+ end
130
+
80
131
  end
data/test/client_test.rb CHANGED
@@ -17,7 +17,7 @@ class ClientTest < Minitest::Test
17
17
 
18
18
  def test_new_with_invalid_url
19
19
  assert_raises URI::InvalidURIError do
20
- EmptyClient.new("lulz")
20
+ EmptyClient.new("invalid uri with unescaped spaces")
21
21
  end
22
22
  end
23
23
 
@@ -72,6 +72,27 @@ class ClientTest < Minitest::Test
72
72
  assert_equal "red", resp.data.color
73
73
  end
74
74
 
75
+ def test_proto_thennable
76
+ c = Example::HaberdasherClient.new(conn_stub_thennable("/example.Haberdasher/MakeHat") {|req|
77
+ [200, protoheader, proto(Example::Hat, inches: 99, color: "red")]
78
+ })
79
+ resp_thennable = c.make_hat({})
80
+
81
+ # the final `.then {}` call will yield a ClientResp
82
+ assert resp_thennable.is_a?(Thennable)
83
+ resp = resp_thennable.value
84
+ assert resp.is_a?(Twirp::ClientResp)
85
+
86
+ # the final Thennable will have come from one with a faraday response
87
+ assert resp_thennable.parent.is_a?(Thennable)
88
+ assert resp_thennable.parent.value.is_a?(Faraday::Response)
89
+
90
+ # the final ClientResp should look the same as when then isn't used
91
+ assert_nil resp.error
92
+ assert_equal 99, resp.data.inches
93
+ assert_equal "red", resp.data.color
94
+ end
95
+
75
96
  def test_proto_send_headers
76
97
  c = Example::HaberdasherClient.new(conn_stub("/example.Haberdasher/MakeHat") {|req|
77
98
  assert_equal "Bar", req.request_headers['My-Foo-Header']
@@ -196,6 +217,27 @@ class ClientTest < Minitest::Test
196
217
  assert_equal "red", resp.data.color
197
218
  end
198
219
 
220
+ def test_json_thennable
221
+ c = Example::HaberdasherClient.new(conn_stub_thennable("/example.Haberdasher/MakeHat") {|req|
222
+ [200, jsonheader, '{"inches": 99, "color": "red"}']
223
+ }, content_type: "application/json")
224
+
225
+ resp_thennable = c.make_hat({})
226
+ # the final `.then {}` call will yield a ClientResp
227
+ assert resp_thennable.is_a?(Thennable)
228
+ resp = resp_thennable.value
229
+ assert resp.is_a?(Twirp::ClientResp)
230
+
231
+ # the final Thennable will have come from one with a faraday response
232
+ assert resp_thennable.parent.is_a?(Thennable)
233
+ assert resp_thennable.parent.value.is_a?(Faraday::Response)
234
+
235
+ # the final ClientResp should look the same as when then isn't used
236
+ assert_nil resp.error
237
+ assert_equal 99, resp.data.inches
238
+ assert_equal "red", resp.data.color
239
+ end
240
+
199
241
  def test_json_send_headers
200
242
  c = Example::HaberdasherClient.new(conn_stub("/example.Haberdasher/MakeHat") {|req|
201
243
  assert_equal "Bar", req.request_headers['My-Foo-Header']
@@ -337,4 +379,31 @@ class ClientTest < Minitest::Test
337
379
  end
338
380
  end
339
381
 
382
+ # mock of a promise-like thennable, allowing a call to ".then" to get the real value
383
+ class Thennable
384
+ attr_reader :value, :parent
385
+
386
+ def initialize(value, parent = nil)
387
+ @value = value
388
+ @parent = parent
389
+ end
390
+
391
+ def then(&block)
392
+ # similar to a promise, but runs immediately
393
+ Thennable.new(block.call(@value), self)
394
+ end
395
+ end
396
+
397
+ module ThennableFaraday
398
+ def post(*)
399
+ Thennable.new(super)
400
+ end
401
+ end
402
+
403
+ def conn_stub_thennable(path, &block)
404
+ s = conn_stub(path, &block)
405
+ s.extend(ThennableFaraday)
406
+ s
407
+ end
408
+
340
409
  end
data/test/service_test.rb CHANGED
@@ -168,7 +168,7 @@ class ServiceTest < Minitest::Test
168
168
  assert_equal({
169
169
  "code" => 'malformed',
170
170
  "msg" => 'Invalid request body for rpc method "MakeHat" with Content-Type=application/json: ' +
171
- "Error occurred during parsing: Parse error at 'bad json'",
171
+ "Error occurred during parsing: Error parsing JSON @1:0: Expected: '{'",
172
172
  "meta" => {"twirp_invalid_route" => "POST /example.Haberdasher/MakeHat"},
173
173
  }, JSON.parse(body[0]))
174
174
  end
@@ -183,7 +183,7 @@ class ServiceTest < Minitest::Test
183
183
  assert_equal({
184
184
  "code" => 'malformed',
185
185
  "msg" => 'Invalid request body for rpc method "MakeHat" with Content-Type=application/protobuf: ' +
186
- 'Error occurred during parsing: Unexpected EOF inside skipped data',
186
+ 'Error occurred during parsing',
187
187
  "meta" => {"twirp_invalid_route" => "POST /example.Haberdasher/MakeHat"},
188
188
  }, JSON.parse(body[0]))
189
189
  end
@@ -206,7 +206,7 @@ class ServiceTest < Minitest::Test
206
206
  assert_equal({
207
207
  "code" => 'malformed',
208
208
  "msg" => 'Invalid request body for rpc method "MakeHat" with Content-Type=application/json; strict=true: ' +
209
- "Error occurred during parsing: No such field: fake",
209
+ "Error occurred during parsing: Error parsing JSON @1:20: No such field: fake",
210
210
  "meta" => {"twirp_invalid_route" => "POST /example.Haberdasher/MakeHat"},
211
211
  }, JSON.parse(body[0]))
212
212
  end
@@ -850,4 +850,3 @@ class ServiceTest < Minitest::Test
850
850
  end)
851
851
  end
852
852
  end
853
-
data/twirp.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.add_runtime_dependency 'google-protobuf', '~> 3.0', '>= 3.7.0'
23
23
  spec.add_runtime_dependency 'faraday', '< 2' # for clients
24
24
 
25
- spec.add_development_dependency 'bundler', '~> 1'
25
+ spec.add_development_dependency 'bundler', '~> 2'
26
26
  spec.add_development_dependency 'minitest', '>= 5'
27
27
  spec.add_development_dependency 'rake'
28
28
  spec.add_development_dependency 'rack', '>= 2.2.3'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twirp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyrus A. Forbes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-11-24 00:00:00.000000000 Z
12
+ date: 2021-10-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: google-protobuf
@@ -51,14 +51,14 @@ dependencies:
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1'
54
+ version: '2'
55
55
  type: :development
56
56
  prerelease: false
57
57
  version_requirements: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1'
61
+ version: '2'
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: minitest
64
64
  requirement: !ruby/object:Gem::Requirement