committee 5.6.1 → 5.6.3

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/committee/drivers/open_api_3/driver.rb +7 -1
  3. data/lib/committee/drivers.rb +2 -3
  4. data/lib/committee/errors.rb +11 -0
  5. data/lib/committee/middleware/base.rb +11 -5
  6. data/lib/committee/middleware/options/base.rb +107 -0
  7. data/lib/committee/middleware/options/request_validation.rb +80 -0
  8. data/lib/committee/middleware/options/response_validation.rb +46 -0
  9. data/lib/committee/middleware/options.rb +12 -0
  10. data/lib/committee/middleware/request_validation.rb +7 -1
  11. data/lib/committee/middleware/response_validation.rb +6 -2
  12. data/lib/committee/middleware.rb +1 -0
  13. data/lib/committee/schema_validator/hyper_schema/response_generator.rb +1 -3
  14. data/lib/committee/schema_validator/hyper_schema/response_validator.rb +14 -1
  15. data/lib/committee/schema_validator/hyper_schema/router.rb +1 -1
  16. data/lib/committee/schema_validator/hyper_schema.rb +3 -14
  17. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +43 -13
  18. data/lib/committee/schema_validator/open_api_3/parameter_deserializer.rb +556 -0
  19. data/lib/committee/schema_validator/open_api_3/response_validator.rb +16 -2
  20. data/lib/committee/schema_validator/open_api_3.rb +19 -13
  21. data/lib/committee/schema_validator/option.rb +6 -17
  22. data/lib/committee/schema_validator.rb +12 -1
  23. data/lib/committee/test/except_parameter.rb +416 -0
  24. data/lib/committee/test/methods.rb +38 -2
  25. data/lib/committee/version.rb +1 -1
  26. data/lib/committee.rb +1 -1
  27. data/test/drivers/open_api_2/driver_test.rb +4 -16
  28. data/test/drivers/open_api_2/parameter_schema_builder_test.rb +4 -50
  29. data/test/drivers_test.rb +35 -21
  30. data/test/middleware/options/base_test.rb +120 -0
  31. data/test/middleware/options/request_validation_test.rb +177 -0
  32. data/test/middleware/options/response_validation_test.rb +121 -0
  33. data/test/middleware/request_validation_open_api_3_test.rb +200 -80
  34. data/test/middleware/request_validation_test.rb +13 -70
  35. data/test/middleware/response_validation_open_api_3_test.rb +40 -17
  36. data/test/middleware/response_validation_test.rb +3 -14
  37. data/test/request_unpacker_test.rb +2 -10
  38. data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +1 -37
  39. data/test/schema_validator/hyper_schema/request_validator_test.rb +6 -30
  40. data/test/schema_validator/hyper_schema/router_test.rb +5 -0
  41. data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +1 -37
  42. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +58 -43
  43. data/test/schema_validator/open_api_3/parameter_deserializer_test.rb +457 -0
  44. data/test/schema_validator/open_api_3/request_validator_test.rb +1 -2
  45. data/test/schema_validator/open_api_3/response_validator_test.rb +3 -11
  46. data/test/schema_validator_test.rb +41 -0
  47. data/test/test/methods_test.rb +238 -105
  48. data/test/test/schema_coverage_test.rb +8 -155
  49. metadata +11 -1
