twirp 1.2.0 → 1.7.2
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 +5 -5
- data/README.md +5 -3
- data/lib/twirp/client_json.rb +3 -1
- data/lib/twirp/encoding.rb +8 -3
- data/lib/twirp/error.rb +10 -8
- data/lib/twirp/service.rb +24 -24
- data/lib/twirp/version.rb +1 -1
- data/test/client_json_test.rb +14 -0
- data/test/error_test.rb +10 -10
- data/test/service_test.rb +60 -4
- data/twirp.gemspec +3 -2
- metadata +25 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3f17d89c29073da7ebffa780c9e8577e10f2537e28900344d8a055e098aa5f3d
|
4
|
+
data.tar.gz: 2932fb1abec1db6b14c3b39e8b7d2f81e318c0bdc02639d83a99bdfacd26001f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aca9edc089a15abfe4426880e8db8a8e2cac3f47ff056703e686dc14a3fe879a952a2b96c2de8bb06301b7406262601102c29fe48af6f9ead8775e9e2c76e35b
|
7
|
+
data.tar.gz: 10fc31c496e2fa4020ff9a643e08aa9ba333b0baa2324d1a42444ca6916d672a449eeef7ab22e674de81cc7a3e906d82fc97736714dfab29a999bfb4dd191f75
|
data/README.md
CHANGED
@@ -2,19 +2,21 @@
|
|
2
2
|
|
3
3
|
[Twirp is a protocol](https://twitchtv.github.io/twirp/docs/spec_v5.html) for routing and serialization of services defined in a [.proto file](https://developers.google.com/protocol-buffers/docs/proto3), allowing easy implementation of RPC services with auto-generated clients in different languages.
|
4
4
|
|
5
|
-
The [canonical implementation](https://github.com/twitchtv/twirp) is in Golang. The Twirp-Ruby project
|
5
|
+
The [canonical implementation](https://github.com/twitchtv/twirp) is in Golang. The Twirp-Ruby project is the official implementation in Ruby for both server and clients.
|
6
6
|
|
7
7
|
|
8
8
|
## Install
|
9
9
|
|
10
10
|
Add `gem "twirp"` to your Gemfile, or install with `gem install twirp`.
|
11
11
|
|
12
|
+
To auto-generate Ruby code from a proto file, use the `protoc` plugin and the `--ruby_out` option ([see Wiki page](https://github.com/twitchtv/twirp-ruby/wiki/Code-Generation)).
|
13
|
+
|
12
14
|
|
13
15
|
## Documentation
|
14
16
|
|
15
|
-
[
|
17
|
+
[On the wiki](https://github.com/twitchtv/twirp-ruby/wiki).
|
16
18
|
|
17
19
|
|
18
20
|
## Contributing
|
19
21
|
|
20
|
-
[
|
22
|
+
[On the CONTRIBUTING file](CONTRIBUTING.md).
|
data/lib/twirp/client_json.rb
CHANGED
@@ -24,6 +24,7 @@ module Twirp
|
|
24
24
|
|
25
25
|
package = opts[:package].to_s
|
26
26
|
service = opts[:service].to_s
|
27
|
+
@strict = opts.fetch( :strict, false )
|
27
28
|
raise ArgumentError.new("Missing option :service") if service.empty?
|
28
29
|
@service_full_name = package.empty? ? service : "#{package}.#{service}"
|
29
30
|
end
|
@@ -33,7 +34,8 @@ module Twirp
|
|
33
34
|
def rpc(rpc_method, attrs={}, req_opts=nil)
|
34
35
|
body = Encoding.encode_json(attrs)
|
35
36
|
|
36
|
-
|
37
|
+
encoding = @strict ? Encoding::JSON_STRICT : Encoding::JSON
|
38
|
+
resp = self.class.make_http_request(@conn, @service_full_name, rpc_method, encoding, req_opts, body)
|
37
39
|
if resp.status != 200
|
38
40
|
return ClientResp.new(nil, self.class.error_from_response(resp))
|
39
41
|
end
|
data/lib/twirp/encoding.rb
CHANGED
@@ -17,13 +17,18 @@ module Twirp
|
|
17
17
|
|
18
18
|
module Encoding
|
19
19
|
JSON = "application/json"
|
20
|
+
# An opt-in content type useful when curling or manually testing a twirp
|
21
|
+
# service. This will fail if unknown fields are encountered. The return
|
22
|
+
# content type will be application/json.
|
23
|
+
JSON_STRICT = "application/json; strict=true"
|
20
24
|
PROTO = "application/protobuf"
|
21
25
|
|
22
26
|
class << self
|
23
27
|
|
24
28
|
def decode(bytes, msg_class, content_type)
|
25
29
|
case content_type
|
26
|
-
when JSON
|
30
|
+
when JSON then msg_class.decode_json(bytes, ignore_unknown_fields: true)
|
31
|
+
when JSON_STRICT then msg_class.decode_json(bytes, ignore_unknown_fields: false)
|
27
32
|
when PROTO then msg_class.decode(bytes)
|
28
33
|
else raise ArgumentError.new("Invalid content_type")
|
29
34
|
end
|
@@ -31,7 +36,7 @@ module Twirp
|
|
31
36
|
|
32
37
|
def encode(msg_obj, msg_class, content_type)
|
33
38
|
case content_type
|
34
|
-
when JSON
|
39
|
+
when JSON, JSON_STRICT then msg_class.encode_json(msg_obj, emit_defaults: true)
|
35
40
|
when PROTO then msg_class.encode(msg_obj)
|
36
41
|
else raise ArgumentError.new("Invalid content_type")
|
37
42
|
end
|
@@ -46,7 +51,7 @@ module Twirp
|
|
46
51
|
end
|
47
52
|
|
48
53
|
def valid_content_type?(content_type)
|
49
|
-
content_type == JSON || content_type == PROTO
|
54
|
+
content_type == JSON || content_type == PROTO || content_type == JSON_STRICT
|
50
55
|
end
|
51
56
|
|
52
57
|
def valid_content_types
|
data/lib/twirp/error.rb
CHANGED
@@ -16,15 +16,16 @@ module Twirp
|
|
16
16
|
# Valid Twirp error codes and their mapping to related HTTP status.
|
17
17
|
# This can also be used to check if a code is valid (check if not nil).
|
18
18
|
ERROR_CODES_TO_HTTP_STATUS = {
|
19
|
-
canceled: 408, #
|
20
|
-
invalid_argument: 400, #
|
21
|
-
|
19
|
+
canceled: 408, # Request Timeout
|
20
|
+
invalid_argument: 400, # Bad Request
|
21
|
+
malformed: 400, # Bad Request
|
22
|
+
deadline_exceeded: 408, # Request Timeout
|
22
23
|
not_found: 404, # Not Found
|
23
24
|
bad_route: 404, # Not Found
|
24
25
|
already_exists: 409, # Conflict
|
25
26
|
permission_denied: 403, # Forbidden
|
26
27
|
unauthenticated: 401, # Unauthorized
|
27
|
-
resource_exhausted:
|
28
|
+
resource_exhausted: 429, # Too Many Requests
|
28
29
|
failed_precondition: 412, # Precondition Failed
|
29
30
|
aborted: 409, # Conflict
|
30
31
|
out_of_range: 400, # Bad Request
|
@@ -49,10 +50,10 @@ module Twirp
|
|
49
50
|
ERROR_CODES_TO_HTTP_STATUS.key? code # one of the valid symbols
|
50
51
|
end
|
51
52
|
|
52
|
-
#
|
53
|
+
# Code constructors to ensure valid error codes. Example:
|
53
54
|
# Twirp::Error.internal("boom")
|
54
|
-
# Twirp::Error.invalid_argument("foo is mandatory",
|
55
|
-
# Twirp::Error.permission_denied("
|
55
|
+
# Twirp::Error.invalid_argument("foo is mandatory", mymeta: "foobar")
|
56
|
+
# Twirp::Error.permission_denied("Thou shall not pass!", target: "Balrog")
|
56
57
|
ERROR_CODES.each do |code|
|
57
58
|
define_singleton_method code do |msg, meta=nil|
|
58
59
|
new(code, msg, meta)
|
@@ -62,7 +63,7 @@ module Twirp
|
|
62
63
|
# Wrap another error as a Twirp::Error :internal.
|
63
64
|
def self.internal_with(err)
|
64
65
|
twerr = internal err.message, cause: err.class.name
|
65
|
-
twerr.cause = err
|
66
|
+
twerr.cause = err # availabe in error hook for inspection, but not in the response
|
66
67
|
twerr
|
67
68
|
end
|
68
69
|
|
@@ -80,6 +81,7 @@ module Twirp
|
|
80
81
|
@meta = validate_meta(meta)
|
81
82
|
end
|
82
83
|
|
84
|
+
# Key-value representation of the error. Can be directly serialized into JSON.
|
83
85
|
def to_h
|
84
86
|
h = {
|
85
87
|
code: @code,
|
data/lib/twirp/service.rb
CHANGED
@@ -23,10 +23,19 @@ module Twirp
|
|
23
23
|
extend ServiceDSL
|
24
24
|
|
25
25
|
class << self
|
26
|
-
|
26
|
+
|
27
|
+
# Whether to raise exceptions instead of handling them with exception_raised hooks.
|
27
28
|
# Useful during tests to easily debug and catch unexpected exceptions.
|
28
|
-
# Default false
|
29
|
-
|
29
|
+
attr_accessor :raise_exceptions # Default: false
|
30
|
+
|
31
|
+
# Rack response with a Twirp::Error
|
32
|
+
def error_response(twerr)
|
33
|
+
status = Twirp::ERROR_CODES_TO_HTTP_STATUS[twerr.code]
|
34
|
+
headers = {'Content-Type' => Encoding::JSON} # Twirp errors are always JSON, even if the request was protobuf
|
35
|
+
resp_body = Encoding.encode_json(twerr.to_h)
|
36
|
+
[status, headers, [resp_body]]
|
37
|
+
end
|
38
|
+
|
30
39
|
end
|
31
40
|
|
32
41
|
def initialize(handler)
|
@@ -98,29 +107,30 @@ module Twirp
|
|
98
107
|
private
|
99
108
|
|
100
109
|
# Parse request and fill env with rpc data.
|
101
|
-
# Returns a bad_route error if
|
110
|
+
# Returns a bad_route error if could not be properly routed to a Twirp method.
|
111
|
+
# Returns a malformed error if could not decode the body (either bad JSON or bad Protobuf)
|
102
112
|
def route_request(rack_env, env)
|
103
113
|
rack_request = Rack::Request.new(rack_env)
|
104
114
|
|
105
115
|
if rack_request.request_method != "POST"
|
106
|
-
return
|
116
|
+
return route_err(:bad_route, "HTTP request method must be POST", rack_request)
|
107
117
|
end
|
108
118
|
|
109
119
|
content_type = rack_request.get_header("CONTENT_TYPE")
|
110
120
|
if !Encoding.valid_content_type?(content_type)
|
111
|
-
return
|
121
|
+
return route_err(:bad_route, "Unexpected Content-Type: #{content_type.inspect}. Content-Type header must be one of #{Encoding.valid_content_types.inspect}", rack_request)
|
112
122
|
end
|
113
123
|
env[:content_type] = content_type
|
114
124
|
|
115
125
|
path_parts = rack_request.fullpath.split("/")
|
116
126
|
if path_parts.size < 3 || path_parts[-2] != self.full_name
|
117
|
-
return
|
127
|
+
return route_err(:bad_route, "Invalid route. Expected format: POST {BaseURL}/#{self.full_name}/{Method}", rack_request)
|
118
128
|
end
|
119
129
|
method_name = path_parts[-1]
|
120
130
|
|
121
131
|
base_env = self.class.rpcs[method_name]
|
122
132
|
if !base_env
|
123
|
-
return
|
133
|
+
return route_err(:bad_route, "Invalid rpc method #{method_name.inspect}", rack_request)
|
124
134
|
end
|
125
135
|
env.merge!(base_env) # :rpc_method, :input_class, :output_class
|
126
136
|
|
@@ -132,7 +142,7 @@ module Twirp
|
|
132
142
|
if e.is_a?(Google::Protobuf::ParseError)
|
133
143
|
error_msg += ": #{e.message.strip}"
|
134
144
|
end
|
135
|
-
return
|
145
|
+
return route_err(:malformed, error_msg, rack_request)
|
136
146
|
end
|
137
147
|
|
138
148
|
env[:input] = input
|
@@ -140,12 +150,11 @@ module Twirp
|
|
140
150
|
return
|
141
151
|
end
|
142
152
|
|
143
|
-
def
|
144
|
-
Twirp::Error.
|
153
|
+
def route_err(code, msg, req)
|
154
|
+
Twirp::Error.new code, msg, twirp_invalid_route: "#{req.request_method} #{req.fullpath}"
|
145
155
|
end
|
146
156
|
|
147
157
|
|
148
|
-
|
149
158
|
# Call handler method and return a Protobuf Message or a Twirp::Error.
|
150
159
|
def call_handler(env)
|
151
160
|
m = env[:ruby_method]
|
@@ -181,11 +190,7 @@ module Twirp
|
|
181
190
|
def error_response(twerr, env)
|
182
191
|
begin
|
183
192
|
@on_error.each{|hook| hook.call(twerr, env) }
|
184
|
-
|
185
|
-
status = Twirp::ERROR_CODES_TO_HTTP_STATUS[twerr.code]
|
186
|
-
resp_body = Encoding.encode_json(twerr.to_h)
|
187
|
-
[status, error_response_headers, [resp_body]]
|
188
|
-
|
193
|
+
self.class.error_response(twerr)
|
189
194
|
rescue => e
|
190
195
|
return exception_response(e, env)
|
191
196
|
end
|
@@ -193,6 +198,7 @@ module Twirp
|
|
193
198
|
|
194
199
|
def exception_response(e, env)
|
195
200
|
raise e if self.class.raise_exceptions
|
201
|
+
|
196
202
|
begin
|
197
203
|
@exception_raised.each{|hook| hook.call(e, env) }
|
198
204
|
rescue => hook_e
|
@@ -200,13 +206,7 @@ module Twirp
|
|
200
206
|
end
|
201
207
|
|
202
208
|
twerr = Twirp::Error.internal_with(e)
|
203
|
-
|
204
|
-
[500, error_response_headers, [resp_body]]
|
205
|
-
end
|
206
|
-
|
207
|
-
def error_response_headers
|
208
|
-
# Twirp errors are always JSON, even if the request was protobuf
|
209
|
-
{'Content-Type' => Encoding::JSON}
|
209
|
+
self.class.error_response(twerr)
|
210
210
|
end
|
211
211
|
|
212
212
|
end
|
data/lib/twirp/version.rb
CHANGED
data/test/client_json_test.rb
CHANGED
@@ -29,6 +29,20 @@ class ClientJSONTest < Minitest::Test
|
|
29
29
|
assert_equal 3, resp.data["blah_resp"]
|
30
30
|
end
|
31
31
|
|
32
|
+
def test_client_json_strict_encoding
|
33
|
+
c = Twirp::ClientJSON.new(conn_stub("/my.pkg.Talking/Blah") {|req|
|
34
|
+
assert_equal "application/json; strict=true", 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", strict: true)
|
39
|
+
|
40
|
+
resp = c.rpc :Blah, blah1: 1, blah2: 2
|
41
|
+
assert_nil resp.error
|
42
|
+
refute_nil resp.data
|
43
|
+
assert_equal 3, resp.data["blah_resp"]
|
44
|
+
end
|
45
|
+
|
32
46
|
def test_client_json_error
|
33
47
|
c = Twirp::ClientJSON.new(conn_stub("/Foo/Foomo") {|req|
|
34
48
|
[400, {}, '{"code": "invalid_argument", "msg": "dont like empty"}']
|
data/test/error_test.rb
CHANGED
@@ -3,9 +3,9 @@ require 'minitest/autorun'
|
|
3
3
|
require_relative '../lib/twirp/error'
|
4
4
|
|
5
5
|
class TestErrorCodes < Minitest::Test
|
6
|
-
|
6
|
+
|
7
7
|
def test_error_codes
|
8
|
-
assert_equal
|
8
|
+
assert_equal 18, Twirp::ERROR_CODES.size
|
9
9
|
|
10
10
|
# all codes should be symbols
|
11
11
|
Twirp::ERROR_CODES.each do |code|
|
@@ -13,13 +13,13 @@ class TestErrorCodes < Minitest::Test
|
|
13
13
|
end
|
14
14
|
|
15
15
|
# check some codes
|
16
|
-
assert_includes Twirp::ERROR_CODES, :internal
|
16
|
+
assert_includes Twirp::ERROR_CODES, :internal
|
17
17
|
assert_includes Twirp::ERROR_CODES, :not_found
|
18
18
|
assert_includes Twirp::ERROR_CODES, :invalid_argument
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_codes_to_http_status
|
22
|
-
assert_equal
|
22
|
+
assert_equal 18, Twirp::ERROR_CODES_TO_HTTP_STATUS.size
|
23
23
|
|
24
24
|
assert_equal 404, Twirp::ERROR_CODES_TO_HTTP_STATUS[:not_found]
|
25
25
|
assert_equal 500, Twirp::ERROR_CODES_TO_HTTP_STATUS[:internal]
|
@@ -52,7 +52,7 @@ class TestTwirpError < Minitest::Test
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def test_invalid_constructor # Make sure that only supported codes are implemented (prevent bad metaprogramming)
|
55
|
-
assert_raises NoMethodError do
|
55
|
+
assert_raises NoMethodError do
|
56
56
|
Twirp::invalid_code_error "should fail"
|
57
57
|
end
|
58
58
|
end
|
@@ -67,7 +67,7 @@ class TestTwirpError < Minitest::Test
|
|
67
67
|
def test_new_with_valid_metadata
|
68
68
|
err = Twirp::Error.new(:internal, "woops", "meta" => "data", "for this" => "error")
|
69
69
|
assert_equal "data", err.meta["meta"]
|
70
|
-
assert_equal "error", err.meta["for this"]
|
70
|
+
assert_equal "error", err.meta["for this"]
|
71
71
|
assert_nil err.meta["something else"]
|
72
72
|
|
73
73
|
err = Twirp::Error.new(:internal, "woops", meta: "data")
|
@@ -78,11 +78,11 @@ class TestTwirpError < Minitest::Test
|
|
78
78
|
def test_invalid_metadata
|
79
79
|
Twirp::Error.new(:internal, "woops") # ensure the base case doesn't error
|
80
80
|
|
81
|
-
assert_raises ArgumentError do
|
81
|
+
assert_raises ArgumentError do
|
82
82
|
Twirp::Error.new(:internal, "woops", "string key" => :non_string_value)
|
83
83
|
end
|
84
84
|
|
85
|
-
assert_raises ArgumentError do
|
85
|
+
assert_raises ArgumentError do
|
86
86
|
Twirp::Error.new(:internal, "woops", "valid key" => "valid val", "bad_one" => 666)
|
87
87
|
end
|
88
88
|
end
|
@@ -91,8 +91,8 @@ class TestTwirpError < Minitest::Test
|
|
91
91
|
# returns a hash with attributes
|
92
92
|
err = Twirp::Error.new(:internal, "err msg", "key" => "val")
|
93
93
|
assert_equal({code: :internal, msg: "err msg", meta: {"key" => "val"}}, err.to_h)
|
94
|
-
|
95
|
-
# skips meta if not included
|
94
|
+
|
95
|
+
# skips meta if not included
|
96
96
|
err = Twirp::Error.new(:internal, "err msg")
|
97
97
|
assert_equal({code: :internal, msg: "err msg"}, err.to_h)
|
98
98
|
end
|
data/test/service_test.rb
CHANGED
@@ -12,6 +12,15 @@ class ServiceTest < Minitest::Test
|
|
12
12
|
Example::Haberdasher.raise_exceptions = true # configure for testing to make debugging easier
|
13
13
|
end
|
14
14
|
|
15
|
+
# Class method to make a Rack response with a Twirp errpr
|
16
|
+
def test_service_error_response
|
17
|
+
twerr = Twirp::Error.invalid_argument('foo')
|
18
|
+
resp = Twirp::Service.error_response(twerr)
|
19
|
+
assert_equal 400, resp[0]
|
20
|
+
assert_equal 'application/json', resp[1]['Content-Type']
|
21
|
+
assert_equal '{"code":"invalid_argument","msg":"foo"}', resp[2][0]
|
22
|
+
end
|
23
|
+
|
15
24
|
# The rpc DSL should properly build the base Twirp environment for each rpc method.
|
16
25
|
def test_rpcs_accessor
|
17
26
|
assert_equal 1, Example::Haberdasher.rpcs.size
|
@@ -55,6 +64,24 @@ class ServiceTest < Minitest::Test
|
|
55
64
|
assert_equal({"inches" => 10, "color" => "white"}, JSON.parse(body[0]))
|
56
65
|
end
|
57
66
|
|
67
|
+
def test_successful_json_strict_request_emit_defaults
|
68
|
+
rack_env = json_strict_req "/example.Haberdasher/MakeHat", inches: 0 # default int value
|
69
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
70
|
+
|
71
|
+
assert_equal 200, status
|
72
|
+
assert_equal 'application/json; strict=true', headers['Content-Type']
|
73
|
+
assert_equal({"inches" => 0, "color" => "white"}, JSON.parse(body[0]))
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_successful_json_request_emit_defaults
|
77
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 0 # default int value
|
78
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
79
|
+
|
80
|
+
assert_equal 200, status
|
81
|
+
assert_equal 'application/json', headers['Content-Type']
|
82
|
+
assert_equal({"inches" => 0, "color" => "white"}, JSON.parse(body[0]))
|
83
|
+
end
|
84
|
+
|
58
85
|
def test_successful_proto_request
|
59
86
|
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new(inches: 10)
|
60
87
|
status, headers, body = haberdasher_service.call(rack_env)
|
@@ -136,10 +163,10 @@ class ServiceTest < Minitest::Test
|
|
136
163
|
method: "POST", input: 'bad json', "CONTENT_TYPE" => "application/json"
|
137
164
|
status, headers, body = haberdasher_service.call(rack_env)
|
138
165
|
|
139
|
-
assert_equal
|
166
|
+
assert_equal 400, status
|
140
167
|
assert_equal 'application/json', headers['Content-Type']
|
141
168
|
assert_equal({
|
142
|
-
"code" => '
|
169
|
+
"code" => 'malformed',
|
143
170
|
"msg" => 'Invalid request body for rpc method "MakeHat" with Content-Type=application/json: ' +
|
144
171
|
"Error occurred during parsing: Parse error at 'bad json'",
|
145
172
|
"meta" => {"twirp_invalid_route" => "POST /example.Haberdasher/MakeHat"},
|
@@ -151,16 +178,39 @@ class ServiceTest < Minitest::Test
|
|
151
178
|
method: "POST", input: 'bad protobuf', "CONTENT_TYPE" => "application/protobuf"
|
152
179
|
status, headers, body = haberdasher_service.call(rack_env)
|
153
180
|
|
154
|
-
assert_equal
|
181
|
+
assert_equal 400, status
|
155
182
|
assert_equal 'application/json', headers['Content-Type']
|
156
183
|
assert_equal({
|
157
|
-
"code" => '
|
184
|
+
"code" => 'malformed',
|
158
185
|
"msg" => 'Invalid request body for rpc method "MakeHat" with Content-Type=application/protobuf: ' +
|
159
186
|
'Error occurred during parsing: Unexpected EOF inside skipped data',
|
160
187
|
"meta" => {"twirp_invalid_route" => "POST /example.Haberdasher/MakeHat"},
|
161
188
|
}, JSON.parse(body[0]))
|
162
189
|
end
|
163
190
|
|
191
|
+
def test_json_request_ignores_unknown_fields
|
192
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10, fake: 3
|
193
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
194
|
+
|
195
|
+
assert_equal 200, status
|
196
|
+
assert_equal 'application/json', headers['Content-Type']
|
197
|
+
assert_equal({"inches" => 10, "color" => "white"}, JSON.parse(body[0]))
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_json_strict_request_fails_unknown_fields
|
201
|
+
rack_env = json_strict_req "/example.Haberdasher/MakeHat", inches: 10, fake: 3
|
202
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
203
|
+
|
204
|
+
assert_equal 400, status
|
205
|
+
assert_equal 'application/json', headers['Content-Type']
|
206
|
+
assert_equal({
|
207
|
+
"code" => 'malformed',
|
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",
|
210
|
+
"meta" => {"twirp_invalid_route" => "POST /example.Haberdasher/MakeHat"},
|
211
|
+
}, JSON.parse(body[0]))
|
212
|
+
end
|
213
|
+
|
164
214
|
def test_bad_route_triggers_on_error_hooks
|
165
215
|
svc = haberdasher_service
|
166
216
|
|
@@ -782,6 +832,12 @@ class ServiceTest < Minitest::Test
|
|
782
832
|
"CONTENT_TYPE" => "application/json"
|
783
833
|
end
|
784
834
|
|
835
|
+
def json_strict_req(path, attrs)
|
836
|
+
Rack::MockRequest.env_for path, method: "POST",
|
837
|
+
input: JSON.generate(attrs),
|
838
|
+
"CONTENT_TYPE" => "application/json; strict=true"
|
839
|
+
end
|
840
|
+
|
785
841
|
def proto_req(path, proto_message)
|
786
842
|
Rack::MockRequest.env_for path, method: "POST",
|
787
843
|
input: proto_message.class.encode(proto_message),
|
data/twirp.gemspec
CHANGED
@@ -19,10 +19,11 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.required_ruby_version = '>= 1.9'
|
22
|
-
spec.add_runtime_dependency 'google-protobuf', '~> 3.0', '>= 3.
|
23
|
-
spec.add_runtime_dependency 'faraday', '
|
22
|
+
spec.add_runtime_dependency 'google-protobuf', '~> 3.0', '>= 3.7.0'
|
23
|
+
spec.add_runtime_dependency 'faraday', '< 2' # for clients
|
24
24
|
|
25
25
|
spec.add_development_dependency 'bundler', '~> 1'
|
26
26
|
spec.add_development_dependency 'minitest', '>= 5'
|
27
27
|
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'rack', '>= 2.2.3'
|
28
29
|
end
|
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.2
|
4
|
+
version: 1.7.2
|
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:
|
12
|
+
date: 2020-11-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: google-protobuf
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
version: '3.0'
|
21
21
|
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 3.
|
23
|
+
version: 3.7.0
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -30,21 +30,21 @@ dependencies:
|
|
30
30
|
version: '3.0'
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 3.
|
33
|
+
version: 3.7.0
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
35
|
name: faraday
|
36
36
|
requirement: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "<"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2'
|
41
41
|
type: :runtime
|
42
42
|
prerelease: false
|
43
43
|
version_requirements: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - "<"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '2'
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: bundler
|
50
50
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,6 +87,20 @@ dependencies:
|
|
87
87
|
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: rack
|
92
|
+
requirement: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.2.3
|
97
|
+
type: :development
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.2.3
|
90
104
|
description: Twirp is a simple RPC framework with protobuf service definitions. The
|
91
105
|
Twirp gem provides native support for Ruby.
|
92
106
|
email:
|
@@ -134,15 +148,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
148
|
- !ruby/object:Gem::Version
|
135
149
|
version: '0'
|
136
150
|
requirements: []
|
137
|
-
|
138
|
-
rubygems_version: 2.6.8
|
151
|
+
rubygems_version: 3.0.3
|
139
152
|
signing_key:
|
140
153
|
specification_version: 4
|
141
154
|
summary: Twirp services in Ruby.
|
142
155
|
test_files:
|
143
|
-
- test/client_json_test.rb
|
144
156
|
- test/client_test.rb
|
145
157
|
- test/error_test.rb
|
146
|
-
- test/fake_services.rb
|
147
158
|
- test/license_header_test.rb
|
159
|
+
- test/fake_services.rb
|
148
160
|
- test/service_test.rb
|
161
|
+
- test/client_json_test.rb
|