Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ This piece of middleware validates the parameters of incoming requests to make s
|coerce_path_params| false | true | The same as `coerce_form_params`, but tries to coerce parameters encoded in a request's URL path. |
|coerce_query_params| false | true | The same as `coerce_form_params`, but tries to coerce `GET` parameters encoded in a request's query string. |
|coerce_recursive| false | always true | Coerce data in arrays and other nested objects |
|coerce_response_values| false | false | Enable type coercion for response body validation. When `true`, allows string values like `"726"` to be coerced to numbers. When `false` (default), response bodies must match exact types defined in the schema. |
|optimistic_json| false | false | Will attempt to parse JSON in the request body even without a `Content-Type: application/json` before falling back to other options. |
|raise| false | false | Raise an exception on error instead of responding with a generic error body. |
|strict| false | false | Puts the middleware into strict mode, meaning that paths which are not defined in the schema will be responded to with a 404 instead of being run. |
Expand Down
12 changes: 8 additions & 4 deletions lib/committee/schema_validator/open_api_3/operation_wrapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,15 @@ def validate_path_and_query_params(path_params, query_params, headers, validator
def response_validate_options(strict, check_header, validator_options: {})
options = { strict: strict, validate_header: check_header }

if OpenAPIParser::SchemaValidator::ResponseValidateOptions.method_defined?(:validator_options)
::OpenAPIParser::SchemaValidator::ResponseValidateOptions.new(**options, **validator_options)
else
::OpenAPIParser::SchemaValidator::ResponseValidateOptions.new(**options)
if validator_options[:coerce_value]
options[:coerce_value] = validator_options[:coerce_value]
end

if validator_options[:allow_empty_date_and_datetime]
options[:allow_empty_date_and_datetime] = validator_options[:allow_empty_date_and_datetime]
end

::OpenAPIParser::SchemaValidator::ResponseValidateOptions.new(**options)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ def initialize(operation_wrapper, validator_option)
@validate_success_only = validator_option.validate_success_only
@check_header = validator_option.check_header
@allow_empty_date_and_datetime = validator_option.allow_empty_date_and_datetime
@coerce_response_values = validator_option.coerce_response_values
end

def call(status, headers, response_data, strict)
return unless Committee::Middleware::ResponseValidation.validate?(status, validate_success_only)

validator_options = { allow_empty_date_and_datetime: @allow_empty_date_and_datetime }
validator_options = { allow_empty_date_and_datetime: @allow_empty_date_and_datetime, coerce_value: @coerce_response_values }

operation_wrapper.validate_response_params(status, headers, response_data, strict, check_header, validator_options: validator_options)
end
Expand Down
3 changes: 2 additions & 1 deletion lib/committee/schema_validator/option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Committee
module SchemaValidator
class Option
# Boolean Options
attr_reader :allow_blank_structures, :allow_empty_date_and_datetime, :allow_form_params, :allow_get_body, :allow_query_params, :allow_non_get_query_params, :check_content_type, :check_header, :coerce_date_times, :coerce_form_params, :coerce_path_params, :coerce_query_params, :coerce_recursive, :optimistic_json, :validate_success_only, :parse_response_by_content_type, :parameter_overwrite_by_rails_rule
attr_reader :allow_blank_structures, :allow_empty_date_and_datetime, :allow_form_params, :allow_get_body, :allow_query_params, :allow_non_get_query_params, :check_content_type, :check_header, :coerce_date_times, :coerce_form_params, :coerce_path_params, :coerce_query_params, :coerce_recursive, :coerce_response_values, :optimistic_json, :validate_success_only, :parse_response_by_content_type, :parameter_overwrite_by_rails_rule

# Non-boolean options:
attr_reader :headers_key, :params_key, :query_hash_key, :request_body_hash_key, :path_hash_key, :prefix
Expand All @@ -28,6 +28,7 @@ def initialize(options, schema, schema_type)
@check_content_type = options.fetch(:check_content_type, true)
@check_header = options.fetch(:check_header, true)
@coerce_recursive = options.fetch(:coerce_recursive, true)
@coerce_response_values = options.fetch(:coerce_response_values, false)
@optimistic_json = options.fetch(:optimistic_json, false)
@parse_response_by_content_type = options.fetch(:parse_response_by_content_type, true)

Expand Down
30 changes: 30 additions & 0 deletions test/middleware/response_validation_open_api_3_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,36 @@ def app
end
end

describe 'response type validation' do
it "detects string value for number field when coerce_response_values is false" do
@app = new_response_rack({ integer: '726' }.to_json, {}, app_status: 400, schema: open_api_3_schema, raise: true, validate_success_only: false, coerce_response_values: false)

e = assert_raises(Committee::InvalidResponse) do
get "/characters"
end

assert_match(/expected integer, but received String/i, e.message)
end

it "passes string value for number field when coerce_response_values is true" do
@app = new_response_rack({ integer: '726' }.to_json, {}, app_status: 400, schema: open_api_3_schema, raise: true, validate_success_only: false, coerce_response_values: true)

get "/characters"

assert_equal 400, last_response.status
end

it "detects string value for number field by default (coerce_response_values defaults to false)" do
@app = new_response_rack({ integer: '726' }.to_json, {}, app_status: 400, schema: open_api_3_schema, raise: true, validate_success_only: false)

e = assert_raises(Committee::InvalidResponse) do
get "/characters"
end

assert_match(/expected integer, but received String/i, e.message)
end
end

it 'does not suppress application error' do
@app = Rack::Builder.new {
use Committee::Middleware::ResponseValidation, { schema: open_api_3_schema, raise: true }
Expand Down