Skip to content

Commit

Permalink
Add option to permit validation when schema and data are empty (#417)
Browse files Browse the repository at this point in the history
This PR add option to permit validation when schema and data are empty in OpenAPI2::Link

## Background

In our project, there are scenarios where the API returns an empty response body with status code 200.
However, the current OpenAPI 2 schema validation does not allow an empty schema for the response body, causing validation errors in these cases.

Specifically, in cases such as the schema and controller example below, I expect the Committee::Middleware::ResponseValidation check to pass successfully. 

```yaml
"/pets/cat": {
  "get": {
    "description": "Returns pets which are cats",
    "operationId": "find pets which are cats",
    "responses": {
      "200": {
        "description": "empty schema"
      }
    }
  }
}
```

```ruby
class CatController
    # pets/cat
    get "/" do
        status 200
        present nil
    end
end
```

## Changes

To address this issue, I have added an option to allow an empty schema for the response body in OpenAPI 2.
This change enables the validation to pass when both the schema and response body are nil, accommodating the specific use case in our project.

1. If allow_blank_structures is set to true, the validation will pass if the schema is empty and the data is nil.
2. I have also modified the error message for the case where data is present and the schema does not exist.
  • Loading branch information
chibicco authored May 13, 2024
1 parent 3fc3139 commit 79bad85
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
2 changes: 1 addition & 1 deletion lib/committee/schema_validator/hyper_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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, allow_blank_structures: validator_option.allow_blank_structures).call(status, headers, data)
end

def link_exist?
Expand Down
18 changes: 14 additions & 4 deletions lib/committee/schema_validator/hyper_schema/response_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ module Committee
module SchemaValidator
class HyperSchema
class ResponseValidator
attr_reader :validate_success_only
attr_reader :allow_blank_structures, :validate_success_only

def initialize(link, options = {})
@link = link
@validate_success_only = options[:validate_success_only]
@allow_blank_structures = options[:allow_blank_structures]

@validator = JsonSchema::Validator.new(target_schema(link))
end
Expand Down Expand Up @@ -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 allow_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

Expand Down
4 changes: 3 additions & 1 deletion lib/committee/schema_validator/option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -38,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)
Expand Down
11 changes: 11 additions & 0 deletions test/data/openapi2/petstore-expanded.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
23 changes: 23 additions & 0 deletions test/middleware/response_validation_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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, allow_blank_structures=true" do
@app = new_rack_app("null", {},
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, allow_blank_structures=false" do
@app = new_rack_app("null", {},
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, allow_blank_structures=true" do
@app = new_rack_app("null", {},
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)
end

it "detects an invalid response for OpenAPI" do
@app = new_rack_app("{_}", {}, schema: open_api_2_schema)
get "/api/pets"
Expand Down

0 comments on commit 79bad85

Please sign in to comment.