grape 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/CONTRIBUTING.md +31 -1
  4. data/README.md +38 -8
  5. data/grape.gemspec +2 -2
  6. data/lib/grape/api.rb +2 -2
  7. data/lib/grape/content_types.rb +2 -8
  8. data/lib/grape/dsl/desc.rb +3 -2
  9. data/lib/grape/dsl/inside_route.rb +6 -6
  10. data/lib/grape/dsl/parameters.rb +6 -1
  11. data/lib/grape/dsl/request_response.rb +3 -2
  12. data/lib/grape/dsl/settings.rb +2 -6
  13. data/lib/grape/endpoint.rb +21 -19
  14. data/lib/grape/error_formatter/base.rb +1 -1
  15. data/lib/grape/exceptions/base.rb +4 -3
  16. data/lib/grape/exceptions/missing_group_type.rb +1 -6
  17. data/lib/grape/exceptions/unsupported_group_type.rb +1 -6
  18. data/lib/grape/exceptions/validation_errors.rb +1 -6
  19. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +3 -3
  20. data/lib/grape/extensions/hash.rb +4 -7
  21. data/lib/grape/extensions/hashie/mash.rb +3 -3
  22. data/lib/grape/formatter/serializable_hash.rb +7 -7
  23. data/lib/grape/middleware/auth/base.rb +1 -1
  24. data/lib/grape/middleware/error.rb +1 -1
  25. data/lib/grape/middleware/formatter.rb +1 -1
  26. data/lib/grape/middleware/stack.rb +1 -1
  27. data/lib/grape/middleware/versioner/header.rb +11 -19
  28. data/lib/grape/request.rb +1 -1
  29. data/lib/grape/router/attribute_translator.rb +1 -1
  30. data/lib/grape/router/route.rb +1 -3
  31. data/lib/grape/types/invalid_value.rb +8 -0
  32. data/lib/grape/util/cache.rb +1 -1
  33. data/lib/grape/util/lazy_value.rb +3 -11
  34. data/lib/grape/util/strict_hash_configuration.rb +3 -4
  35. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  36. data/lib/grape/validations/params_scope.rb +9 -3
  37. data/lib/grape/validations/single_attribute_iterator.rb +3 -1
  38. data/lib/grape/validations/types/custom_type_coercer.rb +2 -16
  39. data/lib/grape/validations/types/invalid_value.rb +0 -7
  40. data/lib/grape/validations/validators/base.rb +9 -20
  41. data/lib/grape/validations/validators/default_validator.rb +2 -20
  42. data/lib/grape/validations/validators/multiple_params_base.rb +4 -8
  43. data/lib/grape/validations/validators/values_validator.rb +14 -5
  44. data/lib/grape/version.rb +1 -1
  45. data/lib/grape.rb +19 -3
  46. data/spec/grape/api/custom_validations_spec.rb +14 -57
  47. data/spec/grape/api_remount_spec.rb +36 -0
  48. data/spec/grape/api_spec.rb +15 -21
  49. data/spec/grape/dsl/desc_spec.rb +84 -85
  50. data/spec/grape/dsl/inside_route_spec.rb +6 -10
  51. data/spec/grape/dsl/request_response_spec.rb +21 -2
  52. data/spec/grape/endpoint_spec.rb +11 -10
  53. data/spec/grape/exceptions/body_parse_errors_spec.rb +40 -0
  54. data/spec/grape/exceptions/invalid_accept_header_spec.rb +3 -0
  55. data/spec/grape/exceptions/missing_group_type_spec.rb +5 -9
  56. data/spec/grape/exceptions/unsupported_group_type_spec.rb +5 -9
  57. data/spec/grape/grape_spec.rb +9 -0
  58. data/spec/grape/integration/rack_spec.rb +6 -5
  59. data/spec/grape/middleware/base_spec.rb +7 -5
  60. data/spec/grape/middleware/formatter_spec.rb +7 -7
  61. data/spec/grape/request_spec.rb +4 -14
  62. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +6 -8
  63. data/spec/grape/validations/single_attribute_iterator_spec.rb +8 -9
  64. data/spec/grape/validations/validators/base_spec.rb +38 -0
  65. data/spec/grape/validations/validators/values_spec.rb +56 -0
  66. data/spec/grape/validations_spec.rb +36 -12
  67. data/spec/shared/deprecated_class_examples.rb +16 -0
  68. metadata +112 -114
  69. data/lib/grape/config.rb +0 -34
  70. data/lib/grape/extensions/deep_mergeable_hash.rb +0 -21
  71. data/lib/grape/extensions/deep_symbolize_hash.rb +0 -32
  72. data/spec/grape/config_spec.rb +0 -17
  73. data/spec/grape/dsl/configuration_spec.rb +0 -14
  74. data/spec/grape/validations/attributes_iterator_spec.rb +0 -4
