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
@@ -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