twirp 1.7.2 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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