committee 3.2.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c978ba3ca98ab6a684b1555ebc3e3575ee5fb20ae11c17bc7b8c2baebd71eb86
4
- data.tar.gz: 0424b819be1a1b0872894d8c0b897e325868347e54d19b443a351f1270d3b097
3
+ metadata.gz: 43984e56c905ff9e141e1ed49af5c9e51b07c39bda8bc268d195dc5799809d89
4
+ data.tar.gz: 76a6269ac7cc8237a63b2ae6c69ac512a60cecabaed7564dee88cd3adfa9328e
5
5
  SHA512:
6
- metadata.gz: 306d7ebf406280f3ad9279543e010046a691bed7b6ba89c61b20663e256e6e7efacd7a4c2a0a2b021d405ab603519c887319ec3963204de09fb9405a66144032
7
- data.tar.gz: d0043bd37f971a053843acc791bb9f9216ef8b2aa58946e5ab6a7d0ddcb9f4190deec402fc5424d4a2c14fe074f581576663f69252319d7c7085b9355ec48355
6
+ metadata.gz: f79ef24531c7b81489d12ea59e9da42e570236554a84c064c8478625e29167c3fef7245196a2c48d2a0a31ab33639417d47527321b321172f32c5f80fce5f46b
7
+ data.tar.gz: 68c8ad83aa2392ba1d31d440edb54b1301b6c37e093f7d466c0a9f406510a0efbdf6c9ea8aacf1ddf051fae31c03d2bd777accd90101bd321037dfdafd0affbf
@@ -21,14 +21,14 @@ module Committee
21
21
  # @param [String] schema_path
22
22
  # @return [Committee::Driver]
23
23
  def self.load_from_json(schema_path)
24
- load_from_data(JSON.parse(File.read(schema_path)))
24
+ load_from_data(JSON.parse(File.read(schema_path)), schema_path)
25
25
  end
26
26
 
27
27
  # load and build drive from YAML file
28
28
  # @param [String] schema_path
29
29
  # @return [Committee::Driver]
30
30
  def self.load_from_yaml(schema_path)
31
- load_from_data(YAML.load_file(schema_path))
31
+ load_from_data(YAML.load_file(schema_path), schema_path)
32
32
  end
33
33
 
34
34
  # load and build drive from file
@@ -48,10 +48,10 @@ module Committee
48
48
  # load and build drive from Hash object
49
49
  # @param [Hash] hash
50
50
  # @return [Committee::Driver]
51
- def self.load_from_data(hash)
51
+ def self.load_from_data(hash, schema_path = nil)
52
52
  if hash['openapi']&.start_with?('3.0.')
53
- parser = OpenAPIParser.parse(hash)
54
- return Committee::Drivers::OpenAPI3::Driver.new.parse(parser)
53
+ openapi = OpenAPIParser.parse_with_filepath(hash, schema_path)
54
+ return Committee::Drivers::OpenAPI3::Driver.new.parse(openapi)
55
55
  end
56
56
 
57
57
  driver = if hash['swagger'] == '2.0'
@@ -8,17 +8,19 @@ module Committee
8
8
 
9
9
  @error_class = options.fetch(:error_class, Committee::ValidationError)
10
10
  @error_handler = options[:error_handler]
11
+ @ignore_error = options.fetch(:ignore_error, false)
11
12
 
12
13
  @raise = options[:raise]
13
14
  @schema = self.class.get_schema(options)
14
15
 
15
16
  @router = @schema.build_router(options)
17
+ @accept_request_filter = options[:accept_request_filter] || -> (_) { true }
16
18
  end
17
19
 
18
20
  def call(env)
19
21
  request = Rack::Request.new(env)
20
22
 
21
- if @router.includes_request?(request)
23
+ if @router.includes_request?(request) && @accept_request_filter.call(request)
22
24
  handle(request)
23
25
  else
24
26
  @app.call(request.env)
@@ -13,27 +13,25 @@ module Committee
13
13
  end
14
14
 
15
15
  def handle(request)
16
- schema_validator = build_schema_validator(request)
17
- schema_validator.request_validate(request)
18
-
19
- raise Committee::NotFound, "That request method and path combination isn't defined." if !schema_validator.link_exist? && @strict
16
+ begin
17
+ schema_validator = build_schema_validator(request)
18
+ schema_validator.request_validate(request)
19
+
20
+ raise Committee::NotFound, "That request method and path combination isn't defined." if !schema_validator.link_exist? && @strict
21
+ rescue Committee::BadRequest, Committee::InvalidRequest
22
+ handle_exception($!, request.env)
23
+ raise if @raise
24
+ return @error_class.new(400, :bad_request, $!.message).render unless @ignore_error
25
+ rescue Committee::NotFound => e
26
+ raise if @raise
27
+ return @error_class.new(404, :not_found, e.message).render unless @ignore_error
28
+ rescue JSON::ParserError
29
+ handle_exception($!, request.env)
30
+ raise Committee::InvalidRequest if @raise
31
+ return @error_class.new(400, :bad_request, "Request body wasn't valid JSON.").render unless @ignore_error
32
+ end
20
33
 
21
34
  @app.call(request.env)
