openapi3_parser 0.7.0 → 0.8.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +2 -4
  4. data/.travis.yml +11 -0
  5. data/CHANGELOG.md +13 -0
  6. data/README.md +4 -1
  7. data/TODO.md +1 -1
  8. data/lib/openapi3_parser.rb +2 -3
  9. data/lib/openapi3_parser/array_sentence.rb +1 -0
  10. data/lib/openapi3_parser/document.rb +2 -1
  11. data/lib/openapi3_parser/node/array.rb +1 -1
  12. data/lib/openapi3_parser/node/context.rb +23 -0
  13. data/lib/openapi3_parser/node/map.rb +1 -1
  14. data/lib/openapi3_parser/node/object.rb +1 -0
  15. data/lib/openapi3_parser/node/operation.rb +8 -0
  16. data/lib/openapi3_parser/node/path_item.rb +8 -0
  17. data/lib/openapi3_parser/node/placeholder.rb +8 -5
  18. data/lib/openapi3_parser/node/schema.rb +3 -2
  19. data/lib/openapi3_parser/node_factory.rb +1 -1
  20. data/lib/openapi3_parser/node_factory/array.rb +34 -26
  21. data/lib/openapi3_parser/node_factory/context.rb +9 -2
  22. data/lib/openapi3_parser/node_factory/discriminator.rb +2 -0
  23. data/lib/openapi3_parser/node_factory/field.rb +2 -0
  24. data/lib/openapi3_parser/node_factory/fields/reference.rb +1 -0
  25. data/lib/openapi3_parser/node_factory/map.rb +6 -4
  26. data/lib/openapi3_parser/node_factory/media_type.rb +1 -0
  27. data/lib/openapi3_parser/node_factory/object.rb +4 -1
  28. data/lib/openapi3_parser/node_factory/object_factory/dsl.rb +1 -1
  29. data/lib/openapi3_parser/node_factory/object_factory/field_config.rb +1 -0
  30. data/lib/openapi3_parser/node_factory/object_factory/node_builder.rb +1 -0
  31. data/lib/openapi3_parser/node_factory/object_factory/validator.rb +6 -4
  32. data/lib/openapi3_parser/node_factory/openapi.rb +2 -0
  33. data/lib/openapi3_parser/node_factory/operation.rb +11 -0
  34. data/lib/openapi3_parser/node_factory/parameter.rb +2 -0
  35. data/lib/openapi3_parser/node_factory/parameter_like.rb +1 -0
  36. data/lib/openapi3_parser/node_factory/path_item.rb +27 -5
  37. data/lib/openapi3_parser/node_factory/paths.rb +1 -1
  38. data/lib/openapi3_parser/node_factory/response.rb +1 -0
  39. data/lib/openapi3_parser/node_factory/responses.rb +1 -1
  40. data/lib/openapi3_parser/node_factory/schema.rb +3 -0
  41. data/lib/openapi3_parser/node_factory/server_variable.rb +1 -0
  42. data/lib/openapi3_parser/node_factory/type_checker.rb +6 -0
  43. data/lib/openapi3_parser/source.rb +2 -0
  44. data/lib/openapi3_parser/source/location.rb +6 -0
  45. data/lib/openapi3_parser/source/pointer.rb +5 -0
  46. data/lib/openapi3_parser/source_input.rb +2 -0
  47. data/lib/openapi3_parser/source_input/file.rb +2 -0
  48. data/lib/openapi3_parser/source_input/raw.rb +3 -0
  49. data/lib/openapi3_parser/source_input/resolve_next.rb +1 -0
  50. data/lib/openapi3_parser/source_input/string_parser.rb +3 -2
  51. data/lib/openapi3_parser/source_input/url.rb +2 -0
  52. data/lib/openapi3_parser/validation/error.rb +2 -0
  53. data/lib/openapi3_parser/validators/component_keys.rb +1 -1
  54. data/lib/openapi3_parser/validators/duplicate_parameters.rb +1 -0
  55. data/lib/openapi3_parser/validators/email.rb +1 -1
  56. data/lib/openapi3_parser/validators/media_type.rb +1 -1
  57. data/lib/openapi3_parser/validators/mutually_exclusive_fields.rb +2 -2
  58. data/lib/openapi3_parser/validators/reference.rb +2 -0
  59. data/lib/openapi3_parser/validators/required_fields.rb +2 -2
  60. data/lib/openapi3_parser/validators/unexpected_fields.rb +3 -2
  61. data/lib/openapi3_parser/version.rb +1 -1
  62. data/openapi3_parser.gemspec +9 -7
  63. metadata +44 -16
