openapi3_parser 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|