committee 5.6.1 → 5.6.2

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 +5 -0
  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 +2 -13
  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 +495 -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 +18 -12
  21. data/lib/committee/schema_validator/option.rb +6 -17
  22. data/lib/committee/schema_validator.rb +5 -1
  23. data/lib/committee/test/except_parameter.rb +416 -0
  24. data/lib/committee/test/methods.rb +27 -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 +113 -80
  34. data/test/middleware/request_validation_test.rb +13 -70
  35. data/test/middleware/response_validation_open_api_3_test.rb +33 -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 +40 -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 +8 -0
  47. data/test/test/methods_test.rb +222 -105
  48. data/test/test/schema_coverage_test.rb +8 -155
  49. metadata +12 -2
@@ -0,0 +1,495 @@
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
+ # Collect parameter names that will be deserialized
56
+ # This includes both the parameter name and any properties (for exploded objects)
57
+ deserialized_keys = Set.new
58
+
59
+ # Deserialize each parameter defined in the schema
60
+ params_for_location.each do |param_def|
61
+ value = extract_and_deserialize(param_def, raw_params)
62
+
63
+ # Only include non-nil values
64
+ if !value.nil?
65
+ # For objects, ensure the result is also an indifferent hash
66
+ value = convert_to_indifferent_hash(value) if value.is_a?(Hash)
67
+
68
+ result[param_def.name] = value
69
+ deserialized_keys.add(param_def.name)
70
+
71
+ # For form-style exploded objects, mark the property keys as deserialized
72
+ if param_def.schema&.type == 'object' &&
73
+ (param_def.style.nil? || param_def.style == 'form') &&
74
+ (param_def.explode.nil? || param_def.explode)
75
+ param_def.schema.properties&.each_key do |prop_name|
76
+ deserialized_keys.add(prop_name.to_s)
77
+ end
78
+ end
79
+
80
+ # For deep object style, mark the bracket-notation keys as deserialized
81
+ if param_def.style == 'deepObject'
82
+ prefix = "#{param_def.name}["
83
+ raw_params.each_key do |key|
84
+ key_str = key.to_s
85
+ deserialized_keys.add(key) if key_str.start_with?(prefix)
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ # Include params not in schema (for additionalProperties and unknown params)
92
+ # Only include params that weren't consumed by deserialization
93
+ raw_params.each do |key, value|
94
+ result[key] = value unless deserialized_keys.include?(key)
95
+ end
96
+
97
+ result
98
+ end
99
+
100
+ # Convert a hash to an indifferent hash (supports both string and symbol keys)
101
+ # @param [Hash] hash
102
+ # @return [Committee::Utils::IndifferentHash]
103
+ def convert_to_indifferent_hash(hash)
104
+ return hash unless hash.is_a?(Hash)
105
+ Committee::Utils.indifferent_hash.merge(hash)
106
+ end
107
+
108
+ # Extract and deserialize a single parameter
109
+ # @param [OpenAPIParser::Schemas::Parameter] param_def Parameter definition
110
+ # @param [Hash] raw_params Raw parameters
111
+ # @return [Object] Deserialized value
112
+ def extract_and_deserialize(param_def, raw_params)
113
+ style = param_def.style || default_style(param_def.in)
114
+ explode = param_def.explode.nil? ? default_explode(style) : param_def.explode
115
+
116
+ case style
117
+ when 'form'
118
+ deserialize_form_style(param_def, raw_params, explode)
119
+ when 'simple'
120
+ deserialize_simple_style(param_def, raw_params, explode)
121
+ when 'label'
122
+ deserialize_label_style(param_def, raw_params, explode)
123
+ when 'matrix'
124
+ deserialize_matrix_style(param_def, raw_params, explode)
125
+ when 'spaceDelimited'
126
+ deserialize_space_delimited(param_def, raw_params, explode)
127
+ when 'pipeDelimited'
128
+ deserialize_pipe_delimited(param_def, raw_params, explode)
129
+ when 'deepObject'
130
+ deserialize_deep_object(param_def, raw_params)
131
+ else
132
+ # Unsupported style - return raw value
133
+ raw_params[param_def.name]
134
+ end
135
+ rescue StandardError => e
136
+ raise Committee::ParameterDeserializationError.new(param_def.name, style, raw_params[param_def.name], e.message)
137
+ end
138
+
139
+ # Get default style for a parameter location
140
+ # @param [String] location Parameter location
141
+ # @return [String] Default style
142
+ def default_style(location)
143
+ case location
144
+ when 'query', 'cookie'
145
+ 'form'
146
+ when 'path', 'header'
147
+ 'simple'
148
+ else
149
+ 'form'
150
+ end
151
+ end
152
+
153
+ # Get default explode setting for a style
154
+ # @param [String] style Parameter style
155
+ # @return [Boolean] Default explode value
156
+ def default_explode(style)
157
+ style == 'form'
158
+ end
159
+
160
+ # Deserialize form style parameter
161
+ # Default for query and cookie parameters
162
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
163
+ # @param [Hash] raw_params
164
+ # @param [Boolean] explode
165
+ # @return [Object] Deserialized value
166
+ def deserialize_form_style(param_def, raw_params, explode)
167
+ param_name = param_def.name
168
+ schema = param_def.schema
169
+ return nil unless schema
170
+
171
+ case schema.type
172
+ when 'object'
173
+ deserialize_form_object(param_name, schema, raw_params, explode)
174
+ when 'array'
175
+ deserialize_form_array(param_name, schema, raw_params, explode)
176
+ else
177
+ # Primitive type - just return the value
178
+ raw_params[param_name]
179
+ end
180
+ end
181
+
182
+ # Deserialize form-style object
183
+ # @param [String] param_name
184
+ # @param [OpenAPIParser::Schemas::Schema] schema
185
+ # @param [Hash] raw_params
186
+ # @param [Boolean] explode
187
+ # @return [Hash, nil] Deserialized object
188
+ def deserialize_form_object(param_name, schema, raw_params, explode)
189
+ if explode
190
+ # explode=true: object properties are separate parameters
191
+ # Example: ?role=admin&status=active → { role: "admin", status: "active" }
192
+ collect_object_properties(schema, raw_params, param_name)
193
+ else
194
+ # explode=false: comma-separated key,value pairs
195
+ # Example: ?id=role,admin,status,active → { role: "admin", status: "active" }
196
+ value = raw_params[param_name]
197
+ value ? parse_comma_separated_object(value) : nil
198
+ end
199
+ end
200
+
201
+ # Deserialize form-style array
202
+ # @param [String] param_name
203
+ # @param [OpenAPIParser::Schemas::Schema] schema
204
+ # @param [Hash] raw_params
205
+ # @param [Boolean] explode
206
+ # @return [Array, nil] Deserialized array
207
+ def deserialize_form_array(param_name, schema, raw_params, explode)
208
+ value = raw_params[param_name]
209
+ return nil unless value
210
+
211
+ if explode
212
+ # explode=true: Rack already collects ?id=1&id=2 into an array
213
+ # Just ensure it's an array
214
+ Array(value)
215
+ else
216
+ # explode=false: comma-separated values
217
+ # Example: ?id=1,2,3 → ["1", "2", "3"]
218
+ value.is_a?(String) ? value.split(',') : Array(value)
219
+ end
220
+ end
221
+
222
+ # Collect object properties from separate parameters (form explode=true)
223
+ # @param [OpenAPIParser::Schemas::Schema] schema
224
+ # @param [Hash] raw_params
225
+ # @param [String] param_name Original parameter name (not used in exploded form)
226
+ # @return [Hash, nil] Collected object properties
227
+ def collect_object_properties(schema, raw_params, param_name)
228
+ result = Committee::Utils.indifferent_hash
229
+ properties = schema.properties || {}
230
+
231
+ properties.each do |prop_name, prop_schema|
232
+ # Look for property directly in raw_params (exploded form)
233
+ if raw_params.key?(prop_name)
234
+ result[prop_name] = raw_params[prop_name]
235
+ elsif raw_params.key?(prop_name.to_s)
236
+ result[prop_name] = raw_params[prop_name.to_s]
237
+ end
238
+ end
239
+
240
+ result.empty? ? nil : result
241
+ end
242
+
243
+ # Parse comma-separated object (form explode=false)
244
+ # Example: "role,admin,status,active" → { "role" => "admin", "status" => "active" }
245
+ # @param [String] value Comma-separated string
246
+ # @return [Hash] Parsed object
247
+ def parse_comma_separated_object(value)
248
+ parts = value.split(',')
249
+ result = Committee::Utils.indifferent_hash
250
+
251
+ # Parts should be in key,value,key,value format
252
+ (0...parts.length).step(2) do |i|
253
+ break if i + 1 >= parts.length
254
+ result[parts[i]] = parts[i + 1]
255
+ end
256
+
257
+ result
258
+ end
259
+
260
+ # Deserialize deep object style (query parameters only)
261
+ # Example: ?filter[role]=admin&filter[status]=active → { filter: { role: "admin", status: "active" } }
262
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
263
+ # @param [Hash] raw_params
264
+ # @return [Hash, nil] Deserialized object
265
+ def deserialize_deep_object(param_def, raw_params)
266
+ param_name = param_def.name
267
+ prefix = "#{param_name}["
268
+ result = Committee::Utils.indifferent_hash
269
+
270
+ raw_params.each do |key, value|
271
+ key_str = key.to_s
272
+ if key_str.start_with?(prefix) && key_str.end_with?(']')
273
+ property_name = key_str[prefix.length...-1]
274
+ result[property_name] = value
275
+ end
276
+ end
277
+
278
+ result.empty? ? nil : result
279
+ end
280
+
281
+ # Deserialize simple style (path and header parameters)
282
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
283
+ # @param [Hash] raw_params
284
+ # @param [Boolean] explode
285
+ # @return [Object] Deserialized value
286
+ def deserialize_simple_style(param_def, raw_params, explode)
287
+ param_name = param_def.name
288
+ value = raw_params[param_name]
289
+ return nil unless value
290
+
291
+ schema = param_def.schema
292
+ return value unless schema
293
+
294
+ case schema.type
295
+ when 'array'
296
+ # Both explode true/false use comma separation for simple style
297
+ # Example: "1,2,3" → ["1", "2", "3"]
298
+ value.is_a?(String) ? value.split(',') : Array(value)
299
+ when 'object'
300
+ if explode
301
+ # explode=true: key=value,key2=value2
302
+ parse_key_value_pairs(value, ',', '=')
303
+ else
304
+ # explode=false: key,value,key2,value2
305
+ parse_comma_separated_object(value)
306
+ end
307
+ else
308
+ value
309
+ end
310
+ end
311
+
312
+ # Deserialize label style (path parameters)
313
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
314
+ # @param [Hash] raw_params
315
+ # @param [Boolean] explode
316
+ # @return [Object] Deserialized value
317
+ def deserialize_label_style(param_def, raw_params, explode)
318
+ param_name = param_def.name
319
+ value = raw_params[param_name]
320
+ return nil unless value
321
+
322
+ # Remove leading dot
323
+ value = value[1..-1] if value.start_with?('.')
324
+
325
+ schema = param_def.schema
326
+ return value unless schema
327
+
328
+ case schema.type
329
+ when 'array'
330
+ if explode
331
+ # explode=true: .3.4.5 → ["3", "4", "5"]
332
+ value.split('.')
333
+ else
334
+ # explode=false: .3,4,5 → ["3", "4", "5"]
335
+ value.split(',')
336
+ end
337
+ when 'object'
338
+ if explode
339
+ # explode=true: .role=admin.status=active
340
+ parse_key_value_pairs(value, '.', '=')
341
+ else
342
+ # explode=false: .role,admin,status,active
343
+ parse_comma_separated_object(value)
344
+ end
345
+ else
346
+ value
347
+ end
348
+ end
349
+
350
+ # Deserialize matrix style (path parameters)
351
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
352
+ # @param [Hash] raw_params
353
+ # @param [Boolean] explode
354
+ # @return [Object] Deserialized value
355
+ def deserialize_matrix_style(param_def, raw_params, explode)
356
+ param_name = param_def.name
357
+ value = raw_params[param_name]
358
+ return nil unless value
359
+
360
+ schema = param_def.schema
361
+ return value unless schema
362
+
363
+ case schema.type
364
+ when 'array'
365
+ if explode
366
+ # explode=true: ;id=3;id=4 (multiple occurrences)
367
+ # Rack should have already collected these into an array
368
+ Array(value)
369
+ else
370
+ # explode=false: ;id=3,4,5
371
+ extract_matrix_array_compact(value, param_name)
372
+ end
373
+ when 'object'
374
+ if explode
375
+ # explode=true: ;role=admin;status=active
376
+ extract_matrix_object_exploded(value, param_name)
377
+ else
378
+ # explode=false: ;id=role,admin,status,active
379
+ extract_matrix_object_compact(value, param_name)
380
+ end
381
+ else
382
+ # Primitive: ;id=5
383
+ extract_matrix_primitive(value, param_name)
384
+ end
385
+ end
386
+
387
+ # Extract matrix-style array (explode=false)
388
+ # @param [String] value Matrix-style string
389
+ # @param [String] param_name Parameter name
390
+ # @return [Array] Extracted array
391
+ def extract_matrix_array_compact(value, param_name)
392
+ # ;id=3,4,5 → ["3", "4", "5"]
393
+ prefix = ";#{param_name}="
394
+ if value.start_with?(prefix)
395
+ value[prefix.length..-1].split(',')
396
+ else
397
+ []
398
+ end
399
+ end
400
+
401
+ # Extract matrix-style object (explode=true)
402
+ # @param [String] value Matrix-style string
403
+ # @param [String] param_name Parameter name
404
+ # @return [Hash] Extracted object
405
+ def extract_matrix_object_exploded(value, param_name)
406
+ # ;role=admin;status=active → { "role" => "admin", "status" => "active" }
407
+ result = Committee::Utils.indifferent_hash
408
+ pairs = value.split(';').reject(&:empty?)
409
+
410
+ pairs.each do |pair|
411
+ next unless pair.include?('=')
412
+ key, val = pair.split('=', 2)
413
+ result[key] = val
414
+ end
415
+
416
+ result
417
+ end
418
+
419
+ # Extract matrix-style object (explode=false)
420
+ # @param [String] value Matrix-style string
421
+ # @param [String] param_name Parameter name
422
+ # @return [Hash] Extracted object
423
+ def extract_matrix_object_compact(value, param_name)
424
+ # ;id=role,admin,status,active → { "role" => "admin", "status" => "active" }
425
+ prefix = ";#{param_name}="
426
+ if value.start_with?(prefix)
427
+ parse_comma_separated_object(value[prefix.length..-1])
428
+ else
429
+ {}
430
+ end
431
+ end
432
+
433
+ # Extract matrix-style primitive value
434
+ # @param [String] value Matrix-style string
435
+ # @param [String] param_name Parameter name
436
+ # @return [String] Extracted value
437
+ def extract_matrix_primitive(value, param_name)
438
+ # ;id=5 → "5"
439
+ prefix = ";#{param_name}="
440
+ if value.start_with?(prefix)
441
+ value[prefix.length..-1]
442
+ else
443
+ value
444
+ end
445
+ end
446
+
447
+ # Deserialize space-delimited style (query parameters, arrays only)
448
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
449
+ # @param [Hash] raw_params
450
+ # @param [Boolean] explode
451
+ # @return [Array, nil] Deserialized array
452
+ def deserialize_space_delimited(param_def, raw_params, explode)
453
+ param_name = param_def.name
454
+ value = raw_params[param_name]
455
+ return nil unless value
456
+
457
+ # Example: "1 2 3" or "1%202%203" → ["1", "2", "3"]
458
+ value.is_a?(String) ? value.split(' ') : Array(value)
459
+ end
460
+
461
+ # Deserialize pipe-delimited style (query parameters, arrays only)
462
+ # @param [OpenAPIParser::Schemas::Parameter] param_def
463
+ # @param [Hash] raw_params
464
+ # @param [Boolean] explode
465
+ # @return [Array, nil] Deserialized array
466
+ def deserialize_pipe_delimited(param_def, raw_params, explode)
467
+ param_name = param_def.name
468
+ value = raw_params[param_name]
469
+ return nil unless value
470
+
471
+ # Example: "1|2|3" → ["1", "2", "3"]
472
+ value.is_a?(String) ? value.split('|') : Array(value)
473
+ end
474
+
475
+ # Parse key-value pairs with custom delimiters
476
+ # @param [String] value String containing key-value pairs
477
+ # @param [String] pair_delimiter Delimiter between pairs
478
+ # @param [String] kv_delimiter Delimiter between key and value
479
+ # @return [Hash] Parsed object
480
+ def parse_key_value_pairs(value, pair_delimiter, kv_delimiter)
481
+ result = Committee::Utils.indifferent_hash
482
+ pairs = value.split(pair_delimiter).reject(&:empty?)
483
+
484
+ pairs.each do |pair|
485
+ next unless pair.include?(kv_delimiter)
486
+ key, val = pair.split(kv_delimiter, 2)
487
+ result[key] = val
488
+ end
489
+
490
+ result
491
+ end
492
+ end
493
+ end
494
+ end
495
+ 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
@@ -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"
@@ -4,23 +4,7 @@ module Committee
4
4
  module SchemaValidator
