openapi3_parser 0.2.0 → 0.3.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/.travis.yml +10 -1
- data/CHANGELOG.md +5 -0
- data/README.md +13 -0
- data/TODO.md +11 -8
- data/lib/openapi3_parser.rb +16 -28
- data/lib/openapi3_parser/context.rb +92 -34
- data/lib/openapi3_parser/context/location.rb +37 -0
- data/lib/openapi3_parser/context/pointer.rb +34 -0
- data/lib/openapi3_parser/document.rb +115 -15
- data/lib/openapi3_parser/document/reference_register.rb +50 -0
- data/lib/openapi3_parser/error.rb +27 -1
- data/lib/openapi3_parser/node/object.rb +26 -1
- data/lib/openapi3_parser/node_factories/array.rb +15 -14
- data/lib/openapi3_parser/node_factories/callback.rb +2 -1
- data/lib/openapi3_parser/node_factories/link.rb +1 -1
- data/lib/openapi3_parser/node_factories/map.rb +13 -11
- data/lib/openapi3_parser/node_factories/openapi.rb +2 -0
- data/lib/openapi3_parser/node_factories/path_item.rb +13 -18
- data/lib/openapi3_parser/node_factories/paths.rb +2 -1
- data/lib/openapi3_parser/node_factories/reference.rb +7 -9
- data/lib/openapi3_parser/node_factories/responses.rb +4 -2
- data/lib/openapi3_parser/node_factories/security_requirement.rb +2 -1
- data/lib/openapi3_parser/node_factory.rb +39 -30
- data/lib/openapi3_parser/node_factory/fields/reference.rb +44 -0
- data/lib/openapi3_parser/node_factory/map.rb +5 -5
- data/lib/openapi3_parser/node_factory/object.rb +6 -5
- data/lib/openapi3_parser/node_factory/object/node_builder.rb +12 -11
- data/lib/openapi3_parser/node_factory/object/validator.rb +25 -14
- data/lib/openapi3_parser/nodes/components.rb +9 -11
- data/lib/openapi3_parser/nodes/discriminator.rb +1 -1
- data/lib/openapi3_parser/nodes/encoding.rb +1 -1
- data/lib/openapi3_parser/nodes/link.rb +1 -1
- data/lib/openapi3_parser/nodes/media_type.rb +2 -2
- data/lib/openapi3_parser/nodes/oauth_flow.rb +1 -1
- data/lib/openapi3_parser/nodes/openapi.rb +3 -4
- data/lib/openapi3_parser/nodes/operation.rb +5 -8
- data/lib/openapi3_parser/nodes/parameter/parameter_like.rb +2 -2
- data/lib/openapi3_parser/nodes/path_item.rb +2 -3
- data/lib/openapi3_parser/nodes/request_body.rb +1 -1
- data/lib/openapi3_parser/nodes/response.rb +3 -3
- data/lib/openapi3_parser/nodes/schema.rb +6 -7
- data/lib/openapi3_parser/nodes/server.rb +1 -2
- data/lib/openapi3_parser/nodes/server_variable.rb +1 -1
- data/lib/openapi3_parser/source.rb +136 -0
- data/lib/openapi3_parser/source/reference.rb +68 -0
- data/lib/openapi3_parser/source/reference_resolver.rb +81 -0
- data/lib/openapi3_parser/source_input.rb +71 -0
- data/lib/openapi3_parser/source_input/file.rb +102 -0
- data/lib/openapi3_parser/source_input/raw.rb +90 -0
- data/lib/openapi3_parser/source_input/resolve_next.rb +62 -0
- data/lib/openapi3_parser/source_input/string_parser.rb +43 -0
- data/lib/openapi3_parser/source_input/url.rb +123 -0
- data/lib/openapi3_parser/validation/error.rb +36 -3
- data/lib/openapi3_parser/validation/error_collection.rb +57 -15
- data/lib/openapi3_parser/validators/reference.rb +40 -0
- data/lib/openapi3_parser/version.rb +1 -1
- data/openapi3_parser.gemspec +10 -5
- metadata +34 -20
- data/lib/openapi3_parser/fields/map.rb +0 -83
- data/lib/openapi3_parser/node.rb +0 -115
@@ -58,13 +58,12 @@ module Openapi3Parser
|
|
58
58
|
node_data["trace"]
|
59
59
|
end
|
60
60
|
|
61
|
-
# @return [Nodes::Array
|
61
|
+
# @return [Nodes::Array<Server>]
|
62
62
|
def servers
|
63
63
|
node_data["servers"]
|
64
64
|
end
|
65
65
|
|
66
|
-
# @return [Nodes::Array
|
67
|
-
# objects
|
66
|
+
# @return [Nodes::Array<Parameter>]
|
68
67
|
def parameters
|
69
68
|
node_data["parameters"]
|
70
69
|
end
|
@@ -13,17 +13,17 @@ module Openapi3Parser
|
|
13
13
|
node_data["description"]
|
14
14
|
end
|
15
15
|
|
16
|
-
# @return [Map
|
16
|
+
# @return [Map<String, Header>]
|
17
17
|
def headers
|
18
18
|
node_data["headers"]
|
19
19
|
end
|
20
20
|
|
21
|
-
# @return [Map
|
21
|
+
# @return [Map<String, MediaType>]
|
22
22
|
def content
|
23
23
|
node_data["content"]
|
24
24
|
end
|
25
25
|
|
26
|
-
# @return [Map
|
26
|
+
# @return [Map<String, Link>]
|
27
27
|
def links
|
28
28
|
node_data["links"]
|
29
29
|
end
|
@@ -79,13 +79,12 @@ module Openapi3Parser
|
|
79
79
|
node_data["minProperties"]
|
80
80
|
end
|
81
81
|
|
82
|
-
# @return [Nodes::Array
|
82
|
+
# @return [Nodes::Array<String>, nil]
|
83
83
|
def required
|
84
84
|
node_data["required"]
|
85
85
|
end
|
86
86
|
|
87
|
-
# @return [Nodes::Array
|
88
|
-
# nil
|
87
|
+
# @return [Nodes::Array<Object>, nil]
|
89
88
|
def enum
|
90
89
|
node_data["enum"]
|
91
90
|
end
|
@@ -95,17 +94,17 @@ module Openapi3Parser
|
|
95
94
|
node_data["type"]
|
96
95
|
end
|
97
96
|
|
98
|
-
# @return [Nodes::Array
|
97
|
+
# @return [Nodes::Array<Schema>, nil]
|
99
98
|
def all_of
|
100
99
|
node_data["allOf"]
|
101
100
|
end
|
102
101
|
|
103
|
-
# @return [Nodes::Array
|
102
|
+
# @return [Nodes::Array<Schema>, nil]
|
104
103
|
def one_of
|
105
104
|
node_data["oneOf"]
|
106
105
|
end
|
107
106
|
|
108
|
-
# @return [Nodes::Array
|
107
|
+
# @return [Nodes::Array<Schema>, nil]
|
109
108
|
def any_of
|
110
109
|
node_data["anyOf"]
|
111
110
|
end
|
@@ -120,7 +119,7 @@ module Openapi3Parser
|
|
120
119
|
node_data["items"]
|
121
120
|
end
|
122
121
|
|
123
|
-
# @return [Map
|
122
|
+
# @return [Map<String, Schema>]
|
124
123
|
def properties
|
125
124
|
node_data["properties"]
|
126
125
|
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openapi3_parser/error"
|
4
|
+
require "openapi3_parser/source/reference"
|
5
|
+
require "openapi3_parser/source/reference_resolver"
|
6
|
+
|
7
|
+
module Openapi3Parser
|
8
|
+
# Represents a source of data used to produce the OpenAPI document. Documents
|
9
|
+
# which do not have any references to external files will only have a single
|
10
|
+
# source
|
11
|
+
#
|
12
|
+
# @attr_reader [SourceInput] source_input
|
13
|
+
# The source input which provides the data
|
14
|
+
# @attr_reader [Document] document
|
15
|
+
# The document that this source is associated with
|
16
|
+
# @attr_reader [Document::ReferenceRegister] reference_register
|
17
|
+
# An object to track references for a document
|
18
|
+
# @attr_reader [Source, nil] parent
|
19
|
+
# Set to a Source if this source was created due to a reference within
|
20
|
+
# a different Source
|
21
|
+
class Source
|
22
|
+
attr_reader :source_input, :document, :reference_register, :parent
|
23
|
+
|
24
|
+
# @param [SourceInput] source_input
|
25
|
+
# @param [Document] document
|
26
|
+
# @param [Document::ReferenceRegister] reference_register
|
27
|
+
# @param [Source, nil] parent
|
28
|
+
def initialize(source_input, document, reference_register, parent = nil)
|
29
|
+
@source_input = source_input
|
30
|
+
@document = document
|
31
|
+
@reference_register = reference_register
|
32
|
+
@parent = parent
|
33
|
+
end
|
34
|
+
|
35
|
+
# The data from the source
|
36
|
+
def data
|
37
|
+
@data ||= normalize_data(source_input.contents)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @see SourceInput#available?
|
41
|
+
def available?
|
42
|
+
source_input.available?
|
43
|
+
end
|
44
|
+
|
45
|
+
# Whether this is the root source of a document
|
46
|
+
def root?
|
47
|
+
document.root_source == self
|
48
|
+
end
|
49
|
+
|
50
|
+
# Used to register a reference with the underlying document and return a
|
51
|
+
# reference resolver to access the object referenced
|
52
|
+
#
|
53
|
+
# @param [String] given_reference The reference as text
|
54
|
+
# @param [NodeFactory] factory Factory class for the expected
|
55
|
+
# eventual resource
|
56
|
+
# @param [Context] context The context of the object
|
57
|
+
# calling this reference
|
58
|
+
# @return [ReferenceResolver]
|
59
|
+
def register_reference(given_reference, factory, context)
|
60
|
+
reference = Reference.new(given_reference)
|
61
|
+
ReferenceResolver.new(
|
62
|
+
reference, factory, context
|
63
|
+
).tap do |resolver|
|
64
|
+
next if resolver.in_root_source?
|
65
|
+
reference_register.register(resolver.reference_factory)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Access/create the source object for a reference
|
70
|
+
#
|
71
|
+
# @param [Reference] reference
|
72
|
+
# @return [Source]
|
73
|
+
def resolve_source(reference)
|
74
|
+
if reference.only_fragment?
|
75
|
+
# I found the spec wasn't fully clear on expected behaviour if a source
|
76
|
+
# references a fragment that doesn't exist in it's current document
|
77
|
+
# and just the root source. I'm assuming to be consistent with URI a
|
78
|
+
# fragment only reference only references current JSON document. This
|
79
|
+
# could be incorrect though.
|
80
|
+
self
|
81
|
+
else
|
82
|
+
next_source_input = source_input.resolve_next(reference)
|
83
|
+
source = document.source_for_source_input(next_source_input)
|
84
|
+
source || self.class.new(next_source_input,
|
85
|
+
document,
|
86
|
+
reference_register,
|
87
|
+
self)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Access the data in a source at a particular pointer
|
92
|
+
#
|
93
|
+
# @param [Array] json_pointer An array of segments of a JSON pointer
|
94
|
+
# @return [Object]
|
95
|
+
def data_at_pointer(json_pointer)
|
96
|
+
return data if json_pointer.empty?
|
97
|
+
data.dig(*json_pointer) if data.respond_to?(:dig)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Whether the source has data at the particular pointer
|
101
|
+
def has_pointer?(json_pointer) # rubocop:disable Naming/PredicateName
|
102
|
+
!data_at_pointer(json_pointer).nil?
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [String]
|
106
|
+
def relative_to_root
|
107
|
+
return "" if root?
|
108
|
+
source_input.relative_to(document.root_source.source_input)
|
109
|
+
end
|
110
|
+
|
111
|
+
def ==(other)
|
112
|
+
source_input == other.source_input && document == other.document
|
113
|
+
end
|
114
|
+
|
115
|
+
# return [String]
|
116
|
+
def inspect
|
117
|
+
%{#{self.class.name}(input: #{source_input})}
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def normalize_data(input)
|
123
|
+
normalized = if input.respond_to?(:keys)
|
124
|
+
input.each_with_object({}) do |(key, value), memo|
|
125
|
+
memo[key.to_s.freeze] = normalize_data(value)
|
126
|
+
end
|
127
|
+
elsif input.respond_to?(:map)
|
128
|
+
input.map { |v| normalize_data(v) }
|
129
|
+
else
|
130
|
+
input
|
131
|
+
end
|
132
|
+
|
133
|
+
normalized.freeze
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openapi3_parser/source"
|
4
|
+
require "openapi3_parser/validators/reference"
|
5
|
+
|
6
|
+
module Openapi3Parser
|
7
|
+
class Source
|
8
|
+
# An object which represents a reference that can be indicated in a OpenAPI
|
9
|
+
# file. Given a string reference it can be used to answer key questions
|
10
|
+
# that aid in resolving the reference
|
11
|
+
#
|
12
|
+
# e.g.
|
13
|
+
# r = Openapi3Parser::Source::Reference.new("test.yaml#/path/to/item")
|
14
|
+
#
|
15
|
+
# r.only_fragment?
|
16
|
+
# => false
|
17
|
+
#
|
18
|
+
# r.rsource_uri
|
19
|
+
# => "test.yaml"
|
20
|
+
class Reference
|
21
|
+
# @param [String] reference reference from an OpenAPI file
|
22
|
+
def initialize(reference)
|
23
|
+
@given_reference = reference
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
given_reference.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def only_fragment?
|
31
|
+
resource_uri.to_s == ""
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String, nil]
|
35
|
+
def fragment
|
36
|
+
uri.fragment
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [URI]
|
40
|
+
def resource_uri
|
41
|
+
uri_without_fragment
|
42
|
+
end
|
43
|
+
|
44
|
+
def absolute?
|
45
|
+
uri.absolute?
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [::Array] an array of strings of the components in the fragment
|
49
|
+
def json_pointer
|
50
|
+
@json_pointer ||= (fragment || "").split("/").drop(1).map do |field|
|
51
|
+
CGI.unescape(field.gsub("+", "%20"))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
attr_reader :given_reference
|
58
|
+
|
59
|
+
def uri
|
60
|
+
@uri = URI.parse(given_reference)
|
61
|
+
end
|
62
|
+
|
63
|
+
def uri_without_fragment
|
64
|
+
@uri_without_fragment ||= uri.dup.tap { |u| u.fragment = nil }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openapi3_parser/context"
|
4
|
+
require "openapi3_parser/source"
|
5
|
+
|
6
|
+
module Openapi3Parser
|
7
|
+
class Source
|
8
|
+
class ReferenceResolver
|
9
|
+
def initialize(reference, factory, context)
|
10
|
+
@reference = reference
|
11
|
+
@factory = factory
|
12
|
+
@context = context
|
13
|
+
end
|
14
|
+
|
15
|
+
def reference_factory
|
16
|
+
@reference_factory ||= begin
|
17
|
+
next_context = Context.reference_field(
|
18
|
+
context,
|
19
|
+
input: source.data_at_pointer(json_pointer),
|
20
|
+
source: source,
|
21
|
+
pointer_segments: json_pointer
|
22
|
+
)
|
23
|
+
build_factory(next_context)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?
|
28
|
+
errors.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def errors
|
32
|
+
@errors ||= Array(build_errors)
|
33
|
+
end
|
34
|
+
|
35
|
+
def node
|
36
|
+
reference_factory.node
|
37
|
+
end
|
38
|
+
|
39
|
+
def in_root_source?
|
40
|
+
source == context.document.root_source
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :reference, :factory, :context
|
46
|
+
|
47
|
+
def source
|
48
|
+
@source ||= context.source.resolve_source(reference)
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_errors
|
52
|
+
return source_unavailabe_error unless source.available?
|
53
|
+
return pointer_missing_error unless source.has_pointer?(json_pointer)
|
54
|
+
resolution_error unless reference_factory.valid?
|
55
|
+
end
|
56
|
+
|
57
|
+
def source_unavailabe_error
|
58
|
+
# @todo include a location
|
59
|
+
"Source is unavailable"
|
60
|
+
end
|
61
|
+
|
62
|
+
def pointer_missing_error
|
63
|
+
# @todo include a location and a pointer
|
64
|
+
"Source does not have pointer"
|
65
|
+
end
|
66
|
+
|
67
|
+
def resolution_error
|
68
|
+
# @todo include exepected object
|
69
|
+
"Reference does not resolve to a valid object"
|
70
|
+
end
|
71
|
+
|
72
|
+
def json_pointer
|
73
|
+
reference.json_pointer
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_factory(context)
|
77
|
+
factory.is_a?(Class) ? factory.new(context) : factory.call(context)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openapi3_parser/error"
|
4
|
+
|
5
|
+
module Openapi3Parser
|
6
|
+
# An abstract class which is used to provide a foundation for classes that
|
7
|
+
# represent the different means of input an OpenAPI document can have. It is
|
8
|
+
# used to represent the underlying source of the data which is used as a
|
9
|
+
# source within an OpenAPI document.
|
10
|
+
#
|
11
|
+
# @see SourceInput::Raw SourceInput::Raw for an input that is done through
|
12
|
+
# data, such as a Hash.
|
13
|
+
#
|
14
|
+
# @see SourceInput::File SourceInput::File for an input that is done through
|
15
|
+
# the path to a file within the local file system.
|
16
|
+
#
|
17
|
+
# @see SourceInput::Url SourceInput::Url for an input that is done through]
|
18
|
+
# a URL to an OpenAPI Document
|
19
|
+
#
|
20
|
+
# @attr_reader [Error::InaccessibleInput, nil] access_error
|
21
|
+
# @attr_reader [Error::UnparsableInput, nil] parse_error
|
22
|
+
class SourceInput
|
23
|
+
attr_reader :access_error, :parse_error
|
24
|
+
|
25
|
+
# Indicates that the data within this input is suitable (i.e. can parse
|
26
|
+
# underlying JSON or YAML) for trying to use as part of a Document
|
27
|
+
def available?
|
28
|
+
access_error.nil? && parse_error.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
# For a given reference use the context of the current SourceInput to
|
32
|
+
# determine which file is required for the reference. This allows
|
33
|
+
# references to use relative file paths because we can combine them witt
|
34
|
+
# the current SourceInput location to determine the next one
|
35
|
+
def resolve_next(_reference); end
|
36
|
+
|
37
|
+
# Used to determine whether a different instance of SourceInput is
|
38
|
+
# the same file/data
|
39
|
+
def ==(_other); end
|
40
|
+
|
41
|
+
# The parsed data from the input
|
42
|
+
#
|
43
|
+
# @raise [Error::InaccessibleInput] In cases where the file does not exist
|
44
|
+
# @raise [Error::UnparsableInput] In cases where the data is not parsable
|
45
|
+
#
|
46
|
+
# @return Object
|
47
|
+
def contents
|
48
|
+
raise access_error if access_error
|
49
|
+
raise parse_error if parse_error
|
50
|
+
@contents
|
51
|
+
end
|
52
|
+
|
53
|
+
# The relative path, if possible, for this source_input compared to a
|
54
|
+
# different one. Defaults to empty string and should be specialised in
|
55
|
+
# subclasses
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
def relative_to(_source_input)
|
59
|
+
""
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def initialize_contents
|
65
|
+
return if access_error
|
66
|
+
@contents = parse_contents
|
67
|
+
rescue ::StandardError => e
|
68
|
+
@parse_error = Error::UnparsableInput.new(e.message)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|