Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
## Unreleased
* support `components.pathItems` so `$ref`s into it resolve, unblocking OpenAPI 3.1 documents that use reusable path items
* add `SpecValidator` with `strict_specification_version` config (`:silent` / `:warn` / `:raise`) to detect version mismatches between declared OpenAPI version and actual field usage
* `NullableDeprecation`: detect `nullable` usage in 3.1 documents (removed in 3.1)
* `ExampleSingularDeprecation`: detect singular `example` on schemas in 3.1 documents (deprecated in 3.1)
* `PathItemsIn30`: detect `components.pathItems` usage in 3.0 documents (3.1 addition)
* `ExclusiveMinimum` / `ExclusiveMaximum`: detect 3.0 Boolean vs 3.1 numeric form mismatch
* support 3.1-style numeric `exclusiveMinimum` / `exclusiveMaximum` in value validation (standalone bound, not a Boolean modifier on `minimum` / `maximum`)

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 @@ -2,6 +2,7 @@
require_relative 'spec_validator/rule'
require_relative 'spec_validator/rules/exclusive_minimum'
require_relative 'spec_validator/rules/exclusive_maximum'
require_relative 'spec_validator/rules/path_items_in_30'
require_relative 'spec_validator/rules/nullable_deprecation'
require_relative 'spec_validator/rules/example_singular_deprecation'

Expand Down Expand Up @@ -53,6 +54,7 @@ def rules
[
Rules::ExclusiveMinimum,
Rules::ExclusiveMaximum,
Rules::PathItemsIn30,
Rules::NullableDeprecation,
Rules::ExampleSingularDeprecation,
]
Expand Down
25 changes: 25 additions & 0 deletions lib/openapi_parser/spec_validator/rules/path_items_in_30.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module OpenAPIParser
class SpecValidator
module Rules
# `components.pathItems` is a 3.1 addition. The parse layer accepts it
# in 3.0 documents permissively; this rule reports the version
# mismatch.
class PathItemsIn30 < Rule
def check(root)
return [] unless version == :v3_0

components = root.components
return [] unless components

raw = components.raw_schema
return [] unless raw.is_a?(Hash) && raw.key?('pathItems')

[violation(
path: "#{components.object_reference}/pathItems",
message: '`components.pathItems` is a 3.1 addition; 3.0 documents should not declare it',
)]
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 @@ -45,6 +45,10 @@ module OpenAPIParser
def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation]
end

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

class NullableDeprecation < Rule
def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation]
end
Expand Down
21 changes: 21 additions & 0 deletions spec/data/openapi_3_1/path_items_30.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
openapi: 3.0.3
info:
title: Reusable Path Items API
version: '1.0'
paths:
/ping:
get:
summary: Health check
responses:
'200':
description: OK
components:
# `components.pathItems` is a 3.1 addition; a 3.0 document should not
# declare it, so this is a spec violation.
pathItems:
SharedItem:
get:
summary: A reusable operation
responses:
'200':
description: OK
21 changes: 21 additions & 0 deletions spec/data/openapi_3_1/path_items_31.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
openapi: 3.1.0
info:
title: Reusable Path Items API
version: '1.0'
paths:
/ping:
get:
summary: Health check
responses:
'200':
description: OK
components:
# `components.pathItems` is legitimate under 3.1, so no violation is
# expected here.
pathItems:
SharedItem:
get:
summary: A reusable operation
responses:
'200':
description: OK
14 changes: 14 additions & 0 deletions spec/openapi_parser/spec_validator/integration_3_1_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ def expect_clean(file)
end
end

describe 'components.pathItems (3.1 addition)' do
it 'warns on the version-mismatched document under :warn' do
expect_mismatch_warns('path_items_30.yaml', [:path_items_in30])
end

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

it 'stays clean on the correctly-versioned document' do
expect_clean('path_items_31.yaml')
end
end

describe 'nullable (3.0 keyword removed in 3.1)' do
it 'warns on the version-mismatched document under :warn' do
expect_mismatch_warns('nullable_31.yaml', [:nullable_deprecation])
Expand Down
54 changes: 54 additions & 0 deletions spec/openapi_parser/spec_validator/rules/path_items_in_30_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require_relative '../../../spec_helper'

RSpec.describe 'OpenAPIParser::SpecValidator::Rules::PathItemsIn30' do
def doc_with_path_items(openapi_version_string, include_path_items: true)
components = { 'schemas' => {} }
if include_path_items
components['pathItems'] = {
'sample' => { 'get' => { 'responses' => { '200' => { 'description' => 'ok' } } } },
}
end
raw = {
'openapi' => openapi_version_string,
'info' => { 'title' => 'test', 'version' => '1.0' },
'paths' => {},
'components' => components,
}
OpenAPIParser.parse(raw, strict_reference_validation: false)
end

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

context 'with a 3.1 document using components.pathItems' do
it 'reports no violation' do
root = doc_with_path_items('3.1.0')
expect(run_rule_for(root)).to eq []
end
end

context 'with a 3.0 document using components.pathItems' do
it 'reports one violation pointing at #/components/pathItems' do
root = doc_with_path_items('3.0.0')
violations = run_rule_for(root)
expect(violations.size).to eq 1
expect(violations.first.path).to eq '#/components/pathItems'
expect(violations.first.rule_name).to eq :path_items_in30
end
end

context 'with a 3.0 document without components.pathItems' do
it 'reports no violation' do
root = doc_with_path_items('3.0.0', include_path_items: false)
expect(run_rule_for(root)).to eq []
end
end

context 'with an :unknown version document using components.pathItems' do
it 'reports no violation (rule skipped)' do
root = doc_with_path_items('4.0.0')
expect(run_rule_for(root)).to eq []
end
end
end
Loading