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 @@ -25,6 +25,8 @@
* `TagFieldsBefore32`: detect Tag Object `summary` / `parent` / `kind` usage in pre-3.2 documents (3.2 additions)
* `ServerNameBefore32`: detect Server Object `name` usage in pre-3.2 documents (3.2 addition)
* `ExampleValueFieldsBefore32`: detect Example Object `dataValue` / `serializedValue` usage in pre-3.2 documents (3.2 additions)
* `XmlNodeTypeBefore32`: detect XML Object `nodeType` usage in pre-3.2 documents (3.2 addition)
* `XmlAttributeDeprecation` / `XmlWrappedDeprecation`: detect XML Object `attribute: true` / `wrapped: true` in 3.2 documents (deprecated in 3.2 in favor of `nodeType`)
* expose the declared version as `OpenAPI#openapi_version` (a `Gem::Version`, or nil when the field is missing or malformed) so `SpecValidator` rules compare version ranges; a 3.2 document is checked by the 3.1-or-later rules
* support 3.1-style numeric `exclusiveMinimum` / `exclusiveMaximum` in value validation (standalone bound, not a Boolean modifier on `minimum` / `maximum`)
* support `type: "null"` (3.1 primitive) in value validation
Expand Down
6 changes: 6 additions & 0 deletions lib/openapi_parser/spec_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
require_relative 'spec_validator/rules/tag_fields_before_32'
require_relative 'spec_validator/rules/server_name_before_32'
require_relative 'spec_validator/rules/example_value_fields_before_32'
require_relative 'spec_validator/rules/xml_node_type_before_32'
require_relative 'spec_validator/rules/xml_attribute_deprecation'
require_relative 'spec_validator/rules/xml_wrapped_deprecation'

module OpenAPIParser
class SpecViolationError < OpenAPIError
Expand Down Expand Up @@ -87,6 +90,9 @@ def rules
Rules::TagFieldsBefore32,
Rules::ServerNameBefore32,
Rules::ExampleValueFieldsBefore32,
Rules::XmlNodeTypeBefore32,
Rules::XmlAttributeDeprecation,
Rules::XmlWrappedDeprecation,
]
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module OpenAPIParser
class SpecValidator
module Rules
# 3.2 deprecates `attribute: true` on the XML Object in favor of
# `nodeType: attribute`. The field is still allowed but discouraged,
# so we report it as a violation on 3.2 documents.
class XmlAttributeDeprecation < Rule
def check(root)
return [] unless version_at_least?('3.2')

violations = []
each_schema(root) do |schema|
raw = schema.raw_schema
next unless raw.is_a?(Hash)

xml = raw['xml']
next unless xml.is_a?(Hash) && xml['attribute'] == true

violations << violation(
path: schema.object_reference,
message: '`attribute` on an XML Object is deprecated in 3.2; use `nodeType: attribute`',
)
end
violations
end
end
end
end
end
29 changes: 29 additions & 0 deletions lib/openapi_parser/spec_validator/rules/xml_node_type_before_32.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module OpenAPIParser
class SpecValidator
module Rules
# 3.2 adds `nodeType` to the XML Object (replacing `attribute` and
# `wrapped`). XML Objects are not modeled by the parse layer, so this
# rule inspects the raw `xml` field on schemas.
class XmlNodeTypeBefore32 < Rule
def check(root)
return [] unless version_before?('3.2')

violations = []
each_schema(root) do |schema|
raw = schema.raw_schema
next unless raw.is_a?(Hash)

xml = raw['xml']
next unless xml.is_a?(Hash) && xml.key?('nodeType')

violations << violation(
path: schema.object_reference,
message: '`nodeType` on an XML Object is a 3.2 addition; earlier documents have no such field',
)
end
violations
end
end
end
end
end
29 changes: 29 additions & 0 deletions lib/openapi_parser/spec_validator/rules/xml_wrapped_deprecation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module OpenAPIParser
class SpecValidator
module Rules
# 3.2 deprecates `wrapped: true` on the XML Object in favor of
# `nodeType: element`. The field is still allowed but discouraged,
# so we report it as a violation on 3.2 documents.
class XmlWrappedDeprecation < Rule
def check(root)
return [] unless version_at_least?('3.2')

violations = []
each_schema(root) do |schema|
raw = schema.raw_schema
next unless raw.is_a?(Hash)

xml = raw['xml']
next unless xml.is_a?(Hash) && xml['wrapped'] == true

violations << violation(
path: schema.object_reference,
message: '`wrapped` on an XML Object is deprecated in 3.2; use `nodeType: element`',
)
end
violations
end
end
end
end
end
12 changes: 12 additions & 0 deletions sig/openapi_parser/spec_validator.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ module OpenAPIParser
NEW_FIELDS: Array[String]
def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation]
end

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

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

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

