openapi3_parser 0.4.0 → 0.5.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 (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