openapi3_parser 0.3.0 → 0.4.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/.travis.yml +3 -5
  4. data/CHANGELOG.md +7 -0
  5. data/README.md +2 -2
  6. data/TODO.md +9 -2
  7. data/lib/openapi3_parser/context/pointer.rb +2 -0
  8. data/lib/openapi3_parser/document.rb +54 -18
  9. data/lib/openapi3_parser/markdown.rb +15 -0
  10. data/lib/openapi3_parser/{nodes → node}/array.rb +1 -1
  11. data/lib/openapi3_parser/{nodes → node}/callback.rb +2 -4
  12. data/lib/openapi3_parser/{nodes → node}/components.rb +2 -4
  13. data/lib/openapi3_parser/{nodes → node}/contact.rb +2 -4
  14. data/lib/openapi3_parser/{nodes → node}/discriminator.rb +2 -4
  15. data/lib/openapi3_parser/{nodes → node}/encoding.rb +2 -4
  16. data/lib/openapi3_parser/{nodes → node}/example.rb +7 -4
  17. data/lib/openapi3_parser/{nodes → node}/external_documentation.rb +7 -4
  18. data/lib/openapi3_parser/{nodes → node}/header.rb +4 -5
  19. data/lib/openapi3_parser/{nodes → node}/info.rb +9 -6
  20. data/lib/openapi3_parser/{nodes → node}/license.rb +2 -4
  21. data/lib/openapi3_parser/{nodes → node}/link.rb +7 -4
  22. data/lib/openapi3_parser/node/map.rb +1 -1
  23. data/lib/openapi3_parser/{nodes → node}/media_type.rb +2 -4
  24. data/lib/openapi3_parser/{nodes → node}/oauth_flow.rb +2 -4
  25. data/lib/openapi3_parser/{nodes → node}/oauth_flows.rb +2 -4
  26. data/lib/openapi3_parser/node/object.rb +11 -1
  27. data/lib/openapi3_parser/{nodes → node}/openapi.rb +6 -8
  28. data/lib/openapi3_parser/{nodes → node}/operation.rb +11 -8
  29. data/lib/openapi3_parser/{nodes → node}/parameter.rb +3 -4
  30. data/lib/openapi3_parser/node/parameter_like.rb +70 -0
  31. data/lib/openapi3_parser/{nodes → node}/path_item.rb +9 -6
  32. data/lib/openapi3_parser/{nodes → node}/paths.rb +2 -4
  33. data/lib/openapi3_parser/{nodes → node}/request_body.rb +7 -4
  34. data/lib/openapi3_parser/{nodes → node}/response.rb +7 -4
  35. data/lib/openapi3_parser/{nodes → node}/responses.rb +2 -4
  36. data/lib/openapi3_parser/{nodes → node}/schema.rb +12 -9
  37. data/lib/openapi3_parser/{nodes → node}/security_requirement.rb +2 -4
  38. data/lib/openapi3_parser/{nodes → node}/security_scheme.rb +7 -5
  39. data/lib/openapi3_parser/{nodes → node}/server.rb +7 -4
  40. data/lib/openapi3_parser/{nodes → node}/server_variable.rb +8 -5
  41. data/lib/openapi3_parser/{nodes → node}/tag.rb +7 -4
  42. data/lib/openapi3_parser/{nodes → node}/xml.rb +2 -4
  43. data/lib/openapi3_parser/node_factories/array.rb +10 -4
  44. data/lib/openapi3_parser/node_factories/callback.rb +2 -2
  45. data/lib/openapi3_parser/node_factories/components.rb +5 -3
  46. data/lib/openapi3_parser/node_factories/contact.rb +10 -4
  47. data/lib/openapi3_parser/node_factories/discriminator.rb +2 -2
  48. data/lib/openapi3_parser/node_factories/encoding.rb +7 -3
  49. data/lib/openapi3_parser/node_factories/example.rb +8 -3
  50. data/lib/openapi3_parser/node_factories/external_documentation.rb +7 -3
  51. data/lib/openapi3_parser/node_factories/header.rb +2 -2
  52. data/lib/openapi3_parser/node_factories/info.rb +6 -3
  53. data/lib/openapi3_parser/node_factories/license.rb +6 -3
  54. data/lib/openapi3_parser/node_factories/link.rb +4 -2
  55. data/lib/openapi3_parser/node_factories/map.rb +12 -4
  56. data/lib/openapi3_parser/node_factories/media_type.rb +36 -5
  57. data/lib/openapi3_parser/node_factories/oauth_flow.rb +2 -2
  58. data/lib/openapi3_parser/node_factories/oauth_flows.rb +2 -2
  59. data/lib/openapi3_parser/node_factories/openapi.rb +13 -3
  60. data/lib/openapi3_parser/node_factories/operation.rb +11 -3
  61. data/lib/openapi3_parser/node_factories/parameter.rb +28 -3
  62. data/lib/openapi3_parser/node_factories/parameter/parameter_like.rb +9 -5
  63. data/lib/openapi3_parser/node_factories/path_item.rb +26 -7
  64. data/lib/openapi3_parser/node_factories/paths.rb +51 -2
  65. data/lib/openapi3_parser/node_factories/reference.rb +4 -0
  66. data/lib/openapi3_parser/node_factories/request_body.rb +32 -3
  67. data/lib/openapi3_parser/node_factories/response.rb +24 -4
  68. data/lib/openapi3_parser/node_factories/responses.rb +28 -2
  69. data/lib/openapi3_parser/node_factories/schema.rb +17 -3
  70. data/lib/openapi3_parser/node_factories/security_requirement.rb +2 -2
  71. data/lib/openapi3_parser/node_factories/security_scheme.rb +2 -2
  72. data/lib/openapi3_parser/node_factories/server.rb +2 -2
  73. data/lib/openapi3_parser/node_factories/server_variable.rb +2 -2
  74. data/lib/openapi3_parser/node_factories/tag.rb +2 -2
  75. data/lib/openapi3_parser/node_factories/xml.rb +6 -3
  76. data/lib/openapi3_parser/node_factory.rb +10 -4
  77. data/lib/openapi3_parser/node_factory/fields/reference.rb +4 -0
  78. data/lib/openapi3_parser/node_factory/map.rb +10 -0
  79. data/lib/openapi3_parser/node_factory/object.rb +29 -0
  80. data/lib/openapi3_parser/node_factory/object/validator.rb +69 -6
  81. data/lib/openapi3_parser/source/reference.rb +2 -0
  82. data/lib/openapi3_parser/source/reference_resolver.rb +5 -1
  83. data/lib/openapi3_parser/validators/absolute_uri.rb +14 -0
  84. data/lib/openapi3_parser/validators/component_keys.rb +17 -0
  85. data/lib/openapi3_parser/validators/duplicate_parameters.rb +30 -0
  86. data/lib/openapi3_parser/validators/email.rb +23 -0
  87. data/lib/openapi3_parser/validators/media_type.rb +20 -0
  88. data/lib/openapi3_parser/validators/url.rb +18 -0
  89. data/lib/openapi3_parser/version.rb +1 -1
  90. data/openapi3_parser.gemspec +5 -2
  91. metadata +70 -36
  92. data/lib/openapi3_parser/nodes/map.rb +0 -17
  93. data/lib/openapi3_parser/nodes/parameter/parameter_like.rb +0 -67
