diff --git a/lib/committee/schema_validator/hyper_schema.rb b/lib/committee/schema_validator/hyper_schema.rb index af8bd0b1..320ba0ce 100644 --- a/lib/committee/schema_validator/hyper_schema.rb +++ b/lib/committee/schema_validator/hyper_schema.rb @@ -33,7 +33,7 @@ def response_validate(status, headers, response, _test_method = false) data = JSON.parse(full_body) if parse_to_json end - Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link, validate_success_only: validator_option.validate_success_only).call(status, headers, data) + Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link, validate_success_only: validator_option.validate_success_only, permit_blank_structures: validator_option.permit_blank_structures).call(status, headers, data) end def link_exist? diff --git a/lib/committee/schema_validator/hyper_schema/response_validator.rb b/lib/committee/schema_validator/hyper_schema/response_validator.rb index 44a3d03d..59bb77df 100644 --- a/lib/committee/schema_validator/hyper_schema/response_validator.rb +++ b/lib/committee/schema_validator/hyper_schema/response_validator.rb @@ -4,11 +4,12 @@ module Committee module SchemaValidator class HyperSchema class ResponseValidator - attr_reader :validate_success_only + attr_reader :validate_success_only, :permit_blank_structures def initialize(link, options = {}) @link = link @validate_success_only = options[:validate_success_only] + @permit_blank_structures = options[:permit_blank_structures] @validator = JsonSchema::Validator.new(target_schema(link)) end @@ -39,9 +40,18 @@ def call(status, headers, data) return if data == nil end - if Committee::Middleware::ResponseValidation.validate?(status, validate_success_only) && !@validator.validate(data) - errors = JsonSchema::SchemaError.aggregate(@validator.errors).join("\n") - raise InvalidResponse, "Invalid response.\n\n#{errors}" + if permit_blank_structures && @link.is_a?(Committee::Drivers::OpenAPI2::Link) && !@link.target_schema + return if data.nil? + end + + begin + if Committee::Middleware::ResponseValidation.validate?(status, validate_success_only) && !@validator.validate(data) + errors = JsonSchema::SchemaError.aggregate(@validator.errors).join("\n") + raise InvalidResponse, "Invalid response.\n\n#{errors}" + end + rescue => e + raise InvalidResponse, "Invalid response.\n\nschema is undefined" if /undefined method .all_of. for nil/ =~ e.message + raise e end end diff --git a/lib/committee/schema_validator/option.rb b/lib/committee/schema_validator/option.rb index 72c773d9..d428eee8 100644 --- a/lib/committee/schema_validator/option.rb +++ b/lib/committee/schema_validator/option.rb @@ -17,7 +17,8 @@ class Option :optimistic_json, :validate_success_only, :parse_response_by_content_type, - :parameter_overwite_by_rails_rule + :parameter_overwite_by_rails_rule, + :permit_blank_structures # Non-boolean options: attr_reader :headers_key, @@ -46,6 +47,7 @@ def initialize(options, schema, schema_type) @optimistic_json = options.fetch(:optimistic_json, false) @parse_response_by_content_type = options.fetch(:parse_response_by_content_type, true) @parameter_overwite_by_rails_rule = options.fetch(:parameter_overwite_by_rails_rule, true) + @permit_blank_structures = options.fetch(:permit_blank_structures, false) # Boolean options and have a different value by default @allow_get_body = options.fetch(:allow_get_body, schema.driver.default_allow_get_body) diff --git a/test/data/openapi2/petstore-expanded.json b/test/data/openapi2/petstore-expanded.json index 06bea91e..fbb34170 100644 --- a/test/data/openapi2/petstore-expanded.json +++ b/test/data/openapi2/petstore-expanded.json @@ -184,6 +184,17 @@ } } } + }, + "/pets/cat": { + "get": { + "description": "Returns pets which are cats", + "operationId": "find pets which are cats", + "responses": { + "200": { + "description": "empty schema" + } + } + } } }, "definitions": { diff --git a/test/middleware/response_validation_test.rb b/test/middleware/response_validation_test.rb index 3b0bde52..6901726c 100644 --- a/test/middleware/response_validation_test.rb +++ b/test/middleware/response_validation_test.rb @@ -136,6 +136,29 @@ def app assert_equal 200, last_response.status end + it "passes through a valid response for OpenAPI when data=nil, target_schema=empty, permit_blank_structures=true" do + @app = new_rack_app("null", {}, + permit_blank_structures: true, schema: open_api_2_schema) + get "/api/pets/cat" + assert_equal 200, last_response.status + end + + it "invalid responses for OpenAPI when data=nil, target_schema=empty, permit_blank_structures=false" do + @app = new_rack_app("null", {}, + permit_blank_structures: false, schema: open_api_2_schema) + get "/api/pets/cat" + assert_equal 500, last_response.status + assert_match(/Invalid response/i, last_response.body) + end + + it "passes through a valid response for OpenAPI when data=nil, target_schema=present, permit_blank_structures=true" do + @app = new_rack_app("null", {}, + permit_blank_structures: true, schema: open_api_2_schema) + get "/api/pets/dog" + assert_equal 500, last_response.status + assert_match(/nil is not an array/i, last_response.body) + end + it "detects an invalid response for OpenAPI" do @app = new_rack_app("{_}", {}, schema: open_api_2_schema) get "/api/pets"