openapi3_parser 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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