@@ -91,6 +91,46 @@ describe Grape::Exceptions::ValidationErrors do
91
91
  end
92
92
  end
93
93
 
94
+ context 'api with rescue_from :grape_exceptions handler with block' do
95
+ subject { Class.new(Grape::API) }
96
+
97
+ before do
98
+ subject.rescue_from :grape_exceptions do |e|
99
+ rack_response "Custom Error Contents, Original Message: #{e.message}", 400
100
+ end
101
+
102
+ subject.params do
103
+ requires :beer
104
+ end
105
+
106
+ subject.post '/beer' do
107
+ 'beer received'
108
+ end
109
+ end
110
+
111
+ def app
112
+ subject
113
+ end
114
+
115
+ context 'with content_type json' do
116
+ it 'returns body parsing error message' do
117
+ post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
118
+ expect(last_response.status).to eq 400
119
+ expect(last_response.body).to include 'message body does not match declared format'
120
+ expect(last_response.body).to include 'Custom Error Contents, Original Message'
121
+ end
122
+ end
123
+
124
+ context 'with content_type xml' do
125
+ it 'returns body parsing error message' do
126
+ post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
127
+ expect(last_response.status).to eq 400
128
+ expect(last_response.body).to include 'message body does not match declared format'
129
+ expect(last_response.body).to include 'Custom Error Contents, Original Message'
130
+ end
131
+ end
132
+ end
133
+
94
134
  context 'api without a rescue handler' do
95
135
  subject { Class.new(Grape::API) }
96
136
 
@@ -10,11 +10,13 @@ describe Grape::Exceptions::InvalidAcceptHeader do
10
10
  expect(last_response.body).to eq('beer received')
11
11
  end
12
12
  end
13
+
13
14
  shared_examples_for 'a cascaded request' do
14
15
  it 'does not find a matching route' do
15
16
  expect(last_response.status).to eq 404
16
17
  end
17
18
  end
19
+
18
20
  shared_examples_for 'a not-cascaded request' do
19
21
  it 'does not include the X-Cascade=pass header' do
20
22
  expect(last_response.headers['X-Cascade']).to be_nil
@@ -24,6 +26,7 @@ describe Grape::Exceptions::InvalidAcceptHeader do
24
26
  expect(last_response.status).to eq 406
25
27
  end
26
28
  end
29
+
27
30
  shared_examples_for 'a rescued request' do
28
31
  it 'does not include the X-Cascade=pass header' do
29
32
  expect(last_response.headers['X-Cascade']).to be_nil
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'shared/deprecated_class_examples'
4
+
3
5
  RSpec.describe Grape::Exceptions::MissingGroupType do
4
6
  describe '#message' do
5
7
  subject { described_class.new.message }
@@ -7,15 +9,9 @@ RSpec.describe Grape::Exceptions::MissingGroupType do
7
9
  it { is_expected.to include 'group type is required' }
8
10
  end
9
11
 
10
- describe 'deprecated Grape::Exceptions::MissingGroupTypeError' do
11
- subject { Grape::Exceptions::MissingGroupTypeError.new }
12
-
13
- it 'puts a deprecation warning' do
14
- expect(Warning).to receive(:warn) do |message|
15
- expect(message).to include('`Grape::Exceptions::MissingGroupTypeError` is deprecated')
16
- end
12
+ describe 'Grape::Exceptions::MissingGroupTypeError' do
13
+ let(:deprecated_class) { Grape::Exceptions::MissingGroupTypeError }
17
14
 
18
- subject
19
- end
15
+ it_behaves_like 'deprecated class'
20
16
  end
21
17
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'shared/deprecated_class_examples'
4
+
3
5
  RSpec.describe Grape::Exceptions::UnsupportedGroupType do