@@ -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
@@ -72,6 +72,7 @@ module Openapi3Parser
72
72
 
73
73
  def create_resolved_reference
74
74
  return unless reference_validator.valid?
75
+
75
76
  context.resolve_reference(reference,
76
77
  factory,
77
78
  recursive: context.self_referencing?)
@@ -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
 
@@ -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.keys.each do |key|
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
- Context.next_field(factory.context, key), raise_on_invalid
183
- )
184
+ check_field_type(Context.next_field(factory.context, key, value),
185
+ raise_on_invalid)
184
186
  end
185
187
  end
186
188
 
@@ -48,6 +48,7 @@ module Openapi3Parser
48
48
  def call(validatable)
49
49
  missing_keys = validatable.input.keys - properties
50
50
  return if missing_keys.empty?
51
+
51
52
  validatable.add_error(error_message(missing_keys))
52
53
  end
53
54
 
@@ -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
- next_context = Context.next_field(context, field)
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
@@ -9,7 +9,7 @@ module Openapi3Parser
9
9
  module Dsl
10
10
  def field(name, **options)
11
11
  @field_configs ||= {}
12
- @field_configs[name] = FieldConfig.new(options)
12
+ @field_configs[name] = FieldConfig.new(**options)
13
13
  end
14
14
 
15
15
  def field_configs
@@ -64,6 +64,7 @@ module Openapi3Parser
64
64
  def default(factory)
65
65
  return given_default.call if given_default.is_a?(Proc)
66
66
  return factory.send(given_default) if given_default.is_a?(Symbol)
67
+
67
68
  given_default
68
69
  end
69
70
 
@@ -31,6 +31,7 @@ module Openapi3Parser
31
31
 
32
32
  def node_data(parent_context)
33
33
  return build_node_data(parent_context) if empty_and_allowed_to_be?
34
+
34
35
  TypeChecker.raise_on_invalid_type(factory.context, type: ::Hash)
35
36
  validate(raise_on_invalid: true)
36
37
  build_node_data(parent_context)
@@ -81,6 +81,7 @@ module Openapi3Parser
81
81
  factory.data.each do |name, field|
82
82
  # references can reference themselves and become in a loop
83
83
  next if in_recursive_loop?(field)
84
+
84
85
  has_factory_errors = handle_factory_checks(name)
85
86
 
86
87
  next if has_factory_errors || !field.respond_to?(:errors)
@@ -108,10 +109,11 @@ module Openapi3Parser
108
109
  def check_field(name, field_config)
109
110
  return if factory.raw_input[name].nil?
110
111
 
111
- field_validatable = Validation::Validatable.new(
112
- factory,
113
- context: Context.next_field(factory.context, name)
114
- )
112
+ context = Context.next_field(factory.context,
113
+ name,
114
+ factory.raw_input[name])
115
+ field_validatable = Validation::Validatable.new(factory,
116
+ context: context)
115
117
 
116
118
  valid_input_type = field_config.check_input_type(field_validatable,
117
119
  building_node)
@@ -32,6 +32,8 @@ module Openapi3Parser
32
32
 
33
33
  def servers_factory(context)
34
34
  NodeFactory::Array.new(context,
35
+ default: [{ "url" => "/" }],
36
+ use_default_on_empty: true,
35
37
  value_factory: NodeFactory::Server)
36
38
  end
37
39
 
@@ -25,6 +25,10 @@ module Openapi3Parser
25
25
  private
26
26
 
27
27
  def build_object(data, context)
28
+ if data["servers"].node.empty?
29
+ data["servers"] = path_item_server_data(context)
30
+ end
31
+
28
32
  Node::Operation.new(data, context)
29
33
  end
30
34
 
@@ -72,6 +76,13 @@ module Openapi3Parser
72
76
  NodeFactory::Array.new(context,
73
77
  value_factory: NodeFactory::Server)
74
78
  end
79
+
80
+ def path_item_server_data(node_context)
81
+ path_item_servers = node_context.parent_node.node_data["servers"]
82
+ Node::Placeholder.new(path_item_servers.node_factory,
83
+ "servers",
84
+ node_context)
85
+ end
75
86
  end
76
87
  end
77
88
  end
@@ -47,11 +47,13 @@ module Openapi3Parser
47
47
 
48
48
  def default_style
