Skip to content

Commit b9e334c

Browse files
committed
WIP json schema dialect warnings
I think the code here is complete but we need to add tests.
1 parent a4d6dbb commit b9e334c

6 files changed

Lines changed: 104 additions & 7 deletions

File tree

lib/openapi3_parser/document.rb

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ module Openapi3Parser
1010
# @attr_reader [Source] root_source
1111
# @attr_reader [Array<String>] warnings
1212
# @attr_reader [Boolean] emit_warnings
13+
# rubocop:disable Metrics/ClassLength
1314
class Document
1415
extend Forwardable
1516
include Enumerable
1617

17-
attr_reader :openapi_version, :root_source, :warnings, :emit_warnings
18+
attr_reader :openapi_version, :root_source, :emit_warnings
1819

1920
# A collection of the openapi versions that are supported
2021
SUPPORTED_OPENAPI_VERSIONS = %w[3.0 3.1].freeze
@@ -92,7 +93,8 @@ def initialize(source_input, emit_warnings: true)
9293
@reference_registry = ReferenceRegistry.new
9394
@root_source = Source.new(source_input, self, reference_registry)
9495
@emit_warnings = emit_warnings
95-
@warnings = []
96+
@build_warnings = []
97+
@unsupported_schema_dialects = Set.new
9698
@openapi_version = determine_openapi_version(root_source.data["openapi"])
9799
@build_in_progress = false
98100
@built = false
@@ -162,15 +164,35 @@ def node_at(pointer, relative_to = nil)
162164
look_up_pointer(pointer, relative_to, root)
163165
end
164166