4
6
  subject { described_class.new }
5
7
 
@@ -9,15 +11,9 @@ RSpec.describe Grape::Exceptions::UnsupportedGroupType do
9
11
  it { is_expected.to include 'group type must be Array, Hash, JSON or Array[JSON]' }
10
12
  end
11
13
 
12
- describe 'deprecated Grape::Exceptions::UnsupportedGroupTypeError' do
13
- subject { Grape::Exceptions::UnsupportedGroupTypeError.new }
14
-
15
- it 'puts a deprecation warning' do
16
- expect(Warning).to receive(:warn) do |message|
17
- expect(message).to include('`Grape::Exceptions::UnsupportedGroupTypeError` is deprecated')
18
- end
14
+ describe 'Grape::Exceptions::UnsupportedGroupTypeError' do
15
+ let(:deprecated_class) { Grape::Exceptions::UnsupportedGroupTypeError }
19
16
 
20
- subject
21
- end
17
+ it_behaves_like 'deprecated class'
22
18
  end
23
19
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Grape do
4
+ describe '.config' do
5
+ subject { described_class.config }
6
+
7
+ it { is_expected.to eq(param_builder: Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder) }
8
+ end
9
+ end
@@ -27,19 +27,20 @@ describe Rack do
27
27
  end
28
28
 
29
29
  context 'when the app is mounted' do
30
- def app
31
- @main_app ||= Class.new(Grape::API) do
30
+ let(:ping_mount) do
31
+ Class.new(Grape::API) do
32
32
  get 'ping'
33
33
  end
34
34
  end
35
35
 
36
- let!(:base) do
37
- app_to_mount = app
38
- Class.new(Grape::API) do
36
+ let(:app) do
37
+ app_to_mount = ping_mount
38
+ app = Class.new(Grape::API) do
39
39
  namespace 'namespace' do
40
40
  mount app_to_mount
41
41
  end
42
42
  end
43
+ Rack::Builder.new(app)
43
44
  end
44
45
 
45
46
  it 'finds the app on the namespace' do
@@ -70,18 +70,18 @@ describe Grape::Middleware::Base do
70
70
 
71
71
  it 'is able to access the response' do
72
72
  subject.call({})
73
- expect(subject.response).to be_kind_of(Rack::Response)
73
+ expect(subject.response).to be_a(Rack::Response)
74
74
  end
75
75
 
76
76
  describe '#response' do
77
77
  subject do
78
- puts described_class
79
78
  described_class.new(response)
80
79
  end
81
80
 
82
81
  before { subject.call({}) }
83
82
 
84
83
  context 'when Array' do
84
+ let(:rack_response) { Rack::Response.new('test', 204, abc: 1) }
85
85
  let(:response) { ->(_) { [204, { abc: 1 }, 'test'] } }
86
86
 
87
87
  it 'status' do
@@ -97,12 +97,14 @@ describe Grape::Middleware::Base do
97
97
  end
98
98
 
99
99
  it 'returns the memoized Rack::Response instance' do
100
- expect(subject.response).to be(subject.response)
100
+ allow(Rack::Response).to receive(:new).and_return(rack_response)
101
+ expect(subject.response).to eq(rack_response)
101
102
  end
102
103
  end
103
104
 
104
105
  context 'when Rack::Response' do
105
- let(:response) { ->(_) { Rack::Response.new('test', 204, abc: 1) } }
106
+ let(:rack_response) { Rack::Response.new('test', 204, abc: 1) }
107
+ let(:response) { ->(_) { rack_response } }
106
108
 
107
109
  it 'status' do
108
110
  expect(subject.response.status).to eq(204)
@@ -117,7 +119,7 @@ describe Grape::Middleware::Base do
117
119
  end
118
120
 
119
121
  it 'returns the memoized Rack::Response instance' do
120
- expect(subject.response).to be(subject.response)
122
+ expect(subject.response).to eq(rack_response)
121
123
  end
122
124
  end
123
125
  end
@@ -13,7 +13,7 @@ describe Grape::Middleware::Formatter do
13
13
 
14
14
  it 'looks at the bodies for possibly serializable data' do
15
15
  _, _, bodies = *subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json')
