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