Skip to content
Merged
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 @@ -4,7 +4,9 @@
* support root-level `jsonSchemaDialect` (OpenAPI 3.1) in the parse layer
* support `prefixItems` (OpenAPI 3.1) with positional tuple validation
* support `contentMediaType` (OpenAPI 3.1) in the parse layer
* support `contentEncoding` (OpenAPI 3.1) in the parse layer
* add `SpecValidator` with `strict_specification_version` config (`:silent` / `:warn` / `:raise`) to detect version mismatches between declared OpenAPI version and actual field usage
* `ContentEncodingIn30`: detect `contentEncoding` usage in 3.0 documents (3.1 addition)
* `ContentMediaTypeIn30`: detect `contentMediaType` usage in 3.0 documents (3.1 addition)
* `PrefixItemsIn30`: detect `prefixItems` usage in 3.0 documents (3.1 addition)
* `JsonSchemaDialectIn30`: detect root-level `jsonSchemaDialect` usage in 3.0 documents (3.1 addition)
Expand Down
3 changes: 2 additions & 1 deletion lib/openapi_parser/schemas/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ class Schema < Base
:example,
:deprecated,
:const,
:contentMediaType
:contentMediaType,
:contentEncoding

# @!attribute [r] read_only
# @return [Boolean, nil]
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/content_encoding_in_30'
require_relative 'spec_validator/rules/content_media_type_in_30'
require_relative 'spec_validator/rules/prefix_items_in_30'
require_relative 'spec_validator/rules/json_schema_dialect_in_30'
Expand Down Expand Up @@ -63,6 +64,7 @@ def rules
[
Rules::ExclusiveMinimum,
Rules::ExclusiveMaximum,
Rules::ContentEncodingIn30,
Rules::ContentMediaTypeIn30,
Rules::PrefixItemsIn30,
Rules::JsonSchemaDialectIn30,
Expand Down
24 changes: 24 additions & 0 deletions lib/openapi_parser/spec_validator/rules/content_encoding_in_30.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module OpenAPIParser
class SpecValidator
module Rules
# `contentEncoding` is a JSON Schema 2020-12 annotation adopted by
# 3.1. Metadata only, no runtime side-effects.
class ContentEncodingIn30 < Rule
def check(root)
return [] unless version == :v3_0

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

violations << violation(
path: schema.object_reference,
message: '`contentEncoding` is a 3.1 addition (from JSON Schema 2020-12); 3.0 has no equivalent',
)
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 @@ -45,6 +45,10 @@ module OpenAPIParser
def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation]
end

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

class ContentMediaTypeIn30 < Rule
def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation]
end
Expand Down
24 changes: 24 additions & 0 deletions spec/data/openapi_3_1/content_encoding_30.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
openapi: 3.0.3
info:
title: Document Upload API
version: '1.0'
paths:
/documents:
post:
summary: Upload a document
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Attachment'
responses:
'201':
description: Created
components:
schemas:
Attachment:
type: string
# `contentEncoding` is a JSON Schema 2020-12 annotation adopted by 3.1;
# 3.0 has no equivalent, so its use on a 3.0 document is a spec
# violation.
contentEncoding: base64
23 changes: 23 additions & 0 deletions spec/data/openapi_3_1/content_encoding_31.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
openapi: 3.1.0
info:
title: Document Upload API
version: '1.0'
paths:
/documents:
post:
summary: Upload a document
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Attachment'
responses:
'201':
description: Created
components:
schemas:
Attachment:
type: string
# `contentEncoding` is legitimate under 3.1 (JSON Schema 2020-12), so
# no violation is expected here.
contentEncoding: base64
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 'contentEncoding (JSON Schema 2020-12 annotation new in 3.1)' do
it 'warns on the version-mismatched document under :warn' do
expect_mismatch_warns('content_encoding_30.yaml', [:content_encoding_in30])
end

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

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

describe 'contentMediaType (JSON Schema 2020-12 annotation new in 3.1)' do
it 'warns on the version-mismatched document under :warn' do
expect_mismatch_warns('content_media_type_30.yaml', [:content_media_type_in30])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require_relative '../../../spec_helper'

RSpec.describe 'OpenAPIParser::SpecValidator::Rules::ContentEncodingIn30' 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_content_encoding(openapi_version_string)
raw = base_doc(openapi_version_string, { 'type' => 'string', 'contentEncoding' => 'base64' })
OpenAPIParser.parse(raw, strict_reference_validation: false)
end

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

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

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

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

context 'with a 3.0 document using contentEncoding' do
it 'reports one violation pointing at the offending schema' do
root = doc_with_content_encoding('3.0.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 :content_encoding_in30
end
end

context 'with a 3.0 document without contentEncoding' do
it 'reports no violation' do
root = doc_without_content_encoding('3.0.0')
expect(run_rule_for(root)).to eq []
end
end

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