From f35101c946027381b8f547ac6cb236efaba8f64c Mon Sep 17 00:00:00 2001 From: jonathan schatz Date: Thu, 24 Sep 2026 15:26:11 -0700 Subject: [PATCH] Detect Parameter style: cookie in pre-3.2 documents (CookieStyleBefore32 rule) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + lib/openapi_parser/spec_validator.rb | 2 + .../rules/cookie_style_before_32.rb | 26 +++++++ sig/openapi_parser/spec_validator.rbs | 4 ++ spec/data/openapi_3_2/cookie_style_31.yaml | 19 +++++ spec/data/openapi_3_2/cookie_style_32.yaml | 17 +++++ .../spec_validator/integration_3_2_spec.rb | 14 ++++ .../rules/cookie_style_before_32_spec.rb | 69 +++++++++++++++++++ 8 files changed, 152 insertions(+) create mode 100644 lib/openapi_parser/spec_validator/rules/cookie_style_before_32.rb create mode 100644 spec/data/openapi_3_2/cookie_style_31.yaml create mode 100644 spec/data/openapi_3_2/cookie_style_32.yaml create mode 100644 spec/openapi_parser/spec_validator/rules/cookie_style_before_32_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 330a7e59..94fd9ca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * `QueryMethodBefore32`: detect Path Item `query` usage in pre-3.2 documents (3.2 addition) * `AdditionalOperationsBefore32`: detect Path Item `additionalOperations` usage in pre-3.2 documents (3.2 addition) * `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) * 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) * support `components.mediaTypes` (OpenAPI 3.2), resolving `$ref`s in request body and response `content` in 3.2 documents; an unresolved one accepts any body unless `strict_reference_validation` is set diff --git a/lib/openapi_parser/spec_validator.rb b/lib/openapi_parser/spec_validator.rb index e61662f4..129b9624 100644 --- a/lib/openapi_parser/spec_validator.rb +++ b/lib/openapi_parser/spec_validator.rb @@ -31,6 +31,7 @@ require_relative 'spec_validator/rules/query_method_before_32' require_relative 'spec_validator/rules/additional_operations_before_32' require_relative 'spec_validator/rules/querystring_before_32' +require_relative 'spec_validator/rules/cookie_style_before_32' module OpenAPIParser class SpecViolationError < OpenAPIError @@ -109,6 +110,7 @@ def rules Rules::QueryMethodBefore32, Rules::AdditionalOperationsBefore32, Rules::QuerystringBefore32, + Rules::CookieStyleBefore32, ] end end diff --git a/lib/openapi_parser/spec_validator/rules/cookie_style_before_32.rb b/lib/openapi_parser/spec_validator/rules/cookie_style_before_32.rb new file mode 100644 index 00000000..ed242076 --- /dev/null +++ b/lib/openapi_parser/spec_validator/rules/cookie_style_before_32.rb @@ -0,0 +1,26 @@ +module OpenAPIParser + class SpecValidator + module Rules + # `style: cookie` is a 3.2 Parameter style for cookie serialization. + # Runtime validation ignores `style`; this rule reports the version + # mismatch. + class CookieStyleBefore32 < Rule + def check(root) + return [] unless version_before?('3.2') + + violations = [] + each_node(root, OpenAPIParser::Schemas::Parameter) do |node| + raw = node.raw_schema + next unless raw.is_a?(Hash) && raw['style'] == 'cookie' + + violations << violation( + path: "#{node.object_reference}/style", + message: '`style: cookie` is a 3.2 Parameter style; earlier documents have no such style', + ) + end + violations + end + end + end + end +end diff --git a/sig/openapi_parser/spec_validator.rbs b/sig/openapi_parser/spec_validator.rbs index ccc19b94..2c635479 100644 --- a/sig/openapi_parser/spec_validator.rbs +++ b/sig/openapi_parser/spec_validator.rbs @@ -171,6 +171,10 @@ module OpenAPIParser class QuerystringBefore32 < Rule def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation] end + + class CookieStyleBefore32 < Rule + def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation] + end end end diff --git a/spec/data/openapi_3_2/cookie_style_31.yaml b/spec/data/openapi_3_2/cookie_style_31.yaml new file mode 100644 index 00000000..2f943b3b --- /dev/null +++ b/spec/data/openapi_3_2/cookie_style_31.yaml @@ -0,0 +1,19 @@ +openapi: 3.1.0 +info: + title: Pet API + version: '1.0' +paths: + /pets: + get: + summary: List pets + parameters: + # `style: cookie` is a 3.2 Parameter style; a 3.1 document has no + # such style, so its use here is a spec violation. + - name: session + in: cookie + style: cookie + schema: + type: string + responses: + '200': + description: OK diff --git a/spec/data/openapi_3_2/cookie_style_32.yaml b/spec/data/openapi_3_2/cookie_style_32.yaml new file mode 100644 index 00000000..baee6887 --- /dev/null +++ b/spec/data/openapi_3_2/cookie_style_32.yaml @@ -0,0 +1,17 @@ +openapi: 3.2.0 +info: + title: Pet API + version: '1.0' +paths: + /pets: + get: + summary: List pets + parameters: + - name: session + in: cookie + style: cookie + 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 ff9c3696..33c3d754 100644 --- a/spec/openapi_parser/spec_validator/integration_3_2_spec.rb +++ b/spec/openapi_parser/spec_validator/integration_3_2_spec.rb @@ -220,4 +220,18 @@ def expect_clean(file) expect_clean('querystring_32.yaml') end end + + describe 'Parameter style: cookie (3.2 addition)' do + it 'warns on the version-mismatched document under :warn' do + expect_mismatch_warns('cookie_style_31.yaml', [:cookie_style_before32]) + end + + it 'raises SpecViolationError on the version-mismatched document under :raise' do + expect_mismatch_raises('cookie_style_31.yaml', [:cookie_style_before32]) + end + + it 'stays clean on the correctly-versioned document' do + expect_clean('cookie_style_32.yaml') + end + end end diff --git a/spec/openapi_parser/spec_validator/rules/cookie_style_before_32_spec.rb b/spec/openapi_parser/spec_validator/rules/cookie_style_before_32_spec.rb new file mode 100644 index 00000000..51461ff6 --- /dev/null +++ b/spec/openapi_parser/spec_validator/rules/cookie_style_before_32_spec.rb @@ -0,0 +1,69 @@ +require_relative '../../../spec_helper' + +RSpec.describe 'OpenAPIParser::SpecValidator::Rules::CookieStyleBefore32' 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' => 'session', 'in' => 'cookie', 'style' => 'cookie', 'schema' => { 'type' => 'string' } }), strict_reference_validation: false) + end + + def doc_without(openapi_version_string) + OpenAPIParser.parse(base_doc(openapi_version_string, { 'name' => 'session', 'in' => 'cookie', 'style' => 'form', 'schema' => { 'type' => 'string' } }), strict_reference_validation: false) + end + + def run_rule_for(root) + OpenAPIParser::SpecValidator::Rules::CookieStyleBefore32.new(root.openapi_version).check(root) + end + + context 'with a 3.2 document using style: cookie' do + it 'reports no violation' do + expect(run_rule_for(doc_with('3.2.0'))).to eq [] + end + end + + context 'with a 3.2 document without style: cookie' 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 style: cookie' do + it 'reports one violation pointing at the field' do + violations = run_rule_for(doc_with('3.1.0')) + expect(violations.size).to eq 1 + expect(violations.first.path).to eq '#/paths/~1pets/get/parameters/0/style' + expect(violations.first.rule_name).to eq :cookie_style_before32 + end + end + + context 'with a 3.1 document without style: cookie' 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 style: cookie' do + it 'reports one violation' do + expect(run_rule_for(doc_with('3.0.0')).size).to eq 1 + 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