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
@@ -26,6 +26,10 @@ module Openapi3Parser
26
26
  def ref_factory(context)
27
27
  Fields::Reference.new(context, factory)
28
28
  end
29
+
30
+ def build_resolved_input
31
+ processed_input["$ref"].data
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/request_body"
3
+ require "openapi3_parser/context"
4
+ require "openapi3_parser/node/request_body"
4
5
  require "openapi3_parser/node_factory/object"
5
6
  require "openapi3_parser/node_factories/media_type"
6
7
  require "openapi3_parser/node_factories/map"
8
+ require "openapi3_parser/validation/error"
9
+ require "openapi3_parser/validators/media_type"
7
10
 
8
11
  module Openapi3Parser
9
12
  module NodeFactories
@@ -18,14 +21,40 @@ module Openapi3Parser
18
21
  private
19
22
 
20
23
  def build_object(data, context)
21
- Nodes::RequestBody.new(data, context)
24
+ Node::RequestBody.new(data, context)
22
25
  end
23
26
 
24
27
  def content_factory(context)
25
28
  NodeFactories::Map.new(
26
- context, value_factory: NodeFactories::MediaType
29
+ context,
30
+ value_factory: NodeFactories::MediaType,
31
+ validate: ContentValidator
27
32
  )
28
33
  end
34
+
35
+ class ContentValidator
36
+ def self.call(*args)
37
+ new.call(*args)
38
+ end
39
+
40
+ def call(input, context)
41
+ # This validation isn't actually mentioned in the spec, but it
42
+ # doesn't seem to make sense if this is an empty hash.
43
+ return "Expected to have at least 1 item" if input.size.zero?
44
+
45
+ input.keys.each_with_object([]) do |key, memo|
46
+ message = Validators::MediaType.call(key)
47
+ memo << create_error(key, context, message) if message
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def create_error(key, parent_context, message)
54
+ context = Context.next_field(parent_context, key)
55
+ Validation::Error.new(message, context)
56
+ end
57
+ end
29
58
  end
30
59
  end
31
60
  end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/response"
3
+ require "openapi3_parser/context"
4
+ require "openapi3_parser/node/response"
4
5
  require "openapi3_parser/node_factory/object"
5
6
  require "openapi3_parser/node_factory/optional_reference"
6
7
  require "openapi3_parser/node_factories/map"
7
8
  require "openapi3_parser/node_factories/header"
8
9
  require "openapi3_parser/node_factories/media_type"
9
10
  require "openapi3_parser/node_factories/link"
11
+ require "openapi3_parser/validation/error"
12
+ require "openapi3_parser/validators/media_type"
13
+ require "openapi3_parser/validators/component_keys"
10
14
 
11
15
  module Openapi3Parser
12
16
  module NodeFactories
@@ -22,7 +26,7 @@ module Openapi3Parser
22
26
  private
23
27
 
24
28
  def build_object(data, context)
25
- Nodes::Response.new(data, context)
29
+ Node::Response.new(data, context)
26
30
  end
27
31
 
28
32
  def headers_factory(context)
@@ -32,13 +36,29 @@ module Openapi3Parser
32
36
 
33
37
  def content_factory(context)
34
38
  NodeFactories::Map.new(
35
- context, value_factory: NodeFactories::MediaType
39
+ context,
40
+ validate: method(:validate_content),
41
+ value_factory: NodeFactories::MediaType
36
42
  )
37
43
  end
38
44
 
39
45
  def links_factory(context)
40
46
  factory = NodeFactory::OptionalReference.new(NodeFactories::Link)
41
- NodeFactories::Map.new(context, value_factory: factory)
47
+ NodeFactories::Map.new(
48
+ context,
49
+ validate: ->(input, _) { Validators::ComponentKeys.call(input) },
50
+ value_factory: factory
51
+ )
52
+ end
53
+
54
+ def validate_content(input, context)
55
+ input.keys.each_with_object([]) do |key, memo|
56
+ message = Validators::MediaType.call(key)
57
+ next unless message
58
+ memo << Validation::Error.new(
59
+ message, Context.next_field(context, key)
60
+ )
61
+ end
42
62
  end
43
63
  end
44
64
  end
@@ -4,13 +4,23 @@ require "openapi3_parser/context"
4
4
  require "openapi3_parser/node_factories/response"
5
5
  require "openapi3_parser/node_factory/map"
6
6
  require "openapi3_parser/node_factory/optional_reference"
7
- require "openapi3_parser/nodes/responses"
7
+ require "openapi3_parser/node/responses"
8
8
 
9
9
  module Openapi3Parser
10
10
  module NodeFactories
11
11
  class Responses
12
12
  include NodeFactory::Map
13
13
 
14
+ KEY_REGEX = /
15
+ \A
16
+ (
17
+ default
18
+ |
19
+ [1-5]([0-9][0-9]|XX)
20
+ )
21
+ \Z
22
+ /x
23
+
14
24
  private