22
- rescue Committee::BadRequest, Committee::InvalidRequest
23
- handle_exception($!, request.env)
24
- raise if @raise
25
- @error_class.new(400, :bad_request, $!.message).render
26
- rescue Committee::NotFound => e
27
- raise if @raise
28
- @error_class.new(
29
- 404,
30
- :not_found,
31
- e.message
32
- ).render
33
- rescue JSON::ParserError
34
- handle_exception($!, request.env)
35
- raise Committee::InvalidRequest if @raise
36
- @error_class.new(400, :bad_request, "Request body wasn't valid JSON.").render
37
35
  end
38
36
 
39
37
  private
@@ -8,13 +8,12 @@ module Committee
8
8
  def initialize(app, options = {})
9
9
  super
10
10
  @validate_success_only = @schema.validator_option.validate_success_only
11
- @ignore_error = options.fetch(:ignore_error, false)
12
11
  end
13
12
 
14
13
  def handle(request)
15
- begin
16
- status, headers, response = @app.call(request.env)
14
+ status, headers, response = @app.call(request.env)
17
15
 
16
+ begin
18
17
  v = build_schema_validator(request)
19
18
  v.response_validate(status, headers, response) if v.link_exist? && self.class.validate?(status, validate_success_only)
20
19
 
@@ -35,7 +35,14 @@ module Committee
35
35
  response.each do |chunk|
36
36
  full_body << chunk
37
37
  end
38
- data = full_body.empty? ? {} : JSON.parse(full_body)
38
+
39
+ data = {}
40
+ unless full_body.empty?
41
+ parse_to_json = !validator_option.parse_response_by_content_type ||
42
+ headers.fetch('Content-Type', nil)&.start_with?('application/json')
43
+ data = JSON.parse(full_body) if parse_to_json
44
+ end
45
+
39
46
  Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link, validate_success_only: validator_option.validate_success_only).call(status, headers, data)
40
47
  end
41
48
 
@@ -30,7 +30,14 @@ module Committee
30
30
  response.each do |chunk|
31
31
  full_body << chunk
32
32
  end
33
- data = full_body.empty? ? {} : JSON.parse(full_body)
33
+
34
+ parse_to_json = !validator_option.parse_response_by_content_type ||
35
+ headers.fetch('Content-Type', nil)&.start_with?('application/json')
36
+ data = if parse_to_json
37
+ full_body.empty? ? {} : JSON.parse(full_body)
38
+ else
39
+ full_body
40
+ end
34
41
 
35
42
  strict = test_method
36
43
  Committee::SchemaValidator::OpenAPI3::ResponseValidator.
@@ -116,7 +116,9 @@ module Committee
116
116
  content_type = headers['Content-Type'].to_s.split(";").first.to_s
117
117
 
118
118
  # bad performance because when we coerce value, same check
119
- return request_operation.validate_request_body(content_type, params, build_openapi_parser_post_option(validator_option))
119
+ schema_validator_options = build_openapi_parser_post_option(validator_option)
120
+ request_operation.validate_request_parameter(params, headers, schema_validator_options)
121
+ request_operation.validate_request_body(content_type, params, schema_validator_options)
120
122
  rescue => e
121
123
  raise Committee::InvalidRequest.new(e.message)
122
124
  end
@@ -17,7 +17,6 @@ module Committee
17
17
  def call(status, headers, response_data, strict)
18
18
  return unless Committee::Middleware::ResponseValidation.validate?(status, validate_success_only)
19
19
 
20
- #content_type = headers['Content-Type'].to_s.split(";").first.to_s
21
20
  operation_wrapper.validate_response_params(status, headers, response_data, strict, check_header)
22
21
  end
23
22
 
@@ -15,7 +15,8 @@ module Committee
15
15
  :coerce_query_params,
16
16
  :coerce_recursive,
17
17
  :optimistic_json,
18
- :validate_success_only
18
+ :validate_success_only,
19
+ :parse_response_by_content_type
19
20
 
20
21
  # Non-boolean options:
21
22
  attr_reader :headers_key,
@@ -35,6 +36,12 @@ module Committee
35
36
  @check_header = options.fetch(:check_header, true)
36
37
  @coerce_recursive = options.fetch(:coerce_recursive, true)
37
38
  @optimistic_json = options.fetch(:optimistic_json, false)
39
+ @parse_response_by_content_type = if options[:parse_response_by_content_type].nil?
40
+ Committee.warn_deprecated('Committee: please set parse_response_by_content_type = false because we\'ll change default value in next major version.')
41
+ false
42
+ else
43
+ options.fetch(:parse_response_by_content_type)
44
+ end
38
45
 
39
46
  # Boolean options and have a different value by default
40
47
  @allow_get_body = options.fetch(:allow_get_body, schema.driver.default_allow_get_body)
@@ -43,7 +43,11 @@ describe Committee::Bin::CommitteeStub, "app" do
43
43
  end
44
44
 
45
45
  def app
46
- @bin.get_app(hyper_schema, {})
46
+ options = {}
47
+ # TODO: delete when 5.0.0 released because default value changed
48
+ options[:parse_response_by_content_type] = false
49
+
50
+ @bin.get_app(hyper_schema, options)
47
51
  end
48
52
 
49
53
  it "defaults to a 404" do
@@ -103,17 +103,20 @@ describe Committee::Middleware::Base do
103
103
 
