committee 3.2.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
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.