openapi3_parser 0.5.2 → 0.6.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 (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
@@ -8,7 +8,7 @@ module Openapi3Parser
8
8
  allow_extensions
9
9
 
10
10
  # @todo The link object in OAS is pretty meaty and there's lot of scope
11
- # for further work here to make use of it's funcationality
11
+ # for further work here to make use of its functionality
12
12
 
13
13
  field "operationRef", input_type: String
14
14
  field "operationId", input_type: String
@@ -46,11 +46,9 @@ module Openapi3Parser
46
46
  @errors ||= ValidNodeBuilder.errors(self)
47
47
  end
48
48
 
49
- def node
50
- @node ||= begin
51
- data = ValidNodeBuilder.data(self)
52
- data.nil? ? nil : build_node(data)
53
- end
49
+ def node(node_context)
50
+ data = ValidNodeBuilder.data(self, node_context)
51
+ data.nil? ? nil : build_node(data, node_context)
54
52
  end
55
53
 
56
54
  def inspect
@@ -84,8 +82,8 @@ module Openapi3Parser
84
82
  end
85
83
  end
86
84
 
87
- def build_node(data)
88
- Node::Map.new(data, context) if data
85
+ def build_node(data, node_context)
86
+ Node::Map.new(data, node_context) if data
89
87
  end
90
88
 
91
89
  def build_resolved_input
@@ -105,8 +103,8 @@ module Openapi3Parser
105
103
  new(factory).errors
106
104
  end
107
105
 
108
- def self.data(factory)
109
- new(factory).data
106
+ def self.data(factory, parent_context)
107
+ new(factory).data(parent_context)
110
108
  end
111
109
 
112
110
  def initialize(factory)
@@ -122,7 +120,7 @@ module Openapi3Parser
122
120
  validatable.collection
123
121
  end
124
122
 
125
- def data
123
+ def data(parent_context)
126
124
  return default_value if factory.nil_input?
127
125
 
128
126
  TypeChecker.raise_on_invalid_type(factory.context, type: ::Hash)
@@ -131,7 +129,11 @@ module Openapi3Parser
131
129
  validate(raise_on_invalid: true)
132
130
 
133
131
  factory.data.each_with_object({}) do |(key, value), memo|
134
- memo[key] = value.respond_to?(:node) ? value.node : value
132
+ memo[key] = if value.respond_to?(:node)
133
+ Node::Placeholder.new(value, key, parent_context)
134
+ else
135
+ value
136
+ end
135
137
  end
136
138
  end
137
139
 
@@ -200,7 +202,7 @@ module Openapi3Parser
200
202
 
201
203
  first_error = validatable.errors.first
202
204
  raise Openapi3Parser::Error::InvalidData,
203
- "Invalid data for #{first_error.context.location_summary}. "\
205
+ "Invalid data for #{first_error.context.location_summary}: "\
204
206
  "#{first_error.message}"
205
207
  end
206
208
 
@@ -18,6 +18,10 @@ module Openapi3Parser
18
18
 
19
19
  attr_reader :context, :data
20
20
 
21
+ def self.object_type
22
+ to_s
23
+ end
24
+
21
25
  def initialize(context)
22
26
  @context = context
23
27
  @data = build_data(context.input)
@@ -43,8 +47,8 @@ module Openapi3Parser
43
47
  @errors ||= ObjectFactory::NodeBuilder.errors(self)
44
48
  end
45
49
 
46
- def node
47
- @node ||= build_node
50
+ def node(node_context)
51
+ build_node(node_context)
48
52
  end
49
53
 
50
54
  def can_use_default?
@@ -99,9 +103,9 @@ module Openapi3Parser
99
103
  end
100
104
  end
101
105
 
102
- def build_node
103
- data = ObjectFactory::NodeBuilder.node_data(self)
104
- build_object(data, context) if data
106
+ def build_node(node_context)
107
+ data = ObjectFactory::NodeBuilder.node_data(self, node_context)
108
+ build_object(data, node_context) if data
105
109
  end
106
110
  end
107
111
  end
@@ -8,8 +8,8 @@ module Openapi3Parser
8
8
  new(factory).errors
9
9
  end
10
10
 
11
- def self.node_data(factory)
12
- new(factory).node_data
11
+ def self.node_data(factory, parent_context)
12
+ new(factory).node_data(parent_context)
13
13
  end
14
14
 
15
15
  def initialize(factory)
@@ -29,11 +29,11 @@ module Openapi3Parser
29
29
  validatable.collection
30
30
  end
31
31
 
32
- def node_data
33
- return build_node_data if empty_and_allowed_to_be?
32
+ def node_data(parent_context)
33
+ return build_node_data(parent_context) if empty_and_allowed_to_be?
34
34
  TypeChecker.raise_on_invalid_type(factory.context, type: ::Hash)
35
35
  validate(raise_on_invalid: true)
36
- build_node_data
36
+ build_node_data(parent_context)
37
37
  end
38
38
 
39
39
  private_class_method :new
@@ -50,40 +50,33 @@ module Openapi3Parser
50
50
  Validator.call(factory, raise_on_invalid)
51
51
  end
52
52
 
53
- def build_node_data
53
+ def build_node_data(parent_context)
54
54
  return if factory.nil_input? && factory.data.nil?
55
55
 
56
- factory.data.each_with_object(NodeData.new) do |(key, value), memo|
57
- memo[key] = if node_is_recursive_pointer?(value)
58
- value.recursive_pointer
59
- else
60
- resolve_value(key, value)
61
- end
56
+ factory.data.each_with_object({}) do |(key, value), memo|
57
+ memo[key] = resolve_value(key, value, parent_context)
62
58
  end
63
59
  end
64
60
 
65
- def resolve_value(key, value)
66
- config = factory.field_configs[key]
67
- resolved_value = value.respond_to?(:node) ? value.node : value
61
+ def resolve_value(key, value, parent_context)
62
+ resolved = determine_value_or_default(key, value)
68
63
 
69
- # let a field config default take precedence if value is a nil_input?
70
- if (value.respond_to?(:nil_input?) && value.nil_input?) || value.nil?
71
- default = config&.default(factory)
72
- default.nil? ? resolved_value : default
64
+ if resolved.respond_to?(:node)
65
+ Node::Placeholder.new(value, key, parent_context)
73
66
  else
74
- resolved_value
67
+ resolved
75
68
  end
76
69
  end
77
70
 
78
- def node_is_recursive_pointer?(value_factory)
79
- return false unless value_factory.respond_to?(:in_recursive_loop?)
80
- value_factory.in_recursive_loop?
81
- end
71
+ def determine_value_or_default(key, value)
72
+ config = factory.field_configs[key]
82
73
 
83
- class NodeData < ::Hash
84
- def [](key)
85
- item = super(key)
86
- item.is_a?(NodeFactory::RecursivePointer) ? item.node : item
74
+ # let a field config default take precedence if value is a nil_input?
75
+ if (value.respond_to?(:nil_input?) && value.nil_input?) || value.nil?
76
+ default = config&.default(factory)
77
+ default.nil? ? value : default
78
+ else
79
+ value
87
80
  end
88
81
  end
89
82
  end
@@ -7,6 +7,10 @@ module Openapi3Parser
7
7
  @factory = factory
8
8
  end
9
9
 
10
+ def object_type
11
+ "#{self.class}[#{factory.object_type}]}"
12
+ end
13
+
10
14
  def call(context)
11
15
  reference = context.input.is_a?(Hash) && context.input["$ref"]
12
16
 
@@ -33,5 +33,3 @@ module Openapi3Parser
33
33
  end
34
34
  end
35
35
  end
36
-
37
- # These are in the footer as a cyclic dependency can stop this module loading
@@ -22,11 +22,14 @@ module Openapi3Parser
22
22
 
23
23
  private
24
24
 
25
- def build_object(data, context)
26
- ref = data.delete("$ref")
27
- return Node::PathItem.new(data, context) unless ref
25
+ def build_object(data, node_context)
26
+ ref = data.delete("$ref").node
27
+ return Node::PathItem.new(data, node_context) unless ref
28
28
 
29
- Node::PathItem.new(merge_data(ref.node_data, data), ref.node_context)
29
+ context = data.empty? ? ref.node_context : node_context
30
+ data = merge_data(ref.node_data, data)
31
+
32
+ Node::PathItem.new(data, context)
30
33
  end
31
34
 
32
35
  def ref_factory(context)
@@ -31,8 +31,8 @@ module Openapi3Parser
31
31
 
32
32
  private
33
33
 
34
- def build_node(data)
35
- Node::Paths.new(data, context)
34
+ def build_node(data, node_context)
35
+ Node::Paths.new(data, node_context)
36
36
  end
37
37
 
38
38
  def validate(validatable)
@@ -15,22 +15,29 @@ module Openapi3Parser
15
15
  end
16
16
 
17
17
  def in_recursive_loop?
18
- data["$ref"].in_recursive_loop?
18
+ data["$ref"].self_referencing?
19
19
  end
20
20
 
21
- def recursive_pointer
22
- NodeFactory::RecursivePointer.new(data["$ref"].reference_context)
21
+ def referenced_factory
22
+ data["$ref"].referenced_factory
23
+ end
24
+
25
+ def resolves?(control_factory = nil)
26
+ control_factory ||= self
27
+
28
+ return true unless referenced_factory.is_a?(Reference)
29
+ # recursive loop of references that never references an object
30
+ return false if referenced_factory == control_factory
31
+
32
+ referenced_factory.resolves?(control_factory)
23
33
  end
24
34
 
25
35
  private
26
36
 
27
- def build_node
28
- if in_recursive_loop?
29
- raise Error::InRecursiveStructure,
30
- "Can't build node as it references itself, use "\
31
- "recursive_pointer"
32
- end
33
- data["$ref"].node
37
+ def build_node(node_context)
38
+ TypeChecker.raise_on_invalid_type(context, type: ::Hash)
39
+ ObjectFactory::Validator.call(self, true)
40
+ data["$ref"].node(node_context)
34
41
  end
35
42
 
36
43
  def ref_factory(context)
@@ -26,8 +26,8 @@ module Openapi3Parser
26
26
 
27
27
  private
28
28
 
29
- def build_node(data)
30
- Node::Responses.new(data, context)
29
+ def build_node(data, node_context)
30
+ Node::Responses.new(data, node_context)
31
31
  end
32
32
 
33
33
  def validate_keys(validatable)
@@ -11,8 +11,8 @@ module Openapi3Parser
11
11
 
12
12
  private
13
13
 
14
- def build_node(data)
15
- Node::SecurityRequirement.new(data, context)
14
+ def build_node(data, node_context)
15
+ Node::SecurityRequirement.new(data, node_context)
16
16
  end
17
17
  end
18
18
  end
@@ -9,22 +9,22 @@ module Openapi3Parser
9
9
  # The source input which provides the data
10
10
  # @attr_reader [Document] document
11
11
  # The document that this source is associated with
12
- # @attr_reader [Document::ReferenceRegister] reference_register
13
- # An object to track references for a document
12
+ # @attr_reader [Document::ReferenceRegistry] reference_registry
13
+ # An object that tracks factories for all references
14
14
  # @attr_reader [Source, nil] parent
15
15
  # Set to a Source if this source was created due to a reference within
16
16
  # a different Source
17
17
  class Source
18
- attr_reader :source_input, :document, :reference_register, :parent
18
+ attr_reader :source_input, :document, :reference_registry, :parent
19
19
 
20
20
  # @param [SourceInput] source_input
21
21
  # @param [Document] document
22
- # @param [Document::ReferenceRegister] reference_register
22
+ # @param [Document::ReferenceRegistry] reference_registry
23
23
  # @param [Source, nil] parent
24
- def initialize(source_input, document, reference_register, parent = nil)
24
+ def initialize(source_input, document, reference_registry, parent = nil)
25
25
  @source_input = source_input
26
26
  @document = document
27
- @reference_register = reference_register
27
+ @reference_registry = reference_registry
28
28
  @parent = parent
29
29
  end
30
30
 
@@ -43,23 +43,26 @@ module Openapi3Parser
43
43
  document.root_source == self
44
44
  end
45
45
 
46
- # Used to register a reference with the underlying document and return a
47
- # reference resolver to access the object referenced
48
- #
49
- # @param [String] given_reference The reference as text
50
- # @param [NodeFactory] factory Factory class for the expected
51
- # eventual resource
52
- # @param [Context] context The context of the object
53
- # calling this reference
54
- # @return [ReferenceResolver]
55
- def register_reference(given_reference, factory, context)
46
+ def resolve_reference(given_reference,
47
+ unbuilt_factory,
48
+ context,
49
+ recursive: false)
56
50
  reference = Reference.new(given_reference)
57
- ReferenceResolver.new(
58
- reference, factory, context
59
- ).tap do |resolver|
60
- next if resolver.in_root_source?
61
- reference_register.register(resolver.reference_factory)
51
+ resolved_source = resolve_source(reference)
52
+ source_location = Source::Location.new(resolved_source,
53
+ reference.json_pointer)
54
+
55
+ unless recursive
56
+ reference_registry.register(unbuilt_factory,
57
+ source_location,
58
+ context)
62
59
  end
60
+
61
+ ResolvedReference.new(
62
+ source_location: source_location,
63
+ object_type: unbuilt_factory.object_type,
64
+ reference_registry: reference_registry
65
+ )
63
66
  end
64
67
 
65
68
  # Access/create the source object for a reference
@@ -71,15 +74,15 @@ module Openapi3Parser
71
74
  # I found the spec wasn't fully clear on expected behaviour if a source
72
75
  # references a fragment that doesn't exist in it's current document
73
76
  # and just the root source. I'm assuming to be consistent with URI a
74
- # fragment only reference only references current JSON document. This
75
- # could be incorrect though.
77
+ # fragment only references the current JSON document. This could be
78
+ # incorrect though.
76
79
  self
77
80
  else
78
81
  next_source_input = source_input.resolve_next(reference)
79
82
  source = document.source_for_source_input(next_source_input)
80
83
  source || self.class.new(next_source_input,
81
84
  document,
82
- reference_register,
85
+ reference_registry,
83
86
  self)
84
87
  end
85
88
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Openapi3Parser
4
- class Context
4
+ class Source
5
5
  # Class used to represent a location within an OpenAPI document.
6
6
  # It contains a source, which is the source file/data used for the contents
7
7
  # and the pointer which indicates where in the object like file the data is
@@ -28,6 +28,18 @@ module Openapi3Parser
28
28
  source.relative_to_root + pointer.fragment
29
29
  end
30
30
 
31
+ def data
32
+ source.data_at_pointer(pointer.segments)
33
+ end
34
+
35
+ def pointer_defined?
36
+ source.has_pointer?(pointer.segments)
37
+ end
38
+
39
+ def source_available?
40
+ source.available?
41
+ end
42
+
31
43
  def inspect
32
44
  %{#{self.class.name}(source: #{source.inspect}, pointer: #{pointer})}
33
45
  end
@@ -3,7 +3,7 @@
3
3
  require "cgi"
4
4
 
5
5
  module Openapi3Parser
6
- class Context
6
+ class Source
7
7
  # A class to decorate the array of fields that make up a pointer and
8
8
  # provide common means to convert it into different representations.
9
9
  class Pointer
@@ -13,7 +13,7 @@ module Openapi3Parser
13
13
  segments = fragment.split("/").map do |part|
14
14
  next if part == ""
15
15
  unescaped = CGI.unescape(part.gsub("%20", "+"))
16
- unescaped =~ /\A\d+\z/ ? unescaped.to_i : unescaped
16
+ unescaped.match?(/\A\d+\z/) ? unescaped.to_i : unescaped
17
17
  end
18
18
  new(segments.compact, absolute)
19
19
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Openapi3Parser
6
+ class Source
7
+ class ResolvedReference
8
+ extend Forwardable
9
+
10
+ def_delegators :source_location, :source
11
+ def_delegators :factory, :resolved_input, :node
12
+
13
+ attr_reader :source_location, :object_type
14
+
15
+ def initialize(source_location:,
16
+ object_type:,
17
+ reference_registry:)
18
+ @source_location = source_location
19
+ @object_type = object_type
20
+ @reference_registry = reference_registry
21
+ end
22
+
23
+ def valid?
24
+ errors.empty?
25
+ end
26
+
27
+ def errors
28
+ @errors ||= Array(build_errors)
29
+ end
30
+
31
+ def factory
32
+ @factory ||= begin
33
+ reference_registry
34
+ .factory(object_type, source_location)
35
+ .tap do |factory|
36
+ message = "Unregistered node factory at #{source_location}"
37
+ raise Openapi3Parser::Error, message unless factory
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :reference_registry
45
+
46
+ def build_errors
47
+ return source_unavailabe_error unless source.available?
48
+ return pointer_missing_error unless source_location.pointer_defined?
49
+
50
+ resolution_error unless factory.valid?
51
+ end
52
+
53
+ def source_unavailabe_error
54
+ "Failed to open source #{source.relative_to_root}"
55
+ end
56
+
57
+ def pointer_missing_error
58
+ suffix = source.root? ? "" : " in source #{source.relative_to_root}"
59
+ "#{source_location.pointer} is not defined#{suffix}"
60
+ end
61
+
62
+ def resolution_error
63
+ "#{source_location} does not resolve to a valid object"
64
+ end
65
+ end
66
+ end
67
+ end