Expand Down
28 changes: 28 additions & 0 deletions spec/data/openapi_3_2/xml_deprecated_fields_31.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
openapi: 3.1.0
info:
title: Pet API
version: '1.0'
paths:
/pets:
get:
summary: List pets
responses:
'200':
description: OK
components:
schemas:
Pet:
type: object
properties:
id:
type: integer
xml:
# `attribute` and `wrapped` are legitimate XML Object fields
# before 3.2, so no violation is expected here.
attribute: true
tags:
type: array
items:
type: string
xml:
wrapped: true
30 changes: 30 additions & 0 deletions spec/data/openapi_3_2/xml_deprecated_fields_32.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
openapi: 3.2.0
info:
title: Pet API
version: '1.0'
paths:
/pets:
get:
summary: List pets
responses:
'200':
description: OK
components:
schemas:
Pet:
type: object
properties:
id:
type: integer
xml:
# `attribute` on an XML Object is deprecated in 3.2 in favor of
# `nodeType: attribute`, so its use here is a spec violation.
attribute: true
tags:
type: array
items:
type: string
xml:
# `wrapped` is likewise deprecated in 3.2 in favor of
# `nodeType: element`.
wrapped: true
23 changes: 23 additions & 0 deletions spec/data/openapi_3_2/xml_node_type_31.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
openapi: 3.1.0
info:
title: Pet API
version: '1.0'
paths:
/pets:
get:
summary: List pets
responses:
'200':
description: OK
components:
schemas:
Pet:
type: object
properties:
id:
type: integer
xml:
name: pet
# `nodeType` on an XML Object is a 3.2 addition; a 3.1 document has
# no such field, so its use here is a spec violation.
nodeType: element
23 changes: 23 additions & 0 deletions spec/data/openapi_3_2/xml_node_type_32.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
openapi: 3.2.0
info:
title: Pet API
version: '1.0'
paths:
/pets:
get:
summary: List pets
responses:
'200':
description: OK
components:
schemas:
Pet:
type: object
properties:
id:
type: integer
xml:
name: pet
# `nodeType` is a legitimate XML Object field under 3.2, so no
# violation is expected here.
nodeType: element
28 changes: 28 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 @@ -94,4 +94,32 @@ def expect_clean(file)
expect_clean('example_value_fields_32.yaml')
end
end

describe 'XML Object nodeType (3.2 addition)' do
it 'warns on the version-mismatched document under :warn' do
expect_mismatch_warns('xml_node_type_31.yaml', [:xml_node_type_before32])
end

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

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

describe 'XML Object attribute/wrapped (deprecated in 3.2)' do
it 'warns on the 3.2 document still using the deprecated fields under :warn' do
expect_mismatch_warns('xml_deprecated_fields_32.yaml', [:xml_attribute_deprecation, :xml_wrapped_deprecation])
end

it 'raises SpecViolationError on the 3.2 document under :raise' do
expect_mismatch_raises('xml_deprecated_fields_32.yaml', [:xml_attribute_deprecation, :xml_wrapped_deprecation])
end

it 'stays clean on the 3.1 document (fields legitimate before 3.2)' do
expect_clean('xml_deprecated_fields_31.yaml')
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
require_relative '../../../spec_helper'

RSpec.describe 'OpenAPIParser::SpecValidator::Rules::XmlAttributeDeprecation' do
def base_doc(openapi_version_string, sample_schema)
{
'openapi' => openapi_version_string,
'info' => { 'title' => 'test', 'version' => '1.0' },
'paths' => {},
'components' => { 'schemas' => { 'Sample' => sample_schema } },
}
end

def doc_with_attribute(openapi_version_string)
raw = base_doc(openapi_version_string, { 'type' => 'string', 'xml' => { 'attribute' => true } })
OpenAPIParser.parse(raw, strict_reference_validation: false)
end

def doc_without_attribute(openapi_version_string)
raw = base_doc(openapi_version_string, { 'type' => 'string', 'xml' => { 'nodeType' => 'attribute' } })
OpenAPIParser.parse(raw, strict_reference_validation: false)
end

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

context 'with a 3.2 document using xml attribute' do
it 'reports one violation pointing at the offending schema' do
root = doc_with_attribute('3.2.0')
violations = run_rule_for(root)
expect(violations.size).to eq 1
expect(violations.first.path).to eq '#/components/schemas/Sample'
expect(violations.first.rule_name).to eq :xml_attribute_deprecation
expect(violations.first.message).to include('deprecated in 3.2')
end
end

context 'with a 3.2 document using nodeType instead' do
it 'reports no violation' do
root = doc_without_attribute('3.2.0')
expect(run_rule_for(root)).to eq []
end
end

context 'with a 3.2 document using xml attribute: false' do
it 'reports no violation (only the true form is deprecated)' do
raw = base_doc('3.2.0', { 'type' => 'string', 'xml' => { 'attribute' => false } })
root = OpenAPIParser.parse(raw, strict_reference_validation: false)
expect(run_rule_for(root)).to eq []
end
end

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

context 'with a 3.1 document without xml attribute' do
it 'reports no violation' do
root = doc_without_attribute('3.1.0')
expect(run_rule_for(root)).to eq []
end
end

context 'with a 3.0 document using xml attribute' do
it 'reports no violation' do
root = doc_with_attribute('3.0.0')
expect(run_rule_for(root)).to eq []
end
end

context 'with a document whose openapi field is not a version' do
it 'reports no violation (rule skipped)' do
root = doc_with_attribute('not-a-version')
expect(run_rule_for(root)).to eq []
end
end
end
Loading