5
5
  class Option
6
6
  # Boolean Options
7
- attr_reader :allow_blank_structures,
8
- :allow_empty_date_and_datetime,
9
- :allow_form_params,
10
- :allow_get_body,
11
- :allow_query_params,
12
- :allow_non_get_query_params,
13
- :check_content_type,
14
- :check_header,
15
- :coerce_date_times,
16
- :coerce_form_params,
17
- :coerce_path_params,
18
- :coerce_query_params,
19
- :coerce_recursive,
20
- :optimistic_json,
21
- :validate_success_only,
22
- :parse_response_by_content_type,
23
- :parameter_overwrite_by_rails_rule
7
+ attr_reader :allow_blank_structures, :allow_empty_date_and_datetime, :allow_form_params, :allow_get_body, :allow_query_params, :allow_non_get_query_params, :check_content_type, :check_header, :coerce_date_times, :coerce_form_params, :coerce_path_params, :coerce_query_params, :coerce_recursive, :coerce_response_values, :deserialize_parameters, :optimistic_json, :validate_success_only, :parse_response_by_content_type, :parameter_overwrite_by_rails_rule, :strict_query_params
24
8
 
25
9
  # Non-boolean options:
26
10
  attr_reader :headers_key, :params_key, :query_hash_key, :request_body_hash_key, :path_hash_key, :prefix
