openapi3_parser 0.6.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ca568bd4f56414a9a7467ba929750fe26ebe2ff6
4
- data.tar.gz: 3e0a2532c02e114140e3ea53458e637308b1ab53
2
+ SHA256:
3
+ metadata.gz: 81001258de6af8c21c8a7db87520d9433e3acb878483a56a616fdcd804e92848
4
+ data.tar.gz: 496f3a02ebcb386e949eab7ec6607f360a0b2e755be6bb4f1df3cd6b260a6e0d
5
5
  SHA512:
6
- metadata.gz: 916678f780b8d71fd83c09a64fc6725e678dd8356a137d4b7295c7c4ca185d824c4803bae74c46de755faa5c52736ddcbada93fc0c88ef12d8b2e0e583edc56a
7
- data.tar.gz: effde9d9458daf6d68bc5c9d79d137c32d583246f620f039b4606f1cf678d6f10bea2553a23b0930b2b73ecb0a665a66c9f5ac227e15956629095746363d634e
6
+ metadata.gz: d789d4ffe423f8854f961e902ffa6ef56b86d7dbcf506292106afa8548b72f2fbe968d98358ea7c80771a3c2e974c302096d43968c0663bdad40dc5f60a676e2
7
+ data.tar.gz: aea92fa77fbb4808872093b9149f8eb933f70a38336a0e415fa1fc9d4c15e691b8307abb0d70c0e51eee8eca89887cbc1a16b21e70f2d1812ae0fd77b75fe2a3
@@ -0,0 +1,23 @@
1
+ on: [push, pull_request]
2
+ jobs:
3
+ test:
4
+ strategy:
5
+ matrix:
6
+ ruby: [2.5, 2.6, 2.7, 3.0]
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - uses: ruby/setup-ruby@v1
11
+ with:
12
+ bundler-cache: true
13
+ ruby-version: ${{ matrix.ruby }}
14
+ - name: Test Ruby ${{ matrix.ruby }}
15
+ run: bundle exec rake
16
+ if: matrix.ruby != '2.5'
17
+ - name: Test and publish coverage for Ruby ${{ matrix.ruby }}
18
+ uses: paambaati/codeclimate-action@v2.7.5
19
+ env:
20
+ CC_TEST_REPORTER_ID: 8bc2d8e54331569aeb442094c21cb64a58d6efa0670f65ff00d9ae887f63c0b4
21
+ with:
22
+ coverageCommand: bundle exec rake
23
+ if: matrix.ruby == '2.5'
data/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  /pkg
3
3
  /.yardoc
4
4
  /doc
5
+ /coverage
data/.rubocop.yml CHANGED
@@ -1,15 +1,30 @@
1
+ require:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+ AllCops:
5
+ NewCops: enable
1
6
  Style/StringLiterals:
2
7
  EnforcedStyle: double_quotes
3
8
  Metrics/MethodLength:
4
9
  Max: 30
5
10
  Metrics/AbcSize:
6
11
  Max: 30
7
- Documentation:
12
+ RSpec/ExampleLength:
13
+ Max: 30
14
+ Style/Documentation:
8
15
  Enabled: false
9
16
  Metrics/BlockLength:
10
17
  Exclude:
11
18
  - 'spec/**/*.rb'
12
- Naming/UncommunicativeMethodParamName:
19
+ - '*.gemspec'
20
+ RSpec/DescribeClass:
21
+ Exclude:
22
+ - 'spec/integration/**/*.rb'
23
+ # I'd rather have multiple expectations than lots of duplicate tests
24
+ RSpec/MultipleExpectations:
25
+ Enabled: false
26
+ # The default arbitrary number (5) is a little painful
27
+ RSpec/MultipleMemoizedHelpers:
13
28
  Enabled: false
14
- Naming/UncommunicativeBlockParamName:
29
+ RSpec/MessageSpies:
15
30
  Enabled: false
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.4.6
1
+ 2.5.8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,44 @@
1
+ # 0.9.0
2
+
3
+ - Fix unexpected fields error when using Example nodes inside a Parameter -
4
+ https://github.com/kevindew/openapi3_parser/pull/19
5
+ - Drop support for Ruby 2.4
6
+
7
+ # 0.8.2
8
+
9
+ - Fix bug falling into recursive loop on Array/Map nodes -
10
+ https://github.com/kevindew/openapi3_parser/issues/13
11
+
12
+ # 0.8.1
13
+
14
+ - Fix incorrectly spelt method name on `Schema` s/disciminator/discriminator/g
15
+
16
+ # 0.8.0
17
+
18
+ - Resolve deprecation warnings for Ruby 2.7
19
+ - Resolve deprecation warnings for Psych
20
+ - Operation and Path Item objects use servers that have cascaded from parent
21
+ objects if they do not have their own servers defined.
22
+ - Add `#relative_node` and `#parent_node` methods to `Node::Context`.
23
+ - Default to a server of "/" when given an empty or null servers input for
24
+ OpenAPI node.
25
+ - Set referenced data as input and source location for a PathItem with only
26
+ a $ref value.
27
+ - Fix data being lost in PathItem reference merges.
28
+
29
+ # 0.7.0
30
+
31
+ - Add `#values` method to `Node::Object` and `Node#Map` to have a method that
32
+ pairs with `#keys`
33
+ - Add `Node::Schema#requires?` method to simplify checking whether a property
34
+ is required by a particular schema.
35
+ - Add `#==` methods to Node objects. This allows checking whether two nodes
36
+ are from the same source location even if they're referenced in different
37
+ places.
38
+ - Add `Node::Schema#name` method that looks up the name of a Schema based
39
+ on it's contextual position in a document. Allows accessing the `Pet` value
40
+ from `#/components/schemas/Pet`.
41
+
1
42
  # 0.6.1
