openapi3_parser 0.4.0 → 0.5.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +8 -0
  4. data/README.md +1 -1
  5. data/TODO.md +4 -4
  6. data/lib/openapi3_parser/array_sentence.rb +12 -0
  7. data/lib/openapi3_parser/cautious_dig.rb +39 -0
  8. data/lib/openapi3_parser/context.rb +53 -1
  9. data/lib/openapi3_parser/context/location.rb +1 -0
  10. data/lib/openapi3_parser/context/pointer.rb +67 -5
  11. data/lib/openapi3_parser/document.rb +45 -4
  12. data/lib/openapi3_parser/error.rb +9 -0
  13. data/lib/openapi3_parser/node/array.rb +14 -4
  14. data/lib/openapi3_parser/node/map.rb +45 -3
  15. data/lib/openapi3_parser/node/object.rb +25 -5
  16. data/lib/openapi3_parser/node_factory.rb +0 -150
  17. data/lib/openapi3_parser/node_factory/array.rb +198 -0
  18. data/lib/openapi3_parser/node_factory/callback.rb +24 -0
  19. data/lib/openapi3_parser/{node_factories → node_factory}/components.rb +24 -25
  20. data/lib/openapi3_parser/{node_factories → node_factory}/contact.rb +5 -6
  21. data/lib/openapi3_parser/{node_factories → node_factory}/discriminator.rb +6 -7
  22. data/lib/openapi3_parser/{node_factories → node_factory}/encoding.rb +6 -8
  23. data/lib/openapi3_parser/{node_factories → node_factory}/example.rb +4 -5
  24. data/lib/openapi3_parser/{node_factories → node_factory}/external_documentation.rb +4 -5
  25. data/lib/openapi3_parser/node_factory/field.rb +129 -0
  26. data/lib/openapi3_parser/node_factory/fields/reference.rb +54 -18
  27. data/lib/openapi3_parser/{node_factories → node_factory}/header.rb +4 -5
  28. data/lib/openapi3_parser/{node_factories → node_factory}/info.rb +6 -7
  29. data/lib/openapi3_parser/{node_factories → node_factory}/license.rb +4 -5
  30. data/lib/openapi3_parser/{node_factories → node_factory}/link.rb +6 -8
  31. data/lib/openapi3_parser/node_factory/map.rb +206 -21
  32. data/lib/openapi3_parser/{node_factories → node_factory}/media_type.rb +17 -16
  33. data/lib/openapi3_parser/{node_factories → node_factory}/oauth_flow.rb +2 -4
  34. data/lib/openapi3_parser/{node_factories → node_factory}/oauth_flows.rb +4 -6
  35. data/lib/openapi3_parser/node_factory/object.rb +66 -63
  36. data/lib/openapi3_parser/node_factory/object_factory/dsl.rb +50 -0
  37. data/lib/openapi3_parser/node_factory/object_factory/field_config.rb +88 -0
  38. data/lib/openapi3_parser/node_factory/object_factory/node_builder.rb +96 -0
  39. data/lib/openapi3_parser/node_factory/object_factory/validator.rb +172 -0
  40. data/lib/openapi3_parser/node_factory/openapi.rb +65 -0
  41. data/lib/openapi3_parser/node_factory/operation.rb +87 -0
  42. data/lib/openapi3_parser/node_factory/optional_reference.rb +7 -3
  43. data/lib/openapi3_parser/{node_factories → node_factory}/parameter.rb +16 -22
  44. data/lib/openapi3_parser/node_factory/parameter_like.rb +42 -0
  45. data/lib/openapi3_parser/{node_factories → node_factory}/path_item.rb +27 -29
  46. data/lib/openapi3_parser/{node_factories → node_factory}/paths.rb +21 -27
  47. data/lib/openapi3_parser/node_factory/recursive_pointer.rb +17 -0
  48. data/lib/openapi3_parser/node_factory/reference.rb +48 -0
  49. data/lib/openapi3_parser/{node_factories → node_factory}/request_body.rb +15 -19
  50. data/lib/openapi3_parser/{node_factories → node_factory}/response.rb +18 -21
  51. data/lib/openapi3_parser/node_factory/responses.rb +51 -0
  52. data/lib/openapi3_parser/{node_factories → node_factory}/schema.rb +33 -33
  53. data/lib/openapi3_parser/node_factory/security_requirement.rb +21 -0
  54. data/lib/openapi3_parser/{node_factories → node_factory}/security_scheme.rb +4 -6
  55. data/lib/openapi3_parser/{node_factories → node_factory}/server.rb +6 -8
  56. data/lib/openapi3_parser/{node_factories → node_factory}/server_variable.rb +10 -12
  57. data/lib/openapi3_parser/{node_factories → node_factory}/tag.rb +4 -6
  58. data/lib/openapi3_parser/node_factory/type_checker.rb +103 -0
  59. data/lib/openapi3_parser/{node_factories → node_factory}/xml.rb +4 -5
  60. data/lib/openapi3_parser/source/reference_resolver.rb +3 -3
  61. data/lib/openapi3_parser/validation/error.rb +9 -0
  62. data/lib/openapi3_parser/validation/input_validator.rb +18 -0
  63. data/lib/openapi3_parser/validation/validatable.rb +44 -0
  64. data/lib/openapi3_parser/validators/mutually_exclusive_fields.rb +121 -0
  65. data/lib/openapi3_parser/validators/required_fields.rb +37 -0
  66. data/lib/openapi3_parser/validators/unexpected_fields.rb +52 -0
  67. data/lib/openapi3_parser/version.rb +1 -1
  68. metadata +48 -38
  69. data/lib/openapi3_parser/node_factories/array.rb +0 -114
  70. data/lib/openapi3_parser/node_factories/callback.rb +0 -27
  71. data/lib/openapi3_parser/node_factories/map.rb +0 -120
  72. data/lib/openapi3_parser/node_factories/openapi.rb +0 -62
  73. data/lib/openapi3_parser/node_factories/operation.rb +0 -84
  74. data/lib/openapi3_parser/node_factories/parameter/parameter_like.rb +0 -41
  75. data/lib/openapi3_parser/node_factories/reference.rb +0 -35
  76. data/lib/openapi3_parser/node_factories/responses.rb +0 -60
  77. data/lib/openapi3_parser/node_factories/security_requirement.rb +0 -26
  78. data/lib/openapi3_parser/node_factory/field_config.rb +0 -88
  79. data/lib/openapi3_parser/node_factory/object/node_builder.rb +0 -97
  80. data/lib/openapi3_parser/node_factory/object/validator.rb +0 -176
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+
3
5
  module Openapi3Parser
