diff --git a/lib/committee/request_unpacker.rb b/lib/committee/request_unpacker.rb index abddd264..c4bff764 100644 --- a/lib/committee/request_unpacker.rb +++ b/lib/committee/request_unpacker.rb @@ -79,12 +79,6 @@ def parse_json(request) request.body.rewind hash = JSON.parse(body) - # We want a hash specifically. '42', 42, and [42] will all be - # decoded properly, but we can't use them here. - if !hash.is_a?(Hash) - raise BadRequest, - "Invalid JSON input. Require object with parameters as keys." - end self.class.indifferent_params(hash) end end diff --git a/lib/committee/schema_validator/hyper_schema.rb b/lib/committee/schema_validator/hyper_schema.rb index af8bd0b1..0eb6ccec 100644 --- a/lib/committee/schema_validator/hyper_schema.rb +++ b/lib/committee/schema_validator/hyper_schema.rb @@ -79,10 +79,16 @@ def request_unpack(request) coerce_form_params(request_param) if validator_option.coerce_form_params && is_form_params request.env[validator_option.request_body_hash_key] = request_param - request.env[validator_option.params_key] = Committee::Utils.indifferent_hash - request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(query_param)) - request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(request_param)) - request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(param_matches_hash)) + if request_param.is_a?(Array) + raise BadRequest, "Invalid JSON input. Require request body typed by object when path/query parameter exists." if !query_param.empty? || !param_matches_hash.empty? + + request.env[validator_option.params_key] = Committee::Utils.deep_copy(request_param) + else + request.env[validator_option.params_key] = Committee::Utils.indifferent_hash + request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(query_param)) + request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(request_param)) + request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(param_matches_hash)) + end end def coerce_form_params(parameter) diff --git a/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb b/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb index 0f552117..2c2da599 100644 --- a/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb +++ b/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb @@ -13,7 +13,7 @@ def initialize(params, schema, options = {}) end def call! - coerce_object!(@params, @schema) + @params.is_a?(Array) ? coerce_array_data!(@params, @schema) : coerce_object!(@params, @schema) end private diff --git a/lib/committee/schema_validator/open_api_3.rb b/lib/committee/schema_validator/open_api_3.rb index 494ba7a1..a87f37d0 100644 --- a/lib/committee/schema_validator/open_api_3.rb +++ b/lib/committee/schema_validator/open_api_3.rb @@ -105,9 +105,17 @@ def copy_coerced_data_to_params(request) [validator_option.query_hash_key, validator_option.request_body_hash_key, validator_option.path_hash_key] end - request.env[validator_option.params_key] = Committee::Utils.indifferent_hash - order.each do |key| - request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(request.env[key])) + if request.env[validator_option.request_body_hash_key].is_a?(Array) + if (!request.env[validator_option.query_hash_key].empty? || !request.env[validator_option.path_hash_key].empty?) + raise BadRequest, "Invalid JSON input. Require request body typed by object when path/query parameter exists." + end + + request.env[validator_option.params_key] = Committee::Utils.deep_copy(request.env[validator_option.request_body_hash_key]) + else + request.env[validator_option.params_key] = Committee::Utils.indifferent_hash + order.each do |key| + request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(request.env[key])) + end end end end diff --git a/lib/committee/schema_validator/open_api_3/operation_wrapper.rb b/lib/committee/schema_validator/open_api_3/operation_wrapper.rb index 376c207a..2daa8bcc 100644 --- a/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +++ b/lib/committee/schema_validator/open_api_3/operation_wrapper.rb @@ -40,7 +40,7 @@ def validate_response_params(status_code, headers, response_data, strict, check_ end def validate_request_params(path_params, query_params, body_params, headers, validator_option) - ret, err = case request_operation.http_method + ret = case request_operation.http_method when 'get', 'delete', 'head' validate_get_request_params(path_params, query_params, headers, validator_option) when 'post', 'put', 'patch', 'options' @@ -48,7 +48,6 @@ def validate_request_params(path_params, query_params, body_params, headers, val else raise "Committee OpenAPI3 not support #{request_operation.http_method} method" end - raise err if err ret end diff --git a/test/data/hyperschema/paas.json b/test/data/hyperschema/paas.json index 6c65d065..d8ec60e4 100644 --- a/test/data/hyperschema/paas.json +++ b/test/data/hyperschema/paas.json @@ -262,6 +262,19 @@ "$ref": "#/definitions/app" }, "title": "abc" + }, + { + "description": "Create new apps.", + "href": "/apps", + "method": "PUT", + "rel": "collection", + "targetSchema": { + "items": { + "$ref": "#/definitions/app" + }, + "type": "array" + }, + "title": "Create Apps" } ], "properties": { diff --git a/test/data/openapi3/normal.yaml b/test/data/openapi3/normal.yaml index fd535293..b9c7d076 100644 --- a/test/data/openapi3/normal.yaml +++ b/test/data/openapi3/normal.yaml @@ -467,6 +467,22 @@ paths: type: string format: binary + /validate_request_array: + post: + description: validate request with array + responses: + '200': + description: no content + requestBody: + content: + application/json: + schema: + type: array + items: + anyOf: + - type: string + - type: boolean + /validate_no_parameter: patch: description: validate no body diff --git a/test/middleware/request_validation_test.rb b/test/middleware/request_validation_test.rb index 930647be..a19c7b25 100644 --- a/test/middleware/request_validation_test.rb +++ b/test/middleware/request_validation_test.rb @@ -53,7 +53,7 @@ def app } ] - it "passes through a valid request" do + it "passes through a valid request typed by object" do @app = new_rack_app(schema: hyper_schema) params = { "name" => "cloudnasium" @@ -63,6 +63,16 @@ def app assert_equal 200, last_response.status end + it "passes through a valid request typed by array" do + @app = new_rack_app(schema: hyper_schema) + params = [{ + "name" => "cloudnasium" + }] + header "Content-Type", "application/json" + put "/apps", JSON.generate(params) + assert_equal 200, last_response.status + end + it "doesn't call error_handler (has a arg) when request is valid" do called_error = false pr = ->(_e) { called_error = true } diff --git a/test/request_unpacker_test.rb b/test/request_unpacker_test.rb index e51d08a3..44baa805 100644 --- a/test/request_unpacker_test.rb +++ b/test/request_unpacker_test.rb @@ -135,15 +135,14 @@ assert_equal({ "a" => "b" }, unpacker.unpack_query_params(request)) end - it "errors if JSON is not an object" do + it "unpacks if JSON is not an object" do env = { "CONTENT_TYPE" => "application/json", "rack.input" => StringIO.new('[2]'), } request = Rack::Request.new(env) - assert_raises(Committee::BadRequest) do - Committee::RequestUnpacker.new.unpack_request_params(request) - end + unpacker = Committee::RequestUnpacker.new + assert_equal([[2], false], unpacker.unpack_request_params(request)) end it "errors on an unknown Content-Type" do diff --git a/test/schema_validator/open_api_3/request_validator_test.rb b/test/schema_validator/open_api_3/request_validator_test.rb index 1df37e40..8db63097 100644 --- a/test/schema_validator/open_api_3/request_validator_test.rb +++ b/test/schema_validator/open_api_3/request_validator_test.rb @@ -95,6 +95,14 @@ def app assert_equal 400, last_response.status end + it "validates array request" do + @app = new_rack_app(check_content_type: true, schema: open_api_3_schema) + params = ["1", true] + header "Content-Type", "application/json" + post "/validate_request_array", JSON.generate(params) + assert_equal 200, last_response.status + end + def new_rack_app(options = {}) # TODO: delete when 5.0.0 released because default value changed options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil