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
@@ -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
@@ -2,17 +2,28 @@
2
2
 
3
3
  module Committee
4
4
  module SchemaValidator
5
+ JSON_MEDIA_TYPE_PATTERN = %r{\Aapplication/(?:.+\+)?json\z}.freeze
6
+
5
7
  class << self
6
8
  def request_media_type(request)
7
9
  Rack::MediaType.type(request.env['CONTENT_TYPE'])
8
10
  end
9
11
 
12
+ def json_media_type?(content_type)
13
+ normalized_content_type = Rack::MediaType.type(content_type)
14
+ normalized_content_type&.match?(JSON_MEDIA_TYPE_PATTERN) || false
15
+ end
16
+
10
17
  # @param [String] prefix
11
18
  # @return [Regexp]
12
19
  def build_prefix_regexp(prefix)
13
20
  return nil unless prefix
14
21
 
15
- /\A#{Regexp.escape(prefix)}/.freeze
22
+ if prefix == "/" || prefix.end_with?("/")
23
+ /\A#{Regexp.escape(prefix)}/.freeze
24
+ else
25
+ /\A#{Regexp.escape(prefix)}(?=\/|\z)/.freeze
26
+ end
16
27
  end
17
28
  end
18
29
  end
@@ -0,0 +1,416 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Committee
4
+ module Test
5
+ # Handles temporary parameter exclusion during schema validation.
6
+ # Allows testing error responses without validation failures for intentionally missing parameters.
7
+ class ExceptParameter
8
+ FORMAT_DUMMIES = { 'date-time': '2000-01-01T00:00:00Z', date: '2000-01-01', email: 'dummy@example.com', uuid: '00000000-0000-0000-0000-000000000000' }.freeze
9
+
10
+ # @param [Rack::Request] request The request object
11
+ # @param [Hash] committee_options Committee options hash
12
+ def initialize(request, committee_options)
13
+ @request = request
14
+ @committee_options = committee_options
15
+ @handlers = {}
16
+ end
17
+
18
+ # Apply dummy values to excepted parameters
19
+ # @param [Hash] except Hash of parameter types to parameter names
20
+ # (e.g., { headers: ['authorization'], query: ['page'] })
21
+ # @return [void]
22
+ def apply(except)
23
+ except.each do |param_type, param_names|
24
+ handler = (@handlers[param_type] ||= handler_for(param_type))
25
+ handler&.apply(param_names)
26
+ end
27
+ end
28
+
29
+ # Restore original parameter values
30
+ # @return [void]
31
+ def restore
32
+ @handlers.each_value(&:restore)
33
+ end
34
+
35
+ private
36
+
37
+ def handler_for(param_type)
38
+ case param_type
39
+ when :headers then HeaderHandler.new(@request, @committee_options)
40
+ when :query then QueryHandler.new(@request, @committee_options)
41
+ when :body then BodyHandler.new(@request, @committee_options)
42
+ end
43
+ end
44
+
45
+ # Shared helpers for looking up OpenAPI3 parameter schemas and generating
46
+ # type/format/enum-aware dummy values encoded as HTTP strings.
47
+ #
48
+ # Included by BaseHashParameterHandler, HeaderHandler, and BodyHandler.
49
+ module StringDummyLookup
50
+ private
51
+
52
+ # Resolve the OpenAPI3 operation object for the current request.
53
+ # Returns nil for non-OpenAPI3 schemas or any lookup failure.
54
+ def resolve_operation
55
+ schema = Committee::Middleware::Base.get_schema(@committee_options)
56
+ return nil unless schema.is_a?(Committee::Drivers::OpenAPI3::Schema)
57
+
58
+ path = @request.path_info
59
+ if (prefix = @committee_options[:prefix])
60
+ path = path.gsub(Regexp.new("\\A#{Regexp.escape(prefix)}"), '')
61
+ end
62
+
63
+ schema.operation_object(path, @request.request_method.downcase)
64
+ end
65
+
66
+ # Find the OpenAPI3 schema object for a parameter by name and location.
67
+ # Returns nil for non-OpenAPI3 schemas or any lookup failure.
68
+ #
69
+ # Searches both operation-level and path item-level parameters (OpenAPI 3 allows
70
+ # parameters to be declared on the path item and shared across operations).
71
+ # Operation-level parameters take precedence per the OpenAPI spec.
72
+ def find_parameter_schema(key, location)
73
+ operation = resolve_operation
74
+ return nil unless operation
75
+
76
+ op_object = operation.request_operation.operation_object
77
+ # Merge operation-level and path item-level parameters; operation level first
78
+ # so that overrides are respected when both define the same parameter.
79
+ params = Array(op_object&.parameters) + Array(op_object&.parent&.parameters)
80
+ params.find { |p| p.name&.casecmp?(key.to_s) && p.in == location }&.schema
81
+ rescue StandardError
82
+ nil
83
+ end
84
+
85
+ # Return a type-appropriate dummy value encoded as a String (or Array of strings
86
+ # for array-typed query/path params). The value must be coerceable to the
87
+ # expected schema type by openapi_parser's coerce_value option.
88
+ def string_dummy_for_schema(key, param_schema)
89
+ return "dummy-#{key}" unless param_schema
90
+ return param_schema.enum.first.to_s if param_schema.enum&.any?
91
+
92
+ case param_schema.type
93
+ when 'integer', 'number' then '0'
94
+ when 'boolean' then 'true'
95
+ when 'array' then ['0']
96
+ when 'string' then string_format_dummy(key, param_schema.format)
97
+ else "dummy-#{key}"
98
+ end
99
+ end
100
+
101
+ # Return a string that satisfies common OpenAPI3 string format constraints.
102
+ def string_format_dummy(key, format)
103
+ FORMAT_DUMMIES.fetch(format&.to_sym, "dummy-#{key}")
104
+ end
105
+ end
106
+
107
+ # Base handler for parameters stored in hash-like structures.
108
+ # Subclasses implement #get_storage and optionally #dummy_value_for.
109
+ class BaseHashParameterHandler
110
+ include StringDummyLookup
111
+
112
+ # @param [Rack::Request] request The request object
113
+ # @param [Hash] committee_options Committee options hash
114
+ def initialize(request, committee_options)
115
+ @request = request
116
+ @committee_options = committee_options
117
+ @original_values = {}
118
+ end
119
+
120
+ # Apply dummy values to parameters
121
+ # @param [Array<String, Symbol>] param_names Parameter names to except
122
+ # @return [void]
123
+ def apply(param_names)
124
+ storage = get_storage
125
+ return unless storage
126
+
127
+ param_names.each do |param_name|
128
+ key = param_name.to_s
129
+ @original_values[key] = storage[key]
130
+ storage[key] ||= dummy_value_for(key)
131
+ end
132
+ end
133
+
134
+ # Restore original parameter values
135
+ # @return [void]
136
+ def restore
137
+ storage = get_storage
138
+ return unless storage
139
+
140
+ @original_values.each do |key, value|
141
+ value.nil? ? storage.delete(key) : storage[key] = value
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ # Override in subclasses to specify the storage location
148
+ # @return [Hash, nil]
149
+ def get_storage
150
+ raise NotImplementedError, "#{self.class} must implement #get_storage"
151
+ end
152
+
153
+ def dummy_value_for(key)
154
+ "dummy-#{key}"
155
+ end
156
+ end
157
+
158
+ # Handler for request headers
159
+ class HeaderHandler
160
+ include StringDummyLookup
161
+
162
+ # In Rack/CGI, Content-Type and Content-Length are stored without the
163
+ # HTTP_ prefix. All other headers use the HTTP_ prefix convention.
164
+ SPECIAL_RACK_HEADERS = { 'content-type' => 'CONTENT_TYPE', 'content-length' => 'CONTENT_LENGTH', }.freeze
165
+ private_constant :SPECIAL_RACK_HEADERS
166
+
167
+ # @param [Rack::Request] request The request object
168
+ # @param [Hash] committee_options Committee options hash
169
+ def initialize(request, committee_options)
170
+ @request = request
171
+ @committee_options = committee_options
172
+ @original_values = {}
173
+ end
174
+
175
+ # Apply dummy values to header parameters
176
+ # @param [Array<String, Symbol>] param_names Header names to except
177
+ # @return [void]
178
+ def apply(param_names)
179
+ param_names.each do |param_name|
180
+ key = rack_header_key(param_name.to_s)
181
+ @original_values[key] = @request.env[key]
182
+ @request.env[key] ||= string_dummy_for_schema(param_name.to_s, find_parameter_schema(param_name.to_s, 'header'))
183
+ end
184
+ end
185
+
186
+ # Restore original header values
187
+ # @return [void]
188
+ def restore
189
+ @original_values.each do |key, value|
190
+ value.nil? ? @request.env.delete(key) : @request.env[key] = value
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ def rack_header_key(name)
197
+ SPECIAL_RACK_HEADERS[name.downcase] || "HTTP_#{name.upcase.tr('-', '_')}"
198
+ end
199
+ end
200
+
201
+ # Handler for query parameters
202
+ class QueryHandler < BaseHashParameterHandler
203
+ private
204
+
205
+ def get_storage
206
+ # Calling request.GET ensures Rack parses the query string into
207
+ # rack.request.query_hash before we inject dummy values, so that
208
+ # any existing query parameters are preserved.
209
+ @request.GET
210
+ end
211
+
212
+ def dummy_value_for(key)
213
+ string_dummy_for_schema(key, find_parameter_schema(key, 'query'))
214
+ end
215
+ end
216
+
217
+ # Handler for request body parameters
218
+ #
219
+ # Supports three content types:
220
+ # - application/json (and variants): replaces rack.input stream so that
221
+ # request_unpack re-parses the modified body during validation.
222
+ # - application/x-www-form-urlencoded / multipart/form-data: pre-populates
223
+ # rack.request.form_hash, which Rack returns directly from request.POST
224
+ # without re-parsing the raw body.
225
+ # - Other content types (e.g. binary): no-op, as RequestUnpacker does not
226
+ # extract named parameters from them.
227
+ class BodyHandler
228
+ include StringDummyLookup
229
+
230
+ # @param [Rack::Request] request The request object
231
+ # @param [Hash] committee_options Committee options hash
232
+ def initialize(request, committee_options)
233
+ @request = request
234
+ @committee_options = committee_options
235
+ @original_body = nil
236
+ @original_form_values = nil
237
+ end
238
+
239
+ # Apply dummy values to body parameters based on content type.
240
+ # @param [Array<String, Symbol>] param_names Body parameter names to except
241
+ # @return [void]
242
+ def apply(param_names)
243
+ if json_content_type?
244
+ apply_json(param_names)
245
+ elsif form_content_type?
246
+ apply_form(param_names)
247
+ end
248
+ # Other content types: no-op
249
+ end
250
+
251
+ # Restore original body content
252
+ # @return [void]
253
+ def restore
254
+ restore_json if @original_body
255
+ restore_form if @original_form_values
256
+ end
257
+
258
+ private
259
+
260
+ def json_content_type?
261
+ mt = @request.media_type
262
+ mt.nil? || mt.match?(%r{application/(?:.*\+)?json})
263
+ end
264
+
265
+ def form_content_type?
266
+ mt = @request.media_type
267
+ mt == 'application/x-www-form-urlencoded' || mt&.start_with?('multipart/form-data')
268
+ end
269
+
270
+ # --- JSON body handling ---
271
+ #
272
+ # Replaces rack.input with a modified JSON body. Also clears any stale
273
+ # rack.request.form_hash / rack.request.form_pairs caches so that Rack
274
+ # does not return outdated form data if POST is called after the swap.
275
+
276
+ def apply_json(param_names)
277
+ original_body = read_body
278
+ body_hash = parse_body(original_body)
279
+
280
+ # Non-Hash bodies (arrays, scalars) cannot have named fields injected.
281
+ # Skip injection and let the schema validator report the type mismatch.
282
+ return unless body_hash.is_a?(Hash)
283
+
284
+ # Commit state only after successful parse so that restore_json is not
285
+ # triggered unnecessarily when parse_body raises (e.g. invalid JSON).
286
+ @original_body = original_body
287
+ @saved_form_hash = @request.env.delete('rack.request.form_hash')
288
+ @saved_form_pairs = @request.env.delete('rack.request.form_pairs')
289
+
290
+ param_names.each do |param_name|
291
+ key = param_name.to_s
292
+ body_hash[key] = dummy_value_for(key) if body_hash[key].nil?
293
+ end
294
+
295
+ replace_body(JSON.generate(body_hash))
296
+ end
297
+
298
+ def restore_json
299
+ replace_body(@original_body)
300
+ @request.env.delete(body_hash_key)
301
+ @request.env['rack.request.form_hash'] = @saved_form_hash if @saved_form_hash
302
+ @request.env['rack.request.form_pairs'] = @saved_form_pairs if @saved_form_pairs
303
+ end
304
+
305
+ # --- Form body handling ---
306
+ #
307
+ # Calls request.POST to trigger Rack's form parsing (populating
308
+ # rack.request.form_hash from rack.input), then injects dummy values
309
+ # for missing params into the live hash. In Rack 3.x, request.POST
310
+ # returns rack.request.form_hash directly on subsequent calls, so the
311
+ # injected values are visible during validation.
312
+
313
+ def apply_form(param_names)
314
+ @request.body&.rewind
315
+ form_hash = @request.POST
316
+ @original_form_values = {}
317
+ param_names.each do |param_name|
318
+ key = param_name.to_s
319
+ @original_form_values[key] = form_hash[key]
320
+ form_hash[key] ||= form_dummy_for(key)
321
+ end
322
+ rescue StandardError
323
+ # If form parsing fails (e.g. malformed body), skip remaining injection.
324
+ # Use ||= so that any originals already saved in the loop are preserved
325
+ # and can be correctly restored by restore_form.
326
+ @original_form_values ||= {}
327
+ end
328
+
329
+ def restore_form
330
+ form_hash = @request.env['rack.request.form_hash']
331
+ return unless form_hash
332
+
333
+ @original_form_values.each do |key, value|
334
+ value.nil? ? form_hash.delete(key) : form_hash[key] = value
335
+ end
336
+ end
337
+
338
+ # --- Dummy value helpers ---
339
+
340
+ def body_hash_key
341
+ @committee_options.fetch(:request_body_hash_key, 'committee.request_body_hash')
342
+ end
343
+
344
+ def read_body
345
+ return '' unless @request.body
346
+
347
+ @request.body.rewind
348
+ body = @request.body.read
349
+ @request.body.rewind
350
+ body || ''
351
+ end
352
+
353
+ def parse_body(body_str)
354
+ return {} if body_str.nil? || body_str.empty?
355
+
356
+ JSON.parse(body_str)
357
+ end
358
+
359
+ def replace_body(body_str)
360
+ @request.env['rack.input'] = StringIO.new(body_str)
361
+ end
362
+
363
+ # Returns a native-typed dummy value for JSON bodies.
364
+ def dummy_value_for(key)
365
+ prop_schema = body_param_schema(key)
366
+ return "dummy-#{key}" unless prop_schema
367
+
368
+ return prop_schema.enum.first if prop_schema.enum&.any?
369
+
370
+ case prop_schema.type
371
+ when 'integer' then 0
372
+ when 'number' then 0.0
373
+ when 'boolean' then false
374
+ when 'array' then []
375
+ when 'object' then {}
376
+ when 'string' then FORMAT_DUMMIES.fetch(prop_schema.format&.to_sym, "dummy-#{key}")
377
+ else "dummy-#{key}"
378
+ end
379
+ end
380
+
381
+ # Returns a string-encoded dummy value for form bodies.
382
+ # Form data is always transmitted as strings; openapi_parser's coerce_value
383
+ # handles conversion to the declared type during validation.
384
+ def form_dummy_for(key)
385
+ prop_schema = body_param_schema(key)
386
+ return "dummy-#{key}" unless prop_schema
387
+
388
+ return prop_schema.enum.first.to_s if prop_schema.enum&.any?
389
+
390
+ case prop_schema.type
391
+ when 'integer', 'number' then '0'
392
+ when 'boolean' then 'true'
393
+ when 'array' then ['0']
394
+ when 'string' then FORMAT_DUMMIES.fetch(prop_schema.format&.to_sym, "dummy-#{key}").to_s
395
+ else "dummy-#{key}"
396
+ end
397
+ end
398
+
399
+ def body_param_schema(key)
400
+ operation = resolve_operation
401
+ return nil unless operation
402
+
403
+ request_body = operation.request_operation.operation_object&.request_body
404
+ return nil unless request_body
405
+
406
+ content = request_body.content
407
+ return nil unless content
408
+
409
+ content[@request.media_type]&.schema&.properties&.[](key)
410
+ rescue StandardError
411
+ nil
412
+ end
413
+ end
414
+ end
415
+ end
416
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'except_parameter'
4
+
3
5
  module Committee
