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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e4d2e3dac62b0199c892ea6d0e9ed34dcea67c9d
4
- data.tar.gz: 93839047c652d0342d0a6a6700863c4a143e3c21
3
+ metadata.gz: 305a84267a984ae06f488b48ed4d9f385fddfccb
4
+ data.tar.gz: 51e8ed34fcc0b665658a4ff08d3ac51c2755f9bd
5
5
  SHA512:
6
- metadata.gz: 06c8d624f8a4a3e95f8838f60682a7a2861e333cfcb4c43a5f9fc4961406c1aeb5ad91da57e1664fa9a9d62eb7deaade81314ec924dec8e09b6eda1b00fb0d2b
7
- data.tar.gz: 1304885f64188113d219df891a3584259cff0a628c696ad74902fd5667da994e9f2116ab880436f4e3c5a31a21605bb3f0c21a1202499780b6a861bb482713ad
6
+ metadata.gz: c77b4149bba7eac8bb043b3c276325a4dc7de5605481e60a63fba01b0476c9f9fcc799ced7eda3bbbdc28cff955a64b578e48633d7f0ba7b0f3cbb659320ce58
7
+ data.tar.gz: 3dbe3f4d0235e8308cbf28aa4ce79e725bd6071b28f0e087e222d8f6a91cb618e8ea91c46bde703f393baf5c09e5bb3f7ed23741b3ae07275b6aed7f9a60b655
data/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  /pkg
3
3
  /.yardoc
4
4
  /doc
5
+ /coverage
@@ -4,12 +4,10 @@ Metrics/MethodLength:
4
4
  Max: 30
5
5
  Metrics/AbcSize:
6
6
  Max: 30
7
- Documentation:
7
+ Style/Documentation:
8
8
  Enabled: false
9
9
  Metrics/BlockLength:
10
10
  Exclude:
11
11
  - 'spec/**/*.rb'
12
- Naming/UncommunicativeMethodParamName:
13
- Enabled: false
14
- Naming/UncommunicativeBlockParamName:
12
+ Style/BracesAroundHashParameters:
15
13
  Enabled: false
@@ -1,11 +1,22 @@
1
1
  language: ruby
2
2
  sudo: false
3
+ env:
4
+ global:
5
+ CC_TEST_REPORTER_ID=8bc2d8e54331569aeb442094c21cb64a58d6efa0670f65ff00d9ae887f63c0b4
6
+ CC_RUBY_VERSION=ruby-2.4.6
3
7
  rvm:
4
8
  - 2.4.6
5
9
  - 2.5.5
6
10
  - 2.6.4
11
+ - 2.7.0
7
12
  - ruby-head
8
13
  matrix:
9
14
  allow_failures:
10
15
  - rvm: ruby-head
11
16
  fast_finish: true
17
+ before_script:
18
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
19
+ - chmod +x ./cc-test-reporter
20
+ - if [ $(rvm current) = $CC_RUBY_VERSION ]; then ./cc-test-reporter before-build; fi
21
+ after_script:
22
+ - if [ $(rvm current) = $CC_RUBY_VERSION ]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT; fi
@@ -1,3 +1,16 @@
1
+ # 0.8.0
2
+
3
+ - Resolve deprecation warnings for Ruby 2.7
4
+ - Resolve deprecation warnings for Psych
5
+ - Operation and Path Item objects use servers that have cascaded from parent
6
+ objects if they do not have their own servers defined.
7
+ - Add `#relative_node` and `#parent_node` methods to `Node::Context`.
8
+ - Default to a server of "/" when given an empty or null servers input for
9
+ OpenAPI node.
10
+ - Set referenced data as input and source location for a PathItem with only
11
+ a $ref value.
12
+ - Fix data being lost in PathItem reference merges.
13
+
1
14
  # 0.7.0
2
15
 
3
16
  - Add `#values` method to `Node::Object` and `Node#Map` to have a method that
data/README.md CHANGED
@@ -33,10 +33,13 @@ being:
33
33
  - Documentation for the API to navigate the OpenAPI nodes is available on
34
34
  [rubydoc.info][docs].
35
35
 
36
+ I've wrote a blog post reflecting on the decisions involved in building this
37
+ parser in [How to write an OpenAPI 3 parser][blog].
36
38
 
37
39
  [openapi-3]: https://github.com/OAI/OpenAPI-Specification
38
40
  [openapi-3-spec]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification
39
41
  [docs]: http://www.rubydoc.info/github/kevindew/openapi3_parser/Openapi3Parser/Node/Openapi
