From d1488a8d11cd323753e42587c48b45285640494f Mon Sep 17 00:00:00 2001 From: chibicco Date: Tue, 27 Feb 2024 22:31:32 +0900 Subject: [PATCH 1/4] Add option to permit validation when schema and data are empty in OpenAPI2::Link --- .../schema_validator/hyper_schema.rb | 2 +- .../hyper_schema/response_validator.rb | 18 +++++++++++---- lib/committee/schema_validator/option.rb | 4 +++- test/data/openapi2/petstore-expanded.json | 11 +++++++++ test/middleware/response_validation_test.rb | 23 +++++++++++++++++++ 5 files changed, 52 insertions(+), 6 deletions(-) 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" From 6de758350e2a7d0b1a7a49720637451d177cf8a3 Mon Sep 17 00:00:00 2001 From: chibicco Date: Sat, 11 May 2024 16:18:56 +0900 Subject: [PATCH 2/4] Rename options from permit_blank_structures to allow_blank_structures --- lib/committee/schema_validator/hyper_schema.rb | 2 +- .../hyper_schema/response_validator.rb | 6 +++--- lib/committee/schema_validator/option.rb | 8 ++++---- test/middleware/response_validation_test.rb | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/committee/schema_validator/hyper_schema.rb b/lib/committee/schema_validator/hyper_schema.rb index 320ba0ce..1419b01e 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, permit_blank_structures: validator_option.permit_blank_structures).call(status, headers, data) + Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link, validate_success_only: validator_option.validate_success_only, allow_blank_structures: validator_option.allow_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 59bb77df..d396219c 100644 --- a/lib/committee/schema_validator/hyper_schema/response_validator.rb +++ b/lib/committee/schema_validator/hyper_schema/response_validator.rb @@ -4,12 +4,12 @@ module Committee module SchemaValidator class HyperSchema class ResponseValidator - attr_reader :validate_success_only, :permit_blank_structures + attr_reader :allow_blank_structures, :validate_success_only def initialize(link, options = {}) @link = link @validate_success_only = options[:validate_success_only] - @permit_blank_structures = options[:permit_blank_structures] + @allow_blank_structures = options[:allow_blank_structures] @validator = JsonSchema::Validator.new(target_schema(link)) end @@ -40,7 +40,7 @@ def call(status, headers, data) return if data == nil end - if permit_blank_structures && @link.is_a?(Committee::Drivers::OpenAPI2::Link) && !@link.target_schema + if allow_blank_structures && @link.is_a?(Committee::Drivers::OpenAPI2::Link) && !@link.target_schema return if data.nil? end diff --git a/lib/committee/schema_validator/option.rb b/lib/committee/schema_validator/option.rb index d428eee8..8b2fae77 100644 --- a/lib/committee/schema_validator/option.rb +++ b/lib/committee/schema_validator/option.rb @@ -4,7 +4,8 @@ module Committee module SchemaValidator class Option # Boolean Options - attr_reader :allow_form_params, + attr_reader :allow_blank_structures, + :allow_form_params, :allow_get_body, :allow_query_params, :check_content_type, @@ -17,8 +18,7 @@ class Option :optimistic_json, :validate_success_only, :parse_response_by_content_type, - :parameter_overwite_by_rails_rule, - :permit_blank_structures + :parameter_overwite_by_rails_rule # Non-boolean options: attr_reader :headers_key, @@ -39,6 +39,7 @@ def initialize(options, schema, schema_type) @prefix = options[:prefix] # Boolean options and have a common value by default + @allow_blank_structures = options.fetch(:allow_blank_structures, false) @allow_form_params = options.fetch(:allow_form_params, true) @allow_query_params = options.fetch(:allow_query_params, true) @check_content_type = options.fetch(:check_content_type, true) @@ -47,7 +48,6 @@ 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/middleware/response_validation_test.rb b/test/middleware/response_validation_test.rb index 6901726c..c5d743df 100644 --- a/test/middleware/response_validation_test.rb +++ b/test/middleware/response_validation_test.rb @@ -136,24 +136,24 @@ 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 + it "passes through a valid response for OpenAPI when data=nil, target_schema=empty, allow_blank_structures=true" do @app = new_rack_app("null", {}, - permit_blank_structures: true, schema: open_api_2_schema) + allow_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 + it "invalid responses for OpenAPI when data=nil, target_schema=empty, allow_blank_structures=false" do @app = new_rack_app("null", {}, - permit_blank_structures: false, schema: open_api_2_schema) + allow_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 + it "passes through a valid response for OpenAPI when data=nil, target_schema=present, allow_blank_structures=true" do @app = new_rack_app("null", {}, - permit_blank_structures: true, schema: open_api_2_schema) + allow_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) From 0d63bfd70e46abd00064613665e76648b21afa39 Mon Sep 17 00:00:00 2001 From: chibicco Date: Sat, 11 May 2024 18:07:22 +0900 Subject: [PATCH 3/4] Add explanation of allow_blank_structures to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 68855ddc..0a56b4e7 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ This piece of middleware validates the parameters of incoming requests to make s | name | Hyper-Schema | OpenAPI 3 | Description | |-----------:|------------:|------------:| :------------ | +|allow_blank_structures | false | always true | Allow Empty Response Body. supported on Hyper-schema parser but will default to true in next major version. | |allow_form_params | true | true | Specifies that input can alternatively be specified as `application/x-www-form-urlencoded` parameters when possible. This won't work for more complex schema validations. | |allow_get_body | true | false | Allow GET request body, which merge to request parameter. See (#211) | |allow_query_params | true | true | Specifies that query string parameters will be taken into consideration when doing validation. | From 97ece37c509b0799ef5943a849b21af92b877563 Mon Sep 17 00:00:00 2001 From: chibicco Date: Sun, 12 May 2024 02:36:54 +0900 Subject: [PATCH 4/4] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7178ca8a..81c9f954 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `allow_blank_structures` option #417 + - Allow Empty Response Body. supported on Hyper-schema parser but will default to true in next major version. + ## [5.2.0] - 2024-05-04 - Error explicitly that OpenAPI 3.1+ isn't supported #418