167+
# An array of any warnings enountered in the initialisation / validation
168+
# of the document. Reflects warnings related to this gems ability to parse
169+
# the document.
170+
#
171+
# @return [Array<String>]
172+
def warnings
173+
@warnings ||= begin
174+
factory.errors # ensure factory has completed validation
175+
@build_warnings.freeze
176+
end
177+
end
178+
165179
# @return [String]
166180
def inspect
167181
%{#{self.class.name}(openapi_version: #{openapi_version}, } +
168182
%{root_source: #{root_source.inspect})}
169183
end
170184

185+
#  :nodoc:
186+
def unsupported_schema_dialect(schema_dialect)
187+
return if @build_warnings.frozen? || unsupported_schema_dialects.include?(schema_dialect)
188+
189+
unsupported_schema_dialects << schema_dialect
190+
add_warning("Unsupported schema dialect (#{schema_dialect}), it may not parse or validate correctly.")
191+
end
192+
171193
private
172194

173-
attr_reader :reference_registry, :built, :build_in_progress
195+
attr_reader :reference_registry, :built, :build_in_progress, :unsupported_schema_dialects, :build_warnings
174196

175197
def look_up_pointer(pointer, relative_pointer, subject)
176198
merged_pointer = Source::Pointer.merge_pointers(relative_pointer,
@@ -179,8 +201,8 @@ def look_up_pointer(pointer, relative_pointer, subject)
179201
end
180202

181203
def add_warning(text)
182-
warn("Warning: #{text} - disable these by opening a document with emit_warnings: false") if emit_warnings
183-
@warnings << text
204+
warn("Warning: #{text} Disable these warnings by opening a document with emit_warnings: false.") if emit_warnings
205+
@build_warnings << text
184206
end
185207

186208
def build
@@ -190,7 +212,6 @@ def build
190212
context = NodeFactory::Context.root(root_source.data, root_source)
191213
@factory = NodeFactory::Openapi.new(context)
192214
reference_registry.freeze
193-
@warnings.freeze
194215
@build_in_progress = false
195216
@built = true
196217
end
@@ -225,4 +246,5 @@ def reference_factories
225246
reference_registry.factories.reject { |f| f.context.source.root? }
226247
end
227248
end
249+
# rubocop:enable Metrics/ClassLength
228250
end

lib/openapi3_parser/node/schema/v3_1.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ def false?
5151
boolean == false
5252
end
5353

54+
# The schema dialect in usage, only https://spec.openapis.org/oas/3.1/dialect/base
55+
# is officially supported so others will receive a warning, but as
56+
# long they don't have different data types for keywords they'll be
57+
# mostly usable.
58+
#
59+
# @return [String]
60+
def json_schema_dialect
61+
self["$schema"] || node_context.document.json_schema_dialect
62+
end
63+
5464
# @return [String, Node::Array<String>, nil]
5565
def type
5666
self["type"]

lib/openapi3_parser/node_factory/openapi.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require "openapi3_parser/node_factory/paths"
66
require "openapi3_parser/node_factory/components"
77
require "openapi3_parser/node_factory/external_documentation"
8+
require "openapi3_parser/node_factory/schema/v3_1"
89

910
module Openapi3Parser
1011
module NodeFactory
@@ -14,7 +15,7 @@ class Openapi < NodeFactory::Object
1415
field "openapi", input_type: String, required: true
1516
field "info", factory: NodeFactory::Info, required: true
1617
field "jsonSchemaDialect",
17-
default: "https://spec.openapis.org/oas/3.1/dialect/base",
18+
default: Schema::V3_1::OAS_DIALECT,
1819
input_type: String,
1920
validate: Validation::InputValidator.new(Validators::Uri),
2021
allowed: ->(context) { context.openapi_version >= "3.1" }

lib/openapi3_parser/node_factory/schema/v3_1.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require "openapi3_parser/node_factory/object"
44
require "openapi3_parser/node_factory/referenceable"
5+
require "openapi3_parser/node_factory/schema/common"
56
require "openapi3_parser/validators/media_type"
67

78
module Openapi3Parser
@@ -13,12 +14,16 @@ class V3_1 < NodeFactory::Object # rubocop:disable Naming/ClassAndModuleCamelCas
1314
include Referenceable
1415
include Schema::Common
1516
JSON_SCHEMA_ALLOWED_TYPES = %w[null boolean object array number string integer].freeze
17+
OAS_DIALECT = "https://spec.openapis.org/oas/3.1/dialect/base"
1618

1719
# Allows any extension as per:
1820
# https://github.com/OAI/OpenAPI-Specification/blob/a1facce1b3621df3630cb692e9fbe18a7612ea6d/versions/3.1.0.md#fixed-fields-20
1921
allow_extensions(regex: /.*/)
2022

2123
field "$ref", input_type: String, factory: :ref_factory
24+
field "$schema",
25+
input_type: String,
26+
validate: Validation::InputValidator.new(Validators::Uri)
2227
field "type", factory: :type_factory, validate: :validate_type
2328
field "const"
2429
field "exclusiveMaximum", input_type: Numeric
@@ -43,6 +48,18 @@ class V3_1 < NodeFactory::Object # rubocop:disable Naming/ClassAndModuleCamelCas
4348
field "unevaluatedItems", factory: :referenceable_schema
4449
field "unevaluatedProperties", factory: :referenceable_schema
4550

51+
validate do |validatable|
52+
# if we do more with supporting $schema we probably want it to be
53+
# a value in the context object so it can cascade appropariately
54+
document = validatable.context.source_location.document
55+
dialect = validatable.input["$schema"] || document.resolved_input_at("#/jsonSchemaDialect")
56+
57+
next if dialect.nil? || dialect == OAS_DIALECT
58+
59+
dialect_string = dialect.to_s if dialect.respond_to?(:to_s)
60+
document.unsupported_schema_dialect(dialect_string)
61+
end
62+
4663
def boolean_input?
4764
[true, false].include?(resolved_input)
4865
end

spec/integration/open_v3.1_examples_spec.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,33 @@
3333
end
3434
end
3535

36+
context "when using the schema dialects example" do
37+
let(:path) { File.join(__dir__, "..", "support", "examples", "v3.1", "schema-dialects-example.yaml") }
38+
39+
it "is valid but outputs warnings" do
40+
expect { document.valid? }.to output.to_stderr
41+
expect(document).to be_valid
42+
end
43+
44+
it "only warns once per dialect" do
45+
expect { document.warnings }.to output.to_stderr
46+
end
47+
48+
it "defaults to using the the jsonSchemaDialect value" do
49+
expect { document.warnings }.to output.to_stderr
50+
expect(document.components.schemas["DefaultDialect"].json_schema_dialect)
51+
.to eq(document.json_schema_dialect)
52+
end
53+
54+
it "can return the other schema dialects" do
55+
expect { document.warnings }.to output.to_stderr
56+
expect(document.components.schemas["DefinedDialect"].json_schema_dialect)
57+
.to eq("https://spec.openapis.org/oas/3.1/dialect/base")
58+
expect(document.components.schemas["CustomDialect1"].json_schema_dialect)
59+
.to eq("https://example.com/custom-dialect")
60+
end
61+
end
62+
3663
context "when using the schema I created to demonstrate changes" do
3764
let(:path) { File.join(__dir__, "..", "support", "examples", "v3.1", "changes.yaml") }
3865

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
openapi: 3.1.0
2+
info:
3+
title: Schema Dialects Example
4+
version: 1.0.0
5+
jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema"
6+
components:
7+
schemas:
8+
DefaultDialect:
9+
type: string
10+
AnotherDefaultDialect:
11+
type: string
12+
DefinedDialect:
13+
$schema: "https://spec.openapis.org/oas/3.1/dialect/base"
14+
type: string
15+
CustomDialect1:
16+
$schema: "https://example.com/custom-dialect"
17+
type: string
18+
CustomDialect2:
19+
$schema: "https://example.com/custom-dialect"
20+
type: string

0 commit comments

Comments
 (0)