4
6
  module Test
5
7
  module Methods
@@ -8,13 +10,21 @@ module Committee
8
10
  assert_response_schema_confirm(expected_status)
9
11
  end
10
12
 
11
- def assert_request_schema_confirm
13
+ def assert_request_schema_confirm(except: {})
12
14
  unless schema_validator.link_exist?
13
15
  request = "`#{request_object.request_method} #{request_object.path_info}` undefined in schema (prefix: #{committee_options[:prefix].inspect})."
14
16
  raise Committee::InvalidRequest.new(request)
15
17
  end
16
18
 
17
- schema_validator.request_validate(request_object)
19
+ if except.empty?
20
+ schema_validator.request_validate(request_object)
21
+ else
22
+ with_except_params(except) do
23
+ schema_validator.request_validate(request_object)
24
+ end
25
+ end
26
+
27
+ increment_assertion_count
18
28
  end
19
29
 
20
30
  def assert_response_schema_confirm(expected_status = nil)
@@ -38,6 +48,8 @@ module Committee
38
48
  end
39
49
 
40
50
  schema_validator.response_validate(status, headers, [body], true) if validate_response?(status)
51
+
52
+ increment_assertion_count
41
53
  end
42
54
 
43
55
  def committee_options
@@ -79,6 +91,30 @@ module Committee
79
91
  def old_behavior
80
92
  committee_options.fetch(:old_assert_behavior, false)
81
93
  end
94
+
95
+ private
96
+
97
+ # assert_*_schema_confirm signal failure by raising, not via `assert`,
98
+ # so Minitest would report "Test is missing assertions" on success.
99
+ # Bump the counter explicitly; no-op outside Minitest (e.g. RSpec).
100
+ def increment_assertion_count
101
+ assert true if respond_to?(:assertions)
102
+ end
103
+
104
+ # Temporarily adds dummy values for excepted parameters during validation
105
+ # @see ExceptParameter
106
+ def with_except_params(except)
107
+ return yield if except.empty?
108
+
109
+ except_handler = ExceptParameter.new(request_object, committee_options)
110
+
111
+ begin
112
+ except_handler.apply(except)
113
+ yield
114
+ ensure
115
+ except_handler.restore
116
+ end
117
+ end
82
118
  end
83
119
  end
84
120
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Committee
4
- VERSION = '5.6.1'.freeze
4
+ VERSION = '5.6.3'.freeze
5
5
  end
data/lib/committee.rb CHANGED
@@ -30,10 +30,10 @@ end
30
30
  require_relative "committee/utils"
31
31
  require_relative "committee/drivers"
32
32
  require_relative "committee/errors"
33
+ require_relative "committee/validation_error"
33
34
  require_relative "committee/middleware"
34
35
  require_relative "committee/request_unpacker"
35
36
  require_relative "committee/schema_validator"
36
- require_relative "committee/validation_error"
37
37
 
38
38
  require_relative "committee/bin/committee_stub"
39
39
  require_relative "committee/test/methods"
@@ -50,10 +50,7 @@ describe Committee::Drivers::OpenAPI2::Driver do
50
50
  end
51
51
 
52
52
  it "prefers a 200 response first" do
53
- schema_data = schema_data_with_responses({
54
- '201' => { 'schema' => { 'description' => '201 response' } },
55
- '200' => { 'schema' => { 'description' => '200 response' } },
56
- })
53
+ schema_data = schema_data_with_responses({ '201' => { 'schema' => { 'description' => '201 response' } }, '200' => { 'schema' => { 'description' => '200 response' } }, })
57
54
 
58
55
  schema = @driver.parse(schema_data)
59
56
  link = schema.routes['GET'][0][1]
@@ -62,10 +59,7 @@ describe Committee::Drivers::OpenAPI2::Driver do
62
59
  end
63
60
 
64
61
  it "prefers a 201 response next" do
65
- schema_data = schema_data_with_responses({
66
- '302' => { 'schema' => { 'description' => '302 response' } },
67
- '201' => { 'schema' => { 'description' => '201 response' } },
68
- })
62
+ schema_data = schema_data_with_responses({ '302' => { 'schema' => { 'description' => '302 response' } }, '201' => { 'schema' => { 'description' => '201 response' } }, })
69
63
 