15
25
 
16
26
  def process_input(input)
@@ -27,7 +37,23 @@ module Openapi3Parser
27
37
  end
28
38
 
29
39
  def build_map(data, context)
30
- Nodes::Responses.new(data, context)
40
+ Node::Responses.new(data, context)
41
+ end
42
+
43
+ def validate(input, _context)
44
+ validate_keys(input.keys)
45
+ end
46
+
47
+ def validate_keys(keys)
48
+ invalid = keys.reject do |key|
49
+ extension?(key) || KEY_REGEX.match(key)
50
+ end
51
+
52
+ return if invalid.empty?
53
+
54
+ codes = invalid.map { |k| "'#{k}'" }.join(", ")
55
+ "Invalid responses keys: #{codes} - default, status codes and status "\
56
+ "code ranges allowed"
31
57
  end
32
58
  end
33
59
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/schema"
3
+ require "openapi3_parser/node/schema"
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"
@@ -23,7 +23,7 @@ module Openapi3Parser
23
23
  field "exclusiveMinimum", input_type: :boolean, default: false
24
24
  field "maxLength", input_type: Integer
25
25
  field "minLength", input_type: Integer, default: 0
26
- field :pattern, input_type: String
26
+ field "pattern", input_type: String
27
27
  field "maxItems", input_type: Integer
28
28
  field "minItems", input_type: Integer, default: 0
29
29
  field "uniqueItems", input_type: :boolean, default: false
@@ -59,7 +59,21 @@ module Openapi3Parser
59
59
  private
60
60
 
61
61
  def build_object(data, context)
62
- Nodes::Schema.new(data, context)
62
+ Node::Schema.new(data, context)
63
+ end
64
+
65
+ def validate(input, _context)
66
+ errors = []
67
+
68
+ if input["type"] == "array" && resolved_input["items"].nil?
69
+ errors << "items must be defined for a type of array"
70
+ end
71
+
72
+ if input["readOnly"] == true && input["writeOnly"] == true
73
+ errors << "readOnly and writeOnly cannot both be true"
74
+ end
75
+
76
+ errors
63
77
  end
64
78
 
65
79
  def required_factory(context)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/security_requirement"
3
+ require "openapi3_parser/node/security_requirement"
4
4
  require "openapi3_parser/node_factory/map"
5
5
  require "openapi3_parser/node_factories/array"
6
6
 
@@ -19,7 +19,7 @@ module Openapi3Parser
19
19
  end
20
20
 
21
21
  def build_map(data, context)
22
- Nodes::SecurityRequirement.new(data, context)
22
+ Node::SecurityRequirement.new(data, context)
23
23
  end
24
24
  end
25
25
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/security_scheme"
3
+ require "openapi3_parser/node/security_scheme"
4
4
  require "openapi3_parser/node_factories/oauth_flows"
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
  private
25
25
 
26
26
  def build_object(data, context)
27
- Nodes::SecurityScheme.new(data, context)
27
+ Node::SecurityScheme.new(data, context)
28
28
  end
29
29
 
30
30
  def flows_factory(context)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/server"
3
+ require "openapi3_parser/node/server"
4
4
  require "openapi3_parser/node_factory/object"
5
5
  require "openapi3_parser/node_factories/server_variable"
6
6
  require "openapi3_parser/node_factories/map"
@@ -18,7 +18,7 @@ module Openapi3Parser
18
18
  private
19
19
 
20
20
  def build_object(data, context)
21
- Nodes::Server.new(data, context)
21
+ Node::Server.new(data, context)
22
22
  end
23
23
 
24
24
  def variables_factory(context)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/server_variable"
3
+ require "openapi3_parser/node/server_variable"
4
4
  require "openapi3_parser/node_factory/object"
5
5
  require "openapi3_parser/node_factories/array"
6
6
 
@@ -29,7 +29,7 @@ module Openapi3Parser
29
29
  end
30
30
 
31
31
  def build_object(data, context)
32
- Nodes::ServerVariable.new(data, context)
32
+ Node::ServerVariable.new(data, context)
33
33
  end
34
34
  end
35
35
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/tag"
3
+ require "openapi3_parser/node/tag"
4
4
  require "openapi3_parser/node_factory/object"
5
5
  require "openapi3_parser/node_factories/external_documentation"
6
6
 
@@ -17,7 +17,7 @@ module Openapi3Parser
17
17
  private
18
18
 
19
19
  def build_object(data, context)
20
- Nodes::Tag.new(data, context)
20
+ Node::Tag.new(data, context)
21
21
  end
22
22
  end
23
23
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/nodes/xml"
3
+ require "openapi3_parser/node/xml"
4
4
  require "openapi3_parser/node_factory/object"
5
+ require "openapi3_parser/validators/absolute_uri"
5
6
 
6
7
  module Openapi3Parser
