openapi_parser_firetail 1.0.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 +7 -0
  2. data/.github/workflows/ci.yaml +25 -0
  3. data/.gitignore +15 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +13 -0
  6. data/.rubocop_ignore.yml +6 -0
  7. data/.ruby-version +1 -0
  8. data/CHANGELOG.md +132 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +6 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +112 -0
  13. data/Rakefile +10 -0
  14. data/Steepfile +11 -0
  15. data/bin/console +14 -0
  16. data/bin/setup +8 -0
  17. data/lib/openapi_parser/concern.rb +5 -0
  18. data/lib/openapi_parser/concerns/expandable.rb +87 -0
  19. data/lib/openapi_parser/concerns/findable.rb +54 -0
  20. data/lib/openapi_parser/concerns/media_type_selectable.rb +29 -0
  21. data/lib/openapi_parser/concerns/parameter_validatable.rb +62 -0
  22. data/lib/openapi_parser/concerns/parser/core.rb +21 -0
  23. data/lib/openapi_parser/concerns/parser/hash.rb +10 -0
  24. data/lib/openapi_parser/concerns/parser/hash_body.rb +12 -0
  25. data/lib/openapi_parser/concerns/parser/list.rb +10 -0
  26. data/lib/openapi_parser/concerns/parser/object.rb +14 -0
  27. data/lib/openapi_parser/concerns/parser/value.rb +14 -0
  28. data/lib/openapi_parser/concerns/parser.rb +45 -0
  29. data/lib/openapi_parser/concerns/schema_loader/base.rb +28 -0
  30. data/lib/openapi_parser/concerns/schema_loader/creator.rb +48 -0
  31. data/lib/openapi_parser/concerns/schema_loader/hash_body_loader.rb +37 -0
  32. data/lib/openapi_parser/concerns/schema_loader/hash_objects_loader.rb +29 -0
  33. data/lib/openapi_parser/concerns/schema_loader/list_loader.rb +28 -0
  34. data/lib/openapi_parser/concerns/schema_loader/objects_loader.rb +21 -0
  35. data/lib/openapi_parser/concerns/schema_loader/values_loader.rb +10 -0
  36. data/lib/openapi_parser/concerns/schema_loader.rb +58 -0
  37. data/lib/openapi_parser/config.rb +55 -0
  38. data/lib/openapi_parser/errors.rb +281 -0
  39. data/lib/openapi_parser/parameter_validator.rb +33 -0
  40. data/lib/openapi_parser/path_item_finder.rb +161 -0
  41. data/lib/openapi_parser/reference_expander.rb +9 -0
  42. data/lib/openapi_parser/request_operation.rb +90 -0
  43. data/lib/openapi_parser/schema_validator/all_of_validator.rb +40 -0
  44. data/lib/openapi_parser/schema_validator/any_of_validator.rb +18 -0
  45. data/lib/openapi_parser/schema_validator/array_validator.rb +32 -0
  46. data/lib/openapi_parser/schema_validator/base.rb +39 -0
  47. data/lib/openapi_parser/schema_validator/boolean_validator.rb +29 -0
  48. data/lib/openapi_parser/schema_validator/enumable.rb +13 -0
  49. data/lib/openapi_parser/schema_validator/float_validator.rb +34 -0
  50. data/lib/openapi_parser/schema_validator/integer_validator.rb +32 -0
  51. data/lib/openapi_parser/schema_validator/minimum_maximum.rb +38 -0
  52. data/lib/openapi_parser/schema_validator/nil_validator.rb +11 -0
  53. data/lib/openapi_parser/schema_validator/object_validator.rb +56 -0
  54. data/lib/openapi_parser/schema_validator/one_of_validator.rb +22 -0
  55. data/lib/openapi_parser/schema_validator/options.rb +29 -0
  56. data/lib/openapi_parser/schema_validator/string_validator.rb +108 -0
  57. data/lib/openapi_parser/schema_validator/unspecified_type_validator.rb +8 -0
  58. data/lib/openapi_parser/schema_validator.rb +164 -0
  59. data/lib/openapi_parser/schemas/base.rb +28 -0
  60. data/lib/openapi_parser/schemas/classes.rb +22 -0
  61. data/lib/openapi_parser/schemas/components.rb +32 -0
  62. data/lib/openapi_parser/schemas/discriminator.rb +11 -0
  63. data/lib/openapi_parser/schemas/header.rb +18 -0
  64. data/lib/openapi_parser/schemas/info.rb +6 -0
  65. data/lib/openapi_parser/schemas/media_type.rb +18 -0
  66. data/lib/openapi_parser/schemas/openapi.rb +63 -0
  67. data/lib/openapi_parser/schemas/operation.rb +50 -0
  68. data/lib/openapi_parser/schemas/parameter.rb +20 -0
  69. data/lib/openapi_parser/schemas/path_item.rb +22 -0
  70. data/lib/openapi_parser/schemas/paths.rb +7 -0
  71. data/lib/openapi_parser/schemas/reference.rb +7 -0
  72. data/lib/openapi_parser/schemas/request_body.rb +34 -0
  73. data/lib/openapi_parser/schemas/response.rb +54 -0
  74. data/lib/openapi_parser/schemas/responses.rb +56 -0
  75. data/lib/openapi_parser/schemas/schema.rb +117 -0
  76. data/lib/openapi_parser/schemas/security.rb +7 -0
  77. data/lib/openapi_parser/schemas/security_schemes.rb +20 -0
  78. data/lib/openapi_parser/schemas.rb +20 -0
  79. data/lib/openapi_parser/version.rb +3 -0
  80. data/lib/openapi_parser.rb +108 -0
  81. data/openapi_parser.gemspec +43 -0
  82. data/sig/openapi_parser/config.rbs +19 -0
  83. data/sig/openapi_parser/errors.rbs +22 -0
  84. data/sig/openapi_parser/reference_expander.rbs +3 -0
  85. data/sig/openapi_parser/schema_validator.rbs +46 -0
  86. data/sig/openapi_parser/schema_validators/base.rbs +18 -0
  87. data/sig/openapi_parser/schema_validators/options.rbs +17 -0
  88. data/sig/openapi_parser/schemas/base.rbs +17 -0
  89. data/sig/openapi_parser/version.rbs +3 -0
  90. data/sig/openapi_parser.rbs +19 -0
  91. data/sig/types.rbs +13 -0
  92. data/sig/wip_types.rbs +64 -0
  93. metadata +288 -0
