diff --git a/CHANGELOG.md b/CHANGELOG.md index 412281f8..df9cfde6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * `QuerystringBefore32`: detect Parameter `in: querystring` usage in pre-3.2 documents (3.2 addition; not validated at runtime) * `CookieStyleBefore32`: detect Parameter `style: cookie` usage in pre-3.2 documents (3.2 addition) * `ResponseSummaryBefore32`: detect Response Object `summary` usage in pre-3.2 documents (3.2 addition) + * `AllowEmptyValueDeprecation`: detect Parameter `allowEmptyValue` usage in 3.2 documents (deprecated in 3.2) * return nil from `request_operation` for a document without `paths` (3.1 allows a document with only `webhooks`), instead of raising `NoMethodError` * validate a value against a schema without `type`: keywords such as `properties`, `required`, `items`, `maxLength`, `enum` and `minimum` now apply when the value has the type the keyword is for, as in JSON Schema. They were all skipped before, so a value that breaks them may now fail validation * add `allow_3_2_features` config to apply OpenAPI 3.2 runtime behavior to documents that declare an earlier version (default `false`: pre-3.2 documents behave as before) diff --git a/lib/openapi_parser/spec_validator.rb b/lib/openapi_parser/spec_validator.rb index c9077450..77357aa5 100644 --- a/lib/openapi_parser/spec_validator.rb +++ b/lib/openapi_parser/spec_validator.rb @@ -33,6 +33,7 @@ require_relative 'spec_validator/rules/querystring_before_32' require_relative 'spec_validator/rules/cookie_style_before_32' require_relative 'spec_validator/rules/response_summary_before_32' +require_relative 'spec_validator/rules/allow_empty_value_deprecation' module OpenAPIParser class SpecViolationError < OpenAPIError @@ -113,6 +114,7 @@ def rules Rules::QuerystringBefore32, Rules::CookieStyleBefore32, Rules::ResponseSummaryBefore32, + Rules::AllowEmptyValueDeprecation, ] end end diff --git a/lib/openapi_parser/spec_validator/rules/allow_empty_value_deprecation.rb b/lib/openapi_parser/spec_validator/rules/allow_empty_value_deprecation.rb new file mode 100644 index 00000000..9a57248f --- /dev/null +++ b/lib/openapi_parser/spec_validator/rules/allow_empty_value_deprecation.rb @@ -0,0 +1,26 @@ +module OpenAPIParser + class SpecValidator + module Rules + # 3.2 deprecates `allowEmptyValue` on the Parameter Object (3.1 only + # called it NOT RECOMMENDED). The field is still allowed but + # discouraged, so we report it as a violation on 3.2 documents. + class AllowEmptyValueDeprecation < Rule + def check(root) + return [] unless version_at_least?('3.2') + + violations = [] + each_node(root, OpenAPIParser::Schemas::Parameter) do |node| + raw = node.raw_schema + next unless raw.is_a?(Hash) && raw.key?('allowEmptyValue') + + violations << violation( + path: "#{node.object_reference}/allowEmptyValue", + message: '`allowEmptyValue` on a Parameter Object is deprecated in 3.2', + ) + end + violations + end + end + end + end +end diff --git a/sig/openapi_parser/spec_validator.rbs b/sig/openapi_parser/spec_validator.rbs index 5490b7c5..bd217786 100644 --- a/sig/openapi_parser/spec_validator.rbs +++ b/sig/openapi_parser/spec_validator.rbs @@ -179,6 +179,10 @@ module OpenAPIParser class ResponseSummaryBefore32 < Rule def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation] end + + class AllowEmptyValueDeprecation < Rule + def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation] + end end end diff --git a/spec/data/openapi_3_2/allow_empty_value_31.yaml b/spec/data/openapi_3_2/allow_empty_value_31.yaml new file mode 100644 index 00000000..eaba6804 --- /dev/null +++ b/spec/data/openapi_3_2/allow_empty_value_31.yaml @@ -0,0 +1,17 @@ +openapi: 3.1.0 +info: + title: Pet API + version: '1.0' +paths: + /pets: + get: + summary: List pets + parameters: + - name: q + in: query + allowEmptyValue: true + schema: + type: string + responses: + '200': + description: OK diff --git a/spec/data/openapi_3_2/allow_empty_value_32.yaml b/spec/data/openapi_3_2/allow_empty_value_32.yaml new file mode 100644 index 00000000..c800dc56 --- /dev/null +++ b/spec/data/openapi_3_2/allow_empty_value_32.yaml @@ -0,0 +1,19 @@ +openapi: 3.2.0 +info: + title: Pet API + version: '1.0' +paths: + /pets: + get: + summary: List pets + parameters: + # `allowEmptyValue` on a Parameter Object is deprecated in 3.2, so its + # use here is a spec violation. + - name: q + in: query + allowEmptyValue: true + schema: + type: string + responses: + '200': + description: OK diff --git a/spec/openapi_parser/spec_validator/integration_3_2_spec.rb b/spec/openapi_parser/spec_validator/integration_3_2_spec.rb index 0c4f57f0..77737178 100644 --- a/spec/openapi_parser/spec_validator/integration_3_2_spec.rb +++ b/spec/openapi_parser/spec_validator/integration_3_2_spec.rb @@ -248,4 +248,18 @@ def expect_clean(file) expect_clean('response_summary_32.yaml') end end + + describe 'Parameter allowEmptyValue (deprecated in 3.2)' do + it 'warns on the version-mismatched document under :warn' do + expect_mismatch_warns('allow_empty_value_32.yaml', [:allow_empty_value_deprecation]) + end + + it 'raises SpecViolationError on the version-mismatched document under :raise' do + expect_mismatch_raises('allow_empty_value_32.yaml', [:allow_empty_value_deprecation]) + end + + it 'stays clean on the correctly-versioned document' do + expect_clean('allow_empty_value_31.yaml') + end + end end diff --git a/spec/openapi_parser/spec_validator/rules/allow_empty_value_deprecation_spec.rb b/spec/openapi_parser/spec_validator/rules/allow_empty_value_deprecation_spec.rb new file mode 100644 index 00000000..313a31de --- /dev/null +++ b/spec/openapi_parser/spec_validator/rules/allow_empty_value_deprecation_spec.rb @@ -0,0 +1,71 @@ +require_relative '../../../spec_helper' + +RSpec.describe 'OpenAPIParser::SpecValidator::Rules::AllowEmptyValueDeprecation' do + def base_doc(openapi_version_string, param) + { + 'openapi' => openapi_version_string, + 'info' => { 'title' => 'test', 'version' => '1.0' }, + 'paths' => { + '/pets' => { + 'get' => { + 'parameters' => [param], + 'responses' => { '200' => { 'description' => 'OK' } }, + }, + }, + }, + } + end + + def doc_with(openapi_version_string) + OpenAPIParser.parse(base_doc(openapi_version_string, { 'name' => 'q', 'in' => 'query', 'allowEmptyValue' => true, 'schema' => { 'type' => 'string' } }), strict_reference_validation: false) + end + + def doc_without(openapi_version_string) + OpenAPIParser.parse(base_doc(openapi_version_string, { 'name' => 'q', 'in' => 'query', 'schema' => { 'type' => 'string' } }), strict_reference_validation: false) + end + + def run_rule_for(root) + OpenAPIParser::SpecValidator::Rules::AllowEmptyValueDeprecation.new(root.openapi_version).check(root) + end + + context 'with a 3.2 document using allowEmptyValue' do + it 'reports one violation pointing at the field' do + root = doc_with('3.2.0') + violations = run_rule_for(root) + expect(violations.size).to eq 1 + expect(violations.first.path).to eq '#/paths/~1pets/get/parameters/0/allowEmptyValue' + expect(violations.first.rule_name).to eq :allow_empty_value_deprecation + expect(violations.first.message).to include('deprecated in 3.2') + end + end + + context 'with a 3.2 document without allowEmptyValue' do + it 'reports no violation' do + expect(run_rule_for(doc_without('3.2.0'))).to eq [] + end + end + + context 'with a 3.1 document using allowEmptyValue' do + it 'reports no violation' do + expect(run_rule_for(doc_with('3.1.0'))).to eq [] + end + end + + context 'with a 3.1 document without allowEmptyValue' do + it 'reports no violation' do + expect(run_rule_for(doc_without('3.1.0'))).to eq [] + end + end + + context 'with a 3.0 document using allowEmptyValue' do + it 'reports no violation' do + expect(run_rule_for(doc_with('3.0.0'))).to eq [] + end + end + + context 'with a document whose openapi field is not a version' do + it 'reports no violation (rule skipped)' do + expect(run_rule_for(doc_with('not-a-version'))).to eq [] + end + end +end