Skip to content

Commit d90137e

Browse files
committed
fix: parse +json response content types
1 parent 33534fc commit d90137e

6 files changed

Lines changed: 55 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ This piece of middleware validates the contents of the response received from up
167167
|raise| false | false | Raise an exception on error instead of responding with a generic error body. |
168168
|validate_success_only| true | false | Also validate non-2xx responses only. |
169169
|ignore_error| false | false | Validate and ignore result even if validation is error. So always return original data. |
170-
|parse_response_by_content_type| true | true | Parse response body to JSON only if Content-Type header is 'application/json'. When false, this always optimistically parses as JSON without checking for Content-Type header. |
170+
|parse_response_by_content_type| true | true | Parse response body to JSON only if Content-Type header is `application/json` or an `application/*+json` media type such as `application/problem+json`. When false, this always optimistically parses as JSON without checking for Content-Type header. |
171171
|strict| false | false | Puts the middleware into strict mode, meaning that response code and content type does not defined in the schema will be responded to with a 500 instead of application's status code. |
172172
|strict_reference_validation| always false | false | Raises an exception (`OpenAPIParser::MissingReferenceError`) on middleware load if the provided schema file contains unresolvable references (`$ref:"#/something/not/here"`). Not supported on Hyper-schema parser. Defaults to `false` on OpenAPI3 but will default to `true` in next major version. |
173173

lib/committee/schema_validator.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@
22

33
module Committee
44
module SchemaValidator
5+
JSON_MEDIA_TYPE_PATTERN = %r{\Aapplication/(?:.+\+)?json\z}.freeze
6+
57
class << self
68
def request_media_type(request)
79
Rack::MediaType.type(request.env['CONTENT_TYPE'])
810
end
911

12+
def json_media_type?(content_type)
13+
normalized_content_type = Rack::MediaType.type(content_type)
14+
normalized_content_type&.match?(JSON_MEDIA_TYPE_PATTERN) || false
15+
end
16+
1017
# @param [String] prefix
1118
# @return [Regexp]
1219
def build_prefix_regexp(prefix)

lib/committee/schema_validator/hyper_schema.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def response_validate(status, headers, response, _test_method = false, custom_bo
3131
elsif !full_body.empty?
3232
parse_to_json = if validator_option.parse_response_by_content_type
3333
content_type_key = headers.keys.detect { |k| k.casecmp?('Content-Type') }
34-
headers.fetch(content_type_key, nil)&.start_with?('application/json')
34+
Committee::SchemaValidator.json_media_type?(headers.fetch(content_type_key, nil))
3535
else
3636
true
3737
end

lib/committee/schema_validator/open_api_3.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def response_validate(status, headers, response, test_method = false, custom_bod
2828

2929
parse_to_json = if validator_option.parse_response_by_content_type
3030
content_type_key = headers.keys.detect { |k| k.casecmp?('Content-Type') }
31-
headers.fetch(content_type_key, nil)&.start_with?('application/json')
31+
Committee::SchemaValidator.json_media_type?(headers.fetch(content_type_key, nil))
3232
else
3333
true
3434
end

test/middleware/response_validation_open_api_3_test.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ def app
3333
assert_equal 200, last_response.status
3434
end
3535

36+
it "passes through a valid response with a +json content-type" do
37+
@app = new_response_rack(
38+
JSON.generate(CHARACTERS_RESPONSE),
39+
{ "Content-Type" => "application/problem+json; charset=utf-8" },
40+
schema: open_api_3_schema,
41+
parse_response_by_content_type: true,
42+
)
43+
44+
get "/characters"
45+
assert_equal 200, last_response.status
46+
end
47+
3648
it "passes through a invalid json" do
3749
@app = new_response_rack("not_json", {}, schema: open_api_3_schema)
3850

test/schema_validator_test.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,39 @@
2121
assert_equal 'multipart/form-data', media_type
2222
end
2323

24+
it "detects application/json as a JSON media type" do
25+
assert_equal true, Committee::SchemaValidator.json_media_type?("application/json")
26+
end
27+
28+
it "detects application/problem+json with parameters as a JSON media type" do
29+
assert_equal true, Committee::SchemaValidator.json_media_type?("application/problem+json; charset=utf-8")
30+
end
31+
32+
it "detects application/vnd.api+json as a JSON media type" do
33+
assert_equal true, Committee::SchemaValidator.json_media_type?("application/vnd.api+json")
34+
end
35+
36+
it "does not detect application/x-ndjson as a JSON media type" do
37+
assert_equal false, Committee::SchemaValidator.json_media_type?("application/x-ndjson")
38+
end
39+
40+
it "does not detect non-JSON content types as JSON media types" do
41+
assert_equal false, Committee::SchemaValidator.json_media_type?("test/csv")
42+
assert_equal false, Committee::SchemaValidator.json_media_type?(nil)
43+
end
44+
45+
it "parses +json responses in the HyperSchema validator" do
46+
schema = Committee::Drivers::OpenAPI2::Driver.new.parse(open_api_2_data)
47+
validator_option = Committee::SchemaValidator::Option.new({ parse_response_by_content_type: true }, schema, :hyper_schema)
48+
router = Committee::SchemaValidator::HyperSchema::Router.new(schema, validator_option)
49+
request = Rack::Request.new({ "REQUEST_METHOD" => "GET", "PATH_INFO" => "/api/pets", "rack.input" => StringIO.new("") })
50+
validator = Committee::SchemaValidator::HyperSchema.new(router, request, validator_option)
51+
52+
validator.link.media_type = "application/vnd.api+json"
53+
54+
validator.response_validate(200, { "Content-Type" => "application/vnd.api+json" }, [JSON.generate([ValidPet])])
55+
end
56+
2457
it "builds prefix regexp with a path segment boundary" do
2558
regexp = Committee::SchemaValidator.build_prefix_regexp("/v1")
2659

0 commit comments

Comments
 (0)