@@ -44,8 +28,10 @@ module Committee
44
28
  @check_content_type = options.fetch(:check_content_type, true)
45
29
  @check_header = options.fetch(:check_header, true)
46
30
  @coerce_recursive = options.fetch(:coerce_recursive, true)
31
+ @coerce_response_values = options.fetch(:coerce_response_values, false)
47
32
  @optimistic_json = options.fetch(:optimistic_json, false)
48
33
  @parse_response_by_content_type = options.fetch(:parse_response_by_content_type, true)
34
+ @strict_query_params = options.fetch(:strict_query_params, false)
49
35
 
50
36
  @parameter_overwrite_by_rails_rule =
51
37
  if options.key?(:parameter_overwite_by_rails_rule)
@@ -61,6 +47,9 @@ module Committee
61
47
  @coerce_form_params = options.fetch(:coerce_form_params, schema.driver.default_coerce_form_params)
62
48
  @coerce_path_params = options.fetch(:coerce_path_params, schema.driver.default_path_params)
63
49
  @coerce_query_params = options.fetch(:coerce_query_params, schema.driver.default_query_params)
50
+ @deserialize_parameters = options.fetch(:deserialize_parameters,
51
+ schema.driver.respond_to?(:default_deserialize_parameters) ?
52
+ schema.driver.default_deserialize_parameters : false)
64
53
  @validate_success_only = options.fetch(:validate_success_only, schema.driver.default_validate_success_only)
65
54
  end
66
55
  end
@@ -12,7 +12,11 @@ module Committee
12
12
  def build_prefix_regexp(prefix)
13
13
  return nil unless prefix
14
14
 
15
- /\A#{Regexp.escape(prefix)}/.freeze
15
+ if prefix == "/" || prefix.end_with?("/")
16
+ /\A#{Regexp.escape(prefix)}/.freeze
17
+ else
18
+ /\A#{Regexp.escape(prefix)}(?=\/|\z)/.freeze
19
+ end
16
20
  end
17
21
  end
18
22
  end