openapi3_parser 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +3 -3
  4. data/CHANGELOG.md +7 -1
  5. data/README.md +102 -15
  6. data/lib/openapi3_parser/document.rb +14 -14
  7. data/lib/openapi3_parser/document/reference_registry.rb +72 -0
  8. data/lib/openapi3_parser/node/array.rb +10 -2
  9. data/lib/openapi3_parser/node/components.rb +9 -9
  10. data/lib/openapi3_parser/node/contact.rb +3 -3
  11. data/lib/openapi3_parser/node/context.rb +129 -0
  12. data/lib/openapi3_parser/node/discriminator.rb +2 -2
  13. data/lib/openapi3_parser/node/encoding.rb +5 -5
  14. data/lib/openapi3_parser/node/example.rb +4 -4
  15. data/lib/openapi3_parser/node/external_documentation.rb +2 -2
  16. data/lib/openapi3_parser/node/info.rb +6 -6
  17. data/lib/openapi3_parser/node/license.rb +2 -2
  18. data/lib/openapi3_parser/node/link.rb +6 -6
  19. data/lib/openapi3_parser/node/map.rb +8 -4
  20. data/lib/openapi3_parser/node/media_type.rb +5 -5
  21. data/lib/openapi3_parser/node/oauth_flow.rb +4 -4
  22. data/lib/openapi3_parser/node/oauth_flows.rb +4 -4
  23. data/lib/openapi3_parser/node/object.rb +8 -4
  24. data/lib/openapi3_parser/node/openapi.rb +8 -8
  25. data/lib/openapi3_parser/node/operation.rb +12 -12
  26. data/lib/openapi3_parser/node/parameter.rb +2 -2
  27. data/lib/openapi3_parser/node/parameter_like.rb +11 -11
  28. data/lib/openapi3_parser/node/path_item.rb +12 -12
  29. data/lib/openapi3_parser/node/placeholder.rb +34 -0
  30. data/lib/openapi3_parser/node/request_body.rb +3 -3
  31. data/lib/openapi3_parser/node/response.rb +4 -4
  32. data/lib/openapi3_parser/node/responses.rb +1 -1
  33. data/lib/openapi3_parser/node/schema.rb +36 -36
  34. data/lib/openapi3_parser/node/security_scheme.rb +8 -8
  35. data/lib/openapi3_parser/node/server.rb +3 -3
  36. data/lib/openapi3_parser/node/server_variable.rb +3 -3
  37. data/lib/openapi3_parser/node/tag.rb +3 -3
  38. data/lib/openapi3_parser/node/xml.rb +5 -5
  39. data/lib/openapi3_parser/node_factory/array.rb +15 -13
  40. data/lib/openapi3_parser/node_factory/callback.rb +2 -2
  41. data/lib/openapi3_parser/node_factory/context.rb +111 -0
  42. data/lib/openapi3_parser/node_factory/field.rb +5 -7
  43. data/lib/openapi3_parser/node_factory/fields/reference.rb +43 -24
  44. data/lib/openapi3_parser/node_factory/link.rb +1 -1
  45. data/lib/openapi3_parser/node_factory/map.rb +14 -12
  46. data/lib/openapi3_parser/node_factory/object.rb +9 -5
  47. data/lib/openapi3_parser/node_factory/object_factory/node_builder.rb +21 -28
  48. data/lib/openapi3_parser/node_factory/optional_reference.rb +4 -0
  49. data/lib/openapi3_parser/node_factory/parameter_like.rb +0 -2
  50. data/lib/openapi3_parser/node_factory/path_item.rb +7 -4
  51. data/lib/openapi3_parser/node_factory/paths.rb +2 -2
  52. data/lib/openapi3_parser/node_factory/reference.rb +17 -10
  53. data/lib/openapi3_parser/node_factory/responses.rb +2 -2
  54. data/lib/openapi3_parser/node_factory/security_requirement.rb +2 -2
  55. data/lib/openapi3_parser/source.rb +27 -24
  56. data/lib/openapi3_parser/{context → source}/location.rb +13 -1
  57. data/lib/openapi3_parser/{context → source}/pointer.rb +2 -2
  58. data/lib/openapi3_parser/source/resolved_reference.rb +67 -0
  59. data/lib/openapi3_parser/validators/duplicate_parameters.rb +8 -4
  60. data/lib/openapi3_parser/validators/reference.rb +3 -3
  61. data/lib/openapi3_parser/version.rb +1 -1
  62. data/openapi3_parser.gemspec +1 -1
  63. metadata +11 -10
  64. data/lib/openapi3_parser/context.rb +0 -162
  65. data/lib/openapi3_parser/document/reference_register.rb +0 -48
  66. data/lib/openapi3_parser/node_factory/recursive_pointer.rb +0 -17
  67. data/lib/openapi3_parser/source/reference_resolver.rb +0 -82
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dc74c9c6170cb4f0bd73f5debc10b7a53317c3f4
4
- data.tar.gz: ffd75a8d5d5ff4d1f40493331645db2785810eb6
3
+ metadata.gz: c9c582d931f4f1353ee60c24d7197af410b6a0fe
4
+ data.tar.gz: 99f57e7b4ed9c359a769dbaff78a82bdeac13e55
5
5
  SHA512:
