twirp 0.4.1 → 0.5.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 +4 -4
- data/README.md +37 -22
- data/lib/twirp.rb +1 -0
- data/lib/twirp/client.rb +3 -35
- data/lib/twirp/client_json.rb +39 -0
- data/lib/twirp/client_resp.rb +11 -0
- data/lib/twirp/version.rb +1 -1
- data/test/client_json_test.rb +67 -0
- data/test/client_test.rb +25 -53
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31b1acc1215a5040a22d6ef3715103a8aa6840c9
|
4
|
+
data.tar.gz: 9cf27b5d6c7232bd6ccbeaf4b54211d97dc1cf45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af3f533423755f9d9ea5e27194a7f7efc857f1dbc4268a3d08548315651b14564f63ea8533b061c9b3c3efc261200c74c7b108696d99905a4ea8eed3c5f47b96
|
7
|
+
data.tar.gz: daf1b600b9df7240fbd99c1289be16f98f60ca0322ce23cfe9c3490611ecf99293d06a6b800ae8221f33c09cc1e0bbb65f536cfeab5b7d5beab678aaeb0590c5
|
data/README.md
CHANGED
@@ -55,7 +55,7 @@ protoc --proto_path=. --ruby_out=. --twirp_ruby_out=. ./example/hello_world/serv
|
|
55
55
|
|
56
56
|
## Twirp Service Handler
|
57
57
|
|
58
|
-
A handler
|
58
|
+
A handler implements the rpc methods. For example a handler for `HelloWorldService`:
|
59
59
|
|
60
60
|
```ruby
|
61
61
|
class HelloWorldHandler
|
@@ -71,14 +71,16 @@ class HelloWorldHandler
|
|
71
71
|
end
|
72
72
|
```
|
73
73
|
|
74
|
-
|
74
|
+
For each rpc method:
|
75
75
|
|
76
|
-
The `
|
76
|
+
* The `req` argument is the input request message, already serialized.
|
77
|
+
* The `env` argument is the Twirp environment with data related to the request (e.g. `env[:output_class]`), and other fields that could have been set from before-hooks (e.g. `env[:user_id]` from authentication).
|
78
|
+
* The returned value is expected to be the response message (or its attributes), or a `Twirp::Error`.
|
77
79
|
|
78
80
|
|
79
|
-
|
81
|
+
#### Start the Service
|
80
82
|
|
81
|
-
The service is a [Rack app](https://rack.github.io/)
|
83
|
+
Instantiate the service with your handler impementation. The service is a [Rack app](https://rack.github.io/). For example:
|
82
84
|
|
83
85
|
```ruby
|
84
86
|
handler = HelloWorldHandler.new()
|
@@ -90,7 +92,7 @@ Rack::Handler::WEBrick.run service
|
|
90
92
|
|
91
93
|
Rack apps can also be mounted as Rails routes (e.g. `mount service, at: service.full_name`) and are compatible with many other HTTP frameworks.
|
92
94
|
|
93
|
-
|
95
|
+
#### Unit Tests
|
94
96
|
|
95
97
|
Twirp already takes care of HTTP routing and serialization, you don't really need to test that part, insteadof that, focus on testing the handler using the method `.call_rpc(rpc_method, attrs={}, env={})` on the service:
|
96
98
|
|
@@ -119,26 +121,37 @@ end
|
|
119
121
|
|
120
122
|
## Twirp Clients
|
121
123
|
|
122
|
-
|
124
|
+
Instantiate a client with the service base url:
|
123
125
|
|
124
126
|
```ruby
|
125
|
-
|
126
|
-
resp = c.hello(name: "World") # serialized as Protobuf
|
127
|
+
client = Example::HelloWorldClient.new("http://localhost:3000")
|
127
128
|
```
|
128
129
|
|
129
|
-
|
130
|
+
Clients implement the same methods as the service handler. For example the client for `HelloWorldService` implements the `hello` method:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
resp = client.hello(name: "World")
|
134
|
+
```
|
135
|
+
|
136
|
+
As an alternative, in case that a service method collides with a Ruby method, you can always use the more general `.rpc` method:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
resp = client.rpc(:Hello, name: "World") # alternative
|
140
|
+
```
|
141
|
+
|
142
|
+
If the request fails, the response has an `error` with a `Twirp::Error`. If the request succeeds, the response has `data` with an instance of the response message class.
|
130
143
|
|
131
144
|
```ruby
|
132
145
|
if resp.error
|
133
|
-
puts resp.error
|
146
|
+
puts resp.error # <Twirp::Error code:... msg:"..." meta:{...}>
|
134
147
|
else
|
135
|
-
puts resp.data
|
148
|
+
puts resp.data # <Example::HelloResponse: message:"Hello World">
|
136
149
|
end
|
137
150
|
```
|
138
151
|
|
139
152
|
#### Configure Clients with Faraday
|
140
153
|
|
141
|
-
While Twirp takes care of routing, serialization and error handling, other advanced HTTP options can be configured with [Faraday](https://github.com/lostisland/faraday) middleware. Clients can be initialized with a Faraday connection:
|
154
|
+
While Twirp takes care of routing, serialization and error handling, other advanced HTTP options can be configured with [Faraday](https://github.com/lostisland/faraday) middleware. Clients can be initialized with a Faraday connection. For example:
|
142
155
|
|
143
156
|
```ruby
|
144
157
|
conn = Faraday.new(:url => 'http://localhost:3000') do |c|
|
@@ -148,25 +161,27 @@ conn = Faraday.new(:url => 'http://localhost:3000') do |c|
|
|
148
161
|
c.use Faraday::Adapter::NetHttp # can use different HTTP libraries
|
149
162
|
end
|
150
163
|
|
151
|
-
|
164
|
+
client = Example::HelloWorldClient.new(conn)
|
152
165
|
```
|
153
166
|
|
154
167
|
#### Protobuf or JSON
|
155
168
|
|
156
|
-
|
169
|
+
Protobuf is used by default. To serialize with JSON, set the `content_type` option as 2nd argument:
|
157
170
|
|
158
171
|
```ruby
|
159
|
-
|
160
|
-
resp =
|
172
|
+
client = Example::HelloWorldClient.new(conn, content_type: "application/json")
|
173
|
+
resp = client.hello(name: "World") # serialized with JSON
|
161
174
|
```
|
162
175
|
|
163
176
|
#### Add-hoc JSON requests
|
164
177
|
|
165
|
-
If you just want to make a few quick requests from the console, you can
|
178
|
+
If you just want to make a few quick requests from the console, you can make a `ClientJSON` instance. This doesn't require a service definition at all, but in the other hand, request and response values are not validated. Responses are just a Hash with attributes.
|
166
179
|
|
167
180
|
```ruby
|
168
|
-
|
169
|
-
|
181
|
+
require 'twirp'
|
182
|
+
client = Twirp::ClientJSON.new(conn, package: "example", service: "HelloWorld")
|
183
|
+
resp = client.rpc(:Hello, name: "World") # serialized with JSON
|
184
|
+
puts resp # resp.data is a plain Hash
|
170
185
|
```
|
171
186
|
|
172
187
|
|
@@ -190,10 +205,10 @@ routing -> before -> handler
|
|
190
205
|
! exception_raised -> on_error
|
191
206
|
```
|
192
207
|
|
193
|
-
Hooks are setup in the service instance:
|
208
|
+
Hooks are setup in the service instance. For example:
|
194
209
|
|
195
210
|
```ruby
|
196
|
-
svc = Example::
|
211
|
+
svc = Example::HelloWorldService.new(handler)
|
197
212
|
|
198
213
|
svc.before do |rack_env, env|
|
199
214
|
# Runs if properly routed to an rpc method, but before calling the method handler.
|
data/lib/twirp.rb
CHANGED
data/lib/twirp/client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'faraday'
|
2
2
|
|
3
|
+
require_relative 'client_resp'
|
3
4
|
require_relative 'encoding'
|
4
5
|
require_relative 'error'
|
5
6
|
require_relative 'service_dsl'
|
@@ -117,11 +118,7 @@ module Twirp
|
|
117
118
|
raise ArgumentError.new("Invalid content_type #{@content_type.inspect}. Expected one of #{Encoding.valid_content_types.inspect}")
|
118
119
|
end
|
119
120
|
|
120
|
-
@service_full_name =
|
121
|
-
opts[:package].to_s.empty? ? opts[:service].to_s : "#{opts[:package]}.#{opts[:service]}"
|
122
|
-
else
|
123
|
-
self.class.service_full_name # defined through DSL
|
124
|
-
end
|
121
|
+
@service_full_name = self.class.service_full_name # defined through DSL
|
125
122
|
end
|
126
123
|
|
127
124
|
# Make a remote procedure call to a defined rpc_method. The input can be a Proto message instance,
|
@@ -140,6 +137,7 @@ module Twirp
|
|
140
137
|
resp = @conn.post do |r|
|
141
138
|
r.url "/#{@service_full_name}/#{rpc_method}"
|
142
139
|
r.headers['Content-Type'] = @content_type
|
140
|
+
r.headers['Accept'] = @content_type
|
143
141
|
r.body = body
|
144
142
|
end
|
145
143
|
|
@@ -155,35 +153,5 @@ module Twirp
|
|
155
153
|
return ClientResp.new(data, nil)
|
156
154
|
end
|
157
155
|
|
158
|
-
# Convenience method to call any rpc method with dynamic json attributes.
|
159
|
-
# It is like .rpc but does not use the defined Protobuf messages to serialize/deserialize data;
|
160
|
-
# the request attrs can be anything and the response data is always a plain Hash of attributes.
|
161
|
-
# This is useful to test a service before doing any code-generation.
|
162
|
-
def json(rpc_method, attrs={})
|
163
|
-
body = Encoding.encode_json(attrs)
|
164
|
-
|
165
|
-
resp = @conn.post do |r|
|
166
|
-
r.url "/#{@service_full_name}/#{rpc_method}"
|
167
|
-
r.headers['Content-Type'] = Encoding::JSON
|
168
|
-
r.body = body
|
169
|
-
end
|
170
|
-
|
171
|
-
if resp.status != 200
|
172
|
-
return ClientResp.new(nil, self.class.error_from_response(resp))
|
173
|
-
end
|
174
|
-
|
175
|
-
data = Encoding.decode_json(resp.body)
|
176
|
-
return ClientResp.new(data, nil)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
class ClientResp
|
181
|
-
attr_accessor :data
|
182
|
-
attr_accessor :error
|
183
|
-
|
184
|
-
def initialize(data, error)
|
185
|
-
@data = data
|
186
|
-
@error = error
|
187
|
-
end
|
188
156
|
end
|
189
157
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative 'client'
|
2
|
+
|
3
|
+
module Twirp
|
4
|
+
|
5
|
+
# Convenience class to call any rpc method with dynamic json attributes, without a service definition.
|
6
|
+
# This is useful to test a service before doing any code-generation.
|
7
|
+
class ClientJSON < Twirp::Client
|
8
|
+
|
9
|
+
def initialize(conn, opts={})
|
10
|
+
super(conn, opts)
|
11
|
+
|
12
|
+
package = opts[:package].to_s
|
13
|
+
service = opts[:service].to_s
|
14
|
+
raise ArgumentError.new("Missing option :service") if service.empty?
|
15
|
+
@service_full_name = package.empty? ? service : "#{package}.#{service}"
|
16
|
+
end
|
17
|
+
|
18
|
+
# This implementation does not use the defined Protobuf messages to serialize/deserialize data;
|
19
|
+
# the request attrs can be anything and the response data is always a plain Hash of attributes.
|
20
|
+
def rpc(rpc_method, attrs={})
|
21
|
+
body = Encoding.encode_json(attrs)
|
22
|
+
|
23
|
+
resp = @conn.post do |r|
|
24
|
+
r.url "/#{@service_full_name}/#{rpc_method}"
|
25
|
+
r.headers['Content-Type'] = Encoding::JSON
|
26
|
+
r.headers['Accept'] = Encoding::JSON
|
27
|
+
r.body = body
|
28
|
+
end
|
29
|
+
|
30
|
+
if resp.status != 200
|
31
|
+
return ClientResp.new(nil, self.class.error_from_response(resp))
|
32
|
+
end
|
33
|
+
|
34
|
+
data = Encoding.decode_json(resp.body)
|
35
|
+
return ClientResp.new(data, nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/twirp/version.rb
CHANGED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'google/protobuf'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
require_relative '../lib/twirp/client_json'
|
7
|
+
require_relative './fake_services'
|
8
|
+
|
9
|
+
class ClientJSONTest < Minitest::Test
|
10
|
+
|
11
|
+
def test_client_json_requires_service
|
12
|
+
assert_raises ArgumentError do
|
13
|
+
Twirp::ClientJSON.new("http://localhost:8080") # missing service
|
14
|
+
end
|
15
|
+
Twirp::ClientJSON.new("http://localhost:8080", service: "FooBar") # ok
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_client_json_success
|
19
|
+
c = Twirp::ClientJSON.new(conn_stub("/my.pkg.Talking/Blah") {|req|
|
20
|
+
assert_equal "application/json", req.request_headers['Content-Type']
|
21
|
+
assert_equal "application/json", req.request_headers['Accept']
|
22
|
+
assert_equal '{"blah1":1,"blah2":2}', req.body # body is json
|
23
|
+
|
24
|
+
[200, {}, '{"blah_resp": 3}']
|
25
|
+
}, package: "my.pkg", service: "Talking")
|
26
|
+
|
27
|
+
resp = c.rpc :Blah, blah1: 1, blah2: 2
|
28
|
+
assert_nil resp.error
|
29
|
+
refute_nil resp.data
|
30
|
+
assert_equal 3, resp.data["blah_resp"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_client_json_error
|
34
|
+
c = Twirp::ClientJSON.new(conn_stub("/Foo/Foomo") {|req|
|
35
|
+
[400, {}, '{"code": "invalid_argument", "msg": "dont like empty"}']
|
36
|
+
}, service: "Foo")
|
37
|
+
|
38
|
+
resp = c.rpc :Foomo, foo: ""
|
39
|
+
assert_nil resp.data
|
40
|
+
refute_nil resp.error
|
41
|
+
assert_equal :invalid_argument, resp.error.code
|
42
|
+
assert_equal "dont like empty", resp.error.msg
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_client_bad_json_route
|
46
|
+
c = Twirp::ClientJSON.new(conn_stub("/Foo/OtherMethod") {|req|
|
47
|
+
[404, {}, 'not here buddy']
|
48
|
+
}, service: "Foo")
|
49
|
+
|
50
|
+
resp = c.rpc :OtherMethod, foo: ""
|
51
|
+
assert_nil resp.data
|
52
|
+
refute_nil resp.error
|
53
|
+
assert_equal :bad_route, resp.error.code
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def conn_stub(path)
|
58
|
+
Faraday.new do |conn|
|
59
|
+
conn.adapter :test do |stub|
|
60
|
+
stub.post(path) do |env|
|
61
|
+
yield(env)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
data/test/client_test.rb
CHANGED
@@ -9,7 +9,7 @@ require_relative './fake_services'
|
|
9
9
|
class ClientTest < Minitest::Test
|
10
10
|
|
11
11
|
def test_new_empty_client
|
12
|
-
c = EmptyClient.new("http://localhost:
|
12
|
+
c = EmptyClient.new("http://localhost:8080")
|
13
13
|
refute_nil c
|
14
14
|
refute_nil c.instance_variable_get(:@conn) # make sure that connection was assigned
|
15
15
|
assert_equal "EmptyClient", c.instance_variable_get(:@service_full_name)
|
@@ -27,22 +27,31 @@ class ClientTest < Minitest::Test
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
31
|
-
# To avoid collisions, the Twirp::Client class should have
|
32
|
-
|
33
|
-
assert_equal [:json, :rpc], mthds
|
30
|
+
def test_dsl_rpc_method_definition_collisions
|
31
|
+
# To avoid collisions, the Twirp::Client class should only have the rpc method.
|
32
|
+
assert_equal [:rpc], Twirp::Client.instance_methods(false)
|
34
33
|
|
35
|
-
# If one of the methods is being implemented through the DSL, the colision should be avoided
|
34
|
+
# If one of the methods is being implemented through the DSL, the colision should be avoided, keeping the previous method.
|
36
35
|
num_mthds = EmptyClient.instance_methods.size
|
37
|
-
EmptyClient.rpc :
|
38
|
-
assert_equal num_mthds, EmptyClient.instance_methods.size # no new method was added (collision)
|
36
|
+
EmptyClient.rpc :Rpc, Example::Empty, Example::Empty, :ruby_method => :rpc
|
37
|
+
assert_equal num_mthds, EmptyClient.instance_methods.size # no new method was added (is a collision)
|
39
38
|
|
40
|
-
# Make sure that the previous .
|
41
|
-
c = EmptyClient.new(conn_stub("/EmptyClient/
|
42
|
-
[200,
|
39
|
+
# Make sure that the previous .rpc method was not modified
|
40
|
+
c = EmptyClient.new(conn_stub("/EmptyClient/Rpc") {|req|
|
41
|
+
[200, protoheader, proto(Example::Empty, {})]
|
43
42
|
})
|
44
|
-
resp = c.
|
45
|
-
|
43
|
+
resp = c.rpc(:Rpc, {})
|
44
|
+
assert_nil resp.error
|
45
|
+
refute_nil resp.data
|
46
|
+
|
47
|
+
# Adding a method that would override super-class methods like .to_s should also be avoided.
|
48
|
+
EmptyClient.rpc :ToString, Example::Empty, Example::Empty, :ruby_method => :to_s
|
49
|
+
assert_equal num_mthds, EmptyClient.instance_methods.size # no new method was added (is a collision)
|
50
|
+
|
51
|
+
# Make sure that the previous .to_s method was not modified
|
52
|
+
c = EmptyClient.new("http://localhost:8080")
|
53
|
+
resp = c.to_s
|
54
|
+
assert_equal String, resp.class
|
46
55
|
|
47
56
|
# Adding any other rpc would work as expected
|
48
57
|
EmptyClient.rpc :Other, Example::Empty, Example::Empty, :ruby_method => :other
|
@@ -78,6 +87,7 @@ class ClientTest < Minitest::Test
|
|
78
87
|
def test_proto_serialized_request_body
|
79
88
|
c = Example::HaberdasherClient.new(conn_stub("/example.Haberdasher/MakeHat") {|req|
|
80
89
|
assert_equal "application/protobuf", req.request_headers['Content-Type']
|
90
|
+
assert_equal "application/protobuf", req.request_headers['Accept']
|
81
91
|
|
82
92
|
size = Example::Size.decode(req.body) # body is valid protobuf
|
83
93
|
assert_equal 666, size.inches
|
@@ -179,6 +189,7 @@ class ClientTest < Minitest::Test
|
|
179
189
|
def test_json_serialized_request_body_attrs
|
180
190
|
c = Example::HaberdasherClient.new(conn_stub("/example.Haberdasher/MakeHat") {|req|
|
181
191
|
assert_equal "application/json", req.request_headers['Content-Type']
|
192
|
+
assert_equal "application/json", req.request_headers['Accept']
|
182
193
|
assert_equal '{"inches":666}', req.body # body is valid json
|
183
194
|
[200, jsonheader, '{}']
|
184
195
|
}, content_type: "application/json")
|
@@ -191,6 +202,7 @@ class ClientTest < Minitest::Test
|
|
191
202
|
def test_json_serialized_request_body_object
|
192
203
|
c = Example::HaberdasherClient.new(conn_stub("/example.Haberdasher/MakeHat") {|req|
|
193
204
|
assert_equal "application/json", req.request_headers['Content-Type']
|
205
|
+
assert_equal "application/json", req.request_headers['Accept']
|
194
206
|
assert_equal '{"inches":666}', req.body # body is valid json
|
195
207
|
[200, jsonheader, '{}']
|
196
208
|
}, content_type: "application/json")
|
@@ -265,46 +277,6 @@ class ClientTest < Minitest::Test
|
|
265
277
|
assert_equal :bad_route, resp.error.code
|
266
278
|
end
|
267
279
|
|
268
|
-
# Call .json
|
269
|
-
# ----------
|
270
|
-
|
271
|
-
def test_direct_json_success
|
272
|
-
c = Twirp::Client.new(conn_stub("/my.pkg.Talking/Blah") {|req|
|
273
|
-
assert_equal "application/json", req.request_headers['Content-Type']
|
274
|
-
assert_equal '{"blah1":1,"blah2":2}', req.body # body is json
|
275
|
-
|
276
|
-
[200, {}, json(blah_resp: 3)]
|
277
|
-
}, package: "my.pkg", service: "Talking")
|
278
|
-
|
279
|
-
resp = c.json :Blah, blah1: 1, blah2: 2
|
280
|
-
assert_nil resp.error
|
281
|
-
refute_nil resp.data
|
282
|
-
assert_equal 3, resp.data["blah_resp"]
|
283
|
-
end
|
284
|
-
|
285
|
-
def test_direct_json_error
|
286
|
-
c = Twirp::Client.new(conn_stub("/Foo/Foomo") {|req|
|
287
|
-
[400, {}, json(code: "invalid_argument", msg: "dont like empty")]
|
288
|
-
}, service: "Foo")
|
289
|
-
|
290
|
-
resp = c.json :Foomo, foo: ""
|
291
|
-
assert_nil resp.data
|
292
|
-
refute_nil resp.error
|
293
|
-
assert_equal :invalid_argument, resp.error.code
|
294
|
-
assert_equal "dont like empty", resp.error.msg
|
295
|
-
end
|
296
|
-
|
297
|
-
def test_direct_json_bad_route
|
298
|
-
c = Twirp::Client.new(conn_stub("/Foo/OtherMethod") {|req|
|
299
|
-
[404, {}, 'not here buddy']
|
300
|
-
}, service: "Foo")
|
301
|
-
|
302
|
-
resp = c.json :OtherMethod, foo: ""
|
303
|
-
assert_nil resp.data
|
304
|
-
refute_nil resp.error
|
305
|
-
assert_equal :bad_route, resp.error.code
|
306
|
-
end
|
307
|
-
|
308
280
|
|
309
281
|
# Test Helpers
|
310
282
|
# ------------
|
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: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyrus A. Forbes
|
@@ -73,11 +73,14 @@ files:
|
|
73
73
|
- README.md
|
74
74
|
- lib/twirp.rb
|
75
75
|
- lib/twirp/client.rb
|
76
|
+
- lib/twirp/client_json.rb
|
77
|
+
- lib/twirp/client_resp.rb
|
76
78
|
- lib/twirp/encoding.rb
|
77
79
|
- lib/twirp/error.rb
|
78
80
|
- lib/twirp/service.rb
|
79
81
|
- lib/twirp/service_dsl.rb
|
80
82
|
- lib/twirp/version.rb
|
83
|
+
- test/client_json_test.rb
|
81
84
|
- test/client_test.rb
|
82
85
|
- test/error_test.rb
|
83
86
|
- test/fake_services.rb
|
@@ -108,6 +111,7 @@ signing_key:
|
|
108
111
|
specification_version: 4
|
109
112
|
summary: Twirp services in Ruby.
|
110
113
|
test_files:
|
114
|
+
- test/client_json_test.rb
|
111
115
|
- test/client_test.rb
|
112
116
|
- test/error_test.rb
|
113
117
|
- test/fake_services.rb
|