4
6
  module Node
5
7
  class Map
8
+ extend Forwardable
6
9
  include Enumerable
7
10
 
11
+ def_delegators :node_data, :each, :keys, :empty?
8
12
  attr_reader :node_data, :node_context
9
13
 
10
14
  def initialize(data, context)
@@ -12,12 +16,50 @@ module Openapi3Parser
12
16
  @node_context = context
13
17
  end
14
18
 
19
+ # Look up an attribute of the node by the name it has in the OpenAPI
20
+ # document.
21
+ #
22
+ # @example Look up by OpenAPI naming
23
+ # obj["externalDocs"]
24
+ #
25
+ # @example Look up by symbol
26
+ # obj[:servers]
27
+ #
28
+ # @example Look up an extension
29
+ # obj["x-myExtension"]
30
+ #
31
+ # @param [String, Symbol] value
32
+ #
33
+ # @return anything
15
34
  def [](value)
16
- node_data[value]
35
+ node_data[value.to_s]
36
+ end
37
+
38
+ # Look up an extension provided for this map, doesn't need a prefix of
39
+ # "x-"
40
+ #
41
+ # @example Looking up an extension provided as "x-extra"
42
+ # obj.extension("extra")
43
+ #
44
+ # @param [String, Symbol] value
45
+ #
46
+ # @return [Hash, Array, Numeric, String, true, false, nil]
47
+ def extension(value)
48
+ node_data["x-#{value}"]
49
+ end
50
+
51
+ # Used to access a node relative to this node
52
+ # @param [Context::Pointer, ::Array, ::String] pointer_like
53
+ # @return anything
54
+ def node_at(pointer_like)
55
+ current_pointer = node_context.document_location.pointer
56
+ node_context.document.node_at(pointer_like, current_pointer)
17
57
  end
