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.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +23 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +18 -3
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +41 -0
  7. data/README.md +7 -4
  8. data/TODO.md +1 -1
  9. data/lib/openapi3_parser.rb +2 -3
  10. data/lib/openapi3_parser/array_sentence.rb +2 -1
  11. data/lib/openapi3_parser/document.rb +3 -2
  12. data/lib/openapi3_parser/error.rb +11 -1
  13. data/lib/openapi3_parser/node/array.rb +9 -1
  14. data/lib/openapi3_parser/node/context.rb +34 -4
  15. data/lib/openapi3_parser/node/map.rb +17 -1
  16. data/lib/openapi3_parser/node/object.rb +17 -0
  17. data/lib/openapi3_parser/node/operation.rb +8 -0
  18. data/lib/openapi3_parser/node/path_item.rb +8 -0
  19. data/lib/openapi3_parser/node/placeholder.rb +16 -12
  20. data/lib/openapi3_parser/node/schema.rb +50 -3
  21. data/lib/openapi3_parser/node_factory.rb +1 -1
  22. data/lib/openapi3_parser/node_factory/array.rb +34 -26
  23. data/lib/openapi3_parser/node_factory/context.rb +9 -2
  24. data/lib/openapi3_parser/node_factory/discriminator.rb +2 -0
  25. data/lib/openapi3_parser/node_factory/field.rb +2 -0
  26. data/lib/openapi3_parser/node_factory/fields/reference.rb +1 -0
  27. data/lib/openapi3_parser/node_factory/map.rb +12 -10
  28. data/lib/openapi3_parser/node_factory/media_type.rb +1 -0
  29. data/lib/openapi3_parser/node_factory/object.rb +4 -1
  30. data/lib/openapi3_parser/node_factory/object_factory/dsl.rb +7 -5
  31. data/lib/openapi3_parser/node_factory/object_factory/field_config.rb +9 -7
  32. data/lib/openapi3_parser/node_factory/object_factory/node_builder.rb +12 -13
  33. data/lib/openapi3_parser/node_factory/object_factory/validator.rb +25 -21
  34. data/lib/openapi3_parser/node_factory/openapi.rb +2 -0
  35. data/lib/openapi3_parser/node_factory/operation.rb +9 -0
  36. data/lib/openapi3_parser/node_factory/parameter.rb +2 -0
  37. data/lib/openapi3_parser/node_factory/parameter_like.rb +2 -1
  38. data/lib/openapi3_parser/node_factory/path_item.rb +27 -5
  39. data/lib/openapi3_parser/node_factory/paths.rb +1 -1
  40. data/lib/openapi3_parser/node_factory/reference.rb +9 -1
  41. data/lib/openapi3_parser/node_factory/request_body.rb +2 -4
  42. data/lib/openapi3_parser/node_factory/response.rb +1 -0
  43. data/lib/openapi3_parser/node_factory/responses.rb +1 -1
  44. data/lib/openapi3_parser/node_factory/schema.rb +5 -2
  45. data/lib/openapi3_parser/node_factory/server_variable.rb +1 -0
  46. data/lib/openapi3_parser/node_factory/type_checker.rb +6 -0
  47. data/lib/openapi3_parser/source.rb +2 -0
  48. data/lib/openapi3_parser/source/location.rb +6 -0
  49. data/lib/openapi3_parser/source/pointer.rb +9 -4
  50. data/lib/openapi3_parser/source_input.rb +9 -9
  51. data/lib/openapi3_parser/source_input/file.rb +3 -1
  52. data/lib/openapi3_parser/source_input/raw.rb +4 -1
  53. data/lib/openapi3_parser/source_input/resolve_next.rb +1 -0
  54. data/lib/openapi3_parser/source_input/string_parser.rb +3 -2
  55. data/lib/openapi3_parser/source_input/url.rb +3 -1
  56. data/lib/openapi3_parser/validation/error.rb +2 -0
  57. data/lib/openapi3_parser/validation/error_collection.rb +1 -1
  58. data/lib/openapi3_parser/validation/input_validator.rb +1 -1
  59. data/lib/openapi3_parser/validators/component_keys.rb +1 -1
  60. data/lib/openapi3_parser/validators/duplicate_parameters.rb +1 -0
  61. data/lib/openapi3_parser/validators/email.rb +3 -3
  62. data/lib/openapi3_parser/validators/media_type.rb +1 -1
  63. data/lib/openapi3_parser/validators/mutually_exclusive_fields.rb +8 -8
  64. data/lib/openapi3_parser/validators/reference.rb +2 -0
  65. data/lib/openapi3_parser/validators/required_fields.rb +2 -2
  66. data/lib/openapi3_parser/validators/unexpected_fields.rb +3 -2
  67. data/lib/openapi3_parser/validators/url.rb +2 -7
  68. data/lib/openapi3_parser/version.rb +1 -1
  69. data/openapi3_parser.gemspec +12 -8
  70. metadata +81 -25
  71. data/.travis.yml +0 -11
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ostruct"
4
3
  require "openapi3_parser/node_factory/object_factory/field_config"