@@ -3,7 +3,7 @@
3
3
  require "openapi3_parser/context"
4
4
  require "openapi3_parser/error"
5
5
  require "openapi3_parser/node_factory/map"
6
- require "openapi3_parser/nodes/map"
6
+ require "openapi3_parser/node/map"
7
7
  require "openapi3_parser/validation/error"
8
8
  require "openapi3_parser/validation/error_collection"
9
9
 
@@ -12,24 +12,28 @@ module Openapi3Parser
12
12
  class Map
13
13
  include NodeFactory::Map
14
14
 
15
+ # rubocop:disable Metrics/ParameterLists
15
16
  def initialize(
16
17
  context,
17
18
  key_input_type: String,
18
19
  value_input_type: nil,
19
20
  value_factory: nil,
20
- validate: nil
21
+ validate: nil,
22
+ default: {}
21
23
  )
22
24
  @given_key_input_type = key_input_type
23
25
  @given_value_input_type = value_input_type
24
26
  @given_value_factory = value_factory
25
27
  @given_validate = validate
28
+ @given_default = default
26
29
  super(context)
27
30
  end
31
+ # rubocop:enable Metrics/ParameterLists
28
32
 
29
33
  private
30
34
 
31
35
  attr_reader :given_key_input_type, :given_value_input_type,