@@ -0,0 +1,556 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Committee
6
+ module SchemaValidator
7
+ class OpenAPI3
8
+ # Deserializes request parameters based on OpenAPI 3 parameter style and explode settings
9
+ #
10
+ # @see https://swagger.io/docs/specification/serialization/
11
+ # @see https://spec.openapis.org/oas/latest.html#parameter-object
12
+ class ParameterDeserializer
13
+ # @param [OpenAPIParser::RequestOperation] request_operation
14
+ def initialize(request_operation)
15
+ @request_operation = request_operation
16
+ @parameters = request_operation.operation_object.parameters || []
17
+ end
18
+
19
+ # Deserialize query parameters
20
+ # @param [Hash] raw_params Raw query parameters from Rack
21
+ # @return [Hash] Deserialized parameters according to OpenAPI schema
22
+ def deserialize_query_params(raw_params)
23
+ deserialize_params_by_location(raw_params, 'query')
24
+ end
25
+
26
+ # Deserialize path parameters
27
+ # @param [Hash] raw_params Raw path parameters
28
+ # @return [Hash] Deserialized parameters according to OpenAPI schema
29
+ def deserialize_path_params(raw_params)
30
+ deserialize_params_by_location(raw_params, 'path')
31
+ end
32
+
33
+ # Deserialize header parameters
34
+ # @param [Hash] raw_headers Raw headers
35
+ # @return [Hash] Deserialized headers according to OpenAPI schema
36
+ def deserialize_headers(raw_headers)
37
+ deserialize_params_by_location(raw_headers, 'header')
38
+ end
39
+
40
+ private
41
+
42
+ # Deserialize parameters for a specific location (query, path, header)
43
+ # @param [Hash] raw_params Raw parameters
44
+ # @param [String] location Parameter location ('query', 'path', 'header')
45
+ # @return [Hash] Deserialized parameters
46
+ def deserialize_params_by_location(raw_params, location)
47
+ return raw_params if raw_params.nil? || raw_params.empty?
48
+
49
+ result = Committee::Utils.indifferent_hash
50
+ params_for_location = @parameters.select { |p| p.in == location }
51
+
52
+ # If no parameters are defined for this location, return raw params as-is
53
+ return raw_params if params_for_location.empty?
54
+
55
+ raw_params = normalize_raw_params(raw_params, location, params_for_location)
56
+
57
+ # Collect parameter names that will be deserialized
58
+ # This includes both the parameter name and any properties (for exploded objects)
59
+ deserialized_keys = Set.new
60
+
61
+ # Deserialize each parameter defined in the schema
62
+ params_for_location.each do |param_def|
63
+ value = extract_and_deserialize(param_def, raw_params)
64
+
65
+ # Only include non-nil values
66
+ if !value.nil?
67
+ # For objects, ensure the result is also an indifferent hash
68
+ value = convert_to_indifferent_hash(value) if value.is_a?(Hash)
69
+
70
+ result[param_def.name] = value
71
+ deserialized_keys.add(param_def.name)
72
+
73
+ # For form-style exploded objects, mark the property keys as deserialized
74
+ if param_def.schema&.type == 'object' &&
75
+ (param_def.style.nil? || param_def.style == 'form') &&
76
+ (param_def.explode.nil? || param_def.explode)
77
+ param_def.schema.properties&.each_key do |prop_name|
78
+ deserialized_keys.add(prop_name.to_s)
79
+ end
80
+ end
81
+
82
+ # For deep object style, mark the bracket-notation keys as deserialized
83
+ if param_def.style == 'deepObject'
84
+ prefix = "#{param_def.name}["
85
+ raw_params.each_key do |key|
86
+ key_str = key.to_s
87
+ deserialized_keys.add(key) if key_str.start_with?(prefix)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ # Include params not in schema (for additionalProperties and unknown params)
94
+ # Only include params that weren't consumed by deserialization
95
+ raw_params.each do |key, value|
96
+ result[key] = value unless deserialized_keys.include?(key)
97
+ end
98
+
99
+ result
100
+ end
101
+
102
+ # Convert a hash to an indifferent hash (supports both string and symbol keys)
103
+ # @param [Hash] hash
104
+ # @return [Committee::Utils::IndifferentHash]
105
+ def convert_to_indifferent_hash(hash)
106
+ return hash unless hash.is_a?(Hash)
107
+ Committee::Utils.indifferent_hash.merge(hash)
108
+ end
109
+
110
+ # Normalize Rack-style nested query hashes into bracket notation when the
111
+ # schema expects bracket-named params or deepObject query params.
112
+ # Example: { "filter" => { "slug" => "/test" } } => { "filter[slug]" => "/test" }
113
+ # @param [Hash] raw_params
114
+ # @param [String] location
115
+ # @param [Array<OpenAPIParser::Schemas::Parameter>] params_for_location
116
+ # @return [Hash]
117
+ def normalize_raw_params(raw_params, location, params_for_location)
118
+ return raw_params unless location == 'query'
119
+ return raw_params unless raw_params.values.any? { |value| value.is_a?(Hash) }
120
+ return raw_params unless requires_query_param_flattening?(params_for_location)
121
+
122
+ normalized = Committee::Utils.indifferent_hash
123
+
124
+ raw_params.each do |key, value|
125
+ if should_flatten_query_param?(key, value, params_for_location)
126
+ flatten_nested_query_param(normalized, key.to_s, value)
127
+ else
128
+ normalized[key] = value
129
+ end
130
+ end
131
+
132
+ normalized
133
+ end
134
+
135
+ # @param [Array<OpenAPIParser::Schemas::Parameter>] params_for_location
136
+ # @return [Boolean]
137
+ def requires_query_param_flattening?(params_for_location)
138
+ params_for_location.any? { |param_def| param_def.style == 'deepObject' || param_def.name.include?('[') }
139
+ end
140
+
141
+ # @param [String, Symbol] key
142
+ # @param [Object] value
143
+ # @param [Array<OpenAPIParser::Schemas::Parameter>] params_for_location
144
+ # @return [Boolean]
145
+ def should_flatten_query_param?(key, value, params_for_location)
146
+ return false unless value.is_a?(Hash)
147
+
148
+ key_name = key.to_s
149
+ params_for_location.any? do |param_def|
150
+ param_def.name == key_name || param_def.name.start_with?("#{key_name}[")
151
+ end
152
+ end
153
+
154
+ # @param [Hash] result
155
+ # @param [String] prefix
156
+ # @param [Object] value
157
+ # @return [void]
158
+ def flatten_nested_query_param(result, prefix, value)
159
+ case value
160
+ when Hash
161
+ value.each do |child_key, child_value|
162
+ flatten_nested_query_param(result, "#{prefix}[#{child_key}]", child_value)
163
+ end
164
+ else
165
+ result[prefix] = value
166
+ end
167
+ end
168
+
169
+ # Extract and deserialize a single parameter
170
+ # @param [OpenAPIParser::Schemas::Parameter] param_def Parameter definition
171
+ # @param [Hash] raw_params Raw parameters
172
+ # @return [Object] Deserialized value
173
+ def extract_and_deserialize(param_def, raw_params)
174
+ style = param_def.style || default_style(param_def.in)
175
+ explode = param_def.explode.nil? ? default_explode(style) : param_def.explode
176
+
177
+ case style
178
+ when 'form'
179
+ deserialize_form_style(param_def, raw_params, explode)
180
+ when 'simple'
181
+ deserialize_simple_style(param_def, raw_params, explode)
182
+ when 'label'
183
+ deserialize_label_style(param_def, raw_params, explode)
184
+ when 'matrix'
185
+ deserialize_matrix_style(param_def, raw_params, explode)
186
+ when 'spaceDelimited'
187
+ deserialize_space_delimited(param_def, raw_params, explode)
188
+ when 'pipeDelimited'
189
+ deserialize_pipe_delimited(param_def, raw_params, explode)
190
+ when 'deepObject'
191
+ deserialize_deep_object(param_def, raw_params)
192
+ else
193
+ # Unsupported style - return raw value
194
+ raw_params[param_def.name]
195
+ end
196
+ rescue StandardError => e
197
+ raise Committee::ParameterDeserializationError.new(param_def.name, style, raw_params[param_def.name], e.message)
198
+ end
199
+
200
+ # Get default style for a parameter location
201
+ # @param [String] location Parameter location
202
+ # @return [String] Default style
203
+ def default_style(location)
204
+ case location
205
+ when 'query', 'cookie'
206
+ 'form'
207
+ when 'path', 'header'
208
+ 'simple'
209
+ else
210
+ 'form'
211
+ end
212
+ end
213
+
214
+ # Get default explode setting for a style
215
+ # @param [String] style Parameter style
216
+ # @return [Boolean] Default explode value
217
+ def default_explode(style)
218
+ style == 'form'
219
+ end
220
+
221
+ # Deserialize form style parameter
222
+ # Default for query and cookie parameters
223
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
224
+ # @param [Hash] raw_params
225
+ # @param [Boolean] explode
226
+ # @return [Object] Deserialized value
227
+ def deserialize_form_style(param_def, raw_params, explode)
228
+ param_name = param_def.name
229
+ schema = param_def.schema
230
+ return nil unless schema
231
+
232
+ case schema.type
233
+ when 'object'
234
+ deserialize_form_object(param_name, schema, raw_params, explode)
235
+ when 'array'
236
+ deserialize_form_array(param_name, schema, raw_params, explode)
237
+ else
238
+ # Primitive type - just return the value
239
+ raw_params[param_name]
240
+ end
241
+ end
242
+
243
+ # Deserialize form-style object
244
+ # @param [String] param_name
245
+ # @param [OpenAPIParser::Schemas::Schema] schema
246
+ # @param [Hash] raw_params
247
+ # @param [Boolean] explode
248
+ # @return [Hash, nil] Deserialized object
249
+ def deserialize_form_object(param_name, schema, raw_params, explode)
250
+ if explode
251
+ # explode=true: object properties are separate parameters
252
+ # Example: ?role=admin&status=active → { role: "admin", status: "active" }
253
+ collect_object_properties(schema, raw_params, param_name)
254
+ else
255
+ # explode=false: comma-separated key,value pairs
256
+ # Example: ?id=role,admin,status,active → { role: "admin", status: "active" }
257
+ value = raw_params[param_name]
258
+ value ? parse_comma_separated_object(value) : nil
259
+ end
260
+ end
261
+
262
+ # Deserialize form-style array
263
+ # @param [String] param_name
264
+ # @param [OpenAPIParser::Schemas::Schema] schema
265
+ # @param [Hash] raw_params
266
+ # @param [Boolean] explode
267
+ # @return [Array, nil] Deserialized array
268
+ def deserialize_form_array(param_name, schema, raw_params, explode)
269
+ value = raw_params[param_name]
270
+ return nil unless value
271
+
272
+ if explode
273
+ # explode=true: Rack already collects ?id=1&id=2 into an array
274
+ # Just ensure it's an array
275
+ Array(value)
276
+ else
277
+ # explode=false: comma-separated values
278
+ # Example: ?id=1,2,3 → ["1", "2", "3"]
279
+ value.is_a?(String) ? value.split(',') : Array(value)
280
+ end
281
+ end
282
+
283
+ # Collect object properties from separate parameters (form explode=true)
284
+ # @param [OpenAPIParser::Schemas::Schema] schema
285
+ # @param [Hash] raw_params
286
+ # @param [String] param_name Original parameter name (not used in exploded form)
287
+ # @return [Hash, nil] Collected object properties
288
+ def collect_object_properties(schema, raw_params, param_name)
289
+ result = Committee::Utils.indifferent_hash
290
+ properties = schema.properties || {}
291
+
292
+ properties.each do |prop_name, prop_schema|
293
+ # Look for property directly in raw_params (exploded form)
294
+ if raw_params.key?(prop_name)
295
+ result[prop_name] = raw_params[prop_name]
296
+ elsif raw_params.key?(prop_name.to_s)
297
+ result[prop_name] = raw_params[prop_name.to_s]
298
+ end
299
+ end
300
+
301
+ result.empty? ? nil : result
302
+ end
303
+
304
+ # Parse comma-separated object (form explode=false)
305
+ # Example: "role,admin,status,active" → { "role" => "admin", "status" => "active" }
306
+ # @param [String] value Comma-separated string
307
+ # @return [Hash] Parsed object
308
+ def parse_comma_separated_object(value)
309
+ parts = value.split(',')
310
+ result = Committee::Utils.indifferent_hash
311
+
312
+ # Parts should be in key,value,key,value format
313
+ (0...parts.length).step(2) do |i|
314
+ break if i + 1 >= parts.length
315
+ result[parts[i]] = parts[i + 1]
316
+ end
317
+
318
+ result
319
+ end
320
+
321
+ # Deserialize deep object style (query parameters only)
322
+ # Example: ?filter[role]=admin&filter[status]=active → { filter: { role: "admin", status: "active" } }
323
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
324
+ # @param [Hash] raw_params
325
+ # @return [Hash, nil] Deserialized object
326
+ def deserialize_deep_object(param_def, raw_params)
327
+ param_name = param_def.name
328
+ prefix = "#{param_name}["
329
+ result = Committee::Utils.indifferent_hash
330
+
331
+ raw_params.each do |key, value|
332
+ key_str = key.to_s
333
+ if key_str.start_with?(prefix) && key_str.end_with?(']')
334
+ property_name = key_str[prefix.length...-1]
335
+ result[property_name] = value
336
+ end
337
+ end
338
+
339
+ result.empty? ? nil : result
340
+ end
341
+
342
+ # Deserialize simple style (path and header parameters)
343
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
344
+ # @param [Hash] raw_params
345
+ # @param [Boolean] explode
346
+ # @return [Object] Deserialized value
347
+ def deserialize_simple_style(param_def, raw_params, explode)
348
+ param_name = param_def.name
349
+ value = raw_params[param_name]
350
+ return nil unless value
351
+
352
+ schema = param_def.schema
353
+ return value unless schema
354
+
355
+ case schema.type
356
+ when 'array'
357
+ # Both explode true/false use comma separation for simple style
358
+ # Example: "1,2,3" → ["1", "2", "3"]
359
+ value.is_a?(String) ? value.split(',') : Array(value)
360
+ when 'object'
361
+ if explode
362
+ # explode=true: key=value,key2=value2
363
+ parse_key_value_pairs(value, ',', '=')
364
+ else
365
+ # explode=false: key,value,key2,value2
366
+ parse_comma_separated_object(value)
367
+ end
368
+ else
369
+ value
370
+ end
371
+ end
372
+
373
+ # Deserialize label style (path parameters)
374
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
375
+ # @param [Hash] raw_params
376
+ # @param [Boolean] explode
377
+ # @return [Object] Deserialized value
378
+ def deserialize_label_style(param_def, raw_params, explode)
379
+ param_name = param_def.name
380
+ value = raw_params[param_name]
381
+ return nil unless value
382
+
383
+ # Remove leading dot
384
+ value = value[1..-1] if value.start_with?('.')
385
+
386
+ schema = param_def.schema
387
+ return value unless schema
388
+
389
+ case schema.type
390
+ when 'array'
391
+ if explode
392
+ # explode=true: .3.4.5 → ["3", "4", "5"]
393
+ value.split('.')
394
+ else
395
+ # explode=false: .3,4,5 → ["3", "4", "5"]
396
+ value.split(',')
397
+ end
398
+ when 'object'
399
+ if explode
400
+ # explode=true: .role=admin.status=active
401
+ parse_key_value_pairs(value, '.', '=')
402
+ else
403
+ # explode=false: .role,admin,status,active
404
+ parse_comma_separated_object(value)
405
+ end
406
+ else
407
+ value
408
+ end
409
+ end
410
+
411
+ # Deserialize matrix style (path parameters)
412
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
413
+ # @param [Hash] raw_params
414
+ # @param [Boolean] explode
415
+ # @return [Object] Deserialized value
416
+ def deserialize_matrix_style(param_def, raw_params, explode)
417
+ param_name = param_def.name
418
+ value = raw_params[param_name]
419
+ return nil unless value
420
+
421
+ schema = param_def.schema
422
+ return value unless schema
423
+
424
+ case schema.type
425
+ when 'array'
426
+ if explode
427
+ # explode=true: ;id=3;id=4 (multiple occurrences)
428
+ # Rack should have already collected these into an array
429
+ Array(value)
430
+ else
431
+ # explode=false: ;id=3,4,5
432
+ extract_matrix_array_compact(value, param_name)
433
+ end
434
+ when 'object'
435
+ if explode
436
+ # explode=true: ;role=admin;status=active
437
+ extract_matrix_object_exploded(value, param_name)
438
+ else
439
+ # explode=false: ;id=role,admin,status,active
440
+ extract_matrix_object_compact(value, param_name)
441
+ end
442
+ else
443
+ # Primitive: ;id=5
444
+ extract_matrix_primitive(value, param_name)
445
+ end
446
+ end
447
+
448
+ # Extract matrix-style array (explode=false)
449
+ # @param [String] value Matrix-style string
450
+ # @param [String] param_name Parameter name
451
+ # @return [Array] Extracted array
452
+ def extract_matrix_array_compact(value, param_name)
453
+ # ;id=3,4,5 → ["3", "4", "5"]
454
+ prefix = ";#{param_name}="
455
+ if value.start_with?(prefix)
456
+ value[prefix.length..-1].split(',')
457
+ else
458
+ []
459
+ end
460
+ end
461
+
462
+ # Extract matrix-style object (explode=true)
463
+ # @param [String] value Matrix-style string
464
+ # @param [String] param_name Parameter name
465
+ # @return [Hash] Extracted object
466
+ def extract_matrix_object_exploded(value, param_name)
467
+ # ;role=admin;status=active → { "role" => "admin", "status" => "active" }
468
+ result = Committee::Utils.indifferent_hash
469
+ pairs = value.split(';').reject(&:empty?)
470
+
471
+ pairs.each do |pair|
472
+ next unless pair.include?('=')
473
+ key, val = pair.split('=', 2)
474
+ result[key] = val
475
+ end
476
+
477
+ result
478
+ end
479
+
480
+ # Extract matrix-style object (explode=false)
481
+ # @param [String] value Matrix-style string
482
+ # @param [String] param_name Parameter name
483
+ # @return [Hash] Extracted object
484
+ def extract_matrix_object_compact(value, param_name)
485
+ # ;id=role,admin,status,active → { "role" => "admin", "status" => "active" }
486
+ prefix = ";#{param_name}="
487
+ if value.start_with?(prefix)
488
+ parse_comma_separated_object(value[prefix.length..-1])
489
+ else
490
+ {}
491
+ end
492
+ end
493
+
494
+ # Extract matrix-style primitive value
495
+ # @param [String] value Matrix-style string
496
+ # @param [String] param_name Parameter name
497
+ # @return [String] Extracted value
498
+ def extract_matrix_primitive(value, param_name)
499
+ # ;id=5 → "5"
500
+ prefix = ";#{param_name}="
501
+ if value.start_with?(prefix)
502
+ value[prefix.length..-1]
503
+ else
504
+ value
505
+ end
506
+ end
507
+
508
+ # Deserialize space-delimited style (query parameters, arrays only)
509
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
510
+ # @param [Hash] raw_params
511
+ # @param [Boolean] explode
512
+ # @return [Array, nil] Deserialized array
513
+ def deserialize_space_delimited(param_def, raw_params, explode)
514
+ param_name = param_def.name
515
+ value = raw_params[param_name]
516
+ return nil unless value
517
+
518
+ # Example: "1 2 3" or "1%202%203" → ["1", "2", "3"]
519
+ value.is_a?(String) ? value.split(' ') : Array(value)
520
+ end
521
+
522
+ # Deserialize pipe-delimited style (query parameters, arrays only)
523
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
524
+ # @param [Hash] raw_params
525
+ # @param [Boolean] explode
526
+ # @return [Array, nil] Deserialized array
527
+ def deserialize_pipe_delimited(param_def, raw_params, explode)
528
+ param_name = param_def.name
529
+ value = raw_params[param_name]
530
+ return nil unless value
531
+
532
+ # Example: "1|2|3" → ["1", "2", "3"]
533
+ value.is_a?(String) ? value.split('|') : Array(value)
534
+ end
535
+
536
+ # Parse key-value pairs with custom delimiters
537
+ # @param [String] value String containing key-value pairs
538
+ # @param [String] pair_delimiter Delimiter between pairs
539
+ # @param [String] kv_delimiter Delimiter between key and value
540
+ # @return [Hash] Parsed object
541
+ def parse_key_value_pairs(value, pair_delimiter, kv_delimiter)
542
+ result = Committee::Utils.indifferent_hash
543
+ pairs = value.split(pair_delimiter).reject(&:empty?)
544
+
545
+ pairs.each do |pair|
546
+ next unless pair.include?(kv_delimiter)
547
+ key, val = pair.split(kv_delimiter, 2)
548
+ result[key] = val
549
+ end
550
+
551
+ result
552
+ end
553
+ end
554
+ end
555
+ end
556
+ end
@@ -13,16 +13,30 @@ module Committee
13
13
  @validate_success_only = validator_option.validate_success_only
