openapi3_parser 0.4.0 → 0.5.0

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