32
- :given_value_factory, :given_validate
36
+ :given_value_factory, :given_validate, :given_default
33
37
 
34
38
  def process_input(input)
35
39
  input.each_with_object({}) do |(key, value), memo|
@@ -68,7 +72,7 @@ module Openapi3Parser
68
72
  end
69
73
 
70
74
  def build_map(data, context)
71
- Nodes::Map.new(data, context)
75
+ Node::Map.new(data, context)
72
76
  end
73
77
 
74
78
  def value_factory?
@@ -107,6 +111,10 @@ module Openapi3Parser
107
111
  return unless type
108
112
  "Expected #{type}" unless value.is_a?(type)
109
113
  end
114
+
115
+ def default
116
+ given_default
117
+ end
110
118
  end
111
119
  end
112
120
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/media_type"
3
+ require "openapi3_parser/node/media_type"
4
4
  require "openapi3_parser/node_factory/object"
5
5
  require "openapi3_parser/node_factory/optional_reference"
6
6
  require "openapi3_parser/node_factories/map"
@@ -14,15 +14,18 @@ module Openapi3Parser
14
14
  include NodeFactory::Object
15
15
 
16
16
  allow_extensions
17
+
17
18
  field "schema", factory: :schema_factory
18
19
  field "example"
19
20
  field "examples", factory: :examples_factory
20
21
  field "encoding", factory: :encoding_factory
21
22
 
23
+ mutually_exclusive "example", "examples"
24
+
22
25
  private
23
26
 
24
27
  def build_object(data, context)
25
- Nodes::MediaType.new(data, context)
28
+ Node::MediaType.new(data, context)
26
29
  end
27
30
 
28
31
  def schema_factory(context)
@@ -32,12 +35,40 @@ module Openapi3Parser
32
35
 
33
36
  def examples_factory(context)
34
37
  factory = NodeFactory::OptionalReference.new(NodeFactories::Example)
35
- NodeFactories::Map.new(context, value_factory: factory)
38
+ NodeFactories::Map.new(context, default: nil, value_factory: factory)
36
39
  end
37
40
 
38
41
  def encoding_factory(context)
39
- factory = NodeFactories::Encoding
40
- NodeFactories::Map.new(context, value_factory: factory)
42
+ NodeFactories::Map.new(
43
+ context,
44
+ validate: EncodingValidator.new(self),
45
+ value_factory: NodeFactories::Encoding
46
+ )
47
+ end
48
+
49
+ class EncodingValidator
50
+ def initialize(factory)
51
+ @factory = factory
52
+ end
53
+
54
+ def call(input, _context)
55
+ missing_keys = input.keys - properties
56
+ error_message(missing_keys) unless missing_keys.empty?
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :factory
62
+
63
+ def properties
64
+ properties = factory.resolved_input.dig("schema", "properties")
65
+ properties.respond_to?(:keys) ? properties.keys : []
66
+ end
67
+
68
+ def error_message(missing_keys)
69
+ keys = missing_keys.join(", ")
70
+ "Keys are not defined as schema properties: #{keys}"
71
+ end
41
72
  end