14
14
  @check_header = validator_option.check_header
15
15
  @allow_empty_date_and_datetime = validator_option.allow_empty_date_and_datetime
16
+ @coerce_response_values = validator_option.coerce_response_values
16
17
  end
17
18
 
18
19
  def call(status, headers, response_data, strict)
19
- return unless Committee::Middleware::ResponseValidation.validate?(status, validate_success_only)
20
+ return unless validate?(status)
20
21
 
21
- validator_options = { allow_empty_date_and_datetime: @allow_empty_date_and_datetime }
22
+ validator_options = { allow_empty_date_and_datetime: @allow_empty_date_and_datetime, coerce_value: @coerce_response_values }
22
23
 
23
24
  operation_wrapper.validate_response_params(status, headers, response_data, strict, check_header, validator_options: validator_options)
24
25
  end
25
26
 
27
+ def validate?(status)
28
+ case status
29
+ when 204
30
+ false
31
+ when 200..299
32
+ true
33
+ when 304
34
+ false
35
+ else
36
+ !validate_success_only
37
+ end
38
+ end
39
+
26
40
  private
27
41
 
28
42
  attr_reader :operation_wrapper, :check_header
@@ -28,7 +28,7 @@ module Committee
28
28
 
29
29
  parse_to_json = if validator_option.parse_response_by_content_type