@@ -0,0 +1,281 @@
1
+ module OpenAPIParser
2
+ class OpenAPIError < StandardError
3
+ def initialize(reference)
4
+ @reference = reference
5
+ end
6
+ end
7
+
8
+ class MissingReferenceError < OpenAPIError
9
+ def message
10
+ "'#{@reference}' was referenced but could not be found"
11
+ end
12
+ end
13
+
14
+ class ValidateError < OpenAPIError
15
+ def initialize(data, type, reference)
16
+ super(reference)
17
+ @data = data
18
+ @type = type
19
+ end
20
+
21
+ def message
22
+ "#{@reference} expected #{@type}, but received #{@data.class}: #{@data.inspect}"
23
+ end
24
+
25
+ class << self
26
+ # create ValidateError for SchemaValidator return data
27
+ # @param [Object] value
28
+ # @param [OpenAPIParser::Schemas::Base] schema
29
+ def build_error_result(value, schema)
30
+ [nil, OpenAPIParser::ValidateError.new(value, schema.type, schema.object_reference)]
31
+ end
32
+ end
33
+ end
34
+
35
+ class NotNullError < OpenAPIError
36
+ def message
37
+ "#{@reference} does not allow null values"
38
+ end
39
+ end
40
+
41
+ class NotExistRequiredKey < OpenAPIError
42
+ def initialize(keys, reference)
43
+ super(reference)
44
+ @keys = keys
45
+ end
46
+
47
+ def message
48
+ "#{@reference} missing required parameters: #{@keys.join(", ")}"
49
+ end
50
+ end
51
+
52
+ class NotExistPropertyDefinition < OpenAPIError
53
+ def initialize(keys, reference)
54
+ super(reference)
55
+ @keys = keys
56
+ end
57
+
58
+ def message
59
+ "#{@reference} does not define properties: #{@keys.join(", ")}"
60
+ end
61
+ end
62
+
63
+ class NotExistDiscriminatorMappedSchema < OpenAPIError
64
+ def initialize(mapped_schema_reference, reference)
65
+ super(reference)
66
+ @mapped_schema_reference = mapped_schema_reference
67
+ end
68
+
69
+ def message
70
+ "discriminator mapped schema #{@mapped_schema_reference} does not exist in #{@reference}"
71
+ end
72
+ end
73
+
74
+ class NotExistDiscriminatorPropertyName < OpenAPIError
75
+ def initialize(key, value, reference)
76
+ super(reference)
77
+ @key = key
78
+ @value = value
79
+ end
80
+
81
+ def message
82
+ "discriminator propertyName #{@key} does not exist in value #{@value.inspect} in #{@reference}"
83
+ end
84
+ end
85
+
86
+ class NotOneOf < OpenAPIError
87
+ def initialize(value, reference)
88
+ super(reference)
89
+ @value = value
90
+ end
91
+
92
+ def message
93
+ "#{@value.inspect} isn't one of in #{@reference}"
94
+ end
95
+ end
96
+
97
+ class NotAnyOf < OpenAPIError
98
+ def initialize(value, reference)
99
+ super(reference)
100
+ @value = value
101
+ end
102
+
103
+ def message
104
+ "#{@value.inspect} isn't any of in #{@reference}"
105
+ end
106
+ end
107
+
108
+ class NotEnumInclude < OpenAPIError
109
+ def initialize(value, reference)
110
+ super(reference)
111
+ @value = value
112
+ end
113
+
114
+ def message
115
+ "#{@value.inspect} isn't part of the enum in #{@reference}"
116
+ end
117
+ end
118
+
119
+ class LessThanMinimum < OpenAPIError
120
+ def initialize(value, reference)
121
+ super(reference)
122
+ @value = value
123
+ end
124
+
125
+ def message
126
+ "#{@reference} #{@value.inspect} is less than minimum value"
127
+ end
128
+ end
129
+
130
+ class LessThanExclusiveMinimum < OpenAPIError
131
+ def initialize(value, reference)
132
+ super(reference)
133
+ @value = value
134
+ end
135
+
136
+ def message
137
+ "#{@reference} #{@value.inspect} cannot be less than or equal to exclusive minimum value"
138
+ end
139
+ end
140
+
141
+ class MoreThanMaximum < OpenAPIError
142
+ def initialize(value, reference)
143
+ super(reference)
144
+ @value = value
145
+ end
146
+
147
+ def message
148
+ "#{@reference} #{@value.inspect} is more than maximum value"
149
+ end
150
+ end
151
+
152
+ class MoreThanExclusiveMaximum < OpenAPIError
153
+ def initialize(value, reference)
154
+ super(reference)
155
+ @value = value
156
+ end
157
+
158
+ def message
159
+ "#{@reference} #{@value.inspect} cannot be more than or equal to exclusive maximum value"
160
+ end
161
+ end
162
+
163
+ class InvalidPattern < OpenAPIError
164
+ def initialize(value, pattern, reference, example)
165
+ super(reference)
166
+ @value = value
167
+ @pattern = pattern
168
+ @example = example
169
+ end
170
+
171
+ def message
172
+ "#{@reference} pattern #{@pattern} does not match value: #{@value.inspect}#{@example ? ", example: #{@example}" : ""}"
173
+ end
174
+ end
175
+
176
+ class InvalidEmailFormat < OpenAPIError
177
+ def initialize(value, reference)
178
+ super(reference)
179
+ @value = value
180
+ end
181
+
182
+ def message
183
+ "#{@reference} email address format does not match value: #{@value.inspect}"
184
+ end
185
+ end
186
+
187
+ class InvalidUUIDFormat < OpenAPIError
188
+ def initialize(value, reference)
189
+ super(reference)
190
+ @value = value
191
+ end
192
+
193
+ def message
194
+ "#{@reference} Value: #{@value.inspect} is not conformant with UUID format"
195
+ end
196
+ end
197
+
198
+ class InvalidDateFormat < OpenAPIError
199
+ def initialize(value, reference)
200
+ super(reference)
201
+ @value = value
202
+ end
203
+
204
+ def message
205
+ "#{@reference} Value: #{@value.inspect} is not conformant with date format"
206
+ end
207
+ end
208
+
209
+ class InvalidDateTimeFormat < OpenAPIError
210
+ def initialize(value, reference)
211
+ super(reference)
212
+ @value = value
213
+ end
214
+
215
+ def message
216
+ "#{@reference} Value: #{@value.inspect} is not conformant with date-time format"
217
+ end
218
+ end
219
+
220
+ class NotExistStatusCodeDefinition < OpenAPIError
221
+ def message
222
+ "#{@reference} status code definition does not exist"
223
+ end
224
+ end
225
+
226
+ class NotExistContentTypeDefinition < OpenAPIError
227
+ def message
228
+ "#{@reference} response definition does not exist"
229
+ end
230
+ end
231
+
232
+ class MoreThanMaxLength < OpenAPIError
233
+ def initialize(value, reference)
234
+ super(reference)
235
+ @value = value
236
+ end
237
+
238
+ def message
239
+ "#{@reference} #{@value.inspect} is longer than max length"
240
+ end
241
+ end
242
+
243
+ class LessThanMinLength < OpenAPIError
244
+ def initialize(value, reference)
245
+ super(reference)
246
+ @value = value
247
+ end
248
+
249
+ def message
250
+ "#{@reference} #{@value.inspect} is shorter than min length"
251
+ end
252
+ end
253
+
254
+ class MoreThanMaxItems < OpenAPIError
255
+ def initialize(value, reference)
256
+ super(reference)
257
+ @value = value
258
+ end
259
+
260
+ def message
261
+ "#{@reference} #{@value.inspect} contains more than max items"
262
+ end
263
+ end
264
+
265
+ class LessThanMinItems < OpenAPIError
266
+ def initialize(value, reference)
267
+ super(reference)
268
+ @value = value
269
+ end
270
+
271
+ def message
272
+ "#{@reference} #{@value.inspect} contains fewer than min items"
273
+ end
274
+ end
275
+
276
+ class ValidateSecurityError < OpenAPIError
277
+ def message
278
+ "access denied"
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,33 @@
1
+ class OpenAPIParser::ParameterValidator
2
+ class << self
3
+ # @param [Hash{String => Parameter}] parameters_hash
4
+ # @param [Hash] params
5
+ # @param [String] object_reference
6
+ # @param [OpenAPIParser::SchemaValidator::Options] options
7
+ # @param [Boolean] is_header is header or not (ignore params key case)
8
+ def validate_parameter(parameters_hash, params, object_reference, options, is_header = false)
9
+ no_exist_required_key = []
10
+
11
+ params_key_converted = params.keys.map { |k| [convert_key(k, is_header), k] }.to_h
12
+ parameters_hash.each do |k, v|
13
+ key = params_key_converted[convert_key(k, is_header)]
14
+ if params.include?(key)
15
+ coerced = v.validate_params(params[key], options)
16
+ params[key] = coerced if options.coerce_value
17
+ elsif v.required
18
+ no_exist_required_key << k
19
+ end
20
+ end
21
+
22
+ raise OpenAPIParser::NotExistRequiredKey.new(no_exist_required_key, object_reference) unless no_exist_required_key.empty?
23
+
24
+ params
25
+ end
26
+
27
+ private
28
+
29
+ def convert_key(k, is_header)
30
+ is_header ? k&.downcase : k
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,161 @@
1
+ class OpenAPIParser::PathItemFinder
2
+ # @param [OpenAPIParser::Schemas::Paths] paths
3
+ def initialize(paths)
4
+ @paths = paths
5
+ end
6
+
7
+ # find operation object and if not exist return nil
8
+ # @param [String, Symbol] http_method like (get, post .... allow symbol)
9
+ # @param [String] request_path
10
+ # @return [Result, nil]
11
+ def operation_object(http_method, request_path)
12
+ if (path_item_object = @paths.path[request_path])
13
+ if (op = path_item_object.operation(http_method))
14
+ return Result.new(path_item_object, op, request_path, {}) # find no path_params path
15
+ end
16
+ end
17
+
18
+ # check with path_params
19
+ parse_request_path(http_method, request_path)
20
+ end
21
+
22
+ class Result
23
+ attr_reader :path_item_object, :operation_object, :original_path, :path_params
24
+ # @!attribute [r] path_item_object
25
+ # @return [OpenAPIParser::Schemas::PathItem]
26
+ # @!attribute [r] operation_object
27
+ # @return [OpenAPIParser::Schemas::Operation]
28
+ # @!attribute [r] path_params
29
+ # @return [Hash{String => String}]
30
+ # @!attribute [r] original_path
31
+ # @return [String]
32
+
33
+ def initialize(path_item_object, operation_object, original_path, path_params)
34
+ @path_item_object = path_item_object
35
+ @operation_object = operation_object
36
+ @original_path = original_path
37
+ @path_params = path_params
38
+ end
39
+ end
40
+
41
+ def parse_path_parameters(schema_path, request_path)
42
+ parameters = path_parameters(schema_path)
43
+ return nil if parameters.empty?
44
+
45
+ # If there are regex special characters in the path, the regex will
46
+ # be too permissive, so escape the non-parameter parts.
47
+ components = []
48
+ unprocessed = schema_path.dup
49
+ parameters.each do |parameter|
50
+ parts = unprocessed.partition(parameter)
51
+ components << Regexp.escape(parts[0]) unless parts[0] == ''
52
+ components << "(?<#{param_name(parameter)}>.+)"
53
+ unprocessed = parts[2]
54
+ end
55
+ components << Regexp.escape(unprocessed) unless unprocessed == ''
56
+
57
+ regex = components.join('')
58
+ matches = request_path.match(regex)
59
+ return nil unless matches
60
+
61
+ # Match up the captured names with the captured values as a hash
62
+ matches.names.zip(matches.captures).to_h
63
+ end
64
+
65
+ private
66
+ def path_parameters(schema_path)
67
+ # OAS3 follows a RFC6570 subset for URL templates
68
+ # https://swagger.io/docs/specification/serialization/#uri-templates
69
+ # A URL template param can be preceded optionally by a "." or ";", and can be succeeded optionally by a "*";
70
+ # this regex returns a match of the full parameter name with all of these modifiers. Ex: {;id*}
71
+ parameters = schema_path.scan(/(\{[\.;]*[^\{\*\}]+\**\})/)
72
+ # The `String#scan` method returns an array of arrays; we want an array of strings
73
+ parameters.collect { |param| param[0] }
74
+ end
75
+
76
+ # check if there is a identical path in the schema (without any param)
77
+ def matches_directly?(request_path, http_method)
78
+ @paths.path[request_path]&.operation(http_method)
79
+ end
80
+
81
+ # used to filter paths with different depth or without given http method
82
+ def different_depth_or_method?(splitted_schema_path, splitted_request_path, path_item, http_method)
83
+ splitted_schema_path.size != splitted_request_path.size || !path_item.operation(http_method)
84
+ end
85
+
86
+ # check if the path item is a template
87
+ # EXAMPLE: path_template?('{id}') => true
88
+ def path_template?(schema_path_item)
89
+ schema_path_item.start_with?('{') && schema_path_item.end_with?('}')
90
+ end
91
+
92
+ # get the parameter name from the schema path item
93
+ # EXAMPLE: param_name('{id}') => 'id'
94
+ def param_name(schema_path_item)
95
+ schema_path_item[1..(schema_path_item.length - 2)]
96
+ end
97
+
98
+ # extract params by comparing the request path and the path from schema
99
+ # EXAMPLE:
100
+ # extract_params(['org', '1', 'user', '2', 'edit'], ['org', '{org_id}', 'user', '{user_id}'])
101
+ # => { 'org_id' => 1, 'user_id' => 2 }
102
+ # return nil if the schema does not match
103
+ def extract_params(splitted_request_path, splitted_schema_path)
104
+ splitted_request_path.zip(splitted_schema_path).reduce({}) do |result, zip_item|
105
+ request_path_item, schema_path_item = zip_item
106
+
107
+ params = parse_path_parameters(schema_path_item, request_path_item)
108
+ if params
109
+ result.merge!(params)
110
+ else
111
+ return if schema_path_item != request_path_item
112
+ end
113
+
114
+ result
115
+ end
116
+ end
117
+
118
+ # find all matching paths with parameters extracted
119
+ # EXAMPLE:
120
+ # [
121
+ # ['/user/{id}/edit', { 'id' => 1 }],
122
+ # ['/user/{id}/{action}', { 'id' => 1, 'action' => 'edit' }],
123
+ # ]
124
+ def matching_paths_with_params(request_path, http_method)
125
+ splitted_request_path = request_path.split('/')
126
+
127
+ @paths.path.reduce([]) do |result, item|
128
+ path, path_item = item
129
+ splitted_schema_path = path.split('/')
130
+
131
+ next result if different_depth_or_method?(splitted_schema_path, splitted_request_path, path_item, http_method)
132
+
133
+ extracted_params = extract_params(splitted_request_path, splitted_schema_path)
134
+ result << [path, extracted_params] if extracted_params
135
+ result
136
+ end
137
+ end
138
+
139
+ # find matching path and extract params
140
+ # EXAMPLE: find_path_and_params('get', '/user/1') => ['/user/{id}', { 'id' => 1 }]
141
+ def find_path_and_params(http_method, request_path)
142
+ return [request_path, {}] if matches_directly?(request_path, http_method)
143
+
144
+ matching = matching_paths_with_params(request_path, http_method)
145
+
146
+ # if there are many matching paths, return the one with the smallest number of params
147
+ # (prefer /user/{id}/action over /user/{param_1}/{param_2} )
148
+ matching.min_by { |match| match[1].size }
149
+ end
150
+
151
+ def parse_request_path(http_method, request_path)
152
+ original_path, path_params = find_path_and_params(http_method, request_path)
153
+ return nil unless original_path # # can't find
154
+
155
+ path_item_object = @paths.path[original_path]
156
+ obj = path_item_object.operation(http_method.to_s)
157
+ return nil unless obj
158
+
159
+ Result.new(path_item_object, obj, original_path, path_params)
160
+ end
161
+ end
@@ -0,0 +1,9 @@
1
+ class OpenAPIParser::ReferenceExpander
2
+ class << self
3
+ # @param [OpenAPIParser::Schemas::OpenAPI] openapi
4
+ def expand(openapi, validate_references)
5
+ openapi.expand_reference(openapi, validate_references)
6
+ openapi.purge_object_cache
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,90 @@
1
+ # binding request data and operation object
2
+
3
+ class OpenAPIParser::RequestOperation
4
+ class << self
5
+ # @param [OpenAPIParser::Config] config
6
+ # @param [OpenAPIParser::PathItemFinder] path_item_finder
7
+ # @return [OpenAPIParser::RequestOperation, nil]
8
+ def create(http_method, request_path, path_item_finder, config, security_schemes = {})
9
+ result = path_item_finder.operation_object(http_method, request_path)
10
+ return nil unless result
11
+
12
+ self.new(http_method, result, config, security_schemes)
13
+ end
14
+ end
15
+
16
+ # @!attribute [r] operation_object
17
+ # @return [OpenAPIParser::Schemas::Operation]
18
+ # @!attribute [r] path_params
19
+ # @return [Hash{String => String}]
20
+ # @!attribute [r] config
21
+ # @return [OpenAPIParser::Config]
22
+ # @!attribute [r] http_method
23
+ # @return [String]
24
+ # @!attribute [r] original_path
25
+ # @return [String]
26
+ # @!attribute [r] path_item
27
+ # @return [OpenAPIParser::Schemas::PathItem]
28
+ attr_reader :operation_object, :path_params, :config, :http_method, :original_path, :path_item, :security_schemes
29
+
30
+ # @param [String] http_method
31
+ # @param [OpenAPIParser::PathItemFinder::Result] result
32
+ # @param [OpenAPIParser::Config] config
33
+ def initialize(http_method, result, config, security_schemes)
34
+ @http_method = http_method.to_s
35
+ @original_path = result.original_path
36
+ @operation_object = result.operation_object
37
+ @path_params = result.path_params || {}
38
+ @path_item = result.path_item_object
39
+ @config = config
40
+ @security_schemes = security_schemes
41
+ end
42
+
43
+ def validate_security(security_schemes, headers)
44
+ operation_object.validate_security_schemes(security_schemes, headers)
45
+ end
46
+
47
+ def validate_path_params(options = nil)
48
+ options ||= config.path_params_options
49
+ operation_object&.validate_path_params(path_params, options)
50
+ end
51
+
52
+ # @param [String] content_type
53
+ # @param [Hash] params
54
+ # @param [OpenAPIParser::SchemaValidator::Options] options
55
+ def validate_request_body(content_type, params, options = nil)
56
+ options ||= config.request_body_options
57
+ operation_object&.validate_request_body(content_type, params, options)
58
+ end
59
+
60
+ # @param [OpenAPIParser::RequestOperation::ValidatableResponseBody] response_body
61
+ # @param [OpenAPIParser::SchemaValidator::ResponseValidateOptions] response_validate_options
62
+ def validate_response_body(response_body, response_validate_options = nil)
63
+ response_validate_options ||= config.response_validate_options
64
+
65
+ operation_object&.validate_response(response_body, response_validate_options)
66
+ end
67
+
68
+ # @param [Hash] params parameter hash
69
+ # @param [Hash] headers headers hash
70
+ # @param [OpenAPIParser::SchemaValidator::Options] options request validator options
71
+ def validate_request_parameter(params, headers, options = nil)
72
+ options ||= config.request_validator_options
73
+ validate_security(security_schemes, headers)
74
+ operation_object&.validate_request_parameter(params, headers, options)
75
+ end
76
+
77
+ class ValidatableResponseBody
78
+ attr_reader :status_code, :response_data, :headers
79
+
80
+ def initialize(status_code, response_data, headers)
81
+ @status_code = status_code
82
+ @response_data = response_data
83
+ @headers = headers
84
+ end
85
+
86
+ def content_type
87
+ headers['Content-Type'].to_s.split(';').first.to_s
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,40 @@
1
+ # validate AllOf schema
2
+ class OpenAPIParser::SchemaValidator
3
+ class AllOfValidator < Base
4
+ # coerce and validate value
5
+ # @param [Object] value
6
+ # @param [OpenAPIParser::Schemas::Schema] schema
7
+ def coerce_and_validate(value, schema, **keyword_args)
8
+ # if any schema return error, it's not valida all of value
9
+ remaining_keys = value.kind_of?(Hash) ? value.keys : []
10
+ nested_additional_properties = false
11
+ schema.all_of.each do |s|
12
+ # We need to store the reference to all of, so we can perform strict check on allowed properties
13
+ _coerced, err = validatable.validate_schema(
14
+ value,
15
+ s,
16
+ :parent_all_of => true,
17
+ parent_discriminator_schemas: keyword_args[:parent_discriminator_schemas]
18
+ )
19
+
20
+ if s.type == "object"
21
+ remaining_keys -= (s.properties || {}).keys
22
+ nested_additional_properties = true if s.additional_properties
23
+ else
24
+ # If this is not allOf having array of objects inside, but e.g. having another anyOf/oneOf nested
25
+ remaining_keys.clear
26
+ end
27
+
28
+ return [nil, err] if err
29
+ end
30
+
31
+ # If there are nested additionalProperites, we allow not defined extra properties and lean on the specific
32
+ # additionalProperties validation
33
+ if !nested_additional_properties && !remaining_keys.empty?
34
+ return [nil, OpenAPIParser::NotExistPropertyDefinition.new(remaining_keys, schema.object_reference)]
35
+ end
36
+
37
+ [value, nil]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class AnyOfValidator < Base
3
+ # @param [Object] value
4
+ # @param [OpenAPIParser::Schemas::Schema] schema
5
+ def coerce_and_validate(value, schema, **_keyword_args)
6
+ if schema.discriminator
7
+ return validate_discriminator_schema(schema.discriminator, value)
8
+ end
9
+
10
+ # in all schema return error (=true) not any of data
11
+ schema.any_of.each do |s|
12
+ coerced, err = validatable.validate_schema(value, s)
13
+ return [coerced, nil] if err.nil?
14
+ end
15
+ [nil, OpenAPIParser::NotAnyOf.new(value, schema.object_reference)]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class ArrayValidator < Base
3
+ # @param [Array] value
4
+ # @param [OpenAPIParser::Schemas::Schema] schema
5
+ def coerce_and_validate(value, schema, **_keyword_args)
6
+ return OpenAPIParser::ValidateError.build_error_result(value, schema) unless value.kind_of?(Array)
7
+
8
+ value, err = validate_max_min_items(value, schema)
9
+ return [nil, err] if err
10
+
11
+ # array type have an schema in items property
12
+ items_schema = schema.items
13
+ coerced_values = value.map do |v|
14
+ coerced, err = validatable.validate_schema(v, items_schema)
15
+ return [nil, err] if err
16
+
17
+ coerced
18
+ end
19
+
20
+ value.each_index { |idx| value[idx] = coerced_values[idx] } if @coerce_value
21
+
22
+ [value, nil]
23
+ end
24
+
25
+ def validate_max_min_items(value, schema)
26
+ return [nil, OpenAPIParser::MoreThanMaxItems.new(value, schema.object_reference)] if schema.maxItems && value.length > schema.maxItems
27
+ return [nil, OpenAPIParser::LessThanMinItems.new(value, schema.object_reference)] if schema.minItems && value.length < schema.minItems
28
+
29
+ [value, nil]
30
+ end
31
+ end
32
+ end