5
4
 
6
5
  module Openapi3Parser
7
6
  module NodeFactory
8
7
  module ObjectFactory
9
8
  module Dsl
9
+ MutuallyExclusiveField = Struct.new(:fields, :required, keyword_init: true)
10
+
10
11
  def field(name, **options)
11
12
  @field_configs ||= {}
12
- @field_configs[name] = FieldConfig.new(options)
13
+ @field_configs[name] = FieldConfig.new(**options)
13
14
  end
14
15
 
15
16
  def field_configs
@@ -30,8 +31,9 @@ module Openapi3Parser
30
31
 
31
32
  def mutually_exclusive(*fields, required: false)
32
33
  @mutually_exclusive_fields ||= []
33
- @mutually_exclusive_fields << OpenStruct.new(
34
- fields: fields, required: required
34
+ @mutually_exclusive_fields << MutuallyExclusiveField.new(
35
+ fields: fields,
36
+ required: required
35
37
  )
36
38
  end
37
39
 
@@ -41,7 +43,7 @@ module Openapi3Parser
41
43
 
42
44
  def validate(*items, &block)
43
45
  @validations ||= []
44
- @validations = @validations.concat(items)
46
+ @validations.concat(items)
45
47
  @validations << block if block
46
48
  end
47
49
 
@@ -22,10 +22,11 @@ module Openapi3Parser
22
22
  !given_factory.nil?
23
23
  end
24
24
 
25
- def initialize_factory(context, parent_factory)
26
- if given_factory.is_a?(Class)
25
+ def initialize_factory(context, parent_factory = nil)
26
+ case given_factory
27
+ when Class
27
28
  given_factory.new(context)
28
- elsif given_factory.is_a?(Symbol)
29
+ when Symbol
29
30
  parent_factory.send(given_factory, context)
30
31
  else
31
32
  given_factory.call(context)
@@ -36,7 +37,7 @@ module Openapi3Parser
36
37
  given_required
37
38
  end
38
39
 
39
- def check_input_type(validatable, building_node)
40
+ def check_input_type(validatable, building_node: false)
40
41
  return true if !given_input_type || validatable.input.nil?
41
42
 
42
43
  if building_node
@@ -47,7 +48,7 @@ module Openapi3Parser
47
48
  end
48
49
  end
49
50
 
50
- def validate_field(validatable, building_node)
51
+ def validate_field(validatable, building_node: false)
51
52
  return true if !given_validate || validatable.input.nil?
52
53
 
53
54
  run_validation(validatable)
@@ -61,9 +62,10 @@ module Openapi3Parser
61
62
  "Invalid data for #{location_summary}: #{error.message}"
62
63
  end
63
64
 
64
- def default(factory)
65
+ def default(factory = nil)
65
66
  return given_default.call if given_default.is_a?(Proc)
66
- return factory.send(given_default) if given_default.is_a?(Symbol)
67
+ return factory&.send(given_default) if given_default.is_a?(Symbol)
68
+
67
69
  given_default
68
70
  end
69
71
 
@@ -8,8 +8,8 @@ module Openapi3Parser
8
8
  new(factory).errors
9
9
  end
10
10
 
11
- def self.node_data(factory, parent_context)
12
- new(factory).node_data(parent_context)
11
+ def self.node_data(factory, node_context)
12
+ new(factory).node_data(node_context)
13
13
  end
14
14
 
15
15
  def initialize(factory)
@@ -22,18 +22,17 @@ module Openapi3Parser
22
22
 
23
23
  TypeChecker.validate_type(validatable, type: ::Hash)
24
24
 
25
- if validatable.errors.empty?
26
- validatable.add_errors(validate(raise_on_invalid: false))
27
- end
25
+ validatable.add_errors(validate(raise_on_invalid: false)) if validatable.errors.empty?
28
26
 
29
27
  validatable.collection
30
28
  end
31
29
 
32
- def node_data(parent_context)
33
- return build_node_data(parent_context) if empty_and_allowed_to_be?
30
+ def node_data(node_context)
31
+ return build_node_data(node_context) if empty_and_allowed_to_be?
32
+
34
33
  TypeChecker.raise_on_invalid_type(factory.context, type: ::Hash)
35
34
  validate(raise_on_invalid: true)
36
- build_node_data(parent_context)
35
+ build_node_data(node_context)
37
36
  end
38
37
 
39
38
  private_class_method :new
@@ -47,22 +46,22 @@ module Openapi3Parser
47
46
  end
48
47
 
49
48
  def validate(raise_on_invalid:)
50
- Validator.call(factory, raise_on_invalid)
49
+ Validator.call(factory, raise_on_invalid: raise_on_invalid)
51
50
  end
52
51
 
53
- def build_node_data(parent_context)
52
+ def build_node_data(node_context)
54
53
  return if factory.nil_input? && factory.data.nil?
55
54
 
56
55
  factory.data.each_with_object({}) do |(key, value), memo|
57
- memo[key] = resolve_value(key, value, parent_context)
56
+ memo[key] = resolve_value(key, value, node_context)
58
57
  end
59
58
  end
60
59
 
61
- def resolve_value(key, value, parent_context)
60
+ def resolve_value(key, value, node_context)
62
61
  resolved = determine_value_or_default(key, value)
63
62
 
64
63
  if resolved.respond_to?(:node)
65
- Node::Placeholder.new(value, key, parent_context)
64
+ Node::Placeholder.new(value, key, node_context)
66
65
  else
67
66
  resolved
68
67
  end
@@ -7,15 +7,15 @@ module Openapi3Parser
7
7
  module ObjectFactory
8
8
  class Validator
9
9
  private_class_method :new
10
- attr_reader :factory, :validatable, :building_node
10
+ attr_reader :factory, :validatable, :raise_on_invalid
11
11
 
12
- def self.call(*args)
13
- new(*args).call
12
+ def self.call(*args, **kwargs)
13
+ new(*args, **kwargs).call
14
14
  end
15
15
 
16
- def initialize(factory, building_node)
16
+ def initialize(factory, raise_on_invalid: false)
17
17
  @factory = factory
18
- @building_node = building_node
18
+ @raise_on_invalid = raise_on_invalid
19
19
  @validatable = Validation::Validatable.new(factory)
20
20
  end
21
21
 
@@ -34,7 +34,7 @@ module Openapi3Parser
34
34
  Validators::RequiredFields.call(
35
35
  validatable,
36
36
  required_fields: factory.required_fields,
37
- raise_on_invalid: building_node
37
+ raise_on_invalid: raise_on_invalid
38
38
  )
39
39
  end
40
40
 
@@ -43,7 +43,7 @@ module Openapi3Parser
43
43
  validatable,
44
44
  allow_extensions: factory.allowed_extensions?,
45
45
  allowed_fields: factory.allowed_fields,
46
- raise_on_invalid: building_node
46
+ raise_on_invalid: raise_on_invalid
47
47
  )