30
30
  content_type_key = headers.keys.detect { |k| k.casecmp?('Content-Type') }
31
- headers.fetch(content_type_key, nil)&.start_with?('application/json')
31
+ Committee::SchemaValidator.json_media_type?(headers.fetch(content_type_key, nil))
32
32
  else
33
33
  true
34
34
  end
@@ -43,9 +43,7 @@ module Committee
43
43
 
44
44
  # TODO: refactoring name
45
45
  strict = test_method
46
- Committee::SchemaValidator::OpenAPI3::ResponseValidator.
47
- new(@operation_object, validator_option).
48
- call(status, headers, data, strict)
46
+ Committee::SchemaValidator::OpenAPI3::ResponseValidator.new(@operation_object, validator_option).call(status, headers, data, strict)
49
47
  end
50
48
 
51
49
  def link_exist?
@@ -57,7 +55,6 @@ module Committee
57
55
  attr_reader :validator_option
58
56
 
59
57
  def coerce_path_params
60
- return Committee::Utils.indifferent_hash unless validator_option.coerce_path_params
61
58
  Committee::RequestUnpacker.indifferent_params(@operation_object.coerce_path_parameter(@validator_option))
62
59
  end
63
60
 
@@ -85,14 +82,7 @@ module Committee
85
82
  end
