Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
* `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)
* `ResponseSummaryBefore32`: detect Response Object `summary` 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
* support `itemSchema` (OpenAPI 3.2) in the parse layer; it is not yet used to validate sequential media type bodies
* support Discriminator `defaultMapping` (OpenAPI 3.2): fallback schema when the discriminator property is absent or its value has no explicit or implicit mapping; honored in 3.2 documents
* support the `query` HTTP method and `additionalOperations` (OpenAPI 3.2) on Path Items, including request-operation lookup, in 3.2 documents
* match HTTP methods case-insensitively in request-operation lookup in 3.2 documents (`request_operation("GET", path)` still returns nil before 3.2)
* support Response Object `summary` (OpenAPI 3.2) in the parse layer

## 2.5.0 (2026-10-21)
* add OpenAPI 3.2 `SpecValidator` rules
Expand Down
4 changes: 4 additions & 0 deletions lib/openapi_parser/schemas/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ class Response < Base

openapi_attr_values :description

# @!attribute [r] summary
# @return [String, nil] short summary of the response (OpenAPI 3.2+)
openapi_attr_value :summary

# @!attribute [r] content
# @return [Hash{String => MediaType}, nil] content_type to MediaType hash
openapi_attr_hash_object :content, MediaType, reference: OpenAPIParser::MediaTypeSelectable::CONTENT_REFERENCE
Expand Down
2 changes: 2 additions & 0 deletions lib/openapi_parser/spec_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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'
require_relative 'spec_validator/rules/response_summary_before_32'

module OpenAPIParser
class SpecViolationError < OpenAPIError
Expand Down Expand Up @@ -111,6 +112,7 @@ def rules
Rules::AdditionalOperationsBefore32,
Rules::QuerystringBefore32,
Rules::CookieStyleBefore32,
Rules::ResponseSummaryBefore32,
]
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module OpenAPIParser
class SpecValidator
module Rules
# 3.2 adds `summary` to the Response Object. The parse layer accepts it
# permissively; this rule reports the version mismatch.
class ResponseSummaryBefore32 < Rule
def check(root)
return [] unless version_before?('3.2')

violations = []
each_node(root, OpenAPIParser::Schemas::Response) do |node|
raw = node.raw_schema
next unless raw.is_a?(Hash) && raw.key?('summary')

violations << violation(
path: "#{node.object_reference}/summary",
message: '`summary` on a Response Object is a 3.2 addition; earlier documents have no such field',
)
end
violations
end
end
end
end
end
4 changes: 4 additions & 0 deletions sig/openapi_parser/spec_validator.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ module OpenAPIParser
class CookieStyleBefore32 < Rule
def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation]
end

class ResponseSummaryBefore32 < Rule
def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation]
end
end
end

Expand Down
14 changes: 14 additions & 0 deletions spec/data/openapi_3_2/response_summary_31.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
openapi: 3.1.0
info:
title: Pet API
version: '1.0'
paths:
/pets:
get:
summary: List pets
responses:
'200':
# `summary` on a Response Object is a 3.2 addition; a 3.1 document
# has no such field, so its use here is a spec violation.
summary: Pets
description: OK
12 changes: 12 additions & 0 deletions spec/data/openapi_3_2/response_summary_32.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
openapi: 3.2.0
info:
title: Pet API
version: '1.0'
paths:
/pets:
get:
summary: List pets
responses:
'200':
summary: Pets
description: OK
9 changes: 9 additions & 0 deletions spec/openapi_parser/schemas/response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,13 @@
expect(subject.content['application/json'].object_reference).to eq '#/paths/~1pets/get/responses/default/content/application~1json'
end
end

describe 'summary (OpenAPI 3.2)' do
let(:root) { OpenAPIParser.parse(load_yaml_file('./spec/data/openapi_3_2/response_summary_32.yaml'), {}) }

it 'is parsed onto the Response object' do
response = root.paths.path['/pets'].get.responses.response['200']
expect(response.summary).to eq 'Pets'
end
end
end
14 changes: 14 additions & 0 deletions spec/openapi_parser/spec_validator/integration_3_2_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,18 @@ def expect_clean(file)
expect_clean('cookie_style_32.yaml')
end
end

describe 'Response Object summary (3.2 addition)' do
it 'warns on the version-mismatched document under :warn' do
expect_mismatch_warns('response_summary_31.yaml', [:response_summary_before32])
end

it 'raises SpecViolationError on the version-mismatched document under :raise' do
expect_mismatch_raises('response_summary_31.yaml', [:response_summary_before32])
end

it 'stays clean on the correctly-versioned document' do
expect_clean('response_summary_32.yaml')
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
require_relative '../../../spec_helper'

RSpec.describe 'OpenAPIParser::SpecValidator::Rules::ResponseSummaryBefore32' do
def base_doc(openapi_version_string, resp)
{
'openapi' => openapi_version_string,
'info' => { 'title' => 'test', 'version' => '1.0' },
'paths' => {
'/pets' => {
'get' => {
'responses' => { '200' => resp },
},
},
},
}
end

def doc_with(openapi_version_string)
OpenAPIParser.parse(base_doc(openapi_version_string, { 'summary' => 'Pets', 'description' => 'OK' }), strict_reference_validation: false)
end

def doc_without(openapi_version_string)
OpenAPIParser.parse(base_doc(openapi_version_string, { 'description' => 'OK' }), strict_reference_validation: false)
end

def run_rule_for(root)
OpenAPIParser::SpecValidator::Rules::ResponseSummaryBefore32.new(root.openapi_version).check(root)
end

context 'with a 3.2 document using a response summary' 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 a response summary' 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 a response summary' 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/responses/200/summary'
expect(violations.first.rule_name).to eq :response_summary_before32
end
end

context 'with a 3.1 document without a response summary' 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 a response summary' 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