diff --git a/lib/committee/request_unpacker.rb b/lib/committee/request_unpacker.rb index 41d99038..5f58c0b0 100644 --- a/lib/committee/request_unpacker.rb +++ b/lib/committee/request_unpacker.rb @@ -24,6 +24,7 @@ def initialize(options = {}) @allow_empty_date_and_datetime = options[:allow_empty_date_and_datetime] @allow_form_params = options[:allow_form_params] @allow_get_body = options[:allow_get_body] + @allow_json_array = options[:allow_json_array] @allow_query_params = options[:allow_query_params] @allow_non_get_query_params = options[:allow_non_get_query_params] @optimistic_json = options[:optimistic_json] @@ -94,9 +95,7 @@ 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) + if !hash.is_a?(Hash) && !(@allow_json_array && hash.is_a?(Array)) raise BadRequest, "Invalid JSON input. Require object with parameters as keys." end self.class.indifferent_params(hash) diff --git a/lib/committee/schema_validator/open_api_3.rb b/lib/committee/schema_validator/open_api_3.rb index 76f30762..61c20af2 100644 --- a/lib/committee/schema_validator/open_api_3.rb +++ b/lib/committee/schema_validator/open_api_3.rb @@ -87,7 +87,9 @@ def header(request) end def request_unpack(request) - unpacker = Committee::RequestUnpacker.new(allow_empty_date_and_datetime: validator_option.allow_empty_date_and_datetime, allow_form_params: validator_option.allow_form_params, allow_get_body: validator_option.allow_get_body, allow_query_params: validator_option.allow_query_params, allow_non_get_query_params: validator_option.allow_non_get_query_params, optimistic_json: validator_option.optimistic_json,) + media_type = @operation_object.request_operation.operation_object.request_body&.select_media_type(request.media_type) + allow_json_array = media_type&.schema&.type == 'array' + unpacker = Committee::RequestUnpacker.new(allow_empty_date_and_datetime: validator_option.allow_empty_date_and_datetime, allow_form_params: validator_option.allow_form_params, allow_get_body: validator_option.allow_get_body, allow_json_array: allow_json_array, allow_query_params: validator_option.allow_query_params, allow_non_get_query_params: validator_option.allow_non_get_query_params, optimistic_json: validator_option.optimistic_json,) request.env[validator_option.headers_key] = unpacker.unpack_headers(request) @@ -96,7 +98,7 @@ def request_unpack(request) request.env[validator_option.path_hash_key] = coerce_path_params query_param = unpacker.unpack_query_params(request) - query_param.merge!(request_param) if request.get? && validator_option.allow_get_body + query_param.merge!(request_param) if request.get? && validator_option.allow_get_body && request_param.is_a?(Hash) if @operation_object && validator_option.deserialize_parameters deserializer = ParameterDeserializer.new(@operation_object.request_operation) @@ -126,7 +128,8 @@ def copy_coerced_data_to_params(request) 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])) + value = request.env[key] + request.env[validator_option.params_key].merge!(Committee::Utils.deep_copy(value)) if value.is_a?(Hash) 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 7092122d..96b239df 100644 --- a/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +++ b/lib/committee/schema_validator/open_api_3/operation_wrapper.rb @@ -55,7 +55,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 + case request_operation.http_method when 'get', 'delete', 'head' validate_get_request_params(query_params, headers, validator_option) when 'post', 'put', 'patch', 'options' @@ -63,8 +63,6 @@ def validate_request_params(_path_params, query_params, body_params, headers, va else raise "Committee OpenAPI3 not support #{request_operation.http_method} method" end - raise err if err - ret end def optional_body? diff --git a/test/middleware/request_validation_open_api_3_test.rb b/test/middleware/request_validation_open_api_3_test.rb index 5a60c20e..7416a6b5 100644 --- a/test/middleware/request_validation_open_api_3_test.rb +++ b/test/middleware/request_validation_open_api_3_test.rb @@ -18,6 +18,29 @@ def app assert_equal 200, last_response.status end + it "accepts a JSON array request body declared by OpenAPI3" do + check_body = lambda { |env| + assert_equal [1, 2], env['committee.request_body_hash'] + assert_equal({ 'source' => 'test' }, env['committee.params']) + [200, {}, []] + } + @app = new_rack_app_with_lambda(check_body, schema: array_body_schema) + + header "Content-Type", "application/json" + post "/numbers?source=test", '[1,2]' + + assert_equal 200, last_response.status + end + + it "validates items in a JSON array request body" do + @app = new_rack_app(schema: array_body_schema) + + header "Content-Type", "application/json" + post "/numbers", '[1,"invalid"]' + + assert_equal 400, last_response.status + end + it "not parameter request" do check_parameter_string = lambda { |_| [200, { integer: 1 }, []] @@ -740,6 +763,30 @@ def query_param_schema(parameter) Committee::Drivers.load_from_data(query_param_document(parameter), nil, parser_options: { strict_reference_validation: true }) end + def array_body_schema + Committee::Drivers.load_from_data(array_body_document, nil, parser_options: { strict_reference_validation: true }) + end + + def array_body_document + { + 'openapi' => '3.0.3', + 'info' => { 'title' => 'test', 'version' => '1.0.0' }, + 'paths' => { + '/numbers' => { + 'post' => { + 'requestBody' => { + 'required' => true, + 'content' => { + 'application/json' => { 'schema' => { 'type' => 'array', 'items' => { 'type' => 'integer' } } }, + }, + }, + 'responses' => { '200' => { 'description' => 'ok' } }, + }, + }, + }, + } + end + def bracket_notation_query_parameter { 'name' => 'filter[slug]',