42
+ [blog]: https://kevindew.me/post/188611423231/how-to-write-an-openapi-3-parser
40
43
 
41
44
  ## Usage
42
45
 
@@ -118,7 +121,7 @@ You can install this gem into your bundler application by adding this line to
118
121
  your Gemfile:
119
122
 
120
123
  ```
121
- gem "openapi3_parser", "~> 0.7.0"
124
+ gem "openapi3_parser", "~> 0.8.0"
122
125
  ```
123
126
 
124
127
  and then running `$ bundle install`
data/TODO.md CHANGED
@@ -32,7 +32,7 @@ These are the steps defined to reach 1.0. Assistance is very welcome.
32
32
  - [x] Ensure Array and Map nodes return empty ones by default rather than nil
33
33
  - [ ] Make JSON pointer public access to be consistent accepting string, array
34
34
  or (potentially) a pointer class
35
- - [ ] Support creating a default Server object on servers property of OpenAPI
35
+ - [x] Support creating a default Server object on servers property of OpenAPI
36
36
  Node
37
37
  - [ ] Support relative URLs being able to be relative the first server object
38
38
  see: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#relative-references-in-urls
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Dir.glob(File.join(__dir__, "openapi3_parser", "**", "*.rb")).each do |file|
4
- require file
5
- end
3
+ files = Dir.glob(File.join(__dir__, "openapi3_parser", "**", "*.rb")).sort
4
+ files.each { |file| require file }
6
5
 
7
6
  module Openapi3Parser
8
7
  # For a variety of inputs this will construct an OpenAPI document. For a
@@ -5,6 +5,7 @@ module Openapi3Parser
5
5
  refine ::Array do
6
6
  def sentence_join
7
7
  return join if count < 2
8
+
8
9
  self[0..-2].join(", ") + " and " + self[-1]
9
10
  end
10
11
  end
@@ -138,7 +138,7 @@ module Openapi3Parser
138
138
  look_up_pointer(pointer, relative_to, factory.resolved_input)
139
139
  end
140
140
 
141
- # Look up a node at a particular location in the OpenAPI docuemnt
141
+ # Look up a node at a particular location in the OpenAPI document
142
142
  #
143
143
  # Examples:
144
144
  #
@@ -174,6 +174,7 @@ module Openapi3Parser
174
174
 
175
175
  def build
176
176
  return if build_in_progress || built
177
+
177
178
  @build_in_progress = true
178
179
  context = NodeFactory::Context.root(root_source.data, root_source)
179
180
  @factory = NodeFactory::Openapi.new(context)
@@ -14,7 +14,7 @@ module Openapi3Parser
14
14
  extend Forwardable
15
15
  include Enumerable
16
16
 
17
- def_delegators :node_data, :empty?
17
+ def_delegators :node_data, :empty?, :length, :size
18
18
  attr_reader :node_data, :node_context
19
19
 
20
20
  # @param [::Array] data data used to populate this node
@@ -133,6 +133,29 @@ module Openapi3Parser
133
133
  def node
134
134
  document.node_at(document_location.pointer)
135
135
  end
136
+
137
+ # Look up a node at a particular location in the OpenAPI docuemnt based
138
+ # on the relative position in the document of this context
139
+ #
140
+ # Examples:
141
+ #
142
+ # context.relative_node("#schemas")
143
+ # context.relative_node(%w[..])
144
+ #
145
+ # @param [Source::Pointer, String, Array] pointer
146
+ # @return anything
147
+ def relative_node(pointer)
148
+ document.node_at(pointer, document_location.pointer)
149
+ end
150
+
151
+ # Return the node that is the parent node for the node at this context
152
+ #
153
+ # @return [Node::Object, Node::Map, Node::Array, nil]
154
+ def parent_node
155
+ return if document_location.root?
156
+
157
+ relative_node("#..")
158
+ end
136
159
  end
137
160
  end
138
161
  end
@@ -8,7 +8,7 @@ module Openapi3Parser
8
8
  extend Forwardable
9
9
  include Enumerable
10
10
 
11
- def_delegators :node_data, :keys, :empty?
11
+ def_delegators :node_data, :keys, :empty?, :length, :size
12
12
  attr_reader :node_data, :node_context
13
13
 
14
14
  def initialize(data, context)
@@ -76,6 +76,7 @@ module Openapi3Parser
76
76
  # @return [String, nil]
77
77
  def render_markdown(value)
78
78
  return if value.nil?
79
+
79
80
  Markdown.to_html(value)
80
81
  end
81
82
 