2
43
 
3
44
  - Fix bug where Node::Object and Node::Map iterated arrays rather than hashes
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # OpenAPI 3 Parser
2
2
 
3
- [![Build Status](https://travis-ci.org/kevindew/openapi3_parser.svg?branch=master)](https://travis-ci.org/kevindew/openapi3_parser)
3
+ [![Build Status](https://travis-ci.org/kevindew/openapi3_parser.svg?branch=main)](https://travis-ci.org/kevindew/openapi3_parser)
4
4
 
5
5
  This a Ruby based parser/validator for [OpenAPI 3][openapi-3]. It is used to
6
6
  convert an OpenAPI file (can be a local file, a URL, a string or even a Ruby
@@ -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
 
@@ -44,7 +47,7 @@ being:
44
47
 
45
48
  ```ruby
46
49
  # by URL
47
- Openapi3Parser.load_url("https://raw.githubusercontent.com/kevindew/openapi3_parser/master/spec/support/examples/petstore-expanded.yaml")
50
+ Openapi3Parser.load_url("https://raw.githubusercontent.com/kevindew/openapi3_parser/main/spec/support/examples/petstore-expanded.yaml")
48
51
 
49
52
  # by path to file
50
53
  Openapi3Parser.load_file("spec/support/examples/uber.yaml")
@@ -73,7 +76,7 @@ document.errors
73
76
  ### Traversing
74
77
 
75
78
  ```ruby
76
- document = Openapi3Parser.load_url("https://raw.githubusercontent.com/kevindew/openapi3_parser/master/spec/support/examples/petstore-expanded.yaml")
79
+ document = Openapi3Parser.load_url("https://raw.githubusercontent.com/kevindew/openapi3_parser/main/spec/support/examples/petstore-expanded.yaml")
77
80
 
78
81
  # by objects
79
82
 
@@ -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.6.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,7 +5,8 @@ module Openapi3Parser
5
5
  refine ::Array do
6
6
  def sentence_join
7
7
  return join if count < 2
8
- self[0..-2].join(", ") + " and " + self[-1]
8
+
9
+ "#{self[0..-2].join(', ')} and #{self[-1]}"
9
10
  end
10
11
  end
11
12
  end
@@ -60,7 +60,7 @@ module Openapi3Parser
60
60
  # @!method external_docs
61
61
  # The value of the external_docs field on the OpenAPI document
62
62
  # @see Node::Openapi#external_docs
63
- # @return [Node::ExternalDocumentation]
63
+ # @return [Node::ExternalDocumentation, nil]
64
64
  # @!method extension
65
65
  # Look up an extension field provided for the root object of the document
66
66
  # @see Node::Object#extension
@@ -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)
@@ -7,32 +7,42 @@ module Openapi3Parser
7
7
  # at runtime when we have tried to access that resource it is not available
8
8
  # for whatever reason.
9
9
  class InaccessibleInput < Error; end
10
+
10
11
  # Raised in cases where we provided data that we expected to be parsable
11
12
  # (such as a string of JSON data) but when we tried to parse it an error
12
13
  # is raised
13
14
  class UnparsableInput < Error; end
15
+
14
16
  # Raised in cases where an object that is in an immutable state is modified
15
17
  #
16
18
  # Typically this would occur when a component that is frozen is modififed.
17
19
  # Some components are mutable during the construction of a document and
18
20
  # then frozen afterwards.
19
21
  class ImmutableObject < Error; end
20
- # Raised when a type that is not a whitelist of valid types is used
22
+
23
+ # Raised when a node is provided data as a type that is outside the allowed
24
+ # list
21
25
  class InvalidType < Error; end
26
+
22
27
  # Raised when we have to abort creating an object due to invalid data
23
28
  class InvalidData < Error; end
29
+
24
30
  # Used when there are fields that are missing from an object which prevents
25
31
  # us from creating a node
26
32
  class MissingFields < Error; end
33
+
27
34
  # Used when there are extra fields that are not expected in the data for
28
35
  # a node
29
36
  class UnexpectedFields < Error; end
37
+
30
38
  # Used when a method we expect to be able to call (through symbol or proc)
31
39
  # is not callable
32
40
  class NotCallable < Error; end
41
+
33
42
  # Raised when we in a recursive data structure and can't perform an
34
43
  # operation
35
44
  class InRecursiveStructure < Error; end
45
+
36
46
  # Used when we're trying to validate that a type is something that is not
37
47
  # validatable, most likely a sign that we're in a bug
38
48
  class UnvalidatableType < Error; end
@@ -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
@@ -35,6 +35,14 @@ module Openapi3Parser
35
35
  Placeholder.each(node_data, &block)
36
36
  end
37
37
 
38
+ # @param [Any] other
39
+ #
40
+ # @return [Boolean]
41
+ def ==(other)
42
+ other.instance_of?(self.class) &&
43
+ node_context.same_data_and_source?(other.node_context)
44
+ end
45
+
38
46
  # Used to access a node relative to this node
39
47
  # @param [Source::Pointer, ::Array, ::String] pointer_like
40
48
  # @return anything
@@ -65,10 +65,19 @@ module Openapi3Parser
65
65
  @source_location = source_location
66
66
  end
67
67
 
68
+ # @param [Context] other
68
69
  # @return [Boolean]
69
70
  def ==(other)
71
+ document_location == other.document_location &&
72
+ same_data_and_source?(other)
73
+ end
74
+
75
+ # Check that contexts are the same without concern for document location
76
+ #
77
+ # @param [Context] other
78
+ # @return [Boolean]
79
+ def same_data_and_source?(other)
70
80
  input == other.input &&
71
- document_location == other.document_location &&
72
81
  source_location == other.source_location
73
82
  end
74
83
 
@@ -98,9 +107,7 @@ module Openapi3Parser
98
107
  def location_summary
99
108
  summary = document_location.to_s
100
109
 
101
- if document_location != source_location
102
- summary += " (#{source_location})"
103
- end
110
+ summary += " (#{source_location})" if document_location != source_location
104
111
 
105
112
  summary
106
113
  end
@@ -124,6 +131,29 @@ module Openapi3Parser
124
131
  def node
125
132
  document.node_at(document_location.pointer)
126
133
  end
134
+
135
+ # Look up a node at a particular location in the OpenAPI docuemnt based
136
+ # on the relative position in the document of this context
137
+ #
138
+ # Examples:
139
+ #
140
+ # context.relative_node("#schemas")
141
+ # context.relative_node(%w[..])
142
+ #
143
+ # @param [Source::Pointer, String, Array] pointer
144
+ # @return anything
145
+ def relative_node(pointer)
146
+ document.node_at(pointer, document_location.pointer)
147
+ end
148
+
149
+ # Return the node that is the parent node for the node at this context
150
+ #
151
+ # @return [Node::Object, Node::Map, Node::Array, nil]
152
+ def parent_node
153
+ return if document_location.root?
154
+
155
+ relative_node("#..")
156
+ end
127
157
  end
128
158
  end
129
159
  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)
@@ -48,6 +48,14 @@ module Openapi3Parser
48
48
  self["x-#{value}"]
49
49
  end
50
50
 
51
+ # @param [Any] other
52
+ #
53
+ # @return [Boolean]
54
+ def ==(other)
55
+ other.instance_of?(self.class) &&
56
+ node_context.same_data_and_source?(other.node_context)
57
+ end
58
+
51
59
  # Iterates through the data of this node, used by Enumerable
52
60
  #
53
61
  # @return [Object]
@@ -55,6 +63,14 @@ module Openapi3Parser
55
63
  Placeholder.each(node_data, &block)
56
64
  end
57
65
 
66
+ # Provide an array of values for this object, a partner to the #keys
67
+ # method
68
+ #
69
+ # @return [Array]
70
+ def values
71
+ map(&:last)
72
+ end
73
+
58
74
  # Used to access a node relative to this node
59
75
  # @param [Source::Pointer, ::Array, ::String] pointer_like
60
76
  # @return anything
@@ -48,6 +48,14 @@ module Openapi3Parser
48
48
  self["x-#{value}"]
49
49
  end
50
50
 
51
+ # @param [Any] other
52
+ #
53
+ # @return [Boolean]
54
+ def ==(other)
55
+ other.instance_of?(self.class) &&
56
+ node_context.same_data_and_source?(other.node_context)
57
+ end
58
+
51
59
  # Iterates through the data of this node, used by Enumerable
52
60
  #
53
61
  # @return [Object]
@@ -55,11 +63,20 @@ module Openapi3Parser
55
63
  Placeholder.each(node_data, &block)
56
64
  end
57
65
 
66
+ # Provide an array of values for this object, a partner to the #keys
67
+ # method
68
+ #
69
+ # @return [Array]
70
+ def values
71
+ map(&:last)
72
+ end
73
+
58
74
  # Used to render fields that can be in markdown syntax into HTML
59
75
  # @param [String, nil] value
60
76
  # @return [String, nil]
61
77
  def render_markdown(value)
62
78
  return if value.nil?
79
+
63
80
  Markdown.to_html(value)
64
81
  end
65
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