49
49
  return "simple" if %w[path header].include?(context.input["in"])
50
+
50
51
  "form"
51
52
  end
52
53
 
53
54
  def validate_in(validatable)
54
55
  return if %w[header query cookie path].include?(validatable.input)
56
+
55
57
  validatable.add_error("in can only be header, query, cookie, or path")
56
58
  end
57
59
  end
@@ -28,6 +28,7 @@ module Openapi3Parser
28
28
 
29
29
  def validate_content(validatable)
30
30
  return if validatable.input.size == 1
31
+
31
32
  validatable.add_error("Must only have one item")
32
33
  end
33
34
  end
@@ -23,11 +23,22 @@ module Openapi3Parser
23
23
  private
24
24
 
25
25
  def build_object(data, node_context)
26
- ref = data.delete("$ref").node
27
- return Node::PathItem.new(data, node_context) unless ref
26
+ ref = data.delete("$ref")
27
+ context = if node_context.input.keys == %w[$ref]
28
+ referenced_factory = ref.node_factory.referenced_factory
29
+ Node::Context.resolved_reference(
30
+ node_context,
31
+ referenced_factory.context
32
+ )
33
+ else
34
+ node_context
35
+ end
28
36
 
29
- context = data.empty? ? ref.node_context : node_context
30
- data = merge_data(ref.node_data, data)
37
+ reference_data = ref.nil_input? ? {} : ref.node.node_data
38
+
39
+ data = merge_data(reference_data, data).tap do |d|
40
+ d["servers"] = root_server_data(context) if d["servers"].node.empty?
41
+ end
31
42
 
32
43
  Node::PathItem.new(data, context)
33
44
  end
@@ -57,7 +68,11 @@ module Openapi3Parser
57
68
 
58
69
  def merge_data(base, priority)
59
70
  base.merge(priority) do |_, old, new|
60
- new.nil? ? old : new
71
+ if new.nil? || new.respond_to?(:nil_input?) && new.nil_input?
72
+ old
73
+ else
74
+ new
75
+ end
61
76
  end
62
77
  end
63
78
 
@@ -76,6 +91,13 @@ module Openapi3Parser
76
91
  value_factory: factory,
77
92
  validate: validate_parameters)
78
93
  end
94
+
95
+ def root_server_data(node_context)
96
+ root_servers = node_context.document.root.node_data["servers"]
97
+ Node::Placeholder.new(root_servers.node_factory,
98
+ "servers",
99
+ node_context)
100
+ end
79
101
  end
80
102
  end
81
103
  end
@@ -18,7 +18,7 @@ module Openapi3Parser
18
18
  /?
19
19
  )*
20
20
  \Z
21
- }x
21
+ }x.freeze
22
22
 
23
23
  def initialize(context)
24
24
  factory = NodeFactory::OptionalReference.new(NodeFactory::PathItem)
@@ -43,6 +43,7 @@ module Openapi3Parser
43
43
  validatable.input.each_key do |key|
44
44
  message = Validators::MediaType.call(key)
45
45
  next unless message
46
+
46
47
  validatable.add_error(message,
47
48
  Context.next_field(validatable.context, key))
48
49
  end
@@ -13,7 +13,7 @@ module Openapi3Parser
13
13
  [1-5]([0-9][0-9]|XX)
14
14
  )
15
15
  \Z
16
- /x
16
+ /x.freeze
17
17
 
18
18
  def initialize(context)
19
19
  factory = NodeFactory::OptionalReference.new(NodeFactory::Response)
@@ -61,6 +61,7 @@ module Openapi3Parser
61
61
  def read_only_or_write_only(validatable)
62
62
  input = validatable.input
63
63
  return if [input["readOnly"], input["writeOnly"]].uniq != [true]
64
+
64
65
  validatable.add_error("readOnly and writeOnly cannot both be true")
65
66
  end
66
67
 
@@ -114,11 +115,13 @@ module Openapi3Parser
114
115
  def additional_properties_input_type(validatable)
115
116
  input = validatable.input
116
117
  return if [true, false].include?(input) || input.is_a?(Hash)
118
+
117
119
  validatable.add_error("Expected a Boolean or an Object")
118
120
  end
119
121
 
120
122
  def additional_properties_factory(context)
121
123
  return context.input if [true, false].include?(context.input)
124
+
122
125
  referenceable_schema(context)
123
126
  end
124
127
  end
@@ -19,6 +19,7 @@ module Openapi3Parser
19
19
  value_input_type: String,