16
- bodies.each { |b| expect(b).to eq(::Grape::Json.dump(body)) }
16
+ bodies.each { |b| expect(b).to eq(::Grape::Json.dump(body)) } # rubocop:disable RSpec/IteratedExpectation
17
17
  end
18
18
 
19
19
  context 'default format' do
@@ -26,7 +26,7 @@ describe Grape::Middleware::Formatter do
26
26
  end
27
27
  end
28
28
 
29
- subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('"bar"') }
29
+ subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('"bar"') } # rubocop:disable RSpec/IteratedExpectation
30
30
  end
31
31
  end
32
32
 
@@ -40,7 +40,7 @@ describe Grape::Middleware::Formatter do
40
40
  end
41
41
  end
42
42
 
43
- subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/vnd.api+json').to_a.last.each { |b| expect(b).to eq('{"foos":[{"bar":"baz"}] }') }
43
+ subject.call('PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/vnd.api+json').to_a.last.each { |b| expect(b).to eq('{"foos":[{"bar":"baz"}] }') } # rubocop:disable RSpec/IteratedExpectation
44
44
  end
45
45
  end
46
46
 
@@ -53,8 +53,7 @@ describe Grape::Middleware::Formatter do
53
53
  '<bar/>'
54
54
  end
55
55
  end
56
-
57
- subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('<bar/>') }
56
+ subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json').to_a.last.each { |b| expect(b).to eq('<bar/>') } # rubocop:disable RSpec/IteratedExpectation
58
57
  end
59
58
  end
60
59
  end
@@ -243,6 +242,7 @@ describe Grape::Middleware::Formatter do
243
242
  end
244
243
 
245
244
  context 'input' do
245
+ content_types = ['application/json', 'application/json; charset=utf-8'].freeze
246
246
  %w[POST PATCH PUT DELETE].each do |method|
247
247
  context 'when body is not nil or empty' do
248
248
  context 'when Content-Type is supported' do
@@ -320,7 +320,7 @@ describe Grape::Middleware::Formatter do
320
320
  end
321
321
  end
322
322
 
323
- ['application/json', 'application/json; charset=utf-8'].each do |content_type|
323
+ content_types.each do |content_type|
324
324
  context content_type do
325
325
  it "parses the body from #{method} and copies values into rack.request.form_hash" do
326
326
  io = StringIO.new('{"is_boolean":true,"string":"thing"}')
@@ -405,7 +405,7 @@ describe Grape::Middleware::Formatter do
405
405
  env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' }
406
406
  status, headers, body = subject.call(env)
407
407
  expect(status).to be == 200
408
- expect(headers).to be == { 'Content-Type' => 'application/json' }
408
+ expect(headers.transform_keys(&:downcase)).to be == { 'content-type' => 'application/json' }
409
409
  expect(read_chunks(body)).to be == ['data']
410
410
  end
411
411
  end
@@ -62,29 +62,19 @@ module Grape
62
62
  end
63
63
  end
64
64
 
65
- describe 'when the param_builder is set to Hashie' do
65
+ describe 'when the build_params_with is set to Hashie' do
66
66
  subject(:request_params) { described_class.new(env, **opts).params }
67
67
 
68
- before do
69
- Grape.configure do |config|
70
- config.param_builder = Grape::Extensions::Hashie::Mash::ParamBuilder
71
- end
72
- end
73
-
74
- after do
75
- Grape.config.reset
76
- end
77
-
78
68
  context 'when the API does not include a specific param builder' do
79
69
  let(:opts) { {} }
80
70
 
81
- it { is_expected.to be_a(Hashie::Mash) }
71
+ it { is_expected.to be_a(Hash) }
82
72
  end
83
73
 
84
74
  context 'when the API includes a specific param builder' do
85
- let(:opts) { { build_params_with: Grape::Extensions::Hash::ParamBuilder } }
75
+ let(:opts) { { build_params_with: Grape::Extensions::Hashie::Mash::ParamBuilder } }
86
76
 
87
- it { is_expected.to be_a(Hash) }
77
+ it { is_expected.to be_a(Hashie::Mash) }
88
78
  end
89
79
  end
90
80
 
@@ -12,8 +12,8 @@ describe Grape::Validations::MultipleAttributesIterator do
12
12
  { first: 'string', second: 'string' }
13
13
  end
14
14
 