42
73
  end
43
74
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/oauth_flow"
3
+ require "openapi3_parser/node/oauth_flow"
4
4
  require "openapi3_parser/node_factory/object"
5
5
 
6
6
  module Openapi3Parser
@@ -17,7 +17,7 @@ module Openapi3Parser
17
17
  private
18
18
 
19
19
  def build_object(data, context)
20
- Nodes::OauthFlow.new(data, context)
20
+ Node::OauthFlow.new(data, context)
21
21
  end
22
22
  end
23
23
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/oauth_flows"
3
+ require "openapi3_parser/node/oauth_flows"
4
4
  require "openapi3_parser/node_factories/oauth_flow"
5
5
  require "openapi3_parser/node_factory/object"
6
6
  require "openapi3_parser/node_factory/optional_reference"
@@ -24,7 +24,7 @@ module Openapi3Parser
24
24
  end
25
25
 
26
26
  def build_object(data, context)
27
- Nodes::OauthFlows.new(data, context)
27
+ Node::OauthFlows.new(data, context)
28
28
  end
29
29
  end
30
30
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/openapi"
3
+ require "openapi3_parser/node/openapi"
4
4
  require "openapi3_parser/node_factory/object"
5
5
  require "openapi3_parser/node_factories/info"
6
6
  require "openapi3_parser/node_factories/array"
@@ -31,7 +31,7 @@ module Openapi3Parser
31
31
  private
32
32
 
33
33
  def build_object(data, context)
34
- Nodes::Openapi.new(data, context)
34
+ Node::Openapi.new(data, context)
35
35
  end
36
36
 
37
37
  def servers_factory(context)
@@ -45,7 +45,17 @@ module Openapi3Parser
45
45
  end
46
46
 
47
47
  def tags_factory(context)
48
- NodeFactories::Array.new(context, value_factory: NodeFactories::Tag)
48
+ validate_unique_tags = lambda do |input, _context|
49
+ names = input.map { |i| i["name"] }
50
+ return if names.uniq.count == names.count
51
+
52
+ dupes = names.find_all { |name| names.count(name) > 1 }
53
+ "Duplicate tag names: #{dupes.uniq.join(', ')}"
54
+ end
55
+
56
+ NodeFactories::Array.new(context,
57
+ value_factory: NodeFactories::Tag,
58
+ validate: validate_unique_tags)
49
59
  end
50
60
  end
51
61
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/operation"
3
+ require "openapi3_parser/node/operation"
4
4
  require "openapi3_parser/node_factory/object"
5
5
  require "openapi3_parser/node_factory/optional_reference"
6
6
  require "openapi3_parser/node_factories/array"
@@ -11,6 +11,7 @@ require "openapi3_parser/node_factories/responses"
11
11
  require "openapi3_parser/node_factories/callback"
12
12
  require "openapi3_parser/node_factories/server"
13
13
  require "openapi3_parser/node_factories/security_requirement"
14
+ require "openapi3_parser/validators/duplicate_parameters"
14
15
 
15
16
  module Openapi3Parser
16
17
  module NodeFactories
@@ -35,7 +36,7 @@ module Openapi3Parser
35
36
  private
36
37
 
37
38
  def build_object(data, context)
38
- Nodes::Operation.new(data, context)
39
+ Node::Operation.new(data, context)
39
40
  end
40
41
 
41
42
  def tags_factory(context)
@@ -44,7 +45,14 @@ module Openapi3Parser
44
45
 
45
46
  def parameters_factory(context)
46
47
  factory = NodeFactory::OptionalReference.new(NodeFactories::Parameter)
47
- NodeFactories::Array.new(context, value_factory: factory)
48
+
49
+ validate = lambda do |_input, array_factory|
50
+ Validators::DuplicateParameters.call(array_factory.resolved_input)
51
+ end
52
+
53
+ NodeFactories::Array.new(context,
54
+ value_factory: factory,
55
+ validate: validate)
48
56
  end
