openapi3_parser 0.7.0 → 0.8.0

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