48
48
  end
49
49
 
@@ -51,7 +51,7 @@ module Openapi3Parser
51
51
  Validators::MutuallyExclusiveFields.call(
52
52
  validatable,
53
53
  mutually_exclusive_fields: factory.mutually_exclusive_fields,
54
- raise_on_invalid: building_node
54
+ raise_on_invalid: raise_on_invalid
55
55
  )
56
56
  end
57
57
 
@@ -66,7 +66,8 @@ module Openapi3Parser
66
66
  class CheckInvalidFields
67
67
  extend Forwardable
68
68
  attr_reader :validator
69
- def_delegators :validator, :factory, :building_node, :validatable
69
+
70
+ def_delegators :validator, :factory, :raise_on_invalid, :validatable
70
71
  private_class_method :new
71
72
 
72
73
  def self.call(validator)
@@ -81,22 +82,21 @@ module Openapi3Parser
81
82
  factory.data.each do |name, field|
82
83
  # references can reference themselves and become in a loop
83
84
  next if in_recursive_loop?(field)
85
+
84
86
  has_factory_errors = handle_factory_checks(name)
85
87
 
86
88
  next if has_factory_errors || !field.respond_to?(:errors)
87
89
 
88
90
  # We don't add errors when we're building a node as they will
89
91
  # be raised when that child node is built
90
- validatable.add_errors(field.errors) unless building_node
92
+ validatable.add_errors(field.errors) unless raise_on_invalid
91
93
  end