20
20
  validate: lambda do |validatable|
21
21
  return if validatable.input.any?
22
+
22
23
  validatable.add_error("Expected atleast one value")
23
24
  end
24
25
  )
@@ -27,9 +27,11 @@ module Openapi3Parser
27
27
 
28
28
  def validate_type(validatable, context)
29
29
  return true unless type
30
+
30
31
  context ||= validatable.context
31
32
  valid_type?(context.input).tap do |valid|
32
33
  next if valid
34
+
33
35
  validatable.add_error("Invalid type. #{field_error_message}",
34
36
  context)
35
37
  end
@@ -37,9 +39,11 @@ module Openapi3Parser
37
39
 
38
40
  def validate_keys(validatable, context)
39
41
  return true unless type
42
+
40
43
  context ||= validatable.context
41
44
  valid_keys?(context.input).tap do |valid|
42
45
  next if valid
46
+
43
47
  validatable.add_error("Invalid keys. #{keys_error_message}",
44
48
  context)
45
49
  end
@@ -47,6 +51,7 @@ module Openapi3Parser
47
51
 
48
52
  def raise_on_invalid_type(context)
49
53
  return true if !type || valid_type?(context.input)
54
+
50
55
  raise Error::InvalidType,
51
56
  "Invalid type for #{context.location_summary}: "\
52
57
  "#{field_error_message}"
@@ -54,6 +59,7 @@ module Openapi3Parser
54
59
 
55
60
  def raise_on_invalid_keys(context)
56
61
  return true if !type || valid_keys?(context.input)
62
+
57
63
  raise Error::InvalidType,
58
64
  "Invalid keys for #{context.location_summary}: "\
59
65
  "#{keys_error_message}"
@@ -93,6 +93,7 @@ module Openapi3Parser
93
93
  # @return [Object]
94
94
  def data_at_pointer(json_pointer)
95
95
  return data if json_pointer.empty?
96
+
96
97
  data.dig(*json_pointer) if data.respond_to?(:dig)
97
98
  end
98
99
 
@@ -104,6 +105,7 @@ module Openapi3Parser
104
105
  # @return [String]
105
106
  def relative_to_root
106
107
  return "" if root?
108
+
107
109
  source_input.relative_to(document.root_source.source_input)
108
110
  end
109
111
 
@@ -1,15 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+
3
5
  module Openapi3Parser
4
6
  class Source
5
7
  # Class used to represent a location within an OpenAPI document.
6
8
  # It contains a source, which is the source file/data used for the contents
7
9
  # and the pointer which indicates where in the object like file the data is
8
10
  class Location
11
+ extend Forwardable
12
+
9
13
  def self.next_field(location, field)
10
14
  new(location.source, location.pointer.segments + [field])
11
15
  end
12
16
 
17
+ def_delegators :pointer, :root?
13
18
  attr_reader :source, :pointer
14
19
 
15
20
  # @param [Openapi3Parser::Source] source
@@ -21,6 +26,7 @@ module Openapi3Parser
21
26
 
22
27
  def ==(other)
23
28
  return false unless other.instance_of?(self.class)
29
+
24
30
  source == other.source && pointer == other.pointer
25
31
  end
26
32
 
@@ -12,6 +12,7 @@ module Openapi3Parser
12
12
  absolute = fragment[0] == "/"
13
13
  segments = fragment.split("/").map do |part|
14
14
  next if part == ""
15
+
15
16
  unescaped = CGI.unescape(part.gsub("%20", "+"))
16
17
  unescaped.match?(/\A\d+\z/) ? unescaped.to_i : unescaped
17
18
  end
@@ -49,6 +50,10 @@ module Openapi3Parser
49
50
  %{#{self.class.name}(segments: #{segments}, fragment: "#{fragment}")}
50
51
  end
51
52
 
53
+ def root?
54
+ segments.empty?
55
+ end
56
+
52
57
  class MergePointers
53
58
  private_class_method :new
54
59
 
@@ -45,6 +45,7 @@ module Openapi3Parser
45
45
  def contents
46
46
  raise access_error if access_error
47
47
  raise parse_error if parse_error
48
+
48
49
  @contents
49
50
  end
50
51
 
@@ -61,6 +62,7 @@ module Openapi3Parser
61
62
 
62
63
  def initialize_contents
63
64
  return if access_error
65
+
64
66
  @contents = parse_contents
65
67
  rescue ::StandardError => e
66
68
  @parse_error = Error::UnparsableInput.new(e.message)