@@ -70,6 +70,14 @@ module Openapi3Parser
70
70
  def servers
71
71
  self["servers"]
72
72
  end
73
+
74
+ # Whether this object uses it's own defined servers instead of falling
75
+ # back to the path items' ones.
76
+ #
77
+ # @return [Boolean]
78
+ def alternative_servers?
79
+ servers != node_context.parent_node.servers
80
+ end
73
81
  end
74
82
  end
75
83
  end
@@ -66,6 +66,14 @@ module Openapi3Parser
66
66
  self["servers"]
67
67
  end
68
68
 
69
+ # Whether this object uses it's own defined servers instead of falling
70
+ # back to the root ones.
71
+ #
72
+ # @return [Boolean]
73
+ def alternative_servers?
74
+ servers != node_context.document.root.servers
75
+ end
76
+
69
77
  # @return [Node::Array<Parameter>]
70
78
  def parameters
71
79
  self["parameters"]
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+
3
5
  module Openapi3Parser
4
6
  module Node
5
7
  class Placeholder
8
+ extend Forwardable
9
+
6
10
  def self.resolve(potential_placeholder)
7
11
  if potential_placeholder.is_a?(Placeholder)
8
12
  potential_placeholder.node
@@ -13,7 +17,7 @@ module Openapi3Parser
13
17
 
14
18
  # Used to iterate through hashes or arrays that may contain
15
19
  # Placeholder objects where these are resolved to being nodes
16
- # befor iteration
20
+ # before iteration
17
21
  def self.each(node_data, &block)
18
22
  resolved =
19
23
  if node_data.respond_to?(:keys)
@@ -27,6 +31,9 @@ module Openapi3Parser
27
31
  resolved.each(&block)
28
32
  end
29
33
 
34
+ attr_reader :node_factory, :field, :parent_context
35
+ def_delegators :node_factory, :nil_input?
36
+
30
37
  def initialize(node_factory, field, parent_context)
31
38
  @node_factory = node_factory
32
39
  @field = field
@@ -41,10 +48,6 @@ module Openapi3Parser
41
48
  node_factory.node(node_context)
42
49
  end
43
50
  end
44
-
45
- private
46
-
47
- attr_reader :node_factory, :field, :parent_context
48
51
  end
49
52
  end
50
53
  end
@@ -5,7 +5,7 @@ require "openapi3_parser/node/object"
5
5
  module Openapi3Parser
6
6
  module Node
7
7
  # @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject
8
- # rubocop:disable ClassLength
8
+ # rubocop:disable Metrics/ClassLength
9
9
  class Schema < Node::Object
10
10
  # This is used to provide a name for the schema based on it's position in
11
11
  # an OpenAPI document.
@@ -177,6 +177,7 @@ module Openapi3Parser
177
177
  def additional_properties_schema
178
178
  properties = self["additionalProperties"]
179
179
  return if [true, false].include?(properties)
180
+
180
181
  properties
181
182
  end
182
183
 
@@ -240,6 +241,6 @@ module Openapi3Parser
240
241
  self["deprecated"]
241
242
  end
242
243
  end
243
- # rubocop:enable ClassLength
244
+ # rubocop:enable Metrics/ClassLength
244
245
  end
245
246
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Openapi3Parser
4
4
  module NodeFactory
5
- EXTENSION_REGEX = /^x-(.*)/
5
+ EXTENSION_REGEX = /^x-(.*)/.freeze
6
6
  end
7
7
  end
@@ -3,23 +3,27 @@
3
3
  module Openapi3Parser
4
4
  module NodeFactory
5
5
  class Array
6
- attr_reader :context, :data, :default, :value_input_type,
7
- :value_factory, :validation
6
+ attr_reader :context, :data, :default, :use_default_on_empty,
7
+ :value_input_type, :value_factory, :validation
8
8
 
9
+ # rubocop:disable Metrics/ParameterLists
9
10
  def initialize(
10
11
  context,
11
12
  default: [],
13
+ use_default_on_empty: false,
12
14
  value_input_type: nil,
13
15
  value_factory: nil,
14
16
  validate: nil
15
17
  )
16
18
  @context = context
17
19
  @default = default
20
+ @use_default_on_empty = use_default_on_empty
18
21
  @value_input_type = value_input_type
19
22
  @value_factory = value_factory
20
23
  @validation = validate
21
24
  @data = build_data(context.input)
22
25
  end
26
+ # rubocop:enable Metrics/ParameterLists
23
27
 
24
28
  def raw_input
25
29
  context.input
