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,77 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ostruct"
3
+ require "forwardable"
4
4
 
5
5
  require "openapi3_parser/context"
6
- require "openapi3_parser/node_factory"
7
- require "openapi3_parser/node_factory/field_config"
8
- require "openapi3_parser/node_factory/object/node_builder"
9
- require "openapi3_parser/node_factory/object/validator"
10
- require "openapi3_parser/validation/error_collection"
6
+ require "openapi3_parser/node_factory/object_factory/dsl"
7
+ require "openapi3_parser/node_factory/object_factory/node_builder"
11
8
 
12
9
  module Openapi3Parser
13
10
  module NodeFactory
14
- module Object
15
- include NodeFactory
11
+ class Object
12
+ extend Forwardable
13
+ extend ObjectFactory::Dsl
14
+
15
+ def_delegators "self.class",
16
+ :field_configs,
17
+ :allowed_extensions?,
18
+ :mutually_exclusive_fields,
19
+ :allowed_default?,
20
+ :validations
21
+
22
+ attr_reader :context, :data
23
+
24
+ def initialize(context)
25
+ @context = context
26
+ @data = build_data(context.input)
27
+ end
16
28
 
17
- module ClassMethods
18
- def field(name, **options)
19
- @field_configs ||= {}
20
- @field_configs[name] = FieldConfig.new(options)
21
- end
29
+ def resolved_input
30
+ @resolved_input ||= build_resolved_input
31
+ end
22
32
 
23
- def field_configs
24
- @field_configs || {}
25
- end
33
+ def raw_input
34
+ context.input
35
+ end
26
36
 
27
- def allow_extensions
28
- @allow_extensions = true
29
- end
37
+ def nil_input?
38
+ context.input.nil?
39
+ end
30
40
 
31
- def disallow_extensions
32
- @allow_extensions = false
33
- end
41
+ def valid?
42
+ errors.empty?
43
+ end
34
44
 
35
- def allowed_extensions?
36
- @allow_extensions == true
37
- end
45
+ def errors
46
+ @errors ||= ObjectFactory::NodeBuilder.errors(self)
47
+ end
38
48
 
39
- def mutually_exclusive(*fields, required: false)
40
- @mutually_exclusive ||= []
41
- @mutually_exclusive << OpenStruct.new(
42
- fields: fields, required: required
43
- )
44
- end
49
+ def node
50
+ @node ||= build_node
51
+ end
45
52
 
46
- def mutually_exclusive_fields
47
- @mutually_exclusive || []
48
- end
53
+ def can_use_default?
54
+ true
49
55
  end
50
56
 
51
- def self.included(base)
52
- base.extend(NodeFactory::ClassMethods)
53
- base.extend(ClassMethods)
54
- base.class_eval do
55
- input_type Hash
56
- end
57
+ def default
58
+ nil
57
59
  end
58
60
 
59
- def allowed_extensions?
60
- self.class.allowed_extensions?
61
+ def allowed_fields
62
+ field_configs.keys
61
63
  end
62
64
 
63
- def mutually_exclusive_fields
64
- self.class.mutually_exclusive_fields
65
+ def required_fields
66
+ field_configs.each_with_object([]) do |(key, config), memo|
67
+ memo << key if config.required?
68
+ end
65
69
  end
66
70
 