92
94
  end
93
95
 
94
96
  private
95
97
 
96
98
  def handle_factory_checks(name)
97
- field_errors = if factory.field_configs[name]
98
- check_field(name, factory.field_configs[name])
99
- end
99
+ field_errors = (check_field(name, factory.field_configs[name]) if factory.field_configs[name])
100
100
 
101
101
  (field_errors || []).any?
102
102
  end
@@ -108,16 +108,20 @@ module Openapi3Parser
108
108
  def check_field(name, field_config)
109
109
  return if factory.raw_input[name].nil?
110
110
 
111
- field_validatable = Validation::Validatable.new(
112
- factory,
113
- context: Context.next_field(factory.context, name)
114
- )
111
+ context = Context.next_field(factory.context,
112
+ name,
113
+ factory.raw_input[name])
114
+ field_validatable = Validation::Validatable.new(factory,
115
+ context: context)
115
116
 
116
- valid_input_type = field_config.check_input_type(field_validatable,
117
- building_node)
117
+ valid_input_type = field_config.check_input_type(
118
+ field_validatable,
119
+ building_node: raise_on_invalid
120
+ )
118
121
 
119
122
  if valid_input_type
120
- field_config.validate_field(field_validatable, building_node)
123
+ field_config.validate_field(field_validatable,
124
+ building_node: raise_on_invalid)
121
125
  end
122
126
 
123
127
  validatable.add_errors(field_validatable.errors)
@@ -137,7 +141,7 @@ module Openapi3Parser
137
141
 
138
142
  errors = validator.validatable.errors
139
143
 
140
- return if errors.empty? || !validator.building_node
144
+ return if errors.empty? || !validator.raise_on_invalid
141
145
 
142
146
  location_summary = errors.first.context.location_summary
143
147
  raise Error::InvalidData,
@@ -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,8 @@ module Openapi3Parser
25
25
  private
26
26
 
27
27
  def build_object(data, context)
28
+ data["servers"] = path_item_server_data(context) if data["servers"].node.empty?
29
+
28
30
  Node::Operation.new(data, context)
29
31
  end
30
32
 
@@ -72,6 +74,13 @@ module Openapi3Parser
72
74
  NodeFactory::Array.new(context,
73
75
  value_factory: NodeFactory::Server)
74
76
  end
77
+
78
+ def path_item_server_data(node_context)
79
+ path_item_servers = node_context.parent_node.node_data["servers"]
80
+ Node::Placeholder.new(path_item_servers.node_factory,
81
+ "servers",
82
+ node_context)
83
+ end
75
84
  end
76
85
  end
77
86
  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
@@ -13,7 +13,7 @@ module Openapi3Parser
13
13
  end
14
14
 
15
15
  def examples_factory(context)
16
- factory = NodeFactory::OptionalReference.new(NodeFactory::Schema)
16
+ factory = NodeFactory::OptionalReference.new(NodeFactory::Example)
17
17
  NodeFactory::Map.new(context,
18
18
  default: nil,
19
19
  value_factory: factory)
@@ -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)
@@ -32,11 +32,19 @@ module Openapi3Parser
32
32
  referenced_factory.resolves?(control_factory)
33
33
  end
34
34
 
35
+ def errors
36
+ if in_recursive_loop?
37
+ @errors ||= Validation::ErrorCollection.new
38
+ else
39
+ super
40
+ end
41
+ end
42
+
35
43
  private
36
44
 
37
45
  def build_node(node_context)
38
46
  TypeChecker.raise_on_invalid_type(context, type: ::Hash)
39
- ObjectFactory::Validator.call(self, true)
47
+ ObjectFactory::Validator.call(self, raise_on_invalid: true)
40
48
  data["$ref"].node(node_context)
41
49
  end
42
50
 
@@ -32,11 +32,9 @@ module Openapi3Parser
32
32
  def call(validatable)
33
33
  # This validation isn't actually mentioned in the spec, but it
34
34
  # doesn't seem to make sense if this is an empty hash.
35
- if validatable.input.size.zero?
36
- return validatable.add_error("Expected to have at least 1 item")
37
- end
35
+ return validatable.add_error("Expected to have at least 1 item") if validatable.input.size.zero?
38
36
 
39
- validatable.input.keys.each do |key|
37
+ validatable.input.each_key do |key|
40
38
  message = Validators::MediaType.call(key)
41
39
  next unless message
42
40
 
@@ -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)