15
- it 'yields the whole params hash and the skipped flag without the list of attrs' do
16
- expect { |b| iterator.each(&b) }.to yield_with_args(params, false)
15
+ it 'yields the whole params hash without the list of attrs' do
16
+ expect { |b| iterator.each(&b) }.to yield_with_args(params)
17
17
  end
18
18
  end
19
19
 
@@ -23,17 +23,15 @@ describe Grape::Validations::MultipleAttributesIterator do
23
23
  end
24
24
 
25
25
  it 'yields each element of the array without the list of attrs' do
26
- expect { |b| iterator.each(&b) }.to yield_successive_args([params[0], false], [params[1], false])
26
+ expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1])
27
27
  end
28
28
  end
29
29
 
30
30
  context 'when params is empty optional placeholder' do
31
- let(:params) do
32
- [Grape::DSL::Parameters::EmptyOptionalValue, { first: 'string2', second: 'string2' }]
33
- end
31
+ let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue] }
34
32
 
35
- it 'yields each element of the array without the list of attrs' do
36
- expect { |b| iterator.each(&b) }.to yield_successive_args([Grape::DSL::Parameters::EmptyOptionalValue, true], [params[1], false])
33
+ it 'does not yield it' do
34
+ expect { |b| iterator.each(&b) }.to yield_successive_args
37
35
  end
38
36
  end
39
37
  end
@@ -14,7 +14,7 @@ describe Grape::Validations::SingleAttributeIterator do
14
14
 
15
15
  it 'yields params and every single attribute from the list' do
16
16
  expect { |b| iterator.each(&b) }
17
- .to yield_successive_args([params, :first, false, false], [params, :second, false, false])
17
+ .to yield_successive_args([params, :first, false], [params, :second, false])
18
18
  end
19
19
  end
20
20
 
@@ -25,8 +25,8 @@ describe Grape::Validations::SingleAttributeIterator do
25
25
 
26
26
  it 'yields every single attribute from the list for each of the array elements' do
27
27
  expect { |b| iterator.each(&b) }.to yield_successive_args(
28
- [params[0], :first, false, false], [params[0], :second, false, false],
29
- [params[1], :first, false, false], [params[1], :second, false, false]
28
+ [params[0], :first, false], [params[0], :second, false],
29
+ [params[1], :first, false], [params[1], :second, false]
30
30
  )
31
31
  end
32
32
 
@@ -35,9 +35,9 @@ describe Grape::Validations::SingleAttributeIterator do
35
35
 
36
36
  it 'marks params with empty values' do
37
37
  expect { |b| iterator.each(&b) }.to yield_successive_args(
38
- [params[0], :first, true, false], [params[0], :second, true, false],
39
- [params[1], :first, true, false], [params[1], :second, true, false],
40
- [params[2], :first, false, false], [params[2], :second, false, false]
38
+ [params[0], :first, true], [params[0], :second, true],
39
+ [params[1], :first, true], [params[1], :second, true],
40
+ [params[2], :first, false], [params[2], :second, false]
41
41
  )
42
42
  end
43
43
  end
@@ -45,10 +45,9 @@ describe Grape::Validations::SingleAttributeIterator do
45
45
  context 'when missing optional value' do
46
46
  let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue, 10] }
47
47
 
48
- it 'marks params with skipped values' do
48
+ it 'does not yield skipped values' do
49
49
  expect { |b| iterator.each(&b) }.to yield_successive_args(
50
- [params[0], :first, false, true], [params[0], :second, false, true],
51
- [params[1], :first, false, false], [params[1], :second, false, false]
50
+ [params[1], :first, false], [params[1], :second, false]
52
51
  )
53
52
  end