6
- metadata.gz: a9c5641522434bb630c27679dcf7a9a93ab3c538e7cbc45597e84f72ba8cdb3ed7d6477cdeee3623becda92a02fa118073e7dcac61780a5d6bab112a763fd75f
7
- data.tar.gz: 07b6ad8941ab2a02b210a25f278287cd7d29f3025b529d540e9fd5ff973e6d51938e2618b085eb3970e79757f21dca9629d6bd4c472f77f29b24493b82c6cb85
6
+ metadata.gz: 880d452e266f6dae6c14083ce278f21f469843e2952e0df3f3a5a11222fe0c9bb2042a2d5a0d7209eab83249556fb01f708bf2ffa624ac7838270199efaeb8a6
7
+ data.tar.gz: 0c4980179959a775d12d913dec31896307e9712ef2ea3585eeba71f2e9fd6bd1ebbaafa90c0b00b1e5fd61967b83131f7e2773a9a7bd0e481e47b5c4922343c2
@@ -1 +1 @@
1
- 2.3.1
1
+ 2.4.6
@@ -1,9 +1,9 @@
1
1
  language: ruby
2
2
  sudo: false
3
3
  rvm:
4
- - 2.3.7
5
- - 2.4.4
6
- - 2.5.1
4
+ - 2.4.6
5
+ - 2.5.5
6
+ - 2.6.4
7
7
  - ruby-head
8
8
  matrix:
9
9
  allow_failures:
@@ -1,3 +1,10 @@
1
+ # 0.6.0
2
+
3
+ - Drop support for Ruby 2.3
4
+ - Re-use references for significantly faster initialisation and validation
5
+ - Only error when accessing an invalid node rather than at root
6
+ - Handle infinitely recursive references that never resolve
7
+
1
8
  # 0.5.2
2
9
 
3
10
  - Fix outputting warnings for cyclic dependencies and undefined variables -
@@ -34,4 +41,3 @@
34
41
  - Allow defaulting to empty arrays and maps
35
42
  - Configure rubydoc
36
43
  - Types returned documented for the nodes