104
104
  describe 'initialize option' do
105
105
  it "schema_path option with hyper-schema" do
106
- b = Committee::Middleware::Base.new(nil, schema_path: hyper_schema_schema_path)
106
+ # TODO: delete when 5.0.0 released because default value changed
107
+ b = Committee::Middleware::Base.new(nil, schema_path: hyper_schema_schema_path, parse_response_by_content_type: false)
107
108
  assert_kind_of Committee::Drivers::HyperSchema::Schema, b.instance_variable_get(:@schema)
108
109
  end
109
110
 
110
111
  it "schema_path option with OpenAPI2" do
111
- b = Committee::Middleware::Base.new(nil, schema_path: open_api_2_schema_path)
112
+ # TODO: delete when 5.0.0 released because default value changed
113
+ b = Committee::Middleware::Base.new(nil, schema_path: open_api_2_schema_path, parse_response_by_content_type: false)
112
114
  assert_kind_of Committee::Drivers::OpenAPI2::Schema, b.instance_variable_get(:@schema)
113
115
  end
114
116
 
115
117
  it "schema_path option with OpenAPI3" do
116
- b = Committee::Middleware::Base.new(nil, schema_path: open_api_3_schema_path)
118
+ # TODO: delete when 5.0.0 released because default value changed
119
+ b = Committee::Middleware::Base.new(nil, schema_path: open_api_3_schema_path, parse_response_by_content_type: false)
117
120
  assert_kind_of Committee::Drivers::OpenAPI3::Schema, b.instance_variable_get(:@schema)
118
121
  end
119
122
  end
@@ -121,6 +124,9 @@ describe Committee::Middleware::Base do
121
124
  private
122
125
 
123
126
  def new_rack_app(options = {})
