From bf3ce4d99bd6ff9c6506ffc5c5e62fa980bf2f0b Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki Date: Wed, 20 May 2026 01:45:53 +0900 Subject: [PATCH 1/4] Add pending specs for jsonSchemaDialect parse + rule (3.1 strategy PR11) --- .../rules/json_schema_dialect_in_30_spec.rb | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 spec/openapi_parser/spec_validator/rules/json_schema_dialect_in_30_spec.rb diff --git a/spec/openapi_parser/spec_validator/rules/json_schema_dialect_in_30_spec.rb b/spec/openapi_parser/spec_validator/rules/json_schema_dialect_in_30_spec.rb new file mode 100644 index 0000000..58de0fe --- /dev/null +++ b/spec/openapi_parser/spec_validator/rules/json_schema_dialect_in_30_spec.rb @@ -0,0 +1,59 @@ +require_relative '../../../spec_helper' + +RSpec.describe 'OpenAPIParser::SpecValidator::Rules::JsonSchemaDialectIn30' do + def base_doc(openapi_version_string) + { + 'openapi' => openapi_version_string, + 'info' => { 'title' => 'test', 'version' => '1.0' }, + 'paths' => {}, + } + end + + def doc_with_dialect(openapi_version_string) + raw = base_doc(openapi_version_string) + raw['jsonSchemaDialect'] = 'https://spec.openapis.org/oas/3.1/dialect/base' + OpenAPIParser.parse(raw, strict_reference_validation: false) + end + + def doc_without_dialect(openapi_version_string) + OpenAPIParser.parse(base_doc(openapi_version_string), strict_reference_validation: false) + end + + def run_rule_for(root) + OpenAPIParser::SpecValidator::Rules::JsonSchemaDialectIn30.new(root.openapi_version).check(root) + end + + context 'with a 3.1 document declaring jsonSchemaDialect' do + it 'reports no violation' + end + + context 'with a 3.1 document without jsonSchemaDialect' do + it 'reports no violation' + end + + context 'with a 3.0 document declaring jsonSchemaDialect' do + it 'reports one violation pointing at #/jsonSchemaDialect' + end + + context 'with a 3.0 document without jsonSchemaDialect' do + it 'reports no violation' + end + + context 'with an :unknown version document' do + it 'reports no violation (rule skipped)' + end +end + +RSpec.describe 'OpenAPI#json_schema_dialect parse layer' do + let(:root) do + raw = { + 'openapi' => '3.1.0', + 'info' => { 'title' => 'test', 'version' => '1.0' }, + 'paths' => {}, + 'jsonSchemaDialect' => 'https://spec.openapis.org/oas/3.1/dialect/base', + } + OpenAPIParser.parse(raw, strict_reference_validation: false) + end + + it 'exposes the dialect URI string' +end From 332a7336d04f6b6461c635eb4d0f748d72d137ff Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki Date: Wed, 20 May 2026 01:46:54 +0900 Subject: [PATCH 2/4] jsonSchemaDialect parse + JsonSchemaDialectIn30 rule (3.1 strategy PR11) OpenAPI root gains a `json_schema_dialect` String accessor (camelCase schema key). The matching SpecValidator rule flags the field on 3.0 documents. --- lib/openapi_parser/schemas/openapi.rb | 4 +++ lib/openapi_parser/spec_validator.rb | 2 ++ .../rules/json_schema_dialect_in_30.rb | 19 +++++++++++ sig/openapi_parser/spec_validator.rbs | 4 +++ .../rules/json_schema_dialect_in_30_spec.rb | 32 +++++++++++++++---- 5 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 lib/openapi_parser/spec_validator/rules/json_schema_dialect_in_30.rb diff --git a/lib/openapi_parser/schemas/openapi.rb b/lib/openapi_parser/schemas/openapi.rb index 4fa2b74..f80cc36 100644 --- a/lib/openapi_parser/schemas/openapi.rb +++ b/lib/openapi_parser/schemas/openapi.rb @@ -50,6 +50,10 @@ def openapi_version # @return [Hash{String => PathItem}, nil] webhook path items (OpenAPI 3.1+) openapi_attr_hash_object :webhooks, PathItem, reference: true + # @!attribute [r] json_schema_dialect + # @return [String, nil] dialect URI for embedded JSON Schemas (OpenAPI 3.1+) + openapi_attr_value :json_schema_dialect, schema_key: :jsonSchemaDialect + # @return [OpenAPIParser::RequestOperation, nil] def request_operation(http_method, request_path) OpenAPIParser::RequestOperation.create(http_method, request_path, @path_item_finder, @config) diff --git a/lib/openapi_parser/spec_validator.rb b/lib/openapi_parser/spec_validator.rb index 6c5c4fb..cb88c59 100644 --- a/lib/openapi_parser/spec_validator.rb +++ b/lib/openapi_parser/spec_validator.rb @@ -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/json_schema_dialect_in_30' require_relative 'spec_validator/rules/type_array_in_30' require_relative 'spec_validator/rules/path_items_in_30' require_relative 'spec_validator/rules/nullable_deprecation' @@ -57,6 +58,7 @@ def rules [ Rules::ExclusiveMinimum, Rules::ExclusiveMaximum, + Rules::JsonSchemaDialectIn30, Rules::TypeArrayIn30, Rules::PathItemsIn30, Rules::NullableDeprecation, diff --git a/lib/openapi_parser/spec_validator/rules/json_schema_dialect_in_30.rb b/lib/openapi_parser/spec_validator/rules/json_schema_dialect_in_30.rb new file mode 100644 index 0000000..c33c3c2 --- /dev/null +++ b/lib/openapi_parser/spec_validator/rules/json_schema_dialect_in_30.rb @@ -0,0 +1,19 @@ +module OpenAPIParser + class SpecValidator + module Rules + # `jsonSchemaDialect` is a 3.1 root-level addition; 3.0 has no + # equivalent. + class JsonSchemaDialectIn30 < Rule + def check(root) + return [] unless version == :v3_0 + return [] unless root.raw_schema.is_a?(Hash) && root.raw_schema.key?('jsonSchemaDialect') + + [violation( + path: '#/jsonSchemaDialect', + message: '`jsonSchemaDialect` is a 3.1 root-level addition; 3.0 documents have no such field', + )] + end + end + end + end +end diff --git a/sig/openapi_parser/spec_validator.rbs b/sig/openapi_parser/spec_validator.rbs index 3eec7d6..1087645 100644 --- a/sig/openapi_parser/spec_validator.rbs +++ b/sig/openapi_parser/spec_validator.rbs @@ -45,6 +45,10 @@ module OpenAPIParser def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation] end + class JsonSchemaDialectIn30 < Rule + def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation] + end + class TypeArrayIn30 < Rule def check: (OpenAPIParser::Schemas::OpenAPI root) -> Array[SpecValidator::SpecViolation] end diff --git a/spec/openapi_parser/spec_validator/rules/json_schema_dialect_in_30_spec.rb b/spec/openapi_parser/spec_validator/rules/json_schema_dialect_in_30_spec.rb index 58de0fe..4739631 100644 --- a/spec/openapi_parser/spec_validator/rules/json_schema_dialect_in_30_spec.rb +++ b/spec/openapi_parser/spec_validator/rules/json_schema_dialect_in_30_spec.rb @@ -24,23 +24,41 @@ def run_rule_for(root) end context 'with a 3.1 document declaring jsonSchemaDialect' do - it 'reports no violation' + it 'reports no violation' do + root = doc_with_dialect('3.1.0') + expect(run_rule_for(root)).to eq [] + end end context 'with a 3.1 document without jsonSchemaDialect' do - it 'reports no violation' + it 'reports no violation' do + root = doc_without_dialect('3.1.0') + expect(run_rule_for(root)).to eq [] + end end context 'with a 3.0 document declaring jsonSchemaDialect' do - it 'reports one violation pointing at #/jsonSchemaDialect' + it 'reports one violation pointing at #/jsonSchemaDialect' do + root = doc_with_dialect('3.0.0') + violations = run_rule_for(root) + expect(violations.size).to eq 1 + expect(violations.first.path).to eq '#/jsonSchemaDialect' + expect(violations.first.rule_name).to eq :json_schema_dialect_in30 + end end context 'with a 3.0 document without jsonSchemaDialect' do - it 'reports no violation' + it 'reports no violation' do + root = doc_without_dialect('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)' + it 'reports no violation (rule skipped)' do + root = doc_with_dialect('4.0.0') + expect(run_rule_for(root)).to eq [] + end end end @@ -55,5 +73,7 @@ def run_rule_for(root) OpenAPIParser.parse(raw, strict_reference_validation: false) end - it 'exposes the dialect URI string' + it 'exposes the dialect URI string' do + expect(root.json_schema_dialect).to eq 'https://spec.openapis.org/oas/3.1/dialect/base' + end end From 2e5e5482d3c2ee34de6a33c55b13f0c0288f63fe Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki Date: Sat, 30 May 2026 16:45:36 +0900 Subject: [PATCH 3/4] Integration test: jsonSchemaDialect new in 3.1 A root-level `jsonSchemaDialect` on a 3.0 document warns and raises (3.1 addition); the same field on a 3.1 document stays clean. --- spec/data/openapi_3_1/json_schema_dialect_30.yaml | 14 ++++++++++++++ spec/data/openapi_3_1/json_schema_dialect_31.yaml | 14 ++++++++++++++ .../spec_validator/integration_3_1_spec.rb | 14 ++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 spec/data/openapi_3_1/json_schema_dialect_30.yaml create mode 100644 spec/data/openapi_3_1/json_schema_dialect_31.yaml diff --git a/spec/data/openapi_3_1/json_schema_dialect_30.yaml b/spec/data/openapi_3_1/json_schema_dialect_30.yaml new file mode 100644 index 0000000..29fcbdb --- /dev/null +++ b/spec/data/openapi_3_1/json_schema_dialect_30.yaml @@ -0,0 +1,14 @@ +openapi: 3.0.3 +# `jsonSchemaDialect` is a 3.1 root-level addition; 3.0 documents have no +# such field, so its presence is a spec violation. +jsonSchemaDialect: https://spec.openapis.org/oas/3.1/dialect/base +info: + title: Inventory API + version: '1.0' +paths: + /items: + get: + summary: List items + responses: + '200': + description: OK diff --git a/spec/data/openapi_3_1/json_schema_dialect_31.yaml b/spec/data/openapi_3_1/json_schema_dialect_31.yaml new file mode 100644 index 0000000..ed367c5 --- /dev/null +++ b/spec/data/openapi_3_1/json_schema_dialect_31.yaml @@ -0,0 +1,14 @@ +openapi: 3.1.0 +# `jsonSchemaDialect` is a legitimate root-level field in 3.1, so no +# violation is expected here. +jsonSchemaDialect: https://spec.openapis.org/oas/3.1/dialect/base +info: + title: Inventory API + version: '1.0' +paths: + /items: + get: + summary: List items + responses: + '200': + description: OK diff --git a/spec/openapi_parser/spec_validator/integration_3_1_spec.rb b/spec/openapi_parser/spec_validator/integration_3_1_spec.rb index 001520f..a49f89b 100644 --- a/spec/openapi_parser/spec_validator/integration_3_1_spec.rb +++ b/spec/openapi_parser/spec_validator/integration_3_1_spec.rb @@ -74,6 +74,20 @@ def expect_clean(file) end end + describe 'jsonSchemaDialect (3.1 root-level addition)' do + it 'warns on the version-mismatched document under :warn' do + expect_mismatch_warns('json_schema_dialect_30.yaml', [:json_schema_dialect_in30]) + end + + it 'raises SpecViolationError on the version-mismatched document under :raise' do + expect_mismatch_raises('json_schema_dialect_30.yaml', [:json_schema_dialect_in30]) + end + + it 'stays clean on the correctly-versioned document' do + expect_clean('json_schema_dialect_31.yaml') + end + end + describe 'type as an Array of names (3.1 form rejected by 3.0)' do it 'warns on the version-mismatched document under :warn' do expect_mismatch_warns('type_array_30.yaml', [:type_array_in30]) From b429d5af98e8d8c73058a70bf596a57f4c8b97ff Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki Date: Sat, 4 Jul 2026 12:09:40 +0900 Subject: [PATCH 4/4] Add CHANGELOG entries for jsonSchemaDialect support --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 780886e..a27f676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ ## Unreleased * support `components.pathItems` so `$ref`s into it resolve, unblocking OpenAPI 3.1 documents that use reusable path items * support array-form `type` (3.1) in value validation +* support root-level `jsonSchemaDialect` (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 + * `JsonSchemaDialectIn30`: detect root-level `jsonSchemaDialect` usage in 3.0 documents (3.1 addition) * `TypeArrayIn30`: detect array-form `type` usage in 3.0 documents (3.1 form) * `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)