@@ -50,18 +54,25 @@ module Openapi3Parser
50
54
  %{#{self.class.name}(#{context.source_location.inspect})}
51
55
  end
52
56
 
57
+ def use_default?
58
+ return true if nil_input? || !raw_input.is_a?(::Array)
59
+ return false unless use_default_on_empty
60
+
61
+ raw_input.empty?
62
+ end
63
+
53
64
  private
54
65
 
55
66
  def build_data(raw_input)
56
- use_default = nil_input? || !raw_input.is_a?(::Array)
57
- return if use_default && default.nil?
58
- process_data(use_default ? default : raw_input)
67
+ return if use_default? && default.nil?
68
+
69
+ process_data(use_default? ? default : raw_input)
59
70
  end
60
71
 
61
72
  def process_data(data)
62
73
  data.each_with_index.map do |value, i|
63
74
  if value_factory
64
- initialize_value_factory(Context.next_field(context, i))
75
+ initialize_value_factory(Context.next_field(context, i, value))
65
76
  else
66
77
  value
67
78
  end
@@ -104,26 +115,24 @@ module Openapi3Parser
104
115
 
105
116
  def errors
106
117
  return validatable.collection if factory.nil_input?
118
+
107
119
  TypeChecker.validate_type(validatable, type: ::Array)
108
120
  return validatable.collection if validatable.errors.any?
121
+
109
122
  collate_errors
110
123
  validatable.collection
111
124
  end
112
125
 
113
126
  def data(parent_context)
114
- return default_value if factory.nil_input?
127
+ if factory.use_default?
128
+ return factory.default.nil? ? nil : build_node_data(parent_context)
129
+ end
115
130
 
116
131
  TypeChecker.raise_on_invalid_type(factory.context, type: ::Array)
117
132
  check_values(raise_on_invalid: true)
118
133
  validate(raise_on_invalid: true)
119
134
 
120
- factory.data.each_with_index.map do |value, i|
121
- if value.respond_to?(:node)
122
- Node::Placeholder.new(value, i, parent_context)
123
- else
124
- value
125
- end
126
- end
135
+ build_node_data(parent_context)
127
136
  end
128
137
 
129
138
  private_class_method :new
@@ -132,6 +141,14 @@ module Openapi3Parser
132
141
 
133
142
  attr_reader :factory, :validatable
134
143
 
144
+ def build_node_data(parent_context)
145
+ factory.data.each_with_index.map do |value, i|
146
+ next value unless value.respond_to?(:node)
147
+
148
+ Node::Placeholder.new(value, i, parent_context)
149
+ end
150
+ end
151
+
135
152
  def collate_errors
136
153
  check_values(raise_on_invalid: false)
137
154
  validate(raise_on_invalid: false)
@@ -141,21 +158,12 @@ module Openapi3Parser
141
158
  end
142
159
  end
143
160
 
144
- def default_value
145
- if factory.nil_input? && factory.default.nil?
146
- nil
147
- else
148
- factory.data
149
- end
150
- end
151
-
152
161
  def check_values(raise_on_invalid: false)
153
162
  return unless factory.value_input_type
154
163
 
155
- factory.context.input.each_index do |index|
156
- check_field_type(
157
- Context.next_field(factory.context, index), raise_on_invalid
158
- )
164
+ factory.context.input.each_with_index do |value, index|
165
+ check_field_type(Context.next_field(factory.context, index, value),
166
+ raise_on_invalid)
159
167
  end
160
168
  end
161
169
 
@@ -12,6 +12,8 @@ module Openapi3Parser
12
12
  # @attr_reader [Array<Source::Location>] refernce_locations
13
13
  #
14
14
  class Context
15
+ UNDEFINED = Class.new
16
+
15
17
  # Create a context for the root of a document
16
18
  #
17
19
  # @param [Any] input
@@ -29,10 +31,15 @@ module Openapi3Parser
29
31
  #
30
32
  # @param [Context] parent_context
31
33
  # @param [String] field
34
+ # @param [Any] input
32
35
  # @return [Context]
33
- def self.next_field(parent_context, field)
36
+ def self.next_field(parent_context, field, given_input = UNDEFINED)
34
37
  pc = parent_context
35
- input = pc.input.respond_to?(:[]) ? pc.input[field] : nil
38
+ input = if given_input == UNDEFINED
39
+ pc.input.respond_to?(:[]) ? pc.input[field] : nil
40
+ else
41
+ given_input
42
+ end
36
43
  source_location = Source::Location.next_field(pc.source_location, field)
37
44
  new(input,
38
45
  source_location: source_location,