49
57
 
50
58
  def request_body_factory(context)
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/parameter"
3
+ require "openapi3_parser/context"
4
+ require "openapi3_parser/node/parameter"
4
5
  require "openapi3_parser/node_factories/parameter/parameter_like"
5
6
  require "openapi3_parser/node_factory/object"
7
+ require "openapi3_parser/validation/error"
6
8
 
7
9
  module Openapi3Parser
8
10
  module NodeFactories
@@ -13,7 +15,9 @@ module Openapi3Parser
13
15
  allow_extensions
14
16
 
15
17
  field "name", input_type: String, required: true
16
- field "in", input_type: String, required: true
18
+ field "in", input_type: String,
19
+ required: true,
20
+ validate: :validate_in
17
21
  field "description", input_type: String
18
22
  field "required", input_type: :boolean, default: false
19
23
  field "deprecated", input_type: :boolean, default: false
@@ -28,16 +32,37 @@ module Openapi3Parser
28
32
 
29
33
  field "content", factory: :content_factory
30
34
 
35
+ mutually_exclusive "example", "examples"
36
+
31
37
  private
32
38
 
33
39
  def build_object(data, context)
34
- Nodes::Parameter.new(data, context)
40
+ Node::Parameter.new(data, context)
41
+ end
42
+
43
+ def validate(input, context)
44
+ errors = []
45
+
46
+ if input["in"] == "path" && !input["required"]
47
+ errors << Validation::Error.new(
48
+ "Must be included and true for a path parameter",
49
+ Context.next_field(context, "required"),
50
+ self.class
51
+ )
52
+ end
53
+
54
+ errors
35
55
  end
36
56
 
37
57
  def default_style
38
58
  return "simple" if %w[path header].include?(context.input["in"])
39
59
  "form"
40
60
  end
61
+
62
+ def validate_in(input)
63
+ valid = %w[header query cookie path].include?(input)
64
+ "in can only be header, query, cookie, or path" unless valid
65
+ end
41
66
  end
42
67
  end
43
68
  end
@@ -15,14 +15,18 @@ module Openapi3Parser
15
15
 
16
16
  def examples_factory(context)
17
17
  factory = NodeFactory::OptionalReference.new(NodeFactories::Schema)
18
- NodeFactories::Map.new(context, value_factory: factory)
18
+ NodeFactories::Map.new(context, default: nil, value_factory: factory)
19
19
  end
20
20
 
21
21
  def content_factory(context)
22
- factory = NodeFactory::OptionalReference.new(
23
- NodeFactories::MediaType
24
- )
25
- NodeFactories::Map.new(context, value_factory: factory)
22
+ NodeFactories::Map.new(context,
23
+ default: nil,
24
+ value_factory: NodeFactories::MediaType,
25
+ validate: method(:validate_content).to_proc)
26
+ end
27
+
28
+ def validate_content(input, _context)
29
+ "Must only have one item" unless input.size == 1
26
30
  end
27
31
  end
28
32
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/path_item"
3
+ require "openapi3_parser/node/path_item"
4
4
  require "openapi3_parser/node_factory/fields/reference"
5
5
  require "openapi3_parser/node_factory/object"
6
6
  require "openapi3_parser/node_factory/object/node_builder"
@@ -9,6 +9,7 @@ require "openapi3_parser/node_factories/array"
9
9
  require "openapi3_parser/node_factories/server"
10
10
  require "openapi3_parser/node_factories/operation"
11
11
  require "openapi3_parser/node_factories/parameter"
12
+ require "openapi3_parser/validators/duplicate_parameters"
12
13
 
13
14
  module Openapi3Parser
14
15
  module NodeFactories
@@ -38,12 +39,9 @@ module Openapi3Parser
38
39
 
39
40
  def build_object(data, context)
40
41
  ref = data.delete("$ref")
41
- return Nodes::PathItem.new(data, context) unless ref
42
+ return Node::PathItem.new(data, context) unless ref
42
43
 
