Skip to content

Commit

Permalink
Merge pull request #61 from alusco-scratch/strictjson
Browse files Browse the repository at this point in the history
Add a strict version of JSON encoding
  • Loading branch information
marioizquierdo authored Nov 16, 2020
2 parents 8f90199 + 04c10ef commit c5d5b19
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 11 deletions.
12 changes: 7 additions & 5 deletions example/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
PATH
remote: ..
specs:
twirp (1.5.0)
twirp (1.7.0)
faraday (< 2)
google-protobuf (~> 3.0, >= 3.0.0)
google-protobuf (~> 3.0, >= 3.7.0)

GEM
remote: https://rubygems.org/
specs:
faraday (1.0.1)
faraday (1.1.0)
multipart-post (>= 1.2, < 3)
google-protobuf (3.11.4)
ruby2_keywords
google-protobuf (3.13.0)
multipart-post (2.1.1)
rack (2.2.3)
ruby2_keywords (0.0.2)

PLATFORMS
ruby
Expand All @@ -22,4 +24,4 @@ DEPENDENCIES
twirp!

BUNDLED WITH
1.17.2
1.17.3
2 changes: 1 addition & 1 deletion example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Now you can send `curl` requests from another terminal window:
```sh
curl --request POST \
--url http://localhost:8080/twirp/example.hello_world.HelloWorld/Hello \
--header 'Content-Type: application/json' \
--header 'Content-Type: application/json; strict=true' \
--data '{"name": "World"}'
```

Expand Down
12 changes: 9 additions & 3 deletions lib/twirp/encoding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,27 @@ module Twirp

module Encoding
JSON = "application/json"
# An opt-in content type useful when curling or manually testing a twirp
# service. This will fail if unknown fields are encountered. The return
# content type will be application/json.
JSON_STRICT = "application/json; strict=true"
PROTO = "application/protobuf"

class << self

def decode(bytes, msg_class, content_type)
case content_type
when JSON then msg_class.decode_json(bytes, ignore_unknown_fields: true)
when JSON then msg_class.decode_json(bytes, ignore_unknown_fields: true)
when JSON_STRICT then msg_class.decode_json(bytes, ignore_unknown_fields: false)
when PROTO then msg_class.decode(bytes)
else raise ArgumentError.new("Invalid content_type")
end
end

def encode(msg_obj, msg_class, content_type)
case content_type
when JSON then msg_class.encode_json(msg_obj, emit_defaults: true)
when JSON then msg_class.encode_json(msg_obj, emit_defaults: false)
when JSON_STRICT then msg_class.encode_json(msg_obj, emit_defaults: true)
when PROTO then msg_class.encode(msg_obj)
else raise ArgumentError.new("Invalid content_type")
end
Expand All @@ -46,7 +52,7 @@ def decode_json(bytes)
end

def valid_content_type?(content_type)
content_type == JSON || content_type == PROTO
content_type == JSON || content_type == PROTO || content_type == JSON_STRICT
end

def valid_content_types
Expand Down
2 changes: 1 addition & 1 deletion lib/twirp/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
# permissions and limitations under the License.

module Twirp
VERSION = "1.7.0"
VERSION = "1.7.1"
end
31 changes: 30 additions & 1 deletion test/service_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,21 @@ def test_successful_json_request
end

def test_successful_json_request_emit_defaults
rack_env = json_strict_req "/example.Haberdasher/MakeHat", inches: 0 # default int value
status, headers, body = haberdasher_service.call(rack_env)

assert_equal 200, status
assert_equal 'application/json; strict=true', headers['Content-Type']
assert_equal({"inches" => 0, "color" => "white"}, JSON.parse(body[0]))
end

def test_successful_json_request_no_emit_defaults
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 0 # default int value
status, headers, body = haberdasher_service.call(rack_env)

assert_equal 200, status
assert_equal 'application/json', headers['Content-Type']
assert_equal({"inches" => 0, "color" => "white"}, JSON.parse(body[0]))
assert_equal({"color" => "white"}, JSON.parse(body[0]))
end

def test_successful_proto_request
Expand Down Expand Up @@ -188,6 +197,20 @@ def test_json_request_ignores_unknown_fields
assert_equal({"inches" => 10, "color" => "white"}, JSON.parse(body[0]))
end

def test_json_strict_request_fails_unknown_fields
rack_env = json_strict_req "/example.Haberdasher/MakeHat", inches: 10, fake: 3
status, headers, body = haberdasher_service.call(rack_env)

assert_equal 400, status
assert_equal 'application/json', headers['Content-Type']
assert_equal({
"code" => 'malformed',
"msg" => 'Invalid request body for rpc method "MakeHat" with Content-Type=application/json; strict=true: ' +
"Error occurred during parsing: No such field: fake",
"meta" => {"twirp_invalid_route" => "POST /example.Haberdasher/MakeHat"},
}, JSON.parse(body[0]))
end

def test_bad_route_triggers_on_error_hooks
svc = haberdasher_service

Expand Down Expand Up @@ -809,6 +832,12 @@ def json_req(path, attrs)
"CONTENT_TYPE" => "application/json"
end

def json_strict_req(path, attrs)
Rack::MockRequest.env_for path, method: "POST",
input: JSON.generate(attrs),
"CONTENT_TYPE" => "application/json; strict=true"
end

def proto_req(path, proto_message)
Rack::MockRequest.env_for path, method: "POST",
input: proto_message.class.encode(proto_message),
Expand Down

0 comments on commit c5d5b19

Please sign in to comment.