127
+ # TODO: delete when 5.0.0 released because default value changed
128
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
129
+
124
130
  Rack::Builder.new {
125
131
  use Committee::Middleware::RequestValidation, options
126
132
  run lambda { |_|
@@ -249,8 +249,7 @@ describe Committee::Middleware::RequestValidation do
249
249
  }
250
250
  post "/characters", JSON.generate(params)
251
251
  assert_equal 400, last_response.status
252
- # FIXME: when ruby 2.3 dropped, fix because ruby 2.3 return Fixnum, ruby 2.4 or later return Integer
253
- assert_match(/1 class is #{1.class}/i, last_response.body)
252
+ assert_match(/expected string, but received Integer:/i, last_response.body)
254
253
  end
255
254
 
256
255
  it "rescues JSON errors" do
@@ -279,7 +278,7 @@ describe Committee::Middleware::RequestValidation do
279
278
  header "Content-Type", "application/json"
280
279
  post "/v1/characters", JSON.generate(params)
281
280
  assert_equal 400, last_response.status
282
- assert_match(/1 class is/i, last_response.body)
281
+ assert_match(/expected string, but received Integer: /i, last_response.body)
283
282
  end
284
283
 
285
284
  it "ignores paths outside the prefix" do
@@ -321,7 +320,7 @@ describe Committee::Middleware::RequestValidation do
321
320
  get "/validate", nil
322
321
  end
323
322
 
324
- assert_match(/required parameters query_string not exist in/i, e.message)
323
+ assert_match(/missing required parameters: query_string/i, e.message)
325
324
  end
326
325
 
327
326
  it "raises error when required path parameter is invalid" do
@@ -332,7 +331,7 @@ describe Committee::Middleware::RequestValidation do
332
331
  get "/coerce_path_params/#{not_an_integer}", nil
333
332
  end
334
333
 
335
- assert_match(/is String but it's not valid integer in/i, e.message)
334
+ assert_match(/expected integer, but received String: abc/i, e.message)
336
335
  end
337
336
 
338
337
  it "optionally raises an error" do
@@ -356,7 +355,7 @@ describe Committee::Middleware::RequestValidation do
356
355
  get "/string_params_coercer", {"integer_1" => "1"}
357
356
 
358
357
  assert_equal 400, last_response.status
359
- assert_match(/1 class/i, last_response.body)
358
+ assert_match(/expected integer, but received String:/i, last_response.body)
360
359
  end
361
360
 
362
361
  it "passes through a valid request for OpenAPI3" do
@@ -375,7 +374,14 @@ describe Committee::Middleware::RequestValidation do
375
374
  get "/characters?limit=foo"
376
375
 
377
376
  assert_equal 400, last_response.status
378
- assert_match(/foo class/i, last_response.body)
377
+ assert_match(/expected integer, but received String: foo/i, last_response.body)
378
+ end
379
+
380
+ it "ignores errors when ignore_error: true" do
381
+ @app = new_rack_app(schema: open_api_3_schema, ignore_error: true)
382
+ get "/characters?limit=foo"
383
+
384
+ assert_equal 200, last_response.status
379
385
  end
380
386
 
381
387
  it "coerce string to integer" do
@@ -399,21 +405,65 @@ describe Committee::Middleware::RequestValidation do
399
405
  end
400
406
 
401
407
  describe 'check header' do
402
- it 'no required header' do
403
- @app = new_rack_app(schema: open_api_3_schema, check_header: true)
404
-
405
- get "/header"
406
-
407
- assert_equal 400, last_response.status
408
- assert_match(/required parameters integer not exist/i, last_response.body)
408
+ [
409
+ { check_header: true, description: 'valid value', value: 1, expected: { status: 200 } },
410
+ { check_header: true, description: 'missing value', value: nil, expected: { status: 400, error: 'missing required parameters: integer' } },
411
+ { check_header: true, description: 'invalid value', value: 'x', expected: { status: 400, error: 'expected integer, but received String: x' } },
412
+
413
+ { check_header: false, description: 'valid value', value: 1, expected: { status: 200 } },
414
+ { check_header: false, description: 'missing value', value: nil, expected: { status: 200 } },
415
+ { check_header: false, description: 'invalid value', value: 'x', expected: { status: 200 } },
416
+ ].each do |h|
417
+ check_header = h[:check_header]
418
+ description = h[:description]
419
+ value = h[:value]
420
+ expected = h[:expected]
421
+ describe "when #{check_header}" do
422
+ %w(get post put patch delete).each do |method|
423
+ describe method do
424
+ describe description do
425
+ it (expected[:error].nil? ? 'should pass' : 'should fail') do
426
+ @app = new_rack_app(schema: open_api_3_schema, check_header: check_header)
427
+
428
+ header 'integer', value
429
+ send(method, "/header")
430
+
431
+ assert_equal expected[:status], last_response.status
432
+ assert_match(expected[:error], last_response.body) if expected[:error]
433
+ end
434
+ end
435
+ end
436
+ end
437
+ end
409
438
  end
439
+ end
410
440
 
411
- it 'no required header but not check' do
412
- @app = new_rack_app(schema: open_api_3_schema, check_header: false)
441
+ describe ':accept_request_filter' do
442
+ [
443
+ { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 400 } },
444
+ { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/c') }, expected: { status: 400 } },
445
+ { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
446
+ ].each do |h|
447
+ description = h[:description]
448
+ accept_request_filter = h[:accept_request_filter]
449
+ expected = h[:expected]
450
+ it description do
451
+ @app = new_rack_app(prefix: '/v1', schema: open_api_3_schema, accept_request_filter: accept_request_filter)
452
+
453
+ post 'v1/characters', JSON.generate(string_post_1: 1)
454
+
455
+ assert_equal expected[:status], last_response.status
456
+ end
457
+ end
458
+ end
413
459
 
414
- get "/header"
460
+ it 'does not suppress application error' do
461
+ @app = new_rack_app_with_lambda(lambda { |_|
462
+ JSON.load('-') # invalid json
463
+ }, schema: open_api_3_schema, raise: true)
415
464
 
416
- assert_equal 200, last_response.status
465
+ assert_raises(JSON::ParserError) do
466
+ get "/error", nil
417
467
  end
418
468
  end
419
469
 
@@ -426,6 +476,9 @@ describe Committee::Middleware::RequestValidation do
426
476
  end
427
477
 
428
478
  def new_rack_app_with_lambda(check_lambda, options = {})
479
+ # TODO: delete when 5.0.0 released because default value changed
480
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
481
+
429
482
  Rack::Builder.new {
430
483
  use Committee::Middleware::RequestValidation, options
431
484
  run check_lambda
@@ -296,6 +296,16 @@ describe Committee::Middleware::RequestValidation do
296
296
  assert_match(/invalid request/i, last_response.body)
297
297
  end
298
298
 
299
+ it "ignores errors when ignore_error: true" do
300
+ @app = new_rack_app(schema: hyper_schema, ignore_error: true)
301
+ header "Content-Type", "application/json"
302
+ params = {
303
+ "name" => 1
304
+ }
305
+ post "/apps", JSON.generate(params)
306
+ assert_equal 200, last_response.status
307
+ end
308
+
299
309
  it "calls error_handler (has a arg) when request is invalid" do
300
310
  called_err = nil
301
311
  pr = ->(e) { called_err = e }
@@ -480,6 +490,25 @@ describe Committee::Middleware::RequestValidation do
480
490
  assert_equal 200, last_response.status
481
491
  end
482
492
 
493
+ describe ':accept_request_filter' do
494
+ [
495
+ { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 400 } },
496
+ { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/a') }, expected: { status: 400 } },
497
+ { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
498
+ ].each do |h|
499
+ description = h[:description]
500
+ accept_request_filter = h[:accept_request_filter]
501
+ expected = h[:expected]
502
+ it description do
503
+ @app = new_rack_app(prefix: '/v1', schema: hyper_schema, accept_request_filter: accept_request_filter)
504
+
505
+ post '/v1/apps', '{x:y}'
506
+
507
+ assert_equal expected[:status], last_response.status
508
+ end
509
+ end
510
+ end
511
+
483
512
  private
484
513
 
485
514
  def new_rack_app(options = {})
@@ -490,6 +519,9 @@ describe Committee::Middleware::RequestValidation do
490
519
 
491
520
 
492
521
  def new_rack_app_with_lambda(check_lambda, options = {})
522
+ # TODO: delete when 5.0.0 released because default value changed
523
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
524
+
493
525
  Rack::Builder.new {
494
526
  use Committee::Middleware::RequestValidation, options
495
527
  run check_lambda
@@ -34,6 +34,14 @@ describe Committee::Middleware::ResponseValidation do
34
34
  assert_equal 200, last_response.status
35
35
  end
36
36
 
37
+ it "passes through a invalid json with parse_response_by_content_type option" do
38
+ @app = new_response_rack("csv response", { "Content-Type" => "test/csv"}, schema: open_api_3_schema, parse_response_by_content_type: true)
39
+
40
+ get "/csv"
41
+
42
+ assert_equal 200, last_response.status
43
+ end
44
+
37
45
  it "passes through not definition" do
38
46
  @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), {}, schema: open_api_3_schema)
39
47
  get "/no_data"
@@ -47,7 +55,7 @@ describe Committee::Middleware::ResponseValidation do
47
55
  get "/characters"
48
56
  }
49
57
 
50
- assert_match(/class is Array but it's not valid object/i, e.message)
58
+ assert_match(/expected object, but received Array: /i, e.message)
51
59
  end
52
60
 
53
61
  it "passes through a 204 (no content) response" do
@@ -99,29 +107,59 @@ describe Committee::Middleware::ResponseValidation do
99
107
  assert_match(/valid JSON/i, last_response.body)
100
108
  end
101
109
 
102
- describe 'check header' do
103
- it 'valid type header' do
104
- @app = new_response_rack({}.to_json, {'x-limit' => 1}, schema: open_api_3_schema, raise: true)
105
-
106
- get "/header"
107
-
110
+ describe "remote schema $ref" do
111
+ it "passes through a valid response" do
112
+ @app = new_response_rack(JSON.generate({ "sample" => "value" }), {}, schema: open_api_3_schema)
113
+ get "/ref-sample"
108
114
  assert_equal 200, last_response.status
109
115
  end
110
116
 
111
- it 'invalid type header' do
112
- @app = new_response_rack({}.to_json, {'x-limit' => '1'}, schema: open_api_3_schema, raise: true)
113
-
114
- assert_raises(Committee::InvalidResponse) do
115
- get "/header"
116
- end
117
+ it "detects a invalid response" do
118
+ @app = new_response_rack("{}", {}, schema: open_api_3_schema)
119
+ get "/ref-sample"
120
+ assert_equal 500, last_response.status
117
121
  end
122
+ end
118
123
 
119
- it 'invalid type but not check' do
120
- @app = new_response_rack({}.to_json, {'x-limit' => '1'}, schema: open_api_3_schema, raise: true, check_header: false)
121
-
122
- get "/header"
123
-
124
- assert_equal 200, last_response.status
124
+ describe 'check header' do
125
+ [
126
+ { check_header: true, description: 'valid value', header: { 'integer' => 1 }, expected: { status: 200 } },
127
+ { check_header: true, description: 'missing value', header: { 'integer' => nil }, expected: { error: 'headers/integer/schema does not allow null values' } },
128
+ { check_header: true, description: 'invalid value', header: { 'integer' => 'x' }, expected: { error: 'headers/integer/schema expected integer, but received String: x' } },
129
+
130
+ { check_header: false, description: 'valid value', header: { 'integer' => 1 }, expected: { status: 200 } },
131
+ { check_header: false, description: 'missing value', header: { 'integer' => nil }, expected: { status: 200 } },
132
+ { check_header: false, description: 'invalid value', header: { 'integer' => 'x' }, expected: { status: 200 } },
133
+ ].each do |h|
134
+ check_header = h[:check_header]
135
+ description = h[:description]
136
+ header = h[:header]
137
+ expected = h[:expected]
138
+ describe "when #{check_header}" do
139
+ %w(get post put patch delete).each do |method|
140
+ describe method do
141
+ describe description do
142
+ if expected[:error].nil?
143
+ it 'should pass' do
144
+ @app = new_response_rack({}.to_json, header, schema: open_api_3_schema, raise: true, check_header: check_header)
145
+
146
+ send(method, "/header")
147
+ assert_equal expected[:status], last_response.status
148
+ end
149
+ else
150
+ it 'should fail' do
151
+ @app = new_response_rack({}.to_json, header, schema: open_api_3_schema, raise: true, check_header: check_header)
152
+
153
+ error = assert_raises(Committee::InvalidResponse) do
154
+ get "/header"
155
+ end
156
+ assert_match(expected[:error], error.message)
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
125
163
  end
126
164
  end
127
165
 
@@ -138,7 +176,8 @@ describe Committee::Middleware::ResponseValidation do
138
176
  e = assert_raises(Committee::InvalidResponse) do
139
177
  get "/characters"
140
178
  end
141
- assert_match(/1 class is String/i, e.message)
179
+
180
+ assert_match(/but received String: 1/i, e.message)
142
181
  end
143
182
 
144
183
  it "detects an invalid response status code with validate_success_only=true" do
@@ -156,9 +195,45 @@ describe Committee::Middleware::ResponseValidation do
156
195
  end
157
196
  end
158
197
 
198
+ describe ':accept_request_filter' do
199
+ [
200
+ { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 500 } },
201
+ { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/c') }, expected: { status: 500 } },
202
+ { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
203
+ ].each do |h|
204
+ description = h[:description]
205
+ accept_request_filter = h[:accept_request_filter]
206
+ expected = h[:expected]
207
+
208
+ it description do
209
+ @app = new_response_rack('not_json', {}, schema: open_api_3_schema, prefix: '/v1', accept_request_filter: accept_request_filter)
210
+
211
+ get 'v1/characters'
212
+
213
+ assert_equal expected[:status], last_response.status
214
+ end
215
+ end
216
+ end
217
+
218
+ it 'does not suppress application error' do
219
+ @app = Rack::Builder.new {
220
+ use Committee::Middleware::ResponseValidation, {schema: open_api_3_schema, raise: true}
221
+ run lambda { |_|
222
+ JSON.load('-') # invalid json
223
+ }
224
+ }
225
+
226
+ assert_raises(JSON::ParserError) do
227
+ get "/error", nil
228
+ end
229
+ end
230
+
159
231
  private
160
232
 
161
233
  def new_response_rack(response, headers = {}, options = {}, rack_options = {})
234
+ # TODO: delete when 5.0.0 released because default value changed
235
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
236
+
162
237
  status = rack_options[:status] || 200
163
238
  headers = {
164
239
  "Content-Type" => "application/json"
@@ -162,9 +162,31 @@ describe Committee::Middleware::ResponseValidation do
162
162
  assert_match(/valid JSON/i, last_response.body)
163
163
  end
164
164
 
165
+ describe ':accept_request_filter' do
166
+ [
167
+ { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 500 } },
168
+ { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/a') }, expected: { status: 500 } },
169
+ { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
170
+ ].each do |h|
171
+ description = h[:description]
172
+ accept_request_filter = h[:accept_request_filter]
173
+ expected = h[:expected]
174
+ it description do
175
+ @app = new_rack_app('not_json', {}, schema: hyper_schema, prefix: '/v1', accept_request_filter: accept_request_filter)
176
+
177
+ get '/v1/apps'
178
+
179
+ assert_equal expected[:status], last_response.status
180
+ end
181
+ end
182
+ end
183
+
165
184
  private
166
185
 
167
186
  def new_rack_app(response, headers = {}, options = {})
187
+ # TODO: delete when 5.0.0 released because default value changed
188
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
189
+
168
190
  headers = {
169
191
  "Content-Type" => "application/json"
170
192
  }.merge(headers)
@@ -112,6 +112,10 @@ describe Committee::Middleware::Stub do
112
112
  def new_rack_app(options = {})
113
113
  response = options.delete(:response)
114
114
  suppress = options.delete(:suppress)
115
+
116
+ # TODO: delete when 5.0.0 released because default value changed
117
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
118
+
115
119
  Rack::Builder.new {
116
120
  use Committee::Middleware::Stub, options
117
121
  run lambda { |env|
@@ -100,7 +100,10 @@ describe Committee::RequestUnpacker do
100
100
  }
101
101
  request = Rack::Request.new(env)
102
102
 
103
- router = hyper_schema.build_router({})
103
+ options = {}
104
+ # TODO: delete when 5.0.0 released because default value changed
105
+ options[:parse_response_by_content_type] = false
106
+ router = hyper_schema.build_router(options)
104
107
  validator = router.build_schema_validator(request)
105
108
 
106
109
  schema = JsonSchema::Schema.new
@@ -133,7 +136,10 @@ describe Committee::RequestUnpacker do
133
136
  }
134
137
  request = Rack::Request.new(env)
135
138
 
136
- router = open_api_3_schema.build_router({})
139
+ options = {}
140
+ # TODO: delete when 5.0.0 released because default value changed
141
+ options[:parse_response_by_content_type] = false
142
+ router = open_api_3_schema.build_router(options)
137
143
  validator = router.build_schema_validator(request)
138
144
 
139
145
  params, _ = Committee::RequestUnpacker.new(
@@ -158,7 +164,10 @@ describe Committee::RequestUnpacker do
158
164
  }
159
165
  request = Rack::Request.new(env)
160
166
 
161
- router = open_api_3_schema.build_router({})
167
+ options = {}
168
+ # TODO: delete when 5.0.0 released because default value changed
169
+ options[:parse_response_by_content_type] = false
170
+ router = open_api_3_schema.build_router(options)
162
171
  validator = router.build_schema_validator(request)
163
172
 
164
173
  params, _ = Committee::RequestUnpacker.new(
@@ -69,6 +69,8 @@ describe Committee::SchemaValidator::HyperSchema::Router do
69
69
  end
70
70
 
71
71
  def hyper_schema_router(options = {})
72
+ # TODO: delete when 5.0.0 released because default value changed
73
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
72
74
  schema = Committee::Drivers::HyperSchema::Driver.new.parse(hyper_schema_data)
73
75
  validator_option = Committee::SchemaValidator::Option.new(options, schema, :hyper_schema)
74
76
 
@@ -76,6 +78,8 @@ describe Committee::SchemaValidator::HyperSchema::Router do
76
78
  end
77
79
 
78
80
  def open_api_2_router(options = {})
81
+ # TODO: delete when 5.0.0 released because default value changed
82
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
79
83
  schema = Committee::Drivers::OpenAPI2::Driver.new.parse(open_api_2_data)
80
84
  validator_option = Committee::SchemaValidator::Option.new(options, schema, :hyper_schema)
81
85
 
@@ -9,7 +9,12 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
9
9
  before do
10
10
  @path = '/validate'
11
11
  @method = 'post'
12
- @validator_option = Committee::SchemaValidator::Option.new({}, open_api_3_schema, :open_api_3)
12
+
13
+ # TODO: delete when 5.0.0 released because default value changed
14
+ options = {}
15
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
16
+
17
+ @validator_option = Committee::SchemaValidator::Option.new(options, open_api_3_schema, :open_api_3)
13
18
  end
14
19
 
15
20
  def operation_object
@@ -52,8 +57,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
52
57
  operation_object.validate_request_params({"string" => 1}, HEADER, @validator_option)
53
58
  }
54
59
 
55
- # FIXME: when ruby 2.3 dropped, fix because ruby 2.3 return Fixnum, ruby 2.4 or later return Integer
56
- assert e.message.start_with?("1 class is #{1.class} but it's not valid")
60
+ assert_match(/expected string, but received Integer: 1/i, e.message)
57
61
  end
58
62
 
59
63
  it 'support put method' do
@@ -64,8 +68,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
64
68
  operation_object.validate_request_params({"string" => 1}, HEADER, @validator_option)
65
69
  }
66
70
 
67
- # FIXME: when ruby 2.3 dropped, fix because ruby 2.3 return Fixnum, ruby 2.4 or later return Integer
68
- assert e.message.start_with?("1 class is #{1.class} but it's not valid")
71
+ assert_match(/expected string, but received Integer: 1/i, e.message)
69
72
  end
70
73
 
71
74
  it 'support patch method' do
@@ -76,7 +79,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
76
79
  operation_object.validate_request_params({"integer" => "str"}, HEADER, @validator_option)
77
80
  }
78
81
 
79
- assert e.message.start_with?("str class is String but it's not valid")
82
+ assert_match(/expected integer, but received String: str/i, e.message)
80
83
  end
81
84
 
82
85
  it 'unknown param' do
@@ -109,7 +112,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
109
112
  operation_object.validate_request_params({"query_integer_list" => [1, 2]}, HEADER, @validator_option)
110
113
  }
111
114
 
112
- assert e.message.start_with?("required parameters query_string not exist in")
115
+ assert_match(/missing required parameters: query_string/i, e.message)
113
116
  end
114
117
 
115
118
  it 'invalid type' do
@@ -121,8 +124,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
121
124
  )
122
125
  }
123
126
 
124
- # FIXME: when ruby 2.3 dropped, fix because ruby 2.3 return Fixnum, ruby 2.4 or later return Integer
125
- assert e.message.start_with?("1 class is #{1.class} but")
127
+ assert_match(/expected string, but received Integer: 1/i, e.message)
126
128
  end
127
129
  end
128
130
 
@@ -143,7 +145,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
143
145
  operation_object.validate_request_params({"limit" => "a"}, HEADER, @validator_option)
144
146
  }
145
147
 
146
- assert e.message.start_with?("a class is String but")
148
+ assert_match(/expected integer, but received String: a/i, e.message)
147
149
  end
148
150
  end
149
151
 
@@ -72,6 +72,9 @@ describe Committee::SchemaValidator::OpenAPI3::RequestValidator do
72
72
  end
73
73
 
74
74
  def new_rack_app(options = {})
75
+ # TODO: delete when 5.0.0 released because default value changed
76
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
77
+
75
78
  Rack::Builder.new {
76
79
  use Committee::Middleware::RequestValidation, options
77
80
  run lambda { |_|
@@ -12,7 +12,12 @@ describe Committee::SchemaValidator::OpenAPI3::ResponseValidator do
12
12
 
13
13
  @path = '/validate'
14
14
  @method = 'post'
15
- @validator_option = Committee::SchemaValidator::Option.new({}, open_api_3_schema, :open_api_3)
15
+
16
+ # TODO: delete when 5.0.0 released because default value changed
17
+ options = {}
18
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
19
+
20
+ @validator_option = Committee::SchemaValidator::Option.new(options, open_api_3_schema, :open_api_3)
16
21
  end
17
22
 
18
23
  it "passes through a valid response" do
@@ -29,7 +34,7 @@ describe Committee::SchemaValidator::OpenAPI3::ResponseValidator do
29
34
  call_response_validator
30
35
  end
31
36
 
32
- it "passes through a valid response with no registered Content-Type with strict = true" do
37
+ it "raises InvalidResponse when a valid response with no registered body with strict option" do
33
38
  @headers = { "Content-Type" => "application/xml" }
34
39
  assert_raises(Committee::InvalidResponse) {
35
40
  call_response_validator(true)
@@ -41,7 +46,7 @@ describe Committee::SchemaValidator::OpenAPI3::ResponseValidator do
41
46
  call_response_validator
42
47
  end
43
48
 
44
- it "passes through a valid response with no Content-Type with strict option" do
49
+ it "raises InvalidResponse when a valid response with no Content-Type headers with strict option" do
45
50
  @headers = {}
46
51
  assert_raises(Committee::InvalidResponse) {
47
52
  call_response_validator(true)
@@ -30,6 +30,9 @@ describe Committee::Test::Methods do
30
30
  @committee_schema = nil
31
31
 
32
32
  @committee_options = {schema: hyper_schema}
33
+
34
+ # TODO: delete when 5.0.0 released because default value changed
35
+ @committee_options[:parse_response_by_content_type] = false
33
36
  end
34
37
 
35
38
  describe "#assert_schema_conform" do
@@ -28,7 +28,10 @@ describe Committee::Test::Methods do
28
28
  # our purposes here in testing the module.
29
29
  @committee_router = nil
30
30
  @committee_schema = nil
31
- @committee_options = nil
31
+ @committee_options = {}
32
+
33
+ # TODO: delete when 5.0.0 released because default value changed
34
+ @committee_options[:parse_response_by_content_type] = true
32
35
  end
33
36
 
34
37
  describe "Hyper-Schema" do
@@ -36,7 +39,7 @@ describe Committee::Test::Methods do
36
39
  sc = JsonSchema.parse!(hyper_schema_data)
37
40
  sc.expand_references!
38
41
  s = Committee::Drivers::HyperSchema::Driver.new.parse(sc)
39
- @committee_options = {schema: s}
42
+ @committee_options.merge!({schema: s})
40
43
  end
41
44
 
42
45
  describe "#assert_schema_conform" do
@@ -120,7 +123,7 @@ describe Committee::Test::Methods do
120
123
 
121
124
  describe "OpenAPI3" do
122
125
  before do
123
- @committee_options = {schema: open_api_3_schema}
126
+ @committee_options.merge!({schema: open_api_3_schema})
124
127
 
125
128
  @correct_response = { string_1: :honoka }
126
129
  end
@@ -138,7 +141,7 @@ describe Committee::Test::Methods do
138
141
  e = assert_raises(Committee::InvalidResponse) do
139
142
  assert_schema_conform
140
143
  end
141
- assert_match(/don't exist response definition/i, e.message)
144
+ assert_match(/response definition does not exist/i, e.message)
142
145
  end
143
146
 
144
147
  it "detects an invalid response status code" do
@@ -149,7 +152,7 @@ describe Committee::Test::Methods do
149
152
  e = assert_raises(Committee::InvalidResponse) do
150
153
  assert_schema_conform
151
154
  end
152
- assert_match(/don't exist status code definition/i, e.message)
155
+ assert_match(/status code definition does not exist/i, e.message)
153
156
  end
154
157
 
155
158
  it "outputs deprecation warning" do
@@ -175,7 +178,8 @@ describe Committee::Test::Methods do
175
178
  e = assert_raises(Committee::InvalidRequest) do
176
179
  assert_request_schema_confirm
177
180
  end
178
- assert_match(/required parameters query_string not exist in #\/paths/i, e.message)
181
+
182
+ assert_match(/missing required parameters: query_string/i, e.message)
179
183
  end
180
184
 
181
185
  it "path undefined in schema" do
@@ -201,7 +205,7 @@ describe Committee::Test::Methods do
201
205
  e = assert_raises(Committee::InvalidResponse) do
202
206
  assert_response_schema_confirm
203
207
  end
204
- assert_match(/don't exist response definition/i, e.message)
208
+ assert_match(/response definition does not exist/i, e.message)
205
209
  end
206
210
 
207
211
  it "detects an invalid response status code" do
@@ -212,7 +216,7 @@ describe Committee::Test::Methods do
212
216
  e = assert_raises(Committee::InvalidResponse) do
213
217
  assert_response_schema_confirm
214
218
  end
215
- assert_match(/don't exist status code definition/i, e.message)
219
+ assert_match(/status code definition does not exist/i, e.message)
216
220
  end
217
221
 
218
222
  it "path undefined in schema" do
@@ -56,7 +56,7 @@ def open_api_2_schema
56
56
  end
57
57
 
58
58
  def open_api_3_schema
59
- @open_api_3_schema ||= Committee::Drivers.load_from_data(open_api_3_data)
59
+ @open_api_3_schema ||= Committee::Drivers.load_from_file(open_api_3_schema_path)
60
60
  end
61
61
 
62
62
  # Don't cache this because we'll often manipulate the created hash in tests.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: committee
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandur
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-09-28 00:00:00.000000000 Z
13
+ date: 2020-08-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json_schema
@@ -52,14 +52,14 @@ dependencies:
52
52
  requirements:
53
53
  - - ">="
54
54
  - !ruby/object:Gem::Version
55
- version: 0.2.2
55
+ version: 0.11.1
56
56
  type: :runtime
57
57
  prerelease: false
58
58
  version_requirements: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: 0.2.2
62
+ version: 0.11.1
63
63
  - !ruby/object:Gem::Dependency
64
64
  name: minitest
65
65
  requirement: !ruby/object:Gem::Requirement
@@ -282,14 +282,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
282
282
  requirements:
283
283
  - - ">="
284
284
  - !ruby/object:Gem::Version
285
- version: 2.3.0
285
+ version: 2.4.0
286
286
  required_rubygems_version: !ruby/object:Gem::Requirement
287
287
  requirements:
288
288
  - - ">="
289
289
  - !ruby/object:Gem::Version
290
290
  version: '0'
291
291
  requirements: []
292
- rubygems_version: 3.0.3
292
+ rubygems_version: 3.1.2
293
293
  signing_key:
294
294
  specification_version: 4
295
295
  summary: A collection of Rack middleware to support JSON Schema.