70
64
  schema = @driver.parse(schema_data)
71
65
  link = schema.routes['GET'][0][1]
@@ -74,10 +68,7 @@ describe Committee::Drivers::OpenAPI2::Driver do
74
68
  end
75
69
 
76
70
  it "prefers any three-digit response next" do
77
- schema_data = schema_data_with_responses({
78
- 'default' => { 'schema' => { 'description' => 'default response' } },
79
- '302' => { 'schema' => { 'description' => '302 response' } },
80
- })
71
+ schema_data = schema_data_with_responses({ 'default' => { 'schema' => { 'description' => 'default response' } }, '302' => { 'schema' => { 'description' => '302 response' } }, })
81
72
 
82
73
  schema = @driver.parse(schema_data)
83
74
  link = schema.routes['GET'][0][1]
@@ -86,10 +77,7 @@ describe Committee::Drivers::OpenAPI2::Driver do
86
77
  end
87
78
 
88
79
  it "prefers any numeric three-digit response next" do
89
- schema_data = schema_data_with_responses({
90
- 'default' => { 'schema' => { 'description' => 'default response' } },
91
- 302 => { 'schema' => { 'description' => '302 response' } },
92
- })
80
+ schema_data = schema_data_with_responses({ 'default' => { 'schema' => { 'description' => 'default response' } }, 302 => { 'schema' => { 'description' => '302 response' } }, })
93
81
 
94
82
  schema = @driver.parse(schema_data)
95
83
  link = schema.routes['GET'][0][1]