18
58
 
19
- def each(&block)
20
- node_data.each(&block)
59
+ # @return [String]
60
+ def inspect
61
+ fragment = node_context.document_location.pointer.fragment
62
+ %{#{self.class.name}(#{fragment})}
21
63
  end
22
64
  end
23
65
  end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+
3
5
  require "openapi3_parser/markdown"
4
6
 
5
7
  module Openapi3Parser
6
8
  module Node
7
9
  class Object
10
+ extend Forwardable
8
11
  include Enumerable
9
12
 
13
+ def_delegators :node_data, :each, :keys, :empty?
10
14
  attr_reader :node_data, :node_context
11
15
 
12
16
  def initialize(data, context)
@@ -46,11 +50,6 @@ module Openapi3Parser
46
50
  node_data["x-#{value}"]
47
51
  end
48
52
 
49
- # Iterate through the attributes of this object
50
- def each(&block)
51
- node_data.each(&block)
52
- end
53
-
54
53
  # Used to render fields that can be in markdown syntax into HTML
55
54
  # @param [String, nil] value
56
55
  # @return [String, nil]
@@ -58,6 +57,27 @@ module Openapi3Parser
58
57
  return if value.nil?
59
58
  Markdown.to_html(value)
60
59
  end
60
+
61
+ # Used to access a node relative to this node
62
+ #
63
+ # @example Looking up the parent node of this node
64
+ # obj.node_at("#..")
65
+ #
66
+ # @example Jumping way down the tree
67
+ # obj.node_at("#properties/Field/type")
68
+ #
69
+ # @param [Context::Pointer, ::Array, ::String] pointer_like
70
+ # @return anything
71
+ def node_at(pointer_like)
72
+ current_pointer = node_context.document_location.pointer
73
+ node_context.document.node_at(pointer_like, current_pointer)
74
+ end
75
+
76
+ # @return [String]
77
+ def inspect
78
+ fragment = node_context.document_location.pointer.fragment
79
+ %{#{self.class.name}(#{fragment})}
80
+ end
61
81
  end
62
82
  end
63
83
  end
@@ -1,157 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/context"
4
- require "openapi3_parser/error"
5
- require "openapi3_parser/validation/error"
6
- require "openapi3_parser/validation/error_collection"
7
-
8
3
  module Openapi3Parser
9
4
  module NodeFactory
10
- module ClassMethods
11
- def input_type(type)
12
- @input_type = type
13
- end
14
-
15
- def valid_input_type?(type)
16
- return true unless @input_type
17
- type.is_a?(@input_type)
18
- end
19
-
20
- def expected_input_type
21
- @input_type
22
- end
23
-
24
- def allow_default
25
- @allow_default = true
26
- end
27
-
28
- def disallow_default
29
- @allow_default = false
30
- end
31
-
32
- def allowed_default?
33
- @allow_default.nil? || @allow_default
34
- end
35
- end
36
-
37
- def self.included(base)
38
- base.extend(ClassMethods)
39
- end
40
-
41
- def allowed_default?
42
- self.class.allowed_default?
43
- end
44
-
45
5
  EXTENSION_REGEX = /^x-(.*)/
46
-
47
- attr_reader :context, :processed_input
48
-
49
- def initialize(context)
50
- @context = context
51
- input = nil_input? ? default : context.input
52
- @processed_input = input.nil? ? nil : process_input(input)
53
- end
54
-
55
- def valid?
56
- errors.empty?
57
- end
58
-
59
- def errors
60
- @errors ||= build_errors
61
- end
62
-
63
- def node
64
- @node ||= build_valid_node
65
- end
66
-
67
- def nil_input?
68
- context.input.nil?
69
- end
70
-
71
- def resolved_input
72
- @resolved_input ||= processed_input ? build_resolved_input : nil
73
- end
74
-
75
- private
76
-
77
- def validate(_input, _context); end
78
-
79
- def validate_input
80
- transform_errors(validate(context.input, context))
81
- end
82
-
83
- def build_resolved_input
84
- context.input
85
- end
86
-
87
- def transform_errors(errors)
88
- error_objects = Array(errors).map do |error|
89
- if error.is_a?(Validation::Error)
90
- error
91
- else
92
- Validation::Error.new(error, context, self.class)
93
- end
94
- end
95
- Validation::ErrorCollection.new(error_objects)
96
- end
97
-
98
- def build_errors
99
- return Validation::ErrorCollection.new if nil_input? && allowed_default?
100
- unless valid_type?
101
- error = Validation::Error.new(
102
- "Invalid type. #{validate_type}", context, self.class
103
- )
104
- return Validation::ErrorCollection.new([error])
105
- end
106
- validate_input
107
- end
108
-
109
- def build_valid_node
110
- if nil_input? && allowed_default?
111
- return default.nil? ? nil : build_node(processed_input)
112
- end
113
-
114
- unless valid_type?
115
- raise Openapi3Parser::Error::InvalidType,
116
- "Invalid type for #{context.location_summary}. "\
117
- "#{validate_type}"
118
- end
119
-
120
- validate_before_build
121
- build_node(processed_input)
122
- end
123
-
124
- def validate_before_build
125
- errors = Array(validate(context.input, context))
126
- return unless errors.any?
127
- raise Openapi3Parser::Error::InvalidData,
128
- "Invalid data for #{context.location_summary}. "\
129
- "#{errors.join(', ')}"
130
- end
131
-
132
- def valid_type?
133
- validate_type.nil?
134
- end
135
-
136
- def validate_type
137
- valid_type = self.class.valid_input_type?(context.input)
138
- return "Expected #{self.class.expected_input_type}" unless valid_type
139
- end
140
-
141
- def process_input(input)
142
- input
143
- end
144
-
145
- def build_node(input)
146
- input
147
- end
148
-
149
- def default
150
- nil
151
- end
152
-
153
- def extension?(key)
154
- key.match(EXTENSION_REGEX)
155
- end
156
6
  end
157
7
  end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openapi3_parser/context"
4
+ require "openapi3_parser/node_factory"
5
+ require "openapi3_parser/node_factory/type_checker"
6
+ require "openapi3_parser/node/array"
7
+ require "openapi3_parser/validation/validatable"
8
+
9
+ module Openapi3Parser
10
+ module NodeFactory
11
+ class Array
12
+ attr_reader :context, :data, :default, :value_input_type,
13
+ :value_factory, :validation
14
+
15
+ def initialize(
16
+ context,
17
+ default: [],
18
+ value_input_type: nil,
19
+ value_factory: nil,
20
+ validate: nil
21
+ )
22
+ @context = context
23
+ @default = default
24
+ @value_input_type = value_input_type
25
+ @value_factory = value_factory
26
+ @validation = validate
27
+ @data = build_data(context.input)
28
+ end
29
+
30
+ def raw_input
31
+ context.input
32
+ end
33
+
34
+ def resolved_input
35
+ @resolved_input ||= build_resolved_input
36
+ end
37
+
38
+ def nil_input?
39
+ context.input.nil?
40
+ end
41
+
42
+ def valid?
43
+ errors.empty?
44
+ end
45
+
46
+ def errors
47
+ @errors ||= ValidNodeBuilder.errors(self)
48
+ end
49
+
50
+ def node
51
+ @node ||= begin
52
+ data = ValidNodeBuilder.data(self)
53
+ data.nil? ? nil : build_node(data)
54
+ end
55
+ end
56
+
57
+ def inspect
58
+ %{#{self.class.name}(#{context.source_location.inspect})}
59
+ end
60
+
61
+ private
62
+
63
+ def build_data(raw_input)
64
+ use_default = nil_input? || !raw_input.is_a?(::Array)
65
+ return if use_default && default.nil?
66
+ process_data(use_default ? default : raw_input)
67
+ end
68
+
69
+ def process_data(data)
70
+ data.each_with_index.map do |value, i|
71
+ if value_factory
72
+ initialize_value_factory(Context.next_field(context, i))
73
+ else
74
+ value
75
+ end
76
+ end
77
+ end
78
+
79
+ def initialize_value_factory(field_context)
80
+ if value_factory.is_a?(Class)
81
+ value_factory.new(field_context)
82
+ else
83
+ value_factory.call(field_context)
84
+ end
85
+ end
86
+
87
+ def build_node(data)
88
+ Node::Array.new(data, context) if data
89
+ end
90
+
91
+ def build_resolved_input
92
+ return unless data
93
+
94
+ data.map do |value|
95
+ value.respond_to?(:resolved_input) ? value.resolved_input : value
96
+ end
97
+ end
98
+
99
+ class ValidNodeBuilder
100
+ def self.errors(factory)
101
+ new(factory).errors
102
+ end
103
+
104
+ def self.data(factory)
105
+ new(factory).data
106
+ end
107
+
108
+ def initialize(factory)
109
+ @factory = factory
110
+ @validatable = Validation::Validatable.new(factory)
111
+ end
112
+
113
+ def errors
114
+ return validatable.collection if factory.nil_input?
115
+ TypeChecker.validate_type(validatable, type: ::Array)
116
+ return validatable.collection if validatable.errors.any?
117
+ collate_errors
118
+ validatable.collection
119
+ end
120
+
121
+ def data
122
+ return default_value if factory.nil_input?
123
+
124
+ TypeChecker.raise_on_invalid_type(factory.context, type: ::Array)
125
+ check_values(raise_on_invalid: true)
126
+ validate(raise_on_invalid: true)
127
+
128
+ factory.data.map do |value|
129
+ value.respond_to?(:node) ? value.node : value
130
+ end
131
+ end
132
+
133
+ private_class_method :new
134
+
135
+ private
136
+
137
+ attr_reader :factory, :validatable
138
+
139
+ def collate_errors
140
+ check_values(raise_on_invalid: false)
141
+ validate(raise_on_invalid: false)
142
+
143
+ factory.data.each do |value|
144
+ validatable.add_errors(value.errors) if value.respond_to?(:errors)
145
+ end
146
+ end
147
+
148
+ def default_value
149
+ if factory.nil_input? && factory.default.nil?
150
+ nil
151
+ else
152
+ factory.data
153
+ end
154
+ end
155
+
156
+ def check_values(raise_on_invalid: false)
157
+ return unless factory.value_input_type
158
+
159
+ factory.context.input.each_index do |index|
160
+ check_field_type(
161
+ Context.next_field(factory.context, index), raise_on_invalid
162
+ )
163
+ end
164
+ end
165
+
166
+ def check_field_type(context, raise_on_invalid)
167
+ if raise_on_invalid
168
+ TypeChecker.raise_on_invalid_type(context,
169
+ type: factory.value_input_type)
170
+ else
171
+ TypeChecker.validate_type(validatable,
172
+ type: factory.value_input_type,
173
+ context: context)
174
+ end
175
+ end
176
+
177
+ def validate(raise_on_invalid: false)
178
+ run_validation
179
+
180
+ return if !raise_on_invalid || validatable.errors.empty?
181
+
182
+ first_error = validatable.errors.first
183
+ raise Openapi3Parser::Error::InvalidData,
184
+ "Invalid data for #{first_error.context.location_summary}. "\
185
+ "#{first_error.message}"
186
+ end
187
+
188
+ def run_validation
189
+ if factory.validation.is_a?(Symbol)
190
+ factory.send(:validation, validatable)
191
+ else
192
+ factory.validation&.call(validatable)
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end