openapi3_parser 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +8 -0
  4. data/README.md +1 -1
  5. data/TODO.md +4 -4
  6. data/lib/openapi3_parser/array_sentence.rb +12 -0
  7. data/lib/openapi3_parser/cautious_dig.rb +39 -0
  8. data/lib/openapi3_parser/context.rb +53 -1
  9. data/lib/openapi3_parser/context/location.rb +1 -0
  10. data/lib/openapi3_parser/context/pointer.rb +67 -5
  11. data/lib/openapi3_parser/document.rb +45 -4
  12. data/lib/openapi3_parser/error.rb +9 -0
  13. data/lib/openapi3_parser/node/array.rb +14 -4
  14. data/lib/openapi3_parser/node/map.rb +45 -3
  15. data/lib/openapi3_parser/node/object.rb +25 -5
  16. data/lib/openapi3_parser/node_factory.rb +0 -150
  17. data/lib/openapi3_parser/node_factory/array.rb +198 -0
  18. data/lib/openapi3_parser/node_factory/callback.rb +24 -0
  19. data/lib/openapi3_parser/{node_factories → node_factory}/components.rb +24 -25
  20. data/lib/openapi3_parser/{node_factories → node_factory}/contact.rb +5 -6
  21. data/lib/openapi3_parser/{node_factories → node_factory}/discriminator.rb +6 -7
  22. data/lib/openapi3_parser/{node_factories → node_factory}/encoding.rb +6 -8
  23. data/lib/openapi3_parser/{node_factories → node_factory}/example.rb +4 -5
  24. data/lib/openapi3_parser/{node_factories → node_factory}/external_documentation.rb +4 -5
  25. data/lib/openapi3_parser/node_factory/field.rb +129 -0
  26. data/lib/openapi3_parser/node_factory/fields/reference.rb +54 -18
  27. data/lib/openapi3_parser/{node_factories → node_factory}/header.rb +4 -5
  28. data/lib/openapi3_parser/{node_factories → node_factory}/info.rb +6 -7
  29. data/lib/openapi3_parser/{node_factories → node_factory}/license.rb +4 -5
  30. data/lib/openapi3_parser/{node_factories → node_factory}/link.rb +6 -8
  31. data/lib/openapi3_parser/node_factory/map.rb +206 -21
  32. data/lib/openapi3_parser/{node_factories → node_factory}/media_type.rb +17 -16
  33. data/lib/openapi3_parser/{node_factories → node_factory}/oauth_flow.rb +2 -4
  34. data/lib/openapi3_parser/{node_factories → node_factory}/oauth_flows.rb +4 -6
  35. data/lib/openapi3_parser/node_factory/object.rb +66 -63
  36. data/lib/openapi3_parser/node_factory/object_factory/dsl.rb +50 -0
  37. data/lib/openapi3_parser/node_factory/object_factory/field_config.rb +88 -0
  38. data/lib/openapi3_parser/node_factory/object_factory/node_builder.rb +96 -0
  39. data/lib/openapi3_parser/node_factory/object_factory/validator.rb +172 -0
  40. data/lib/openapi3_parser/node_factory/openapi.rb +65 -0
  41. data/lib/openapi3_parser/node_factory/operation.rb +87 -0
  42. data/lib/openapi3_parser/node_factory/optional_reference.rb +7 -3
  43. data/lib/openapi3_parser/{node_factories → node_factory}/parameter.rb +16 -22
  44. data/lib/openapi3_parser/node_factory/parameter_like.rb +42 -0
  45. data/lib/openapi3_parser/{node_factories → node_factory}/path_item.rb +27 -29
  46. data/lib/openapi3_parser/{node_factories → node_factory}/paths.rb +21 -27
  47. data/lib/openapi3_parser/node_factory/recursive_pointer.rb +17 -0
  48. data/lib/openapi3_parser/node_factory/reference.rb +48 -0
  49. data/lib/openapi3_parser/{node_factories → node_factory}/request_body.rb +15 -19
  50. data/lib/openapi3_parser/{node_factories → node_factory}/response.rb +18 -21
  51. data/lib/openapi3_parser/node_factory/responses.rb +51 -0
  52. data/lib/openapi3_parser/{node_factories → node_factory}/schema.rb +33 -33
  53. data/lib/openapi3_parser/node_factory/security_requirement.rb +21 -0
  54. data/lib/openapi3_parser/{node_factories → node_factory}/security_scheme.rb +4 -6
  55. data/lib/openapi3_parser/{node_factories → node_factory}/server.rb +6 -8
  56. data/lib/openapi3_parser/{node_factories → node_factory}/server_variable.rb +10 -12
  57. data/lib/openapi3_parser/{node_factories → node_factory}/tag.rb +4 -6
  58. data/lib/openapi3_parser/node_factory/type_checker.rb +103 -0
  59. data/lib/openapi3_parser/{node_factories → node_factory}/xml.rb +4 -5
  60. data/lib/openapi3_parser/source/reference_resolver.rb +3 -3
  61. data/lib/openapi3_parser/validation/error.rb +9 -0
  62. data/lib/openapi3_parser/validation/input_validator.rb +18 -0
  63. data/lib/openapi3_parser/validation/validatable.rb +44 -0
  64. data/lib/openapi3_parser/validators/mutually_exclusive_fields.rb +121 -0
  65. data/lib/openapi3_parser/validators/required_fields.rb +37 -0
  66. data/lib/openapi3_parser/validators/unexpected_fields.rb +52 -0
  67. data/lib/openapi3_parser/version.rb +1 -1
  68. metadata +48 -38
  69. data/lib/openapi3_parser/node_factories/array.rb +0 -114
  70. data/lib/openapi3_parser/node_factories/callback.rb +0 -27
  71. data/lib/openapi3_parser/node_factories/map.rb +0 -120
  72. data/lib/openapi3_parser/node_factories/openapi.rb +0 -62
  73. data/lib/openapi3_parser/node_factories/operation.rb +0 -84
  74. data/lib/openapi3_parser/node_factories/parameter/parameter_like.rb +0 -41
  75. data/lib/openapi3_parser/node_factories/reference.rb +0 -35
  76. data/lib/openapi3_parser/node_factories/responses.rb +0 -60
  77. data/lib/openapi3_parser/node_factories/security_requirement.rb +0 -26
  78. data/lib/openapi3_parser/node_factory/field_config.rb +0 -88
  79. data/lib/openapi3_parser/node_factory/object/node_builder.rb +0 -97
  80. data/lib/openapi3_parser/node_factory/object/validator.rb +0 -176
