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
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/nullable_deprecation'

module OpenAPIParser
class SpecViolationError < OpenAPIError
Expand Down Expand Up @@ -51,6 +52,7 @@ def rules
[
Rules::ExclusiveMinimum,
Rules::ExclusiveMaximum,
Rules::NullableDeprecation,
]
end
end
Expand Down
25 changes: 25 additions & 0 deletions lib/openapi_parser/spec_validator/rules/nullable_deprecation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module OpenAPIParser
class SpecValidator
module Rules
# `nullable` is a 3.0 keyword that 3.1 removed in favor of
# `type: [..., "null"]`. The field's mere presence on a 3.1 document
# is a spec violation, regardless of true/false.
class NullableDeprecation < Rule
def check(root)
return [] unless version == :v3_1

violations = []
each_schema(root) do |schema|
next unless schema.raw_schema.is_a?(Hash) && schema.raw_schema.key?('nullable')

violations << violation(
path: schema.object_reference,
message: '`nullable` was removed in 3.1; use `type: [..., "null"]` instead',
)
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 @@ -44,6 +44,10 @@ module OpenAPIParser
class ExclusiveMaximum < Rule
def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation]
end

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

Expand Down
31 changes: 31 additions & 0 deletions spec/data/openapi_3_1/nullable_30.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
openapi: 3.0.3
info:
title: Profile API
version: '1.0'
paths:
/profiles/{id}:
get:
summary: Fetch a profile
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Profile'
components:
schemas:
Profile:
type: object
properties:
# 3.0 form: `nullable: true` is the legitimate way to allow null in
# 3.0, so no violation is expected on a 3.0 document.
nickname:
type: string
nullable: true
31 changes: 31 additions & 0 deletions spec/data/openapi_3_1/nullable_31.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
openapi: 3.1.0
info:
title: Profile API
version: '1.0'
paths:
/profiles/{id}:
get:
summary: Fetch a profile
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Profile'
components:
schemas:
Profile:
type: object
properties:
# 3.0 form leaking into a 3.1 document: `nullable` was removed in 3.1
# in favor of `type: [..., "null"]`, so its presence is a violation.
nickname:
type: string
nullable: true
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 @@ -73,4 +73,18 @@ def expect_clean(file)
expect_clean('exclusive_maximum_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])
end

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

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

RSpec.describe 'OpenAPIParser::SpecValidator::Rules::NullableDeprecation' do
def schema_with_nullable(openapi_version_string, nullable_value)
schema_payload = { 'type' => 'string' }
schema_payload['nullable'] = nullable_value unless nullable_value == :absent
raw = {
'openapi' => openapi_version_string,
'info' => { 'title' => 'test', 'version' => '1.0' },
'paths' => {},
'components' => { 'schemas' => { 'Sample' => schema_payload } },
}
OpenAPIParser.parse(raw, strict_reference_validation: false)
end

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

context 'with a 3.0 document using nullable: true' do
it 'reports no violation' do
root = schema_with_nullable('3.0.0', true)
expect(run_rule_for(root)).to eq []
end
end

context 'with a 3.0 document using nullable: false' do
it 'reports no violation' do
root = schema_with_nullable('3.0.0', false)
expect(run_rule_for(root)).to eq []
end
end

context 'with a 3.1 document using nullable: true' do
it 'reports one violation pointing at the offending schema' do
root = schema_with_nullable('3.1.0', true)
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 :nullable_deprecation
expect(violations.first.message).to include('removed in 3.1')
end
end

context 'with a 3.1 document using nullable: false' do
it 'reports one violation (the field itself is removed in 3.1)' do
root = schema_with_nullable('3.1.0', false)
violations = run_rule_for(root)
expect(violations.size).to eq 1
end
end

context 'with a 3.1 document that does not use nullable' do
it 'reports no violation' do
root = schema_with_nullable('3.1.0', :absent)
expect(run_rule_for(root)).to eq []
end
end

context 'with an :unknown version document' do
it 'reports no violation (rule skipped)' do
root = schema_with_nullable('4.0.0', true)
expect(run_rule_for(root)).to eq []
end
end
end