86
83
 
87
84
  def request_unpack(request)
88
- unpacker = Committee::RequestUnpacker.new(
89
- allow_empty_date_and_datetime: validator_option.allow_empty_date_and_datetime,
90
- allow_form_params: validator_option.allow_form_params,
91
- allow_get_body: validator_option.allow_get_body,
92
- allow_query_params: validator_option.allow_query_params,
93
- allow_non_get_query_params: validator_option.allow_non_get_query_params,
94
- optimistic_json: validator_option.optimistic_json,
95
- )
85
+ unpacker = Committee::RequestUnpacker.new(allow_empty_date_and_datetime: validator_option.allow_empty_date_and_datetime, allow_form_params: validator_option.allow_form_params, allow_get_body: validator_option.allow_get_body, allow_query_params: validator_option.allow_query_params, allow_non_get_query_params: validator_option.allow_non_get_query_params, optimistic_json: validator_option.optimistic_json,)
96
86
 
97
87
  request.env[validator_option.headers_key] = unpacker.unpack_headers(request)
98
88
 
@@ -102,6 +92,21 @@ module Committee
102
92
 
103
93
  query_param = unpacker.unpack_query_params(request)
104
94
  query_param.merge!(request_param) if request.get? && validator_option.allow_get_body
95
+
96
+ if @operation_object && validator_option.deserialize_parameters
97
+ deserializer = ParameterDeserializer.new(@operation_object.request_operation)
98
+
99
+ query_param = deserializer.deserialize_query_params(query_param)
100
+
101
+ path_param = request.env[validator_option.path_hash_key]
102
+ path_param = deserializer.deserialize_path_params(path_param)
103
+ request.env[validator_option.path_hash_key] = path_param
104
+
105
+ headers = request.env[validator_option.headers_key]
106
+ headers = deserializer.deserialize_headers(headers)
107
+ request.env[validator_option.headers_key] = headers
108
+ end
109
+
105
110
  request.env[validator_option.query_hash_key] = query_param
106
111
  end
107
112
 
@@ -125,5 +130,6 @@ end
125
130
 
126
131
  require_relative "open_api_3/router"
127
132
  require_relative "open_api_3/operation_wrapper"
133
+ require_relative "open_api_3/parameter_deserializer"
128
134
  require_relative "open_api_3/request_validator"
129
135
  require_relative "open_api_3/response_validator"