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
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openapi3_parser/source_input"
|
4
|
+
require "openapi3_parser/source_input/string_parser"
|
5
|
+
require "openapi3_parser/source_input/resolve_next"
|
6
|
+
require "openapi3_parser/error"
|
7
|
+
|
8
|
+
module Openapi3Parser
|
9
|
+
class SourceInput
|
10
|
+
# An input of a file on the file system
|
11
|
+
#
|
12
|
+
# @attr_reader [String] path The absolute path to this file
|
13
|
+
# @attr_reader [String] working_directory The abolsute path of the
|
14
|
+
# working directory to use when
|
15
|
+
# opening relative references to
|
16
|
+
# this file
|
17
|
+
class File < SourceInput
|
18
|
+
attr_reader :path, :working_directory
|
19
|
+
|
20
|
+
# @param [String] path The path to the file to open
|
21
|
+
# as this source
|
22
|
+
# @param [String, nil] working_directory The path to the
|
23
|
+
# working directory to use, will
|
24
|
+
# be calculated from path if not
|
25
|
+
# provided
|
26
|
+
def initialize(path, working_directory: nil)
|
27
|
+
@path = ::File.absolute_path(path)
|
28
|
+
working_directory ||= resolve_working_directory
|
29
|
+
@working_directory = ::File.absolute_path(working_directory)
|
30
|
+
initialize_contents
|
31
|
+
end
|
32
|
+
|
33
|
+
# @see SourceInput#resolve_next
|
34
|
+
# @param [Source::Reference] reference
|
35
|
+
# @return [SourceInput]
|
36
|
+
def resolve_next(reference)
|
37
|
+
ResolveNext.call(reference, self, working_directory: working_directory)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @see SourceInput#other
|
41
|
+
# @param [SourceInput] other
|
42
|
+
# @return [Boolean]
|
43
|
+
def ==(other)
|
44
|
+
return false unless other.instance_of?(self.class)
|
45
|
+
path == other.path &&
|
46
|
+
working_directory == other.working_directory
|
47
|
+
end
|
48
|
+
|
49
|
+
# return [String]
|
50
|
+
def inspect
|
51
|
+
%{#{self.class.name}(path: #{path}, working_directory: } +
|
52
|
+
%{#{working_directory})}
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [String]
|
56
|
+
def to_s
|
57
|
+
path
|
58
|
+
end
|
59
|
+
|
60
|
+
# Attempt to return a shorter relative path to the other source input
|
61
|
+
# so we can produce succinct output
|
62
|
+
#
|
63
|
+
# @return [String]
|
64
|
+
def relative_to(source_input)
|
65
|
+
other_path = if source_input.respond_to?(:path)
|
66
|
+
::File.dirname(source_input.path)
|
67
|
+
elsif source_input.respond_to?(:working_directory)
|
68
|
+
source_input.working_directory
|
69
|
+
end
|
70
|
+
|
71
|
+
return path unless other_path
|
72
|
+
other_path ? relative_path(other_path, path) : path
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def resolve_working_directory
|
78
|
+
::File.dirname(path)
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_contents
|
82
|
+
begin
|
83
|
+
contents = ::File.read(path)
|
84
|
+
rescue ::StandardError => e
|
85
|
+
@access_error = Error::InaccessibleInput.new(e.message)
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
filename = ::File.basename(path)
|
90
|
+
StringParser.call(contents, filename)
|
91
|
+
end
|
92
|
+
|
93
|
+
def relative_path(from, to)
|
94
|
+
from_path = Pathname.new(from)
|
95
|
+
to_path = Pathname.new(to)
|
96
|
+
relative = to_path.relative_path_from(from_path).to_s
|
97
|
+
|
98
|
+
relative.size > to.size ? to : relative
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openapi3_parser/source_input"
|
4
|
+
require "openapi3_parser/source_input/string_parser"
|
5
|
+
require "openapi3_parser/source_input/resolve_next"
|
6
|
+
|
7
|
+
module Openapi3Parser
|
8
|
+
class SourceInput
|
9
|
+
# An input of data (typically a Hash) to for initialising an OpenAPI
|
10
|
+
# document. Most likely used in development scenarios when you want to
|
11
|
+
# test things without creating/tweaking an OpenAPI source file
|
12
|
+
#
|
13
|
+
# @attr_reader [Object] raw_input The data for the document
|
14
|
+
# @attr_reader [String, nil] base_url A url to be used for
|
15
|
+
# resolving relative
|
16
|
+
# references
|
17
|
+
# @attr_reader [String, nil] working_directory A path to be used for
|
18
|
+
# resolving relative
|
19
|
+
# references
|
20
|
+
class Raw < SourceInput
|
21
|
+
attr_reader :raw_input, :base_url, :working_directory
|
22
|
+
|
23
|
+
# @param [Object] raw_input
|
24
|
+
# @param [String, nil] base_url
|
25
|
+
# @param [String, nil] working_directory
|
26
|
+
def initialize(raw_input, base_url: nil, working_directory: nil)
|
27
|
+
@raw_input = raw_input
|
28
|
+
@base_url = base_url
|
29
|
+
working_directory ||= resolve_working_directory
|
30
|
+
@working_directory = ::File.absolute_path(working_directory)
|
31
|
+
initialize_contents
|
32
|
+
end
|
33
|
+
|
34
|
+
# @see SourceInput#resolve_next
|
35
|
+
# @param [Source::Reference] reference
|
36
|
+
# @return [SourceInput]
|
37
|
+
def resolve_next(reference)
|
38
|
+
ResolveNext.call(reference,
|
39
|
+
self,
|
40
|
+
base_url: base_url,
|
41
|
+
working_directory: working_directory)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @see SourceInput#other
|
45
|
+
# @param [SourceInput] other
|
46
|
+
# @return [Boolean]
|
47
|
+
def ==(other)
|
48
|
+
return false unless other.instance_of?(self.class)
|
49
|
+
raw_input == other.raw_input &&
|
50
|
+
base_url == other.base_url &&
|
51
|
+
working_directory == other.working_directory
|
52
|
+
end
|
53
|
+
|
54
|
+
# return [String]
|
55
|
+
def inspect
|
56
|
+
%{#{self.class.name}(input: #{raw_input.inspect}, base_url: } +
|
57
|
+
%{#{base_url}, working_directory: #{working_directory})}
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [String]
|
61
|
+
def to_s
|
62
|
+
raw_input.to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def resolve_working_directory
|
68
|
+
if raw_input.respond_to?(:path)
|
69
|
+
::File.dirname(raw_input)
|
70
|
+
else
|
71
|
+
Dir.pwd
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_contents
|
76
|
+
return raw_input if raw_input.respond_to?(:keys)
|
77
|
+
StringParser.call(
|
78
|
+
input_to_string(raw_input),
|
79
|
+
raw_input.respond_to?(:path) ? ::File.basename(raw_input.path) : nil
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
def input_to_string(input)
|
84
|
+
return input.read if input.respond_to?(:read)
|
85
|
+
return input.to_s if input.respond_to?(:to_s)
|
86
|
+
input
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openapi3_parser/source_input/file"
|
4
|
+
require "openapi3_parser/source_input/url"
|
5
|
+
|
6
|
+
module Openapi3Parser
|
7
|
+
class SourceInput
|
8
|
+
class ResolveNext
|
9
|
+
# @param reference [Source::Reference]
|
10
|
+
# @param current_source_input [SourceInput]
|
11
|
+
# @param base_url [String, nil]
|
12
|
+
# @param working_directory [String, nil]
|
13
|
+
# @return [SourceInput]
|
14
|
+
def self.call(reference,
|
15
|
+
current_source_input,
|
16
|
+
base_url: nil,
|
17
|
+
working_directory: nil)
|
18
|
+
new(reference, current_source_input, base_url, working_directory)
|
19
|
+
.source_input
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(reference,
|
23
|
+
current_source_input,
|
24
|
+
base_url,
|
25
|
+
working_directory)
|
26
|
+
@reference = reference
|
27
|
+
@current_source_input = current_source_input
|
28
|
+
@base_url = base_url
|
29
|
+
@working_directory = working_directory
|
30
|
+
end
|
31
|
+
|
32
|
+
private_class_method :new
|
33
|
+
|
34
|
+
def source_input
|
35
|
+
return current_source_input if reference.only_fragment?
|
36
|
+
if reference.absolute?
|
37
|
+
SourceInput::Url.new(reference.resource_uri)
|
38
|
+
else
|
39
|
+
base_url ? url_source_input : file_source_input
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :reference, :current_source_input, :base_url,
|
46
|
+
:working_directory
|
47
|
+
|
48
|
+
def url_source_input
|
49
|
+
url = URI.join(base_url, reference.resource_uri)
|
50
|
+
SourceInput::Url.new(url)
|
51
|
+
end
|
52
|
+
|
53
|
+
def file_source_input
|
54
|
+
path = reference.resource_uri.path
|
55
|
+
return SourceInput::File.new(path) if path[0] == "/"
|
56
|
+
|
57
|
+
expanded_path = ::File.expand_path(path, working_directory)
|
58
|
+
SourceInput::File.new(expanded_path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module Openapi3Parser
|
7
|
+
class SourceInput
|
8
|
+
class StringParser
|
9
|
+
def self.call(input, filename = nil)
|
10
|
+
new(input, filename).call
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(input, filename)
|
14
|
+
@input = input
|
15
|
+
@filename = filename
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
json? ? parse_json : parse_yaml
|
20
|
+
end
|
21
|
+
|
22
|
+
private_class_method :new
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :input, :filename
|
27
|
+
|
28
|
+
def json?
|
29
|
+
return false if filename && ::File.extname(filename) == ".yaml"
|
30
|
+
json_filename = filename && ::File.extname(filename) == ".json"
|
31
|
+
json_filename || input.strip[0] == "{"
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_json
|
35
|
+
JSON.parse(input)
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_yaml
|
39
|
+
YAML.safe_load(input, [], [], true)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open-uri"
|
4
|
+
require "openapi3_parser/source_input"
|
5
|
+
require "openapi3_parser/source_input/string_parser"
|
6
|
+
require "openapi3_parser/source_input/resolve_next"
|
7
|
+
require "openapi3_parser/error"
|
8
|
+
|
9
|
+
module Openapi3Parser
|
10
|
+
class SourceInput
|
11
|
+
# An input of a URL to an OpenAPI file
|
12
|
+
#
|
13
|
+
# @attr_reader [String] request_url The URL that will be requested
|
14
|
+
# @attr_reader [String] resolved_url The eventual URL of the file that was
|
15
|
+
# accessed, this may differ to the
|
16
|
+
# request_url in the case of redirects
|
17
|
+
class Url < SourceInput
|
18
|
+
attr_reader :request_url, :resolved_url
|
19
|
+
|
20
|
+
# @param [String, URI] request_url
|
21
|
+
def initialize(request_url)
|
22
|
+
@request_url = request_url.to_s
|
23
|
+
initialize_contents
|
24
|
+
end
|
25
|
+
|
26
|
+
# @see SourceInput#resolve_next
|
27
|
+
# @param [Source::Reference] reference
|
28
|
+
# @return [SourceInput]
|
29
|
+
def resolve_next(reference)
|
30
|
+
ResolveNext.call(reference, self, base_url: resolved_url)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @see SourceInput#other
|
34
|
+
# @param [SourceInput] other
|
35
|
+
# @return [Boolean]
|
36
|
+
def ==(other)
|
37
|
+
return false unless other.instance_of?(self.class)
|
38
|
+
[request_url, resolved_url].include?(other.request_url) ||
|
39
|
+
[request_url, resolved_url].include?(other.resolved_url)
|
40
|
+
end
|
41
|
+
|
42
|
+
# return [String]
|
43
|
+
def url
|
44
|
+
resolved_url || request_url
|
45
|
+
end
|
46
|
+
|
47
|
+
# return [String]
|
48
|
+
def inspect
|
49
|
+
%{#{self.class.name}(url: #{url})}
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String]
|
53
|
+
def to_s
|
54
|
+
request_url
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [String]
|
58
|
+
def relative_to(source_input)
|
59
|
+
other_url = if source_input.respond_to?(:url)
|
60
|
+
source_input.url
|
61
|
+
elsif source_input.respond_to?(:base_url)
|
62
|
+
source_input.base_url
|
63
|
+
end
|
64
|
+
|
65
|
+
return url unless other_url
|
66
|
+
|
67
|
+
other_url ? RelativeUrlDifference.call(other_url, url) : url
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def parse_contents
|
73
|
+
begin
|
74
|
+
file = URI.parse(request_url).open
|
75
|
+
rescue ::StandardError => e
|
76
|
+
@access_error = Error::InaccessibleInput.new(e.message)
|
77
|
+
return
|
78
|
+
end
|
79
|
+
@resolved_url = file.base_uri.to_s
|
80
|
+
StringParser.call(file.read, resolved_url)
|
81
|
+
end
|
82
|
+
|
83
|
+
class RelativeUrlDifference
|
84
|
+
def initialize(from_url, to_url)
|
85
|
+
@from_uri = URI.parse(from_url)
|
86
|
+
@to_uri = URI.parse(to_url)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.call(from_url, to_url)
|
90
|
+
new(from_url, to_url).call
|
91
|
+
end
|
92
|
+
|
93
|
+
def call
|
94
|
+
return to_uri.to_s if different_hosts?
|
95
|
+
relative_path
|
96
|
+
end
|
97
|
+
|
98
|
+
private_class_method :new
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
attr_reader :from_uri, :to_uri
|
103
|
+
|
104
|
+
def different_hosts?
|
105
|
+
URI.join(from_uri, "/") != URI.join(to_uri, "/")
|
106
|
+
end
|
107
|
+
|
108
|
+
def relative_path
|
109
|
+
relative = to_uri.route_from(from_uri)
|
110
|
+
return relative.to_s unless relative.path.empty?
|
111
|
+
|
112
|
+
# if we have same path it's nice to show just the filename
|
113
|
+
file_and_query(to_uri)
|
114
|
+
end
|
115
|
+
|
116
|
+
def file_and_query(uri)
|
117
|
+
Pathname.new(uri.path).basename.to_s +
|
118
|
+
(uri.query ? "?#{uri.query}" : "")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -1,13 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "forwardable"
|
4
|
+
|
3
5
|
module Openapi3Parser
|
4
6
|
module Validation
|
7
|
+
# Represents a validation error for an OpenAPI document
|
8
|
+
# @attr_reader [String] message The error message
|
9
|
+
# @attr_reader [Context] context The context where this was
|
10
|
+
# validated
|
11
|
+
# @attr_reader [Class, nil] factory_class The NodeFactory that was being
|
12
|
+
# created when this error was found
|
5
13
|
class Error
|
6
|
-
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
attr_reader :message, :context, :factory_class
|
17
|
+
|
18
|
+
# @!method source_location
|
19
|
+
# The source file and pointer for where this error occurred
|
20
|
+
# @return [Context::Location]
|
21
|
+
def_delegator :context, :source_location
|
22
|
+
|
23
|
+
alias to_s message
|
7
24
|
|
8
|
-
|
9
|
-
|
25
|
+
# @param [String] message
|
26
|
+
# @param [Context] context
|
27
|
+
# @param [Class, nil] factory_class
|
28
|
+
def initialize(message, context, factory_class = nil)
|
10
29
|
@message = message
|
30
|
+
@context = context
|
31
|
+
@factory_class = factory_class
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String, nil]
|
35
|
+
def for_type
|
36
|
+
return unless factory_class
|
37
|
+
factory_class.name.split("::").last
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [String]
|
41
|
+
def inspect
|
42
|
+
"#{self.class.name}(message: #{message}, context: #{context}, " \
|
43
|
+
"for_type: #{for_type})"
|
11
44
|
end
|
12
45
|
end
|
13
46
|
end
|