openapi3_parser 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +3 -3
- data/CHANGELOG.md +7 -1
- data/README.md +102 -15
- data/lib/openapi3_parser/document.rb +14 -14
- data/lib/openapi3_parser/document/reference_registry.rb +72 -0
- data/lib/openapi3_parser/node/array.rb +10 -2
- data/lib/openapi3_parser/node/components.rb +9 -9
- data/lib/openapi3_parser/node/contact.rb +3 -3
- data/lib/openapi3_parser/node/context.rb +129 -0
- data/lib/openapi3_parser/node/discriminator.rb +2 -2
- data/lib/openapi3_parser/node/encoding.rb +5 -5
- data/lib/openapi3_parser/node/example.rb +4 -4
- data/lib/openapi3_parser/node/external_documentation.rb +2 -2
- data/lib/openapi3_parser/node/info.rb +6 -6
- data/lib/openapi3_parser/node/license.rb +2 -2
- data/lib/openapi3_parser/node/link.rb +6 -6
- data/lib/openapi3_parser/node/map.rb +8 -4
- data/lib/openapi3_parser/node/media_type.rb +5 -5
- data/lib/openapi3_parser/node/oauth_flow.rb +4 -4
- data/lib/openapi3_parser/node/oauth_flows.rb +4 -4
- data/lib/openapi3_parser/node/object.rb +8 -4
- data/lib/openapi3_parser/node/openapi.rb +8 -8
- data/lib/openapi3_parser/node/operation.rb +12 -12
- data/lib/openapi3_parser/node/parameter.rb +2 -2
- data/lib/openapi3_parser/node/parameter_like.rb +11 -11
- data/lib/openapi3_parser/node/path_item.rb +12 -12
- data/lib/openapi3_parser/node/placeholder.rb +34 -0
- data/lib/openapi3_parser/node/request_body.rb +3 -3
- data/lib/openapi3_parser/node/response.rb +4 -4
- data/lib/openapi3_parser/node/responses.rb +1 -1
- data/lib/openapi3_parser/node/schema.rb +36 -36
- data/lib/openapi3_parser/node/security_scheme.rb +8 -8
- data/lib/openapi3_parser/node/server.rb +3 -3
- data/lib/openapi3_parser/node/server_variable.rb +3 -3
- data/lib/openapi3_parser/node/tag.rb +3 -3
- data/lib/openapi3_parser/node/xml.rb +5 -5
- data/lib/openapi3_parser/node_factory/array.rb +15 -13
- data/lib/openapi3_parser/node_factory/callback.rb +2 -2
- data/lib/openapi3_parser/node_factory/context.rb +111 -0
- data/lib/openapi3_parser/node_factory/field.rb +5 -7
- data/lib/openapi3_parser/node_factory/fields/reference.rb +43 -24
- data/lib/openapi3_parser/node_factory/link.rb +1 -1
- data/lib/openapi3_parser/node_factory/map.rb +14 -12
- data/lib/openapi3_parser/node_factory/object.rb +9 -5
- data/lib/openapi3_parser/node_factory/object_factory/node_builder.rb +21 -28
- data/lib/openapi3_parser/node_factory/optional_reference.rb +4 -0
- data/lib/openapi3_parser/node_factory/parameter_like.rb +0 -2
- data/lib/openapi3_parser/node_factory/path_item.rb +7 -4
- data/lib/openapi3_parser/node_factory/paths.rb +2 -2
- data/lib/openapi3_parser/node_factory/reference.rb +17 -10
- data/lib/openapi3_parser/node_factory/responses.rb +2 -2
- data/lib/openapi3_parser/node_factory/security_requirement.rb +2 -2
- data/lib/openapi3_parser/source.rb +27 -24
- data/lib/openapi3_parser/{context → source}/location.rb +13 -1
- data/lib/openapi3_parser/{context → source}/pointer.rb +2 -2
- data/lib/openapi3_parser/source/resolved_reference.rb +67 -0
- data/lib/openapi3_parser/validators/duplicate_parameters.rb +8 -4
- data/lib/openapi3_parser/validators/reference.rb +3 -3
- data/lib/openapi3_parser/version.rb +1 -1
- data/openapi3_parser.gemspec +1 -1
- metadata +11 -10
- data/lib/openapi3_parser/context.rb +0 -162
- data/lib/openapi3_parser/document/reference_register.rb +0 -48
- data/lib/openapi3_parser/node_factory/recursive_pointer.rb +0 -17
- data/lib/openapi3_parser/source/reference_resolver.rb +0 -82
@@ -8,7 +8,7 @@ module Openapi3Parser
|
|
8
8
|
allow_extensions
|
9
9
|
|
10
10
|
# @todo The link object in OAS is pretty meaty and there's lot of scope
|
11
|
-
# for further work here to make use of
|
11
|
+
# for further work here to make use of its functionality
|
12
12
|
|
13
13
|
field "operationRef", input_type: String
|
14
14
|
field "operationId", input_type: String
|
@@ -46,11 +46,9 @@ module Openapi3Parser
|
|
46
46
|
@errors ||= ValidNodeBuilder.errors(self)
|
47
47
|
end
|
48
48
|
|
49
|
-
def node
|
50
|
-
|
51
|
-
|
52
|
-
data.nil? ? nil : build_node(data)
|
53
|
-
end
|
49
|
+
def node(node_context)
|
50
|
+
data = ValidNodeBuilder.data(self, node_context)
|
51
|
+
data.nil? ? nil : build_node(data, node_context)
|
54
52
|
end
|
55
53
|
|
56
54
|
def inspect
|
@@ -84,8 +82,8 @@ module Openapi3Parser
|
|
84
82
|
end
|
85
83
|
end
|
86
84
|
|
87
|
-
def build_node(data)
|
88
|
-
Node::Map.new(data,
|
85
|
+
def build_node(data, node_context)
|
86
|
+
Node::Map.new(data, node_context) if data
|
89
87
|
end
|
90
88
|
|
91
89
|
def build_resolved_input
|
@@ -105,8 +103,8 @@ module Openapi3Parser
|
|
105
103
|
new(factory).errors
|
106
104
|
end
|
107
105
|
|
108
|
-
def self.data(factory)
|
109
|
-
new(factory).data
|
106
|
+
def self.data(factory, parent_context)
|
107
|
+
new(factory).data(parent_context)
|
110
108
|
end
|
111
109
|
|
112
110
|
def initialize(factory)
|
@@ -122,7 +120,7 @@ module Openapi3Parser
|
|
122
120
|
validatable.collection
|
123
121
|
end
|
124
122
|
|
125
|
-
def data
|
123
|
+
def data(parent_context)
|
126
124
|
return default_value if factory.nil_input?
|
127
125
|
|
128
126
|
TypeChecker.raise_on_invalid_type(factory.context, type: ::Hash)
|
@@ -131,7 +129,11 @@ module Openapi3Parser
|
|
131
129
|
validate(raise_on_invalid: true)
|
132
130
|
|
133
131
|
factory.data.each_with_object({}) do |(key, value), memo|
|
134
|
-
memo[key] = value.respond_to?(:node)
|
132
|
+
memo[key] = if value.respond_to?(:node)
|
133
|
+
Node::Placeholder.new(value, key, parent_context)
|
134
|
+
else
|
135
|
+
value
|
136
|
+
end
|
135
137
|
end
|
136
138
|
end
|
137
139
|
|
@@ -200,7 +202,7 @@ module Openapi3Parser
|
|
200
202
|
|
201
203
|
first_error = validatable.errors.first
|
202
204
|
raise Openapi3Parser::Error::InvalidData,
|
203
|
-
"Invalid data for #{first_error.context.location_summary}
|
205
|
+
"Invalid data for #{first_error.context.location_summary}: "\
|
204
206
|
"#{first_error.message}"
|
205
207
|
end
|
206
208
|
|
@@ -18,6 +18,10 @@ module Openapi3Parser
|
|
18
18
|
|
19
19
|
attr_reader :context, :data
|
20
20
|
|
21
|
+
def self.object_type
|
22
|
+
to_s
|
23
|
+
end
|
24
|
+
|
21
25
|
def initialize(context)
|
22
26
|
@context = context
|
23
27
|
@data = build_data(context.input)
|
@@ -43,8 +47,8 @@ module Openapi3Parser
|
|
43
47
|
@errors ||= ObjectFactory::NodeBuilder.errors(self)
|
44
48
|
end
|
45
49
|
|
46
|
-
def node
|
47
|
-
|
50
|
+
def node(node_context)
|
51
|
+
build_node(node_context)
|
48
52
|
end
|
49
53
|
|
50
54
|
def can_use_default?
|
@@ -99,9 +103,9 @@ module Openapi3Parser
|
|
99
103
|
end
|
100
104
|
end
|
101
105
|
|
102
|
-
def build_node
|
103
|
-
data = ObjectFactory::NodeBuilder.node_data(self)
|
104
|
-
build_object(data,
|
106
|
+
def build_node(node_context)
|
107
|
+
data = ObjectFactory::NodeBuilder.node_data(self, node_context)
|
108
|
+
build_object(data, node_context) if data
|
105
109
|
end
|
106
110
|
end
|
107
111
|
end
|
@@ -8,8 +8,8 @@ module Openapi3Parser
|
|
8
8
|
new(factory).errors
|
9
9
|
end
|
10
10
|
|
11
|
-
def self.node_data(factory)
|
12
|
-
new(factory).node_data
|
11
|
+
def self.node_data(factory, parent_context)
|
12
|
+
new(factory).node_data(parent_context)
|
13
13
|
end
|
14
14
|
|
15
15
|
def initialize(factory)
|
@@ -29,11 +29,11 @@ module Openapi3Parser
|
|
29
29
|
validatable.collection
|
30
30
|
end
|
31
31
|
|
32
|
-
def node_data
|
33
|
-
return build_node_data if empty_and_allowed_to_be?
|
32
|
+
def node_data(parent_context)
|
33
|
+
return build_node_data(parent_context) if empty_and_allowed_to_be?
|
34
34
|
TypeChecker.raise_on_invalid_type(factory.context, type: ::Hash)
|
35
35
|
validate(raise_on_invalid: true)
|
36
|
-
build_node_data
|
36
|
+
build_node_data(parent_context)
|
37
37
|
end
|
38
38
|
|
39
39
|
private_class_method :new
|
@@ -50,40 +50,33 @@ module Openapi3Parser
|
|
50
50
|
Validator.call(factory, raise_on_invalid)
|
51
51
|
end
|
52
52
|
|
53
|
-
def build_node_data
|
53
|
+
def build_node_data(parent_context)
|
54
54
|
return if factory.nil_input? && factory.data.nil?
|
55
55
|
|
56
|
-
factory.data.each_with_object(
|
57
|
-
memo[key] =
|
58
|
-
value.recursive_pointer
|
59
|
-
else
|
60
|
-
resolve_value(key, value)
|
61
|
-
end
|
56
|
+
factory.data.each_with_object({}) do |(key, value), memo|
|
57
|
+
memo[key] = resolve_value(key, value, parent_context)
|
62
58
|
end
|
63
59
|
end
|
64
60
|
|
65
|
-
def resolve_value(key, value)
|
66
|
-
|
67
|
-
resolved_value = value.respond_to?(:node) ? value.node : value
|
61
|
+
def resolve_value(key, value, parent_context)
|
62
|
+
resolved = determine_value_or_default(key, value)
|
68
63
|
|
69
|
-
|
70
|
-
|
71
|
-
default = config&.default(factory)
|
72
|
-
default.nil? ? resolved_value : default
|
64
|
+
if resolved.respond_to?(:node)
|
65
|
+
Node::Placeholder.new(value, key, parent_context)
|
73
66
|
else
|
74
|
-
|
67
|
+
resolved
|
75
68
|
end
|
76
69
|
end
|
77
70
|
|
78
|
-
def
|
79
|
-
|
80
|
-
value_factory.in_recursive_loop?
|
81
|
-
end
|
71
|
+
def determine_value_or_default(key, value)
|
72
|
+
config = factory.field_configs[key]
|
82
73
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
74
|
+
# let a field config default take precedence if value is a nil_input?
|
75
|
+
if (value.respond_to?(:nil_input?) && value.nil_input?) || value.nil?
|
76
|
+
default = config&.default(factory)
|
77
|
+
default.nil? ? value : default
|
78
|
+
else
|
79
|
+
value
|
87
80
|
end
|
88
81
|
end
|
89
82
|
end
|
@@ -22,11 +22,14 @@ module Openapi3Parser
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
def build_object(data,
|
26
|
-
ref = data.delete("$ref")
|
27
|
-
return Node::PathItem.new(data,
|
25
|
+
def build_object(data, node_context)
|
26
|
+
ref = data.delete("$ref").node
|
27
|
+
return Node::PathItem.new(data, node_context) unless ref
|
28
28
|
|
29
|
-
|
29
|
+
context = data.empty? ? ref.node_context : node_context
|
30
|
+
data = merge_data(ref.node_data, data)
|
31
|
+
|
32
|
+
Node::PathItem.new(data, context)
|
30
33
|
end
|
31
34
|
|
32
35
|
def ref_factory(context)
|
@@ -15,22 +15,29 @@ module Openapi3Parser
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def in_recursive_loop?
|
18
|
-
data["$ref"].
|
18
|
+
data["$ref"].self_referencing?
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
|
21
|
+
def referenced_factory
|
22
|
+
data["$ref"].referenced_factory
|
23
|
+
end
|
24
|
+
|
25
|
+
def resolves?(control_factory = nil)
|
26
|
+
control_factory ||= self
|
27
|
+
|
28
|
+
return true unless referenced_factory.is_a?(Reference)
|
29
|
+
# recursive loop of references that never references an object
|
30
|
+
return false if referenced_factory == control_factory
|
31
|
+
|
32
|
+
referenced_factory.resolves?(control_factory)
|
23
33
|
end
|
24
34
|
|
25
35
|
private
|
26
36
|
|
27
|
-
def build_node
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
"recursive_pointer"
|
32
|
-
end
|
33
|
-
data["$ref"].node
|
37
|
+
def build_node(node_context)
|
38
|
+
TypeChecker.raise_on_invalid_type(context, type: ::Hash)
|
39
|
+
ObjectFactory::Validator.call(self, true)
|
40
|
+
data["$ref"].node(node_context)
|
34
41
|
end
|
35
42
|
|
36
43
|
def ref_factory(context)
|
@@ -9,22 +9,22 @@ module Openapi3Parser
|
|
9
9
|
# The source input which provides the data
|
10
10
|
# @attr_reader [Document] document
|
11
11
|
# The document that this source is associated with
|
12
|
-
# @attr_reader [Document::
|
13
|
-
# An object
|
12
|
+
# @attr_reader [Document::ReferenceRegistry] reference_registry
|
13
|
+
# An object that tracks factories for all references
|
14
14
|
# @attr_reader [Source, nil] parent
|
15
15
|
# Set to a Source if this source was created due to a reference within
|
16
16
|
# a different Source
|
17
17
|
class Source
|
18
|
-
attr_reader :source_input, :document, :
|
18
|
+
attr_reader :source_input, :document, :reference_registry, :parent
|
19
19
|
|
20
20
|
# @param [SourceInput] source_input
|
21
21
|
# @param [Document] document
|
22
|
-
# @param [Document::
|
22
|
+
# @param [Document::ReferenceRegistry] reference_registry
|
23
23
|
# @param [Source, nil] parent
|
24
|
-
def initialize(source_input, document,
|
24
|
+
def initialize(source_input, document, reference_registry, parent = nil)
|
25
25
|
@source_input = source_input
|
26
26
|
@document = document
|
27
|
-
@
|
27
|
+
@reference_registry = reference_registry
|
28
28
|
@parent = parent
|
29
29
|
end
|
30
30
|
|
@@ -43,23 +43,26 @@ module Openapi3Parser
|
|
43
43
|
document.root_source == self
|
44
44
|
end
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
# @param [NodeFactory] factory Factory class for the expected
|
51
|
-
# eventual resource
|
52
|
-
# @param [Context] context The context of the object
|
53
|
-
# calling this reference
|
54
|
-
# @return [ReferenceResolver]
|
55
|
-
def register_reference(given_reference, factory, context)
|
46
|
+
def resolve_reference(given_reference,
|
47
|
+
unbuilt_factory,
|
48
|
+
context,
|
49
|
+
recursive: false)
|
56
50
|
reference = Reference.new(given_reference)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
51
|
+
resolved_source = resolve_source(reference)
|
52
|
+
source_location = Source::Location.new(resolved_source,
|
53
|
+
reference.json_pointer)
|
54
|
+
|
55
|
+
unless recursive
|
56
|
+
reference_registry.register(unbuilt_factory,
|
57
|
+
source_location,
|
58
|
+
context)
|
62
59
|
end
|
60
|
+
|
61
|
+
ResolvedReference.new(
|
62
|
+
source_location: source_location,
|
63
|
+
object_type: unbuilt_factory.object_type,
|
64
|
+
reference_registry: reference_registry
|
65
|
+
)
|
63
66
|
end
|
64
67
|
|
65
68
|
# Access/create the source object for a reference
|
@@ -71,15 +74,15 @@ module Openapi3Parser
|
|
71
74
|
# I found the spec wasn't fully clear on expected behaviour if a source
|
72
75
|
# references a fragment that doesn't exist in it's current document
|
73
76
|
# and just the root source. I'm assuming to be consistent with URI a
|
74
|
-
# fragment only
|
75
|
-
#
|
77
|
+
# fragment only references the current JSON document. This could be
|
78
|
+
# incorrect though.
|
76
79
|
self
|
77
80
|
else
|
78
81
|
next_source_input = source_input.resolve_next(reference)
|
79
82
|
source = document.source_for_source_input(next_source_input)
|
80
83
|
source || self.class.new(next_source_input,
|
81
84
|
document,
|
82
|
-
|
85
|
+
reference_registry,
|
83
86
|
self)
|
84
87
|
end
|
85
88
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Openapi3Parser
|
4
|
-
class
|
4
|
+
class Source
|
5
5
|
# Class used to represent a location within an OpenAPI document.
|
6
6
|
# It contains a source, which is the source file/data used for the contents
|
7
7
|
# and the pointer which indicates where in the object like file the data is
|
@@ -28,6 +28,18 @@ module Openapi3Parser
|
|
28
28
|
source.relative_to_root + pointer.fragment
|
29
29
|
end
|
30
30
|
|
31
|
+
def data
|
32
|
+
source.data_at_pointer(pointer.segments)
|
33
|
+
end
|
34
|
+
|
35
|
+
def pointer_defined?
|
36
|
+
source.has_pointer?(pointer.segments)
|
37
|
+
end
|
38
|
+
|
39
|
+
def source_available?
|
40
|
+
source.available?
|
41
|
+
end
|
42
|
+
|
31
43
|
def inspect
|
32
44
|
%{#{self.class.name}(source: #{source.inspect}, pointer: #{pointer})}
|
33
45
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require "cgi"
|
4
4
|
|
5
5
|
module Openapi3Parser
|
6
|
-
class
|
6
|
+
class Source
|
7
7
|
# A class to decorate the array of fields that make up a pointer and
|
8
8
|
# provide common means to convert it into different representations.
|
9
9
|
class Pointer
|
@@ -13,7 +13,7 @@ module Openapi3Parser
|
|
13
13
|
segments = fragment.split("/").map do |part|
|
14
14
|
next if part == ""
|
15
15
|
unescaped = CGI.unescape(part.gsub("%20", "+"))
|
16
|
-
unescaped
|
16
|
+
unescaped.match?(/\A\d+\z/) ? unescaped.to_i : unescaped
|
17
17
|
end
|
18
18
|
new(segments.compact, absolute)
|
19
19
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Openapi3Parser
|
6
|
+
class Source
|
7
|
+
class ResolvedReference
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :source_location, :source
|
11
|
+
def_delegators :factory, :resolved_input, :node
|
12
|
+
|
13
|
+
attr_reader :source_location, :object_type
|
14
|
+
|
15
|
+
def initialize(source_location:,
|
16
|
+
object_type:,
|
17
|
+
reference_registry:)
|
18
|
+
@source_location = source_location
|
19
|
+
@object_type = object_type
|
20
|
+
@reference_registry = reference_registry
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
errors.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def errors
|
28
|
+
@errors ||= Array(build_errors)
|
29
|
+
end
|
30
|
+
|
31
|
+
def factory
|
32
|
+
@factory ||= begin
|
33
|
+
reference_registry
|
34
|
+
.factory(object_type, source_location)
|
35
|
+
.tap do |factory|
|
36
|
+
message = "Unregistered node factory at #{source_location}"
|
37
|
+
raise Openapi3Parser::Error, message unless factory
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :reference_registry
|
45
|
+
|
46
|
+
def build_errors
|
47
|
+
return source_unavailabe_error unless source.available?
|
48
|
+
return pointer_missing_error unless source_location.pointer_defined?
|
49
|
+
|
50
|
+
resolution_error unless factory.valid?
|
51
|
+
end
|
52
|
+
|
53
|
+
def source_unavailabe_error
|
54
|
+
"Failed to open source #{source.relative_to_root}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def pointer_missing_error
|
58
|
+
suffix = source.root? ? "" : " in source #{source.relative_to_root}"
|
59
|
+
"#{source_location.pointer} is not defined#{suffix}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def resolution_error
|
63
|
+
"#{source_location} does not resolve to a valid object"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|