openapi3_parser 0.6.1 → 0.9.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 +5 -5
- data/.github/workflows/ci.yml +23 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +18 -3
- data/.ruby-version +1 -1
- data/CHANGELOG.md +41 -0
- data/README.md +7 -4
- data/TODO.md +1 -1
- data/lib/openapi3_parser.rb +2 -3
- data/lib/openapi3_parser/array_sentence.rb +2 -1
- data/lib/openapi3_parser/document.rb +3 -2
- data/lib/openapi3_parser/error.rb +11 -1
- data/lib/openapi3_parser/node/array.rb +9 -1
- data/lib/openapi3_parser/node/context.rb +34 -4
- data/lib/openapi3_parser/node/map.rb +17 -1
- data/lib/openapi3_parser/node/object.rb +17 -0
- data/lib/openapi3_parser/node/operation.rb +8 -0
- data/lib/openapi3_parser/node/path_item.rb +8 -0
- data/lib/openapi3_parser/node/placeholder.rb +16 -12
- data/lib/openapi3_parser/node/schema.rb +50 -3
- data/lib/openapi3_parser/node_factory.rb +1 -1
- data/lib/openapi3_parser/node_factory/array.rb +34 -26
- data/lib/openapi3_parser/node_factory/context.rb +9 -2
- data/lib/openapi3_parser/node_factory/discriminator.rb +2 -0
- data/lib/openapi3_parser/node_factory/field.rb +2 -0
- data/lib/openapi3_parser/node_factory/fields/reference.rb +1 -0
- data/lib/openapi3_parser/node_factory/map.rb +12 -10
- data/lib/openapi3_parser/node_factory/media_type.rb +1 -0
- data/lib/openapi3_parser/node_factory/object.rb +4 -1
- data/lib/openapi3_parser/node_factory/object_factory/dsl.rb +7 -5
- data/lib/openapi3_parser/node_factory/object_factory/field_config.rb +9 -7
- data/lib/openapi3_parser/node_factory/object_factory/node_builder.rb +12 -13
- data/lib/openapi3_parser/node_factory/object_factory/validator.rb +25 -21
- data/lib/openapi3_parser/node_factory/openapi.rb +2 -0
- data/lib/openapi3_parser/node_factory/operation.rb +9 -0
- data/lib/openapi3_parser/node_factory/parameter.rb +2 -0
- data/lib/openapi3_parser/node_factory/parameter_like.rb +2 -1
- data/lib/openapi3_parser/node_factory/path_item.rb +27 -5
- data/lib/openapi3_parser/node_factory/paths.rb +1 -1
- data/lib/openapi3_parser/node_factory/reference.rb +9 -1
- data/lib/openapi3_parser/node_factory/request_body.rb +2 -4
- data/lib/openapi3_parser/node_factory/response.rb +1 -0
- data/lib/openapi3_parser/node_factory/responses.rb +1 -1
- data/lib/openapi3_parser/node_factory/schema.rb +5 -2
- data/lib/openapi3_parser/node_factory/server_variable.rb +1 -0
- data/lib/openapi3_parser/node_factory/type_checker.rb +6 -0
- data/lib/openapi3_parser/source.rb +2 -0
- data/lib/openapi3_parser/source/location.rb +6 -0
- data/lib/openapi3_parser/source/pointer.rb +9 -4
- data/lib/openapi3_parser/source_input.rb +9 -9
- data/lib/openapi3_parser/source_input/file.rb +3 -1
- data/lib/openapi3_parser/source_input/raw.rb +4 -1
- data/lib/openapi3_parser/source_input/resolve_next.rb +1 -0
- data/lib/openapi3_parser/source_input/string_parser.rb +3 -2
- data/lib/openapi3_parser/source_input/url.rb +3 -1
- data/lib/openapi3_parser/validation/error.rb +2 -0
- data/lib/openapi3_parser/validation/error_collection.rb +1 -1
- data/lib/openapi3_parser/validation/input_validator.rb +1 -1
- data/lib/openapi3_parser/validators/component_keys.rb +1 -1
- data/lib/openapi3_parser/validators/duplicate_parameters.rb +1 -0
- data/lib/openapi3_parser/validators/email.rb +3 -3
- data/lib/openapi3_parser/validators/media_type.rb +1 -1
- data/lib/openapi3_parser/validators/mutually_exclusive_fields.rb +8 -8
- data/lib/openapi3_parser/validators/reference.rb +2 -0
- data/lib/openapi3_parser/validators/required_fields.rb +2 -2
- data/lib/openapi3_parser/validators/unexpected_fields.rb +3 -2
- data/lib/openapi3_parser/validators/url.rb +2 -7
- data/lib/openapi3_parser/version.rb +1 -1
- data/openapi3_parser.gemspec +12 -8
- metadata +81 -25
- data/.travis.yml +0 -11
|
@@ -66,6 +66,14 @@ module Openapi3Parser
|
|
|
66
66
|
self["servers"]
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# Whether this object uses it's own defined servers instead of falling
|
|
70
|
+
# back to the root ones.
|
|
71
|
+
#
|
|
72
|
+
# @return [Boolean]
|
|
73
|
+
def alternative_servers?
|
|
74
|
+
servers != node_context.document.root.servers
|
|
75
|
+
end
|
|
76
|
+
|
|
69
77
|
# @return [Node::Array<Parameter>]
|
|
70
78
|
def parameters
|
|
71
79
|
self["parameters"]
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
3
5
|
module Openapi3Parser
|
|
4
6
|
module Node
|
|
5
7
|
class Placeholder
|
|
8
|
+
extend Forwardable
|
|
9
|
+
|
|
6
10
|
def self.resolve(potential_placeholder)
|
|
7
11
|
if potential_placeholder.is_a?(Placeholder)
|
|
8
12
|
potential_placeholder.node
|
|
@@ -13,12 +17,12 @@ module Openapi3Parser
|
|
|
13
17
|
|
|
14
18
|
# Used to iterate through hashes or arrays that may contain
|
|
15
19
|
# Placeholder objects where these are resolved to being nodes
|
|
16
|
-
#
|
|
20
|
+
# before iteration
|
|
17
21
|
def self.each(node_data, &block)
|
|
18
22
|
resolved =
|
|
19
23
|
if node_data.respond_to?(:keys)
|
|
20
|
-
node_data.
|
|
21
|
-
|
|
24
|
+
node_data.transform_values do |value|
|
|
25
|
+
resolve(value)
|
|
22
26
|
end
|
|
23
27
|
else
|
|
24
28
|
node_data.map { |item| resolve(item) }
|
|
@@ -27,6 +31,10 @@ module Openapi3Parser
|
|
|
27
31
|
resolved.each(&block)
|
|
28
32
|
end
|
|
29
33
|
|
|
34
|
+
attr_reader :node_factory, :field, :parent_context
|
|
35
|
+
|
|
36
|
+
def_delegators :node_factory, :nil_input?
|
|
37
|
+
|
|
30
38
|
def initialize(node_factory, field, parent_context)
|
|
31
39
|
@node_factory = node_factory
|
|
32
40
|
@field = field
|
|
@@ -35,16 +43,12 @@ module Openapi3Parser
|
|
|
35
43
|
|
|
36
44
|
def node
|
|
37
45
|
@node ||= begin
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
node_context = Context.next_field(parent_context,
|
|
47
|
+
field,
|
|
48
|
+
node_factory.context)
|
|
49
|
+
node_factory.node(node_context)
|
|
50
|
+
end
|
|
43
51
|
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
attr_reader :node_factory, :field, :parent_context
|
|
48
52
|
end
|
|
49
53
|
end
|
|
50
54
|
end
|
|
@@ -5,8 +5,39 @@ require "openapi3_parser/node/object"
|
|
|
5
5
|
module Openapi3Parser
|
|
6
6
|
module Node
|
|
7
7
|
# @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject
|
|
8
|
-
# rubocop:disable ClassLength
|
|
8
|
+
# rubocop:disable Metrics/ClassLength
|
|
9
9
|
class Schema < Node::Object
|
|
10
|
+
# This is used to provide a name for the schema based on it's position in
|
|
11
|
+
# an OpenAPI document.
|
|
12
|
+
#
|
|
13
|
+
# For example it's common to have an OpenAPI document structured like so:
|
|
14
|
+
# components:
|
|
15
|
+
# schemas:
|
|
16
|
+
# Product:
|
|
17
|
+
# properties:
|
|
18
|
+
# product_id:
|
|
19
|
+
# type: string
|
|
20
|
+
# description:
|
|
21
|
+
# type: string
|
|
22
|
+
#
|
|
23
|
+
# and there is then implied meaning in the field name of Product, ie
|
|
24
|
+
# that schema now represents a product. This data is not easily or
|
|
25
|
+
# consistently made available as it is part of the path to the data
|
|
26
|
+
# rather than the data itself. Instead the field that would be more
|
|
27
|
+
# appropriate would be "title" within a schema.
|
|
28
|
+
#
|
|
29
|
+
# As this is a common pattern in OpenAPI docs this provides a method
|
|
30
|
+
# to look up this contextual name of the schema so it can be referenced
|
|
31
|
+
# when working with the document, it only considers a field to be
|
|
32
|
+
# name if it is within a group called schemas (as is the case
|
|
33
|
+
# in #/components/schemas)
|
|
34
|
+
#
|
|
35
|
+
# @return [String, nil]
|
|
36
|
+
def name
|
|
37
|
+
segments = node_context.source_location.pointer.segments
|
|
38
|
+
segments[-1] if segments[-2] == "schemas"
|
|
39
|
+
end
|
|
40
|
+
|
|
10
41
|
# @return [String, nil]
|
|
11
42
|
def title
|
|
12
43
|
self["title"]
|
|
@@ -82,6 +113,21 @@ module Openapi3Parser
|
|
|
82
113
|
self["required"]
|
|
83
114
|
end
|
|
84
115
|
|
|
116
|
+
# Returns whether a property is a required field or not. Can accept the
|
|
117
|
+
# property name or a schema
|
|
118
|
+
#
|
|
119
|
+
# @param [String, Schema] property
|
|
120
|
+
# @return [Boolean]
|
|
121
|
+
def requires?(property)
|
|
122
|
+
if property.is_a?(Schema)
|
|
123
|
+
properties.to_h
|
|
124
|
+
.select { |k, _| required.to_a.include?(k) }
|
|
125
|
+
.any? { |_, schema| schema == property }
|
|
126
|
+
else
|
|
127
|
+
required.to_a.include?(property)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
85
131
|
# @return [Node::Array<Object>, nil]
|
|
86
132
|
def enum
|
|
87
133
|
self["enum"]
|
|
@@ -131,6 +177,7 @@ module Openapi3Parser
|
|
|
131
177
|
def additional_properties_schema
|
|
132
178
|
properties = self["additionalProperties"]
|
|
133
179
|
return if [true, false].include?(properties)
|
|
180
|
+
|
|
134
181
|
properties
|
|
135
182
|
end
|
|
136
183
|
|
|
@@ -160,7 +207,7 @@ module Openapi3Parser
|
|
|
160
207
|
end
|
|
161
208
|
|
|
162
209
|
# @return [Discriminator, nil]
|
|
163
|
-
def
|
|
210
|
+
def discriminator
|
|
164
211
|
self["discriminator"]
|
|
165
212
|
end
|
|
166
213
|
|
|
@@ -194,6 +241,6 @@ module Openapi3Parser
|
|
|
194
241
|
self["deprecated"]
|
|
195
242
|
end
|
|
196
243
|
end
|
|
197
|
-
# rubocop:enable ClassLength
|
|
244
|
+
# rubocop:enable Metrics/ClassLength
|
|
198
245
|
end
|
|
199
246
|
end
|
|
@@ -3,23 +3,27 @@
|
|
|
3
3
|
module Openapi3Parser
|
|
4
4
|
module NodeFactory
|
|
5
5
|
class Array
|
|
6
|
-
attr_reader :context, :data, :default, :
|
|
7
|
-
:value_factory, :validation
|
|
6
|
+
attr_reader :context, :data, :default, :use_default_on_empty,
|
|
7
|
+
:value_input_type, :value_factory, :validation
|
|
8
8
|
|
|
9
|
+
# rubocop:disable Metrics/ParameterLists
|
|
9
10
|
def initialize(
|
|
10
11
|
context,
|
|
11
12
|
default: [],
|
|
13
|
+
use_default_on_empty: false,
|
|
12
14
|
value_input_type: nil,
|
|
13
15
|
value_factory: nil,
|
|
14
16
|
validate: nil
|
|
15
17
|
)
|
|
16
18
|
@context = context
|
|
17
19
|
@default = default
|
|
20
|
+
@use_default_on_empty = use_default_on_empty
|
|
18
21
|
@value_input_type = value_input_type
|
|
19
22
|
@value_factory = value_factory
|
|
20
23
|
@validation = validate
|
|
21
24
|
@data = build_data(context.input)
|
|
22
25
|
end
|
|
26
|
+
# rubocop:enable Metrics/ParameterLists
|
|
23
27
|
|
|
24
28
|
def raw_input
|
|
25
29
|
context.input
|
|
@@ -50,18 +54,25 @@ module Openapi3Parser
|
|
|
50
54
|
%{#{self.class.name}(#{context.source_location.inspect})}
|
|
51
55
|
end
|
|
52
56
|
|
|
57
|
+
def use_default?
|
|
58
|
+
return true if nil_input? || !raw_input.is_a?(::Array)
|
|
59
|
+
return false unless use_default_on_empty
|
|
60
|
+
|
|
61
|
+
raw_input.empty?
|
|
62
|
+
end
|
|
63
|
+
|
|
53
64
|
private
|
|
54
65
|
|
|
55
66
|
def build_data(raw_input)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
process_data(use_default ? default : raw_input)
|
|
67
|
+
return if use_default? && default.nil?
|
|
68
|
+
|
|
69
|
+
process_data(use_default? ? default : raw_input)
|
|
59
70
|
end
|
|
60
71
|
|
|
61
72
|
def process_data(data)
|
|
62
73
|
data.each_with_index.map do |value, i|
|
|
63
74
|
if value_factory
|
|
64
|
-
initialize_value_factory(Context.next_field(context, i))
|
|
75
|
+
initialize_value_factory(Context.next_field(context, i, value))
|
|
65
76
|
else
|
|
66
77
|
value
|
|
67
78
|
end
|
|
@@ -104,26 +115,24 @@ module Openapi3Parser
|
|
|
104
115
|
|
|
105
116
|
def errors
|
|
106
117
|
return validatable.collection if factory.nil_input?
|
|
118
|
+
|
|
107
119
|
TypeChecker.validate_type(validatable, type: ::Array)
|
|
108
120
|
return validatable.collection if validatable.errors.any?
|
|
121
|
+
|
|
109
122
|
collate_errors
|
|
110
123
|
validatable.collection
|
|
111
124
|
end
|
|
112
125
|
|
|
113
126
|
def data(parent_context)
|
|
114
|
-
|
|
127
|
+
if factory.use_default?
|
|
128
|
+
return factory.default.nil? ? nil : build_node_data(parent_context)
|
|
129
|
+
end
|
|
115
130
|
|
|
116
131
|
TypeChecker.raise_on_invalid_type(factory.context, type: ::Array)
|
|
117
132
|
check_values(raise_on_invalid: true)
|
|
118
133
|
validate(raise_on_invalid: true)
|
|
119
134
|
|
|
120
|
-
|
|
121
|
-
if value.respond_to?(:node)
|
|
122
|
-
Node::Placeholder.new(value, i, parent_context)
|
|
123
|
-
else
|
|
124
|
-
value
|
|
125
|
-
end
|
|
126
|
-
end
|
|
135
|
+
build_node_data(parent_context)
|
|
127
136
|
end
|
|
128
137
|
|
|
129
138
|
private_class_method :new
|
|
@@ -132,6 +141,14 @@ module Openapi3Parser
|
|
|
132
141
|
|
|
133
142
|
attr_reader :factory, :validatable
|
|
134
143
|
|
|
144
|
+
def build_node_data(parent_context)
|
|
145
|
+
factory.data.each_with_index.map do |value, i|
|
|
146
|
+
next value unless value.respond_to?(:node)
|
|
147
|
+
|
|
148
|
+
Node::Placeholder.new(value, i, parent_context)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
135
152
|
def collate_errors
|
|
136
153
|
check_values(raise_on_invalid: false)
|
|
137
154
|
validate(raise_on_invalid: false)
|
|
@@ -141,21 +158,12 @@ module Openapi3Parser
|
|
|
141
158
|
end
|
|
142
159
|
end
|
|
143
160
|
|
|
144
|
-
def default_value
|
|
145
|
-
if factory.nil_input? && factory.default.nil?
|
|
146
|
-
nil
|
|
147
|
-
else
|
|
148
|
-
factory.data
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
161
|
def check_values(raise_on_invalid: false)
|
|
153
162
|
return unless factory.value_input_type
|
|
154
163
|
|
|
155
|
-
factory.context.input.
|
|
156
|
-
check_field_type(
|
|
157
|
-
|
|
158
|
-
)
|
|
164
|
+
factory.context.input.each_with_index do |value, index|
|
|
165
|
+
check_field_type(Context.next_field(factory.context, index, value),
|
|
166
|
+
raise_on_invalid)
|
|
159
167
|
end
|
|
160
168
|
end
|
|
161
169
|
|
|
@@ -12,6 +12,8 @@ module Openapi3Parser
|
|
|
12
12
|
# @attr_reader [Array<Source::Location>] refernce_locations
|
|
13
13
|
#
|
|
14
14
|
class Context
|
|
15
|
+
UNDEFINED = Class.new
|
|
16
|
+
|
|
15
17
|
# Create a context for the root of a document
|
|
16
18
|
#
|
|
17
19
|
# @param [Any] input
|
|
@@ -29,10 +31,15 @@ module Openapi3Parser
|
|
|
29
31
|
#
|
|
30
32
|
# @param [Context] parent_context
|
|
31
33
|
# @param [String] field
|
|
34
|
+
# @param [Any] input
|
|
32
35
|
# @return [Context]
|
|
33
|
-
def self.next_field(parent_context, field)
|
|
36
|
+
def self.next_field(parent_context, field, given_input = UNDEFINED)
|
|
34
37
|
pc = parent_context
|
|
35
|
-
input =
|
|
38
|
+
input = if given_input == UNDEFINED
|
|
39
|
+
pc.input.respond_to?(:[]) ? pc.input[field] : nil
|
|
40
|
+
else
|
|
41
|
+
given_input
|
|
42
|
+
end
|
|
36
43
|
source_location = Source::Location.next_field(pc.source_location, field)
|
|
37
44
|
new(input,
|
|
38
45
|
source_location: source_location,
|
|
@@ -19,9 +19,11 @@ module Openapi3Parser
|
|
|
19
19
|
def validate_mapping(validatable)
|
|
20
20
|
input = validatable.input
|
|
21
21
|
return if input.empty?
|
|
22
|
+
|
|
22
23
|
string_keys = input.keys.map(&:class).uniq == [String]
|
|
23
24
|
string_values = input.values.map(&:class).uniq == [String]
|
|
24
25
|
return if string_keys && string_values
|
|
26
|
+
|
|
25
27
|
validatable.add_error("Expected string keys and string values")
|
|
26
28
|
end
|
|
27
29
|
end
|
|
@@ -70,8 +70,10 @@ module Openapi3Parser
|
|
|
70
70
|
|
|
71
71
|
def errors
|
|
72
72
|
return validatable.collection if factory.nil_input?
|
|
73
|
+
|
|
73
74
|
TypeChecker.validate_type(validatable, type: factory.input_type)
|
|
74
75
|
return validatable.collection if validatable.errors.any?
|
|
76
|
+
|
|
75
77
|
validate(raise_on_invalid: false)
|
|
76
78
|
validatable.collection
|
|
77
79
|
end
|
|
@@ -60,6 +60,7 @@ module Openapi3Parser
|
|
|
60
60
|
def build_data(raw_input)
|
|
61
61
|
use_default = nil_input? || !raw_input.is_a?(::Hash)
|
|
62
62
|
return if use_default && default.nil?
|
|
63
|
+
|
|
63
64
|
process_data(use_default ? default : raw_input)
|
|
64
65
|
end
|
|
65
66
|
|
|
@@ -89,12 +90,12 @@ module Openapi3Parser
|
|
|
89
90
|
def build_resolved_input
|
|
90
91
|
return unless data
|
|
91
92
|
|
|
92
|
-
data.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
data.transform_values do |value|
|
|
94
|
+
if value.respond_to?(:resolved_input)
|
|
95
|
+
value.resolved_input
|
|
96
|
+
else
|
|
97
|
+
value
|
|
98
|
+
end
|
|
98
99
|
end
|
|
99
100
|
end
|
|
100
101
|
|
|
@@ -114,8 +115,10 @@ module Openapi3Parser
|
|
|
114
115
|
|
|
115
116
|
def errors
|
|
116
117
|
return validatable.collection if factory.nil_input?
|
|
118
|
+
|
|
117
119
|
TypeChecker.validate_type(validatable, type: ::Hash)
|
|
118
120
|
return validatable.collection if validatable.errors.any?
|
|
121
|
+
|
|
119
122
|
collate_errors
|
|
120
123
|
validatable.collection
|
|
121
124
|
end
|
|
@@ -175,12 +178,11 @@ module Openapi3Parser
|
|
|
175
178
|
def check_values(raise_on_invalid: false)
|
|
176
179
|
return unless factory.value_input_type
|
|
177
180
|
|
|
178
|
-
factory.context.input.
|
|
181
|
+
factory.context.input.each do |key, value|
|
|
179
182
|
next if factory.allow_extensions && key.to_s =~ EXTENSION_REGEX
|
|
180
183
|
|
|
181
|
-
check_field_type(
|
|
182
|
-
|
|
183
|
-
)
|
|
184
|
+
check_field_type(Context.next_field(factory.context, key, value),
|
|
185
|
+
raise_on_invalid)
|
|
184
186
|
end
|
|
185
187
|
end
|
|
186
188
|
|
|
@@ -78,6 +78,7 @@ module Openapi3Parser
|
|
|
78
78
|
def build_data(raw_input)
|
|
79
79
|
use_default = nil_input? || !raw_input.is_a?(::Hash)
|
|
80
80
|
return if use_default && default.nil?
|
|
81
|
+
|
|
81
82
|
process_data(use_default ? default : raw_input)
|
|
82
83
|
end
|
|
83
84
|
|
|
@@ -85,7 +86,8 @@ module Openapi3Parser
|
|
|
85
86
|
field_configs.each_with_object(raw_data.dup) do |(field, config), memo|
|
|
86
87
|
memo[field] = nil unless memo[field]
|
|
87
88
|
next unless config.factory?
|
|
88
|
-
|
|
89
|
+
|
|
90
|
+
next_context = Context.next_field(context, field, memo[field])
|
|
89
91
|
memo[field] = config.initialize_factory(next_context, self)
|
|
90
92
|
end
|
|
91
93
|
end
|
|
@@ -95,6 +97,7 @@ module Openapi3Parser
|
|
|
95
97
|
|
|
96
98
|
data.each_with_object({}) do |(key, value), memo|
|
|
97
99
|
next if value.respond_to?(:nil_input?) && value.nil_input?
|
|
100
|
+
|
|
98
101
|
memo[key] = if value.respond_to?(:resolved_input)
|
|
99
102
|
value.resolved_input
|
|
100
103
|
else
|