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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +8 -0
- data/README.md +1 -1
- data/TODO.md +4 -4
- data/lib/openapi3_parser/array_sentence.rb +12 -0
- data/lib/openapi3_parser/cautious_dig.rb +39 -0
- data/lib/openapi3_parser/context.rb +53 -1
- data/lib/openapi3_parser/context/location.rb +1 -0
- data/lib/openapi3_parser/context/pointer.rb +67 -5
- data/lib/openapi3_parser/document.rb +45 -4
- data/lib/openapi3_parser/error.rb +9 -0
- data/lib/openapi3_parser/node/array.rb +14 -4
- data/lib/openapi3_parser/node/map.rb +45 -3
- data/lib/openapi3_parser/node/object.rb +25 -5
- data/lib/openapi3_parser/node_factory.rb +0 -150
- data/lib/openapi3_parser/node_factory/array.rb +198 -0
- data/lib/openapi3_parser/node_factory/callback.rb +24 -0
- data/lib/openapi3_parser/{node_factories → node_factory}/components.rb +24 -25
- data/lib/openapi3_parser/{node_factories → node_factory}/contact.rb +5 -6
- data/lib/openapi3_parser/{node_factories → node_factory}/discriminator.rb +6 -7
- data/lib/openapi3_parser/{node_factories → node_factory}/encoding.rb +6 -8
- data/lib/openapi3_parser/{node_factories → node_factory}/example.rb +4 -5
- data/lib/openapi3_parser/{node_factories → node_factory}/external_documentation.rb +4 -5
- data/lib/openapi3_parser/node_factory/field.rb +129 -0
- data/lib/openapi3_parser/node_factory/fields/reference.rb +54 -18
- data/lib/openapi3_parser/{node_factories → node_factory}/header.rb +4 -5
- data/lib/openapi3_parser/{node_factories → node_factory}/info.rb +6 -7
- data/lib/openapi3_parser/{node_factories → node_factory}/license.rb +4 -5
- data/lib/openapi3_parser/{node_factories → node_factory}/link.rb +6 -8
- data/lib/openapi3_parser/node_factory/map.rb +206 -21
- data/lib/openapi3_parser/{node_factories → node_factory}/media_type.rb +17 -16
- data/lib/openapi3_parser/{node_factories → node_factory}/oauth_flow.rb +2 -4
- data/lib/openapi3_parser/{node_factories → node_factory}/oauth_flows.rb +4 -6
- data/lib/openapi3_parser/node_factory/object.rb +66 -63
- data/lib/openapi3_parser/node_factory/object_factory/dsl.rb +50 -0
- data/lib/openapi3_parser/node_factory/object_factory/field_config.rb +88 -0
- data/lib/openapi3_parser/node_factory/object_factory/node_builder.rb +96 -0
- data/lib/openapi3_parser/node_factory/object_factory/validator.rb +172 -0
- data/lib/openapi3_parser/node_factory/openapi.rb +65 -0
- data/lib/openapi3_parser/node_factory/operation.rb +87 -0
- data/lib/openapi3_parser/node_factory/optional_reference.rb +7 -3
- data/lib/openapi3_parser/{node_factories → node_factory}/parameter.rb +16 -22
- data/lib/openapi3_parser/node_factory/parameter_like.rb +42 -0
- data/lib/openapi3_parser/{node_factories → node_factory}/path_item.rb +27 -29
- data/lib/openapi3_parser/{node_factories → node_factory}/paths.rb +21 -27
- data/lib/openapi3_parser/node_factory/recursive_pointer.rb +17 -0
- data/lib/openapi3_parser/node_factory/reference.rb +48 -0
- data/lib/openapi3_parser/{node_factories → node_factory}/request_body.rb +15 -19
- data/lib/openapi3_parser/{node_factories → node_factory}/response.rb +18 -21
- data/lib/openapi3_parser/node_factory/responses.rb +51 -0
- data/lib/openapi3_parser/{node_factories → node_factory}/schema.rb +33 -33
- data/lib/openapi3_parser/node_factory/security_requirement.rb +21 -0
- data/lib/openapi3_parser/{node_factories → node_factory}/security_scheme.rb +4 -6
- data/lib/openapi3_parser/{node_factories → node_factory}/server.rb +6 -8
- data/lib/openapi3_parser/{node_factories → node_factory}/server_variable.rb +10 -12
- data/lib/openapi3_parser/{node_factories → node_factory}/tag.rb +4 -6
- data/lib/openapi3_parser/node_factory/type_checker.rb +103 -0
- data/lib/openapi3_parser/{node_factories → node_factory}/xml.rb +4 -5
- data/lib/openapi3_parser/source/reference_resolver.rb +3 -3
- data/lib/openapi3_parser/validation/error.rb +9 -0
- data/lib/openapi3_parser/validation/input_validator.rb +18 -0
- data/lib/openapi3_parser/validation/validatable.rb +44 -0
- data/lib/openapi3_parser/validators/mutually_exclusive_fields.rb +121 -0
- data/lib/openapi3_parser/validators/required_fields.rb +37 -0
- data/lib/openapi3_parser/validators/unexpected_fields.rb +52 -0
- data/lib/openapi3_parser/version.rb +1 -1
- metadata +48 -38
- data/lib/openapi3_parser/node_factories/array.rb +0 -114
- data/lib/openapi3_parser/node_factories/callback.rb +0 -27
- data/lib/openapi3_parser/node_factories/map.rb +0 -120
- data/lib/openapi3_parser/node_factories/openapi.rb +0 -62
- data/lib/openapi3_parser/node_factories/operation.rb +0 -84
- data/lib/openapi3_parser/node_factories/parameter/parameter_like.rb +0 -41
- data/lib/openapi3_parser/node_factories/reference.rb +0 -35
- data/lib/openapi3_parser/node_factories/responses.rb +0 -60
- data/lib/openapi3_parser/node_factories/security_requirement.rb +0 -26
- data/lib/openapi3_parser/node_factory/field_config.rb +0 -88
- data/lib/openapi3_parser/node_factory/object/node_builder.rb +0 -97
- 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/
|
5
|
+
require "openapi3_parser/node_factory/array"
|
6
6
|
|
7
7
|
module Openapi3Parser
|
8
|
-
module
|
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
|
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
|
-
|
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/
|
5
|
+
require "openapi3_parser/node_factory/external_documentation"
|
6
6
|
|
7
7
|
module Openapi3Parser
|
8
|
-
module
|
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:
|
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
|
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:
|
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:
|
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
|
28
|
-
|
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
|