67
- def field_configs
68
- self.class.field_configs || {}
71
+ def inspect
72
+ %{#{self.class.name}(#{context.source_location.inspect})}
69
73
  end
70
74
 
71
75
  private
72
76
 
73
- def process_input(input)
74
- field_configs.each_with_object(input.dup) do |(field, config), memo|
77
+ def build_data(raw_input)
78
+ use_default = nil_input? || !raw_input.is_a?(::Hash)
79
+ return if use_default && default.nil?
80
+ process_data(use_default ? default : raw_input)
81
+ end
82
+
83
+ def process_data(raw_data)
84
+ field_configs.each_with_object(raw_data.dup) do |(field, config), memo|
75
85
  memo[field] = nil unless memo[field]
76
86
  next unless config.factory?
77
87
  next_context = Context.next_field(context, field)
@@ -79,22 +89,10 @@ module Openapi3Parser
79
89
  end
80
90
  end
81
91
 
82
- def validate_input
83
- validator = Validator.new(processed_input, self)
84
- Validation::ErrorCollection.combine(super, validator.errors)
85
- end
86
-
87
- def build_node(input)
88
- data = NodeBuilder.new(input, self).data
89
- build_object(data, context)
90
- end
91
-
92
- def build_object(data, _context)
93
- data
94
- end
95
-
96
92
  def build_resolved_input
97
- processed_input.each_with_object({}) do |(key, value), memo|
93
+ return unless data
94
+
95
+ data.each_with_object({}) do |(key, value), memo|
98
96
  next if value.respond_to?(:nil_input?) && value.nil_input?
99
97
  memo[key] = if value.respond_to?(:resolved_input)
100
98
  value.resolved_input
@@ -103,6 +101,11 @@ module Openapi3Parser
103
101
  end
104
102
  end
105
103
  end
104
+
105
+ def build_node
106
+ data = ObjectFactory::NodeBuilder.node_data(self)
107
+ build_object(data, context) if data
108
+ end
106
109
  end
107
110
  end
108
111
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+
5
+ require "openapi3_parser/node_factory/object_factory/field_config"
6
+
7
+ module Openapi3Parser
8
+ module NodeFactory
9
+ module ObjectFactory
10
+ module Dsl
11
+ def field(name, **options)
12
+ @field_configs ||= {}
13
+ @field_configs[name] = FieldConfig.new(options)
14
+ end
15
+
16
+ def field_configs
17
+ @field_configs || {}
18
+ end
19
+
20
+ def allow_extensions
21
+ @allow_extensions = true
22
+ end
23
+
24
+ def allowed_extensions?
25
+ @allow_extensions == true
26
+ end
27
+
28
+ def mutually_exclusive(*fields, required: false)
29
+ @mutually_exclusive ||= []
30
+ @mutually_exclusive << OpenStruct.new(
31
+ fields: fields, required: required
32
+ )
33
+ end
34
+
35
+ def mutually_exclusive_fields
36
+ @mutually_exclusive || []
37
+ end
38
+
39
+ def validate(*items, &block)
40
+ @validations = (@validations || []).concat(items)
41
+ @validations << block if block
42
+ end
43
+
44
+ def validations
45
+ @validations || []
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openapi3_parser/error"
4
+ require "openapi3_parser/node_factory/type_checker"
5
+
6
+ module Openapi3Parser
7
+ module NodeFactory
8
+ module ObjectFactory
9
+ class FieldConfig
10
+ def initialize(
11
+ input_type: nil,
12
+ factory: nil,
13
+ required: false,
14
+ default: nil,
15
+ validate: nil
16
+ )
17
+ @given_input_type = input_type
18
+ @given_factory = factory
19
+ @given_required = required
20
+ @given_default = default
21
+ @given_validate = validate
22
+ end
23
+
24
+ def factory?
25
+ !given_factory.nil?
26
+ end
27
+
28
+ def initialize_factory(context, parent_factory)
29
+ if given_factory.is_a?(Class)
30
+ given_factory.new(context)
31
+ elsif given_factory.is_a?(Symbol)
32
+ parent_factory.send(given_factory, context)
33
+ else
34
+ given_factory.call(context)
35
+ end
36
+ end
37
+
38
+ def required?
39
+ given_required
40
+ end
41
+
42
+ def check_input_type(validatable, building_node)
43
+ return true if !given_input_type || validatable.input.nil?
44
+
45
+ if building_node
46
+ TypeChecker.raise_on_invalid_type(validatable.context,
47
+ type: given_input_type)
48
+ else
49
+ TypeChecker.validate_type(validatable, type: given_input_type)
50
+ end
51
+ end
52
+
53
+ def validate_field(validatable, building_node)
54
+ return true if !given_validate || validatable.input.nil?
55
+
56
+ run_validation(validatable)
57
+
58
+ return validatable.errors.empty? unless building_node
59
+ return true if validatable.errors.empty?
60
+
61
+ error = validatable.errors.first
62
+ location_summary = error.context.location_summary
63
+ raise Error::InvalidData,
64
+ "Invalid data for #{location_summary}: #{error.message}"
65
+ end
66
+
67
+ def default(factory)
68
+ return given_default.call if given_default.is_a?(Proc)
69
+ return factory.send(given_default) if given_default.is_a?(Symbol)
70
+ given_default
71
+ end
72
+
73
+ private
74
+
75
+ attr_reader :given_input_type, :given_factory, :given_required,
76
+ :given_default, :given_validate
77
+
78
+ def run_validation(validatable)
79
+ if given_validate.is_a?(Symbol)
80
+ validatable.factory.send(given_validate, validatable)
81
+ else
82
+ given_validate.call(validatable)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openapi3_parser/node_factory/type_checker"
4
+ require "openapi3_parser/node_factory/object_factory/validator"
5
+ require "openapi3_parser/node_factory/recursive_pointer"
6
+
7
+ module Openapi3Parser
8
+ module NodeFactory
9
+ module ObjectFactory
10
+ class NodeBuilder
11
+ def self.errors(factory)
12
+ new(factory).errors
13
+ end
14
+
15
+ def self.node_data(factory)
16
+ new(factory).node_data
17
+ end
18
+
19
+ def initialize(factory)
20
+ @factory = factory
21
+ @validatable = Validation::Validatable.new(factory)
22
+ end
23
+
24
+ def errors
25
+ return validatable.collection if empty_and_allowed_to_be?
26
+
27
+ TypeChecker.validate_type(validatable, type: ::Hash)
28
+
29
+ if validatable.errors.empty?
30
+ validatable.add_errors(validate(raise_on_invalid: false))
31
+ end
32
+
33
+ validatable.collection
34
+ end
35
+
36
+ def node_data
37
+ return build_node_data if empty_and_allowed_to_be?
38
+ TypeChecker.raise_on_invalid_type(factory.context, type: ::Hash)
39
+ validate(raise_on_invalid: true)
40
+ build_node_data
41
+ end
42
+
43
+ private_class_method :new
44
+
45
+ private
46
+
47
+ attr_reader :factory, :validatable
48
+
49
+ def empty_and_allowed_to_be?
50
+ factory.nil_input? && factory.can_use_default?
51
+ end
52
+
53
+ def validate(raise_on_invalid:)
54
+ Validator.call(factory, raise_on_invalid)
55
+ end
56
+
57
+ def build_node_data
58
+ return if factory.nil_input? && factory.data.nil?
59
+
60
+ factory.data.each_with_object(NodeData.new) do |(key, value), memo|
61
+ memo[key] = if node_is_recursive_pointer?(value)
62
+ value.recursive_pointer
63
+ else
64
+ resolve_value(key, value)
65
+ end
66
+ end
67
+ end
68
+
69
+ def resolve_value(key, value)
70
+ config = factory.field_configs[key]
71
+ resolved_value = value.respond_to?(:node) ? value.node : value
72
+
73
+ # let a field config default take precedence if value is a nil_input?
74
+ if (value.respond_to?(:nil_input?) && value.nil_input?) || value.nil?
75
+ default = config&.default(factory)
76
+ default.nil? ? resolved_value : default
77
+ else
78
+ resolved_value
79
+ end
80
+ end
81
+
82
+ def node_is_recursive_pointer?(value_factory)
83
+ return false unless value_factory.respond_to?(:in_recursive_loop?)
84
+ value_factory.in_recursive_loop?
85
+ end
86
+
87
+ class NodeData < ::Hash
88
+ def [](key)
89
+ item = super(key)
90
+ item.is_a?(NodeFactory::RecursivePointer) ? item.node : item
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ require "openapi3_parser/array_sentence"
6
+ require "openapi3_parser/error"
7
+ require "openapi3_parser/validation/validatable"
8
+ require "openapi3_parser/validators/mutually_exclusive_fields"
9
+ require "openapi3_parser/validators/required_fields"
10
+ require "openapi3_parser/validators/unexpected_fields"
11
+
12
+ module Openapi3Parser
13
+ module NodeFactory
14
+ module ObjectFactory
15
+ class Validator
16
+ private_class_method :new
17
+ attr_reader :factory, :validatable, :building_node
18
+
19
+ def self.call(*args)
20
+ new(*args).call
21
+ end
22
+
23
+ def initialize(factory, building_node)
24
+ @factory = factory
25
+ @building_node = building_node
26
+ @validatable = Validation::Validatable.new(factory)
27
+ end
28
+
29
+ def call
30
+ check_required_fields
31
+ check_unexpected_fields
32
+ check_mutually_exclusive_fields
33
+ check_invalid_fields
34
+ check_factory_validations
35
+ validatable.collection
36
+ end
37
+
38
+ private
39
+
40
+ def check_required_fields
41
+ Validators::RequiredFields.call(
42
+ validatable,
43
+ required_fields: factory.required_fields,
44
+ raise_on_invalid: building_node
45
+ )
46
+ end
47
+
48
+ def check_unexpected_fields
49
+ Validators::UnexpectedFields.call(
50
+ validatable,
51
+ allow_extensions: factory.allowed_extensions?,
52
+ allowed_fields: factory.allowed_fields,
53
+ raise_on_invalid: building_node
54
+ )
55
+ end
56
+
57
+ def check_mutually_exclusive_fields
58
+ Validators::MutuallyExclusiveFields.call(
59
+ validatable,
60
+ mutually_exclusive_fields: factory.mutually_exclusive_fields,
61
+ raise_on_invalid: building_node
62
+ )
63
+ end
64
+
65
+ def check_invalid_fields
66
+ CheckInvalidFields.call(self)
67
+ end
68
+
69
+ def check_factory_validations
70
+ CheckFactoryValidations.call(self)
71
+ end
72
+
73
+ class CheckInvalidFields
74
+ extend Forwardable
75
+ attr_reader :validator
76
+ def_delegators :validator, :factory, :building_node, :validatable
77
+ private_class_method :new
78
+
79
+ def self.call(validator)
80
+ new(validator).call
81
+ end
82
+
83
+ def initialize(validator)
84
+ @validator = validator
85
+ end
86
+
87
+ def call
88
+ factory.data.each do |name, field|
89
+ # references can reference themselves and become in a loop
90
+ next if in_recursive_loop?(field)
91
+ has_factory_errors = handle_factory_checks(name)
92
+
93
+ next if has_factory_errors || !field.respond_to?(:errors)
94
+
95
+ # We don't add errors when we're building a node as they will
96
+ # be raised when that child node is built
97
+ validatable.add_errors(field.errors) unless building_node
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def handle_factory_checks(name)
104
+ field_errors = if factory.field_configs[name]
105
+ check_field(name, factory.field_configs[name])
106
+ end
107
+
108
+ (field_errors || []).any?
109
+ end
110
+
111
+ def in_recursive_loop?(field)
112
+ field.respond_to?(:in_recursive_loop?) && field.in_recursive_loop?
113
+ end
114
+
115
+ def check_field(name, field_config)
116
+ return if factory.raw_input[name].nil?
117
+
118
+ field_validatable = Validation::Validatable.new(
119
+ factory,
120
+ context: Context.next_field(factory.context, name)
121
+ )
122
+
123
+ valid_input_type = field_config.check_input_type(field_validatable,
124
+ building_node)
125
+
126
+ if valid_input_type
127
+ field_config.validate_field(field_validatable, building_node)
128
+ end
129
+
130
+ validatable.add_errors(field_validatable.errors)
131
+ field_validatable.errors
132
+ end
133
+ end
134
+
135
+ class CheckFactoryValidations
136
+ private_class_method :new
137
+
138
+ def self.call(validator)
139
+ new.call(validator)
140
+ end
141
+
142
+ def call(validator)
143
+ run_validations(validator)
144
+
145
+ errors = validator.validatable.errors
146
+
147
+ return if errors.empty? || !validator.building_node
148
+
149
+ location_summary = errors.first.context.location_summary
150
+ raise Error::InvalidData,
151
+ "Invalid data for #{location_summary}: "\
152
+ "#{errors.first.message}"
153
+ end
154
+
155
+ private
156
+
157
+ def run_validations(validator)
158
+ validator.factory.validations.each do |validation|
159
+ if validation.respond_to?(:call)
160
+ validation.call(validator.validatable)
161
+ elsif validation.is_a?(Symbol)
162
+ validator.factory.send(validation, validator.validatable)
163
+ else
164
+ raise Error::NotCallable, "expected a symbol or a callable"
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end