7
8
  module NodeFactories
@@ -10,7 +11,9 @@ module Openapi3Parser
10
11
 
11
12
  allow_extensions
12
13
  field "name", input_type: String
13
- field "namespace", input_type: String
14
+ field "namespace",
15
+ input_type: String,
16
+ validate: ->(input) { Validators::AbsoluteUri.call(input) }
14
17
  field "prefix", input_type: String
15
18
  field "attribute", input_type: :boolean, default: false
16
19
  field "wrapped", input_type: :boolean, default: false
@@ -18,7 +21,7 @@ module Openapi3Parser
18
21
  private
19
22
 
20
23
  def build_object(data, context)
21
- Nodes::Xml.new(data, context)
24
+ Node::Xml.new(data, context)
22
25
  end
23
26
  end
24
27
  end
@@ -44,7 +44,7 @@ module Openapi3Parser
44
44
 
45
45
  EXTENSION_REGEX = /^x-(.*)/
46
46
 
47
- attr_reader :context
47
+ attr_reader :context, :processed_input
48
48
 
49
49
  def initialize(context)
50
50
  @context = context
@@ -68,9 +68,11 @@ module Openapi3Parser
68
68
  context.input.nil?
69
69
  end
70
70
 
71
- private
71
+ def resolved_input
72
+ @resolved_input ||= processed_input ? build_resolved_input : nil
73
+ end
72
74
 
73
- attr_reader :processed_input
75
+ private
74
76
 
75
77
  def validate(_input, _context); end
76
78
 
@@ -78,9 +80,13 @@ module Openapi3Parser
78
80
  transform_errors(validate(context.input, context))
79
81
  end
80
82
 
83
+ def build_resolved_input
84
+ context.input
85
+ end
86
+
81
87
  def transform_errors(errors)
82
88
  error_objects = Array(errors).map do |error|
83
- if !error.is_a?(Validation::Error)
89
+ if error.is_a?(Validation::Error)
84
90
  error
85
91
  else
86
92
  Validation::Error.new(error, context, self.class)
@@ -17,6 +17,10 @@ module Openapi3Parser
17
17
  @reference_resolver = create_reference_resolver
18
18
  end
19
19
 
20
+ def data
21
+ reference_resolver&.data
22
+ end
23
+
20
24
  private
21
25
 
22
26
  attr_reader :given_reference, :factory, :reference_resolver
@@ -35,6 +35,16 @@ module Openapi3Parser
35
35
  data
36
36
  end
37
37
 
38
+ def build_resolved_input
39
+ processed_input.each_with_object({}) do |(key, value), memo|
40
+ memo[key] = if value.respond_to?(:resolved_input)
41
+ value.resolved_input
42
+ else
43
+ value
44
+ end
45
+ end
46
+ end
47
+
38
48
  def default
39
49
  {}
40
50
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ostruct"
4
+
3
5
  require "openapi3_parser/context"
4
6
  require "openapi3_parser/node_factory"
5
7
  require "openapi3_parser/node_factory/field_config"
@@ -33,6 +35,17 @@ module Openapi3Parser
33
35
  def allowed_extensions?
34
36
  @allow_extensions == true
35
37
  end
38
+
39
+ def mutually_exclusive(*fields, required: false)
40
+ @mutually_exclusive ||= []
41
+ @mutually_exclusive << OpenStruct.new(
42
+ fields: fields, required: required
43
+ )
44
+ end
45
+
46
+ def mutually_exclusive_fields
47
+ @mutually_exclusive || []
48
+ end
36
49
  end
37
50
 
38
51
  def self.included(base)
@@ -47,6 +60,10 @@ module Openapi3Parser
47
60
  self.class.allowed_extensions?
48
61
  end
49
62
 
63
+ def mutually_exclusive_fields
64
+ self.class.mutually_exclusive_fields
65
+ end
66
+
50
67
  def field_configs
51
68
  self.class.field_configs || {}
52
69
  end
@@ -55,6 +72,7 @@ module Openapi3Parser
55
72
 
56
73
  def process_input(input)
57
74
  field_configs.each_with_object(input.dup) do |(field, config), memo|
75
+ memo[field] = nil unless memo[field]
58
76
  next unless config.factory?
59
77
  next_context = Context.next_field(context, field)
60
78
  memo[field] = config.initialize_factory(next_context, self)
@@ -74,6 +92,17 @@ module Openapi3Parser
74
92
  def build_object(data, _context)
75
93
  data
76
94
  end
95
+
96
+ def build_resolved_input
97
+ processed_input.each_with_object({}) do |(key, value), memo|
98
+ next if value.respond_to?(:nil_input?) && value.nil_input?
99
+ memo[key] = if value.respond_to?(:resolved_input)
100
+ value.resolved_input
101
+ else
102
+ value
103
+ end
104
+ end
105
+ end
77
106
  end
78
107
  end
79
108
  end