@@ -2,32 +2,30 @@
2
2
 
3
3
  require "openapi3_parser/node/server_variable"
4
4
  require "openapi3_parser/node_factory/object"
5
- require "openapi3_parser/node_factories/array"
5
+ require "openapi3_parser/node_factory/array"
6
6
 
7
7
  module Openapi3Parser
8
- module NodeFactories
9
- class ServerVariable
10
- include NodeFactory::Object
11
-
8
+ module NodeFactory
9
+ class ServerVariable < NodeFactory::Object
12
10
  allow_extensions
13
- field "enum", factory: :enum_factory, validate: :validate_enum
11
+ field "enum", factory: :enum_factory
14
12
  field "default", input_type: String, required: true
15
13
  field "description", input_type: String
16
14
 
17
15
  private
18
16
 
19
17
  def enum_factory(context)
20
- NodeFactories::Array.new(
18
+ NodeFactory::Array.new(
21
19
  context,
22
20
  default: nil,
23
- value_input_type: String
21
+ value_input_type: String,
22
+ validate: lambda do |validatable|
23
+ return if validatable.input.any?
24
+ validatable.add_error("Expected atleast one value")
25
+ end
24
26
  )
25
27
  end
26
28
 
27
- def validate_enum(input)
28
- return "Expected atleast one value" if input.empty?
29
- end
30
-
31
29
  def build_object(data, context)
32
30
  Node::ServerVariable.new(data, context)
33
31
  end
@@ -2,17 +2,15 @@
2
2
 
3
3
  require "openapi3_parser/node/tag"
4
4
  require "openapi3_parser/node_factory/object"
5
- require "openapi3_parser/node_factories/external_documentation"
5
+ require "openapi3_parser/node_factory/external_documentation"
6
6
 
7
7
  module Openapi3Parser
8
- module NodeFactories
9
- class Tag
10
- include NodeFactory::Object
11
-
8
+ module NodeFactory
9
+ class Tag < NodeFactory::Object
12
10
  allow_extensions
13
11
  field "name", input_type: String, required: true
14
12
  field "description", input_type: String
15
- field "externalDocs", factory: NodeFactories::ExternalDocumentation
13
+ field "externalDocs", factory: NodeFactory::ExternalDocumentation
16
14
 
17
15
  private
18
16
 
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openapi3_parser/error"
4
+ require "openapi3_parser/node_factory"
5
+
6
+ module Openapi3Parser
7
+ module NodeFactory
8
+ class TypeChecker
9
+ def self.validate_type(validatable, type:, context: nil)
10
+ new(type).validate_type(validatable, context)
11
+ end
12
+
13
+ def self.raise_on_invalid_type(context, type:)
14
+ new(type).raise_on_invalid_type(context)
15
+ end
16
+
17
+ def self.validate_keys(validatable, type:, context: nil)
18
+ new(type).validate_keys(validatable, context)
19
+ end
20
+
21
+ def self.raise_on_invalid_keys(context, type:)
22
+ new(type).raise_on_invalid_keys(context)
23
+ end
24
+
25
+ private_class_method :new
26
+
27
+ def initialize(type)
28
+ @type = type
29
+ end
30
+
31
+ def validate_type(validatable, context)
32
+ return true unless type
33
+ context ||= validatable.context
34
+ valid_type?(context.input).tap do |valid|
35
+ next if valid
36
+ validatable.add_error("Invalid type. #{field_error_message}",
37
+ context)
38
+ end
39
+ end
40
+
41
+ def validate_keys(validatable, context)
42
+ return true unless type
43
+ context ||= validatable.context
44
+ valid_keys?(context.input).tap do |valid|
45
+ next if valid
46
+ validatable.add_error("Invalid keys. #{keys_error_message}",
47
+ context)
48
+ end
49
+ end
50
+
51
+ def raise_on_invalid_type(context)
52
+ return true if !type || valid_type?(context.input)
53
+ raise Error::InvalidType,
54
+ "Invalid type for #{context.location_summary}: "\
55
+ "#{field_error_message}"
56
+ end
57
+
58
+ def raise_on_invalid_keys(context)
59
+ return true if !type || valid_keys?(context.input)
60
+ raise Error::InvalidType,
61
+ "Invalid keys for #{context.location_summary}: "\
62
+ "#{keys_error_message}"
63
+ end
64
+
65
+ private
66
+
67
+ attr_reader :type
68
+
69
+ def valid_type?(input)
70
+ return [true, false].include?(input) if type == :boolean
71
+
72
+ unless type.is_a?(Class)
73
+ raise Error::UnvalidatableType,
74
+ "Expected #{type} to be a Class not a #{type.class}"
75
+ end
76
+
77
+ input.is_a?(type)
78
+ end
79
+
80
+ def field_error_message
81
+ "Expected #{type_name_for_error}"
82
+ end
83
+
84
+ def keys_error_message
85
+ "Expected keys to be of type #{type_name_for_error}"
86
+ end
87
+
88
+ def type_name_for_error
89
+ if type == Hash
90
+ "Object"
91
+ elsif type == :boolean
92
+ "Boolean"
93
+ else
94
+ type.to_s
95
+ end
96
+ end
97
+
98
+ def valid_keys?(input)
99
+ input.keys.all? { |key| valid_type?(key) }
100
+ end
101
+ end
102
+ end
103
+ end
@@ -3,17 +3,16 @@
3
3
  require "openapi3_parser/node/xml"
4
4
  require "openapi3_parser/node_factory/object"
5
5
  require "openapi3_parser/validators/absolute_uri"
6
+ require "openapi3_parser/validation/input_validator"
6
7
 
7
8
  module Openapi3Parser
8
- module NodeFactories
9
- class Xml
10
- include NodeFactory::Object
11
-
9
+ module NodeFactory
10
+ class Xml < NodeFactory::Object
12
11
  allow_extensions
13
12
  field "name", input_type: String
14
13
  field "namespace",
15
14
  input_type: String,
16
- validate: ->(input) { Validators::AbsoluteUri.call(input) }
15
+ validate: Validation::InputValidator.new(Validators::AbsoluteUri)
17
16
  field "prefix", input_type: String
18
17
  field "attribute", input_type: :boolean, default: false
19
18
  field "wrapped", input_type: :boolean, default: false
@@ -16,7 +16,7 @@ module Openapi3Parser
16
16
  @reference_factory ||= begin
17
17
  next_context = Context.reference_field(
18
18
  context,
19
- input: data,
19
+ input: source.data_at_pointer(json_pointer),
20
20
  source: source,
21
21
  pointer_segments: json_pointer
22
22
  )
@@ -24,8 +24,8 @@ module Openapi3Parser
24
24
  end
25
25
  end
26
26
 
27
- def data
28
- source.data_at_pointer(json_pointer)
27
+ def resolved_input
28
+ reference_factory.resolved_input
29
29
  end
30
30
 
31
31
  def valid?
@@ -34,6 +34,7 @@ module Openapi3Parser
34
34
  # @return [String, nil]
35
35
  def for_type
36
36
  return unless factory_class
37
+ return "(anonymous)" unless factory_class.name
37
38
  factory_class.name.split("::").last
38
39
  end
39
40
 
@@ -42,6 +43,14 @@ module Openapi3Parser
42
43
  "#{self.class.name}(message: #{message}, context: #{context}, " \
43
44
  "for_type: #{for_type})"
44
45
  end
46
+
47
+ # @return [Boolean]
48
+ def ==(other)
49
+ return false unless other.instance_of?(self.class)
50
+ message == other.message &&
51
+ context == other.context &&
52
+ factory_class == other.factory_class
53
+ end
45
54
  end
46
55
  end
47
56
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openapi3Parser
4
+ module Validation
5
+ class InputValidator
6
+ attr_reader :callable
7
+
8
+ def initialize(callable)
9
+ @callable = callable
10
+ end
11
+
12
+ def call(validatable)
13
+ error = @callable.call(validatable.input)
14
+ validatable.add_error(error) if error
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openapi3_parser/validation/error"
4
+ require "openapi3_parser/validation/error_collection"
5
+
6
+ module Openapi3Parser
7
+ module Validation
8
+ class Validatable
9
+ attr_reader :context, :errors, :factory
10
+
11
+ UNDEFINED = Class.new
12
+
13
+ def initialize(factory, context: nil)
14
+ @factory = factory
15
+ @context = context || factory.context
16
+ @errors = []
17
+ end
18
+
19
+ def input
20
+ context.input
21
+ end
22
+
23
+ def add_error(error, given_context = nil, factory_class = UNDEFINED)
24
+ return unless error
25
+ return @errors << error if error.is_a?(Validation::Error)
26
+
27
+ @errors << Validation::Error.new(
28
+ error,
29
+ given_context || context,
30
+ factory_class == UNDEFINED ? factory.class : factory_class
31
+ )
32
+ end
33
+
34
+ def add_errors(errors)
35
+ errors = errors.to_a if errors.respond_to?(:to_a)
36
+ errors.each { |e| add_error(e) }
37
+ end
38
+
39
+ def collection
40
+ ErrorCollection.new(errors)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openapi3_parser/array_sentence"
4
+ require "openapi3_parser/error"
5
+
6
+ module Openapi3Parser
7
+ module Validators
8
+ class MutuallyExclusiveFields
9
+ using ArraySentence
10
+ private_class_method :new
11
+
12
+ def self.call(*args)
13
+ new.call(*args)
14
+ end
15
+
16
+ def call(validatable,
17
+ mutually_exclusive_fields:,
18
+ raise_on_invalid: true)
19
+ mutually_exclusive = MutuallyExclusiveFieldErrors.new(
20
+ mutually_exclusive_fields, validatable.input
21
+ )
22
+
23
+ handle_required_errors(validatable,
24
+ mutually_exclusive.required_errors,
25
+ raise_on_invalid)
26
+ handle_exclusive_errors(validatable,
27
+ mutually_exclusive.exclusive_errors,
28
+ raise_on_invalid)
29
+ end
30
+
31
+ private
32
+
33
+ def handle_required_errors(validatable,
34
+ required_errors,
35
+ raise_on_invalid)
36
+ return unless required_errors.any?
37
+
38
+ if raise_on_invalid
39
+ location_summary = validatable.context.location_summary
40
+ raise Error::MissingFields,
41
+ "Mutually exclusive fields for "\
42
+ "#{location_summary}: #{required_errors.first}"
43
+ else
44
+ validatable.add_errors(required_errors)
45
+ end
46
+ end
47
+
48
+ def handle_exclusive_errors(validatable,
49
+ exclusive_errors,
50
+ raise_on_invalid)
51
+ return unless exclusive_errors.any?
52
+
53
+ if raise_on_invalid
54
+ location_summary = validatable.context.location_summary
55
+ raise Error::UnexpectedFields,
56
+ "Mutually exclusive fields for "\
57
+ "#{location_summary}: "\
58
+ "#{exclusive_errors.first}"
59
+ else
60
+ validatable.add_errors(exclusive_errors)
61
+ end
62
+ end
63
+
64
+ class MutuallyExclusiveFieldErrors
65
+ using ArraySentence
66
+
67
+ def initialize(mutually_exclusive_fields, input)
68
+ @mutually_exclusive_fields = mutually_exclusive_fields
69
+ @input = input
70
+ end
71
+
72
+ def required_errors
73
+ errors[:required]
74
+ end
75
+
76
+ def exclusive_errors
77
+ errors[:exclusive]
78
+ end
79
+
80
+ def errors
81
+ @errors ||= begin
82
+ default = { required: [], exclusive: [] }
83
+ mutually_exclusive_fields
84
+ .each_with_object(default) do |exclusive, errors|
85
+ add_error(errors, exclusive)
86
+ end
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ attr_reader :mutually_exclusive_fields, :input
93
+
94
+ def add_error(errors, mutually_exclusive)
95
+ fields = mutually_exclusive.fields
96
+ number_non_nil = count_non_nil_fields(fields)
97
+ if number_non_nil.zero? && mutually_exclusive.required
98
+ errors[:required] << required_error(fields)
99
+ elsif number_non_nil > 1
100
+ errors[:exclusive] << exclusive_error(fields)
101
+ end
102
+ end
103
+
104
+ def count_non_nil_fields(fields)
105
+ fields.count do |field|
106
+ data = input[field]
107
+ data.respond_to?(:nil_input?) ? !data.nil_input? : !data.nil?
108
+ end
109
+ end
110
+
111
+ def required_error(fields)
112
+ "One of #{fields.sentence_join} is required"
113
+ end
114
+
115
+ def exclusive_error(fields)
116
+ "#{fields.sentence_join} are mutually exclusive fields"
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openapi3_parser/array_sentence"
4
+ require "openapi3_parser/error"
5
+
6
+ module Openapi3Parser
7
+ module Validators
8
+ class RequiredFields
9
+ using ArraySentence
10
+ private_class_method :new
11
+
12
+ def self.call(*args)
13
+ new.call(*args)
14
+ end
15
+
16
+ def call(validatable,
17
+ required_fields:,
18
+ raise_on_invalid: true)
19
+ input = validatable.input
20
+ missing_fields = required_fields.select { |name| input[name].nil? }
21
+
22
+ return if missing_fields.empty?
23
+
24
+ if raise_on_invalid
25
+ location_summary = validatable.context.location_summary
26
+ raise Openapi3Parser::Error::MissingFields,
27
+ "Missing required fields for "\
28
+ "#{location_summary}: #{missing_fields.sentence_join}"
29
+ else
30
+ validatable.add_error(
31
+ "Missing required fields: #{missing_fields.sentence_join}"
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end