54
53
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Grape::Validations::Validators::Base do
4
+ describe '#inherited' do
5
+ context 'when validator is anonymous' do
6
+ subject(:custom_validator) { Class.new(described_class) }
7
+
8
+ it 'does not register the validator' do
9
+ expect(Grape::Validations).not_to receive(:register_validator)
10
+ custom_validator
11
+ end
12
+ end
13
+
14
+ # Anonymous class does not have a name and class A < B would leak.
15
+ # Simulates inherited callback
16
+ context "when validator's underscored name does not end with _validator" do
17
+ subject(:custom_validator) { described_class.inherited(TestModule::CustomValidatorABC) }
18
+
19
+ before { stub_const('TestModule::CustomValidatorABC', Class.new) }
20
+
21
+ it 'registers the custom validator with a short name' do
22
+ expect(Grape::Validations).to receive(:register_validator).with('custom_validator_abc', TestModule::CustomValidatorABC)
23
+ custom_validator
24
+ end
25
+ end
26
+
27
+ context "when validator's underscored name ends with _validator" do
28
+ subject(:custom_validator) { described_class.inherited(TestModule::CustomValidator) }
29
+
30
+ before { stub_const('TestModule::CustomValidator', Class.new) }
31
+
32
+ it 'registers the custom validator with short name not ending with validator' do
33
+ expect(Grape::Validations).to receive(:register_validator).with('custom', TestModule::CustomValidator)
34
+ custom_validator
35
+ end
36
+ end
37
+ end
38
+ end
@@ -30,6 +30,10 @@ describe Grape::Validations::Validators::ValuesValidator do
30
30
  def include?(value)
31
31
  values.include?(value)
32
32
  end
33
+
34
+ def even?(value)
35
+ value.to_i.even?
36
+ end
33
37
  end
34
38
  end
35
39
  end
@@ -114,6 +118,13 @@ describe Grape::Validations::Validators::ValuesValidator do
114
118
  { type: params[:type] }
115
119
  end
116
120
 
121
+ params do
122
+ optional :type, type: Integer, values: 1..
123
+ end
124
+ get '/endless' do
125
+ { type: params[:type] }
126
+ end
127
+
117
128
  params do
118
129
  requires :type, values: ->(v) { ValuesModel.include? v }
119
130
  end
@@ -234,6 +245,18 @@ describe Grape::Validations::Validators::ValuesValidator do
234
245
  end
235
246
  get '/proc/message'
236
247
 
248
+ params do
249
+ requires :number, values: { value: ->(v) { ValuesModel.even? v }, message: 'must be even' }
250
+ end
251
+ get '/proc/custom_message' do
252
+ { message: 'success' }
253
+ end
254
+
255
+ params do
256
+ requires :input_one, :input_two, values: { value: ->(v1, v2) { v1 + v2 > 10 } }
257
+ end
258
+ get '/proc/arity2'
259
+
237
260
  params do
238
261
  optional :name, type: String, values: %w[a b], allow_blank: true
239
262
  end
@@ -374,6 +397,18 @@ describe Grape::Validations::Validators::ValuesValidator do
374
397
  expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
375
398
  end
376
399
 
400
+ it 'validates against values in an endless range', if: ActiveSupport::VERSION::MAJOR >= 6 do
401
+ get('/endless', type: 10)
402
+ expect(last_response.status).to eq 200
403
+ expect(last_response.body).to eq({ type: 10 }.to_json)
404
+ end
405
+
406
+ it 'does not allow an invalid value for a parameter using an endless range', if: ActiveSupport::VERSION::MAJOR >= 6 do
407
+ get('/endless', type: 0)
408
+ expect(last_response.status).to eq 400
409
+ expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
410
+ end
411
+
377
412
  it 'does not allow non-numeric string value for int value using lambda' do
378
413
  get('/lambda_int_val', number: 'foo')
379
414
  expect(last_response.status).to eq 400
@@ -673,5 +708,26 @@ describe Grape::Validations::Validators::ValuesValidator do
673
708
  expect(last_response.status).to eq 400
674
709
  expect(last_response.body).to eq({ error: 'type failed check' }.to_json)
675
710
  end
711
+
712
+ context 'when proc has an arity of 1' do
713
+ it 'accepts a valid value' do
714
+ get '/proc/custom_message', number: 4
715
+ expect(last_response.status).to eq 200
716
+ expect(last_response.body).to eq({ message: 'success' }.to_json)
717
+ end
718
+
719
+ it 'rejects an invalid value' do
720
+ get '/proc/custom_message', number: 5
721
+ expect(last_response.status).to eq 400
722
+ expect(last_response.body).to eq({ error: 'number must be even' }.to_json)
723
+ end
724
+ end
725
+
726
+ context 'when arity is > 1' do
727
+ it 'returns an error status code' do
728
+ get '/proc/arity2', input_one: 2, input_two: 3
729
+ expect(last_response.status).to eq 400
730
+ end
731
+ end
676
732
  end