43
- merged_data = ref.node_data.merge(data) do |_, new, old|
44
- new.nil? ? old : new
45
- end
46
- Nodes::PathItem.new(merged_data, ref.node_context)
44
+ Node::PathItem.new(merge_data(ref.node_data, data), ref.node_context)
47
45
  end
48
46
 
49
47
  def ref_factory(context)
@@ -61,9 +59,30 @@ module Openapi3Parser
61
59
  )
62
60
  end
63
61
 
62
+ def build_resolved_input
63
+ ref = processed_input["$ref"]
64
+ data = super.tap { |d| d.delete("$ref") }
65
+ return data unless ref
66
+
67
+ merge_data(ref.data || {}, data)
68
+ end
69
+
70
+ def merge_data(base, priority)
71
+ base.merge(priority) do |_, new, old|
72
+ new.nil? ? old : new
73
+ end
74
+ end
75
+
64
76
  def parameters_factory(context)
65
77
  factory = NodeFactory::OptionalReference.new(NodeFactories::Parameter)
66
- NodeFactories::Array.new(context, value_factory: factory)
78
+
79
+ validate = lambda do |_input, array_factory|
80
+ Validators::DuplicateParameters.call(array_factory.resolved_input)
81
+ end
82
+
83
+ NodeFactories::Array.new(context,
84
+ value_factory: factory,
85
+ validate: validate)
67
86
  end
68
87
  end
69
88
  end
@@ -3,12 +3,26 @@
3
3
  require "openapi3_parser/node_factory/map"
4
4
  require "openapi3_parser/node_factory/optional_reference"
5
5
  require "openapi3_parser/node_factories/path_item"
6
- require "openapi3_parser/nodes/paths"
6
+ require "openapi3_parser/node/paths"
7
7
 
8
8
  module Openapi3Parser
9
9
  module NodeFactories
10
10
  class Paths
11
11
  include NodeFactory::Map
12
+ PATH_REGEX = %r{
13
+ \A
14
+ # required prefix slash
15
+ /
16
+ (
17
+ # Match a path
18
+ ([\-;_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*
19
+ # Match a path template parameter
20
+ ({([\-;_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})+})*
21
+ # optional segment separating slash
22
+ /?
23
+ )*
24
+ \Z
25
+ }x
12
26
 
13
27
  private
14
28
 
@@ -26,7 +40,42 @@ module Openapi3Parser
26
40
  end
27
41
 
28
42
  def build_map(data, context)
29
- Nodes::Paths.new(data, context)
43
+ Node::Paths.new(data, context)
44
+ end
45
+
46
+ def validate(input, _context)
47
+ paths = input.keys.reject { |key| extension?(key) }
48
+ validate_paths(paths)
49
+ end
50
+
51
+ def validate_paths(paths)
52
+ invalid_paths = paths.reject { |p| PATH_REGEX.match(p) }
53
+ errors = []
54
+ unless invalid_paths.empty?
55
+ joined = invalid_paths.map { |p| "'#{p}'" }.join(", ")
56
+ errors << %(There are invalid paths: #{joined})
57
+ end
58
+
59
+ conflicts = conflicting_paths(paths)
60
+
61
+ unless conflicts.empty?
62
+ joined = conflicts.map { |p| "'#{p}'" }.join(", ")
63
+ errors << %(There are paths that conflict: #{joined})
64
+ end
65
+
66
+ errors
67
+ end
68
+
69
+ def conflicting_paths(paths)
70
+ potential_conflicts = paths.each_with_object({}) do |path, memo|
71
+ without_params = path.gsub(/{.*?}/, "")
72
+ memo[path] = without_params if path != without_params
73
+ end
74
+
75
+ grouped_paths = potential_conflicts.group_by(&:last)
76
+ .map { |_k, v| v.map(&:first) }
77
+
78
+ grouped_paths.select { |group| group.size > 1 }.flatten
30
79
  end
31
80
  end
32
81
  end