37
-
data/README.md CHANGED
@@ -2,47 +2,134 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/kevindew/openapi3_parser.svg?branch=master)](https://travis-ci.org/kevindew/openapi3_parser)
4
4
 
5
+ This a Ruby based parser/validator for [OpenAPI 3][openapi-3]. It is used to
6
+ convert an OpenAPI file (can be a local file, a URL, a string or even a Ruby
7
+ hash) into an object graph with a simple API that follows the [OpenAPI
8
+ specification][openapi-3-spec].
5
9
 
6
- This is a parser/validator for [Open API 3][openapi-3] built in Ruby.
10
+ Basic example:
7
11
 
8
- Example usage:
9
-
10
- ```
12
+ ```ruby
11
13
  require "openapi3_parser"
12
14
 
13
- document = Openapi3Parser.load_file("path/to/example.yaml")
15
+ document = Openapi3Parser.load_url("https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v3.0/petstore.yaml")
14
16
 
15
- # check whether document is valid
16
- document.valid?
17
-
18
- # traverse document
19
- document.paths["/"]
17
+ document.paths["/pets"].get.summary
18
+ # => "List all pets"
20
19
  ```
21
20
 
22
- Documentation for the API to navigate the OpenAPI nodes is available on
23
- [rubydoc.info][docs].
21
+ It aims to support 100% of the OpenAPI 3.0 specification, with key features
22
+ being:
23
+
24
+ - Supports loading a specification by path to a file, URL, Ruby file objects,
25
+ and strings in YAML and JSON formats, it even supports loading via a Ruby hash;
26
+ - Support for loading references from external files including URLs;
27
+ - Handles recursive references;
28
+ - All of OpenAPI specification mapped to Ruby objects, providing a natural
29
+ Ruby interface that maps clearly to the specification;
30
+ - OpenAPI files validated with a simple API to quickly and simply see all
31
+ problems with a file
32
+ - Built-in Markdown to HTML conversion;
33
+ - Documentation for the API to navigate the OpenAPI nodes is available on
34
+ [rubydoc.info][docs].
35
+
24
36
 
25
37
  [openapi-3]: https://github.com/OAI/OpenAPI-Specification
38
+ [openapi-3-spec]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification
26
39
  [docs]: http://www.rubydoc.info/github/kevindew/openapi3_parser/Openapi3Parser/Node/Openapi
27
40
 
41
+ ## Usage
42
+
43
+ ### Loading a specification
44
+
45
+ ```ruby
46
+ # by URL
47
+ Openapi3Parser.load_url("https://raw.githubusercontent.com/kevindew/openapi3_parser/master/spec/support/examples/petstore-expanded.yaml")
48
+
49
+ # by path to file
50
+ Openapi3Parser.load_file("spec/support/examples/uber.yaml")
51
+
52
+ # by File
53
+ Openapi3Parser.load(File.open("spec/support/examples/uber.yaml"))
54
+
55
+ # by String
56
+ Openapi3Parser.load('{ "openapi": "3.0.0", "info": { "title": "API", "version": "1.0.0" }, "paths": {} }')
57
+
58
+ # by Hash
59
+ Openapi3Parser.load(openapi: "3.0.0", info: { title: "API", version: "1.0.0" }, paths: {})
60
+
61
+ ```
62
+
63
+ ### Validating
64
+
65
+ ```ruby
66
+ document = Openapi3Parser.load(openapi: "3.0.0", info: {}, paths: {})
67
+ document.valid?
68
+ # => false
69
+ document.errors
70
+ # => Openapi3Parser::Validation::ErrorCollection(errors: {"#/info"=>["Missing required fields: title and version"]})
71
+ ```
72
+
73
+ ### Traversing
74
+
75
+ ```ruby
76
+ document = Openapi3Parser.load_url("https://raw.githubusercontent.com/kevindew/openapi3_parser/master/spec/support/examples/petstore-expanded.yaml")
77
+
78
+ # by objects
79
+
80
+ document.info.terms_of_service
81
+ # => "http://swagger.io/terms/"
82
+
83
+ document.paths.keys
84
+ # => ["/pets", "/pets/{id}"]
85
+
86
+ document.paths["/pets"].get.parameters.map(&:name)
87
+ # => ["tags", "limit"]
88
+
89
+ # by hash syntax
90
+
91
+ document["info"]["termsOfService"]
92
+ => "http://swagger.io/terms/"
93
+
94
+ document["paths"].keys
95
+ # => ["/pets", "/pets/{id}"]
96
+
97
+ document["paths"]["/pets"]["get"]["parameters"].map(&:name)
98
+ # => ["tags", "limit"]
99
+
100
+ # by a path to a node
101
+ document.node_at("#/paths/%2Fpets/get/operationId")
102
+ => "findPets"
103
+
104
+ document.node_at("#/components/schemas/Pet/allOf/0/required/0")
105
+ => "name"
106
+
107
+ # or combining
108
+
109
+ document.components.schemas["Pet"].node_at("#../NewPet")
110
+ => Openapi3Parser::Node::Schema(#/components/schemas/NewPet)
111
+ ```
112
+
113
+ You can learn more about the API on [rubydoc.info][docs]
114
+
28
115
  ## Installation
29
116
 
30
117
  You can install this gem into your bundler application by adding this line to
31
118
  your Gemfile:
32
119
 
33
120
  ```
34
- gem "openapi3_parser", "~> 0.5.0"
121
+ gem "openapi3_parser", "~> 0.6.0"
35
122
  ```
36
123
 
37
124
  and then running `$ bundle install`
38
125
 
39
- Or install the gem onto your machine via ` $ gem install openapi3_parser`
126
+ Or install the gem onto your machine via `$ gem install openapi3_parser`
40
127
 
41
128
  ## Status
42
129
 
43
130
  This is currently a work in progress and will remain so until it reaches 1.0.
44
131
 
45
- See [TODO](TODO.md) for details of the roadmap there.
132
+ See [TODO](TODO.md) for details of the features still to implement.
46
133
 
47
134
  ## Licence
48
135
 
@@ -80,8 +80,8 @@ module Openapi3Parser
80
80
 
81
81
  # @param [SourceInput] source_input
82
82
  def initialize(source_input)
83
- @reference_register = ReferenceRegister.new
84
- @root_source = Source.new(source_input, self, reference_register)
83
+ @reference_registry = ReferenceRegistry.new
84
+ @root_source = Source.new(source_input, self, reference_registry)
85
85
  @warnings = []
86
86
  @openapi_version = determine_openapi_version(root_source.data["openapi"])
87
87
  @build_in_progress = false
@@ -90,7 +90,7 @@ module Openapi3Parser
90
90
 
91
91
  # @return [Node::Openapi]
92
92
  def root
93
- factory.node
93
+ @root ||= factory.node(Node::Context.root(factory.context))
94
94
  end
95
95
 
96
96
  # All the additional sources that have been referenced as part of loading
@@ -99,7 +99,7 @@ module Openapi3Parser
99
99
  # @return [Array<Source>]
100
100
  def reference_sources
101
101
  build unless built
102
- reference_register.sources
102
+ reference_registry.sources.reject(&:root?)
103
103
  end
104
104
 
105
105
  # All of the sources involved in this OpenAPI document
@@ -131,8 +131,8 @@ module Openapi3Parser
131
131
  # resolved_input refers to the input with references resolevd and all
132
132
  # optional fields existing
133
133
  #
134
- # @param [Context::Pointer, String, Array] pointer
135
- # @param [Context::Pointer, String, Array, nil] relative_to
134
+ # @param [Source::Pointer, String, Array] pointer
135
+ # @param [Source::Pointer, String, Array, nil] relative_to
136
136
  # @return anything
137
137
  def resolved_input_at(pointer, relative_to = nil)
138
138
  look_up_pointer(pointer, relative_to, factory.resolved_input)
@@ -145,8 +145,8 @@ module Openapi3Parser
145
145
  # document.node_at("#/components/schemas")
146
146
  # document.node_at(%w[components schemas])
147
147
  #
148
- # @param [Context::Pointer, String, Array] pointer
149
- # @param [Context::Pointer, String, Array, nil] relative_to
148
+ # @param [Source::Pointer, String, Array] pointer
149
+ # @param [Source::Pointer, String, Array, nil] relative_to
150
150
  # @return anything
151
151
  def node_at(pointer, relative_to = nil)
152
152
  look_up_pointer(pointer, relative_to, root)
@@ -160,11 +160,11 @@ module Openapi3Parser
160
160
 
161
161
  private
162
162
 
163
- attr_reader :reference_register, :built, :build_in_progress
163
+ attr_reader :reference_registry, :built, :build_in_progress
164
164
 
165
165
  def look_up_pointer(pointer, relative_pointer, subject)
166
- merged_pointer = Context::Pointer.merge_pointers(relative_pointer,
167
- pointer)
166
+ merged_pointer = Source::Pointer.merge_pointers(relative_pointer,
167
+ pointer)
168
168
  CautiousDig.call(subject, *merged_pointer.segments)
169
169
  end
170
170
 
@@ -175,9 +175,9 @@ module Openapi3Parser
175
175
  def build
176
176
  return if build_in_progress || built
177
177
  @build_in_progress = true
178
- context = Context.root(root_source.data, root_source)
178
+ context = NodeFactory::Context.root(root_source.data, root_source)
179
179
  @factory = NodeFactory::Openapi.new(context)
180
- reference_register.freeze
180
+ reference_registry.freeze
181
181
  @warnings.freeze
182
182
  @build_in_progress = false
183
183
  @built = true
@@ -210,7 +210,7 @@ module Openapi3Parser
210
210
 
211
211
  def reference_factories
212
212
  build unless built
213
- reference_register.factories
213
+ reference_registry.factories.reject { |f| f.context.source.root? }
214
214
  end
215
215
  end
216
216
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openapi3Parser
4
+ class Document
5
+ class ReferenceRegistry
6
+ attr_reader :sources
7
+
8
+ def initialize
9
+ @sources = []
10
+ @factories_by_type = {}
11
+ end
12
+
13
+ def freeze
14
+ sources.freeze
15
+ factories_by_type.freeze.each(&:freeze)
16
+ super
17
+ end
18
+
19
+ def factories
20
+ factories_by_type.values.flatten
21
+ end
22
+
23
+ def register(unbuilt_factory, source_location, reference_factory_context)
24
+ register_source(source_location.source)
25
+ object_type = unbuilt_factory.object_type
26
+ existing_factory = factory(object_type, source_location)
27
+
28
+ return existing_factory if existing_factory
29
+
30
+ build_factory(
31
+ unbuilt_factory,
32
+ source_location,
33
+ reference_factory_context
34
+ ).tap { |f| register_factory(object_type, f) }
35
+ end
36
+
37
+ def factory(object_type, source_location)
38
+ factories_by_type[object_type]&.find do |f|
39
+ f.context.source_location == source_location
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :factories_by_type
46
+
47
+ def register_source(source)
48
+ sources << source unless sources.include?(source)
49
+ end
50
+
51
+ def register_factory(object_type, factory)
52
+ factories_by_type[object_type] ||= []
53
+ factories_by_type[object_type] << factory
54
+ end
55
+
56
+ def build_factory(unbuilt_factory,
57
+ source_location,
58
+ reference_factory_context)
59
+ next_context = NodeFactory::Context.resolved_reference(
60
+ reference_factory_context,
61
+ source_location: source_location
62
+ )
63
+
64
+ if unbuilt_factory.is_a?(Class)
65
+ unbuilt_factory.new(next_context)
66
+ else
67
+ unbuilt_factory.call(next_context)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -14,7 +14,7 @@ module Openapi3Parser
14
14
  extend Forwardable
15
15
  include Enumerable
16
16
 
17
- def_delegators :node_data, :each, :[], :empty?
17
+ def_delegators :node_data, :empty?
18
18
  attr_reader :node_data, :node_context
19
19
 
20
20
  # @param [::Array] data data used to populate this node
@@ -24,8 +24,16 @@ module Openapi3Parser
24
24
  @node_context = context
25
25
  end
26
26
 
27
+ def [](index)
28
+ Placeholder.resolve(node_data[index])
29
+ end
30
+
31
+ def each
32
+ node_data.each_index { |index| yield(self[index]) }
33
+ end
34
+
27
35
  # Used to access a node relative to this node
28
- # @param [Context::Pointer, ::Array, ::String] pointer_like
36
+ # @param [Source::Pointer, ::Array, ::String] pointer_like
29
37
  # @return anything
30
38
  def node_at(pointer_like)
31
39
  current_pointer = node_context.document_location.pointer
@@ -8,47 +8,47 @@ module Openapi3Parser
8
8
  class Components < Node::Object
9
9
  # @return [Map<String, Schema>]
10
10
  def schemas
11
- node_data["schemas"]
11
+ self["schemas"]
12
12
  end
13
13
 
14
14
  # @return [Map<String, Response>]
15
15
  def responses
16
- node_data["responses"]
16
+ self["responses"]
17
17
  end
18
18
 
19
19
  # @return [Map<String, Parameter>]
20
20
  def parameters
21
- node_data["parameters"]
21
+ self["parameters"]
22
22
  end
23
23
 
24
24
  # @return [Map<String, Example>]
25
25
  def examples
26
- node_data["examples"]
26
+ self["examples"]
27
27
  end
28
28
 
29
29
  # @return [Map<String, RequestBody>]
30
30
  def request_bodies
31
- node_data["requestBodies"]
31
+ self["requestBodies"]
32
32
  end
33
33
 
34
34
  # @return [Map<String, Header>]
35
35
  def headers
36
- node_data["headers"]
36
+ self["headers"]
37
37
  end
38
38
 
39
39
  # @return [Map<String, SecurityScheme>]
40
40
  def security_schemes
41
- node_data["securitySchemes"]
41
+ self["securitySchemes"]
42
42
  end
43
43
 
44
44
  # @return [Map<String, Link>]
45
45
  def links
46
- node_data["links"]
46
+ self["links"]
47
47
  end
48
48
 
49
49
  # @return [Map<String, Callback>]
50
50
  def callbacks
51
- node_data["callbacks"]
51
+ self["callbacks"]
52
52
  end
53
53
  end
54
54
  end
@@ -8,17 +8,17 @@ module Openapi3Parser
8
8
  class Contact < Node::Object
9
9
  # @return [String, nil]
10
10
  def name
11
- data["name"]
11
+ self["name"]
12
12
  end
13
13
 
14
14
  # @return [String, nil]
15
15
  def url
16
- data["url"]
16
+ self["url"]
17
17
  end
18
18
 
19
19
  # @return [String, nil]
20
20
  def email
21
- data["email"]
21
+ self["email"]
22
22
  end
23
23
  end
24
24
  end