grape 1.7.0 → 1.8.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.
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