677
733
  end
@@ -179,11 +179,12 @@ describe Grape::Validations do
179
179
  context 'requires :all using Grape::Entity documentation' do
180
180
  def define_requires_all
181
181
  documentation = {
182
- required_field: { type: String },
183
- optional_field: { type: String }
182
+ required_field: { type: String, required: true, param_type: 'query' },
183
+ optional_field: { type: String },
184
+ optional_array_field: { type: Array[String], is_array: true }
184
185
  }
185
186
  subject.params do
186
- requires :all, except: :optional_field, using: documentation
187
+ requires :all, except: %i[optional_field optional_array_field], using: documentation
187
188
  end
188
189
  end
189
190
  before do
@@ -195,7 +196,7 @@ describe Grape::Validations do
195
196
 
196
197
  it 'adds entity documentation to declared params' do
197
198
  define_requires_all
198
- expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq(%i[required_field optional_field])
199
+ expect(Grape::Validations::ParamsScope::Attr.attrs_keys(declared_params)).to eq(%i[required_field optional_field optional_array_field])
199
200
  end
200
201
 
201
202
  it 'errors when required_field is not present' do
@@ -214,8 +215,8 @@ describe Grape::Validations do
214
215
  context 'requires :none using Grape::Entity documentation' do
215
216
  def define_requires_none
216
217
  documentation = {
217
- required_field: { type: String },
218
- optional_field: { type: String }
218
+ required_field: { type: String, example: 'Foo' },
219
+ optional_field: { type: Integer, format: 'int64' }
219
220
  }
220
221
  subject.params do
221
222
  requires :none, except: :required_field, using: documentation
@@ -1071,12 +1072,12 @@ describe Grape::Validations do
1071
1072
  }
1072
1073
  # debugger
1073
1074
  get '/multi_level', data
1074
- expect(last_response.body.split(', ')).to match_array([
1075
- 'top[3][top_id] is empty',
1076
- 'top[2][middle_1][0][middle_1_id] is empty',
1077
- 'top[1][middle_1][1][middle_2][0][middle_2_id] is empty',
1078
- 'top[0][middle_1][1][middle_2][1][bottom][0][bottom_id] is empty'
1079
- ])
1075
+ expect(last_response.body.split(', ')).to contain_exactly(
1076
+ 'top[3][top_id] is empty',
1077
+ 'top[2][middle_1][0][middle_1_id] is empty',
1078
+ 'top[1][middle_1][1][middle_2][0][middle_2_id] is empty',
1079
+ 'top[0][middle_1][1][middle_2][1][bottom][0][bottom_id] is empty'
1080
+ )
1080
1081
  expect(last_response.status).to eq(400)
1081
1082
  end
1082
1083
  end
@@ -1509,6 +1510,29 @@ describe Grape::Validations do
1509
1510
  end
1510
1511
  end
1511
1512
 
1513
+ context 'with block and empty args' do
1514
+ before do
1515
+ subject.helpers do
1516
+ params :shared_params do |empty_args|
1517
+ optional :param, default: empty_args[:some]
1518
+ end
1519
+ end
1520
+ subject.format :json
1521
+ subject.params do
1522
+ use :shared_params
1523
+ end
1524
+ subject.get '/shared_params' do
1525
+ :ok
1526
+ end
1527
+ end
1528
+
1529
+ it 'works' do
1530
+ get '/shared_params'
1531
+
1532
+ expect(last_response.status).to eq(200)
1533
+ end
1534
+ end
1535
+
1512
1536
  context 'all or none' do
1513
1537
  context 'optional params' do
1514
1538
  before do
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples 'deprecated class' do
4
+ subject { deprecated_class.new }
5
+
6
+ around do |example|
7
+ old_deprec_behavior = ActiveSupport::Deprecation.behavior
8
+ ActiveSupport::Deprecation.behavior = :raise
9
+ example.run
10
+ ActiveSupport::Deprecation.behavior = old_deprec_behavior
11
+ end
12
+
13
+ it 'raises an ActiveSupport::DeprecationException' do
14
+ expect { subject }.to raise_error(ActiveSupport::DeprecationException)
15
+ end
16
+ end