grape 1.7.1 → 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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -1
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +12 -4
  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 +1 -1
  9. data/lib/grape/dsl/inside_route.rb +5 -5
  10. data/lib/grape/dsl/request_response.rb +2 -1
  11. data/lib/grape/dsl/settings.rb +2 -6
  12. data/lib/grape/endpoint.rb +19 -17
  13. data/lib/grape/error_formatter/base.rb +1 -1
  14. data/lib/grape/exceptions/base.rb +2 -2
  15. data/lib/grape/exceptions/missing_group_type.rb +1 -6
  16. data/lib/grape/exceptions/unsupported_group_type.rb +1 -6
  17. data/lib/grape/exceptions/validation_errors.rb +1 -6
  18. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +3 -3
  19. data/lib/grape/extensions/hash.rb +4 -7
  20. data/lib/grape/extensions/hashie/mash.rb +3 -3
  21. data/lib/grape/formatter/serializable_hash.rb +7 -7
  22. data/lib/grape/middleware/auth/base.rb +1 -1
  23. data/lib/grape/middleware/error.rb +1 -1
  24. data/lib/grape/middleware/formatter.rb +1 -1
  25. data/lib/grape/middleware/versioner/header.rb +11 -19
  26. data/lib/grape/router/route.rb +1 -3
  27. data/lib/grape/util/lazy_value.rb +3 -11
  28. data/lib/grape/util/strict_hash_configuration.rb +3 -4
  29. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  30. data/lib/grape/validations/params_scope.rb +8 -2
  31. data/lib/grape/validations/single_attribute_iterator.rb +3 -1
  32. data/lib/grape/validations/types/custom_type_coercer.rb +2 -16
  33. data/lib/grape/validations/validators/base.rb +9 -20
  34. data/lib/grape/validations/validators/default_validator.rb +2 -20
  35. data/lib/grape/validations/validators/multiple_params_base.rb +4 -8
  36. data/lib/grape/validations/validators/values_validator.rb +14 -5
  37. data/lib/grape/version.rb +1 -1
  38. data/lib/grape.rb +11 -3
  39. data/spec/grape/api/custom_validations_spec.rb +14 -57
  40. data/spec/grape/api_remount_spec.rb +36 -0
  41. data/spec/grape/api_spec.rb +10 -1
  42. data/spec/grape/dsl/desc_spec.rb +84 -87
  43. data/spec/grape/dsl/inside_route_spec.rb +6 -10
  44. data/spec/grape/dsl/request_response_spec.rb +21 -2
  45. data/spec/grape/endpoint_spec.rb +8 -8
  46. data/spec/grape/exceptions/body_parse_errors_spec.rb +40 -0
  47. data/spec/grape/exceptions/missing_group_type_spec.rb +5 -9
  48. data/spec/grape/exceptions/unsupported_group_type_spec.rb +5 -9
  49. data/spec/grape/grape_spec.rb +9 -0
  50. data/spec/grape/middleware/formatter_spec.rb +1 -1
  51. data/spec/grape/request_spec.rb +4 -14
  52. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +6 -8
  53. data/spec/grape/validations/single_attribute_iterator_spec.rb +8 -9
  54. data/spec/grape/validations/validators/base_spec.rb +38 -0
  55. data/spec/grape/validations/validators/values_spec.rb +37 -0
  56. data/spec/grape/validations_spec.rb +7 -6
  57. data/spec/shared/deprecated_class_examples.rb +16 -0
  58. metadata +17 -22
  59. data/lib/grape/config.rb +0 -34
  60. data/lib/grape/extensions/deep_mergeable_hash.rb +0 -21
  61. data/lib/grape/extensions/deep_symbolize_hash.rb +0 -32
  62. data/spec/grape/config_spec.rb +0 -17
@@ -1,101 +1,98 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Grape
4
- module DSL
5
- module DescSpec
6
- class Dummy
7
- extend Grape::DSL::Desc
8
- end
3
+ describe Grape::DSL::Desc do
4
+ subject { dummy_class }
5
+
6
+ let(:dummy_class) do
7
+ Class.new do
8
+ extend Grape::DSL::Desc
9
9
  end
10
- describe Desc do
11
- subject { Class.new(DescSpec::Dummy) }
10
+ end
12
11
 
13
- describe '.desc' do
14
- it 'sets a description' do
15
- desc_text = 'The description'
16
- options = { message: 'none' }
17
- subject.desc desc_text, options
18
- expect(subject.namespace_setting(:description)).to eq(options.merge(description: desc_text))
19
- expect(subject.route_setting(:description)).to eq(options.merge(description: desc_text))
20
- end
12
+ describe '.desc' do
13
+ it 'sets a description' do
14
+ desc_text = 'The description'
15
+ options = { message: 'none' }
16
+ subject.desc desc_text, options
17
+ expect(subject.namespace_setting(:description)).to eq(options.merge(description: desc_text))
18
+ expect(subject.route_setting(:description)).to eq(options.merge(description: desc_text))
19
+ end
21
20
 
22
- it 'can be set with a block' do
23
- expected_options = {
24
- summary: 'summary',
25
- description: 'The description',
26
- detail: 'more details',
27
- params: { first: :param },
28
- entity: Object,
29
- default: { code: 400, message: 'Invalid' },
30
- http_codes: [[401, 'Unauthorized', 'Entities::Error']],
31
- named: 'My named route',
32
- body_name: 'My body name',
33
- headers: [
34
- XAuthToken: {
35
- description: 'Valdates your identity',
36
- required: true
37
- },
38
- XOptionalHeader: {
39
- description: 'Not really needed',
40
- required: false
41
- }
42
- ],
43
- hidden: false,
44
- deprecated: false,
45
- is_array: true,
46
- nickname: 'nickname',
47
- produces: %w[array of mime_types],
48
- consumes: %w[array of mime_types],
49
- tags: %w[tag1 tag2],
50
- security: %w[array of security schemes]
21
+ it 'can be set with a block' do
22
+ expected_options = {
23
+ summary: 'summary',
24
+ description: 'The description',
25
+ detail: 'more details',
26
+ params: { first: :param },
27
+ entity: Object,
28
+ default: { code: 400, message: 'Invalid' },
29
+ http_codes: [[401, 'Unauthorized', 'Entities::Error']],
30
+ named: 'My named route',
31
+ body_name: 'My body name',
32
+ headers: [
33
+ XAuthToken: {
34
+ description: 'Valdates your identity',
35
+ required: true
36
+ },
37
+ XOptionalHeader: {
38
+ description: 'Not really needed',
39
+ required: false
51
40
  }
41
+ ],
42
+ hidden: false,
43
+ deprecated: false,
44
+ is_array: true,
45
+ nickname: 'nickname',
46
+ produces: %w[array of mime_types],
47
+ consumes: %w[array of mime_types],
48
+ tags: %w[tag1 tag2],
49
+ security: %w[array of security schemes]
50
+ }
52
51
 
53
- subject.desc 'The description' do
54
- summary 'summary'
55
- detail 'more details'
56
- params(first: :param)
57
- success Object
58
- default code: 400, message: 'Invalid'
59
- failure [[401, 'Unauthorized', 'Entities::Error']]
60
- named 'My named route'
61
- body_name 'My body name'
62
- headers [
63
- XAuthToken: {
64
- description: 'Valdates your identity',
65
- required: true
66
- },
67
- XOptionalHeader: {
68
- description: 'Not really needed',
69
- required: false
70
- }
71
- ]
72
- hidden false
73
- deprecated false
74
- is_array true
75
- nickname 'nickname'
76
- produces %w[array of mime_types]
77
- consumes %w[array of mime_types]
78
- tags %w[tag1 tag2]
79
- security %w[array of security schemes]
80
- end
52
+ subject.desc 'The description' do
53
+ summary 'summary'
54
+ detail 'more details'
55
+ params(first: :param)
56
+ success Object
57
+ default code: 400, message: 'Invalid'
58
+ failure [[401, 'Unauthorized', 'Entities::Error']]
59
+ named 'My named route'
60
+ body_name 'My body name'
61
+ headers [
62
+ XAuthToken: {
63
+ description: 'Valdates your identity',
64
+ required: true
65
+ },
66
+ XOptionalHeader: {
67
+ description: 'Not really needed',
68
+ required: false
69
+ }
70
+ ]
71
+ hidden false
72
+ deprecated false
73
+ is_array true
74
+ nickname 'nickname'
75
+ produces %w[array of mime_types]
76
+ consumes %w[array of mime_types]
77
+ tags %w[tag1 tag2]
78
+ security %w[array of security schemes]
79
+ end
81
80
 
82
- expect(subject.namespace_setting(:description)).to eq(expected_options)
83
- expect(subject.route_setting(:description)).to eq(expected_options)
84
- end
81
+ expect(subject.namespace_setting(:description)).to eq(expected_options)
82
+ expect(subject.route_setting(:description)).to eq(expected_options)
83
+ end
85
84
 
86
- it 'can be set with options and a block' do
87
- expect(subject).to receive(:warn).with('[DEPRECATION] Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.')
85
+ it 'can be set with options and a block' do
86
+ expect(ActiveSupport::Deprecation).to receive(:warn).with('Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.')
88
87
 
89
- desc_text = 'The description'
90
- detail_text = 'more details'
91
- options = { message: 'none' }
92
- subject.desc desc_text, options do
93
- detail detail_text
94
- end
95
- expect(subject.namespace_setting(:description)).to eq(description: desc_text, detail: detail_text)
96
- expect(subject.route_setting(:description)).to eq(description: desc_text, detail: detail_text)
97
- end
88
+ desc_text = 'The description'
89
+ detail_text = 'more details'
90
+ options = { message: 'none' }
91
+ subject.desc desc_text, options do
92
+ detail detail_text
98
93
  end
94
+ expect(subject.namespace_setting(:description)).to eq(description: desc_text, detail: detail_text)
95
+ expect(subject.route_setting(:description)).to eq(description: desc_text, detail: detail_text)
99
96
  end
100
97
  end
101
98
  end
@@ -203,16 +203,12 @@ describe Grape::Endpoint do
203
203
  end
204
204
 
205
205
  describe '#file' do
206
- before do
207
- allow(subject).to receive(:warn)
208
- end
209
-
210
206
  describe 'set' do
211
207
  context 'as file path' do
212
208
  let(:file_path) { '/some/file/path' }
213
209
 
214
210
  it 'emits a warning that this method is deprecated' do
215
- expect(subject).to receive(:warn).with(/Use sendfile or stream/)
211
+ expect(ActiveSupport::Deprecation).to receive(:warn).with(/Use sendfile or stream/)
216
212
 
217
213
  subject.file file_path
218
214
  end
@@ -228,7 +224,7 @@ describe Grape::Endpoint do
228
224
  let(:file_object) { double('StreamerObject', each: nil) }
229
225
 
230
226
  it 'emits a warning that this method is deprecated' do
231
- expect(subject).to receive(:warn).with(/Use stream to use a Stream object/)
227
+ expect(ActiveSupport::Deprecation).to receive(:warn).with(/Use stream to use a Stream object/)
232
228
 
233
229
  subject.file file_object
234
230
  end
@@ -243,7 +239,7 @@ describe Grape::Endpoint do
243
239
 
244
240
  describe 'get' do
245
241
  it 'emits a warning that this method is deprecated' do
246
- expect(subject).to receive(:warn).with(/Use sendfile or stream/)
242
+ expect(ActiveSupport::Deprecation).to receive(:warn).with(/Use sendfile or stream/)
247
243
 
248
244
  subject.file
249
245
  end
@@ -273,7 +269,7 @@ describe Grape::Endpoint do
273
269
  end
274
270
 
275
271
  it 'sends no deprecation warnings' do
276
- expect(subject).not_to receive(:warn)
272
+ expect(ActiveSupport::Deprecation).not_to receive(:warn)
277
273
 
278
274
  subject.sendfile file_path
279
275
  end
@@ -334,7 +330,7 @@ describe Grape::Endpoint do
334
330
  end
335
331
 
336
332
  it 'emits no deprecation warnings' do
337
- expect(subject).not_to receive(:warn)
333
+ expect(ActiveSupport::Deprecation).not_to receive(:warn)
338
334
 
339
335
  subject.stream file_path
340
336
  end
@@ -384,7 +380,7 @@ describe Grape::Endpoint do
384
380
  end
385
381
 
386
382
  it 'emits no deprecation warnings' do
387
- expect(subject).not_to receive(:warn)
383
+ expect(ActiveSupport::Deprecation).not_to receive(:warn)
388
384
 
389
385
  subject.stream stream_object
390
386
  end
@@ -148,13 +148,32 @@ module Grape
148
148
  it 'sets rescue all to true' do
149
149
  expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
150
150
  expect(subject).to receive(:namespace_inheritable).with(:rescue_grape_exceptions, true)
151
+ expect(subject).to receive(:namespace_inheritable).with(:grape_exceptions_rescue_handler, nil)
151
152
  subject.rescue_from :grape_exceptions
152
153
  end
153
154
 
154
- it 'sets rescue_grape_exceptions to true' do
155
+ it 'sets given proc as rescue handler' do
156
+ rescue_handler_proc = proc {}
155
157
  expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
156
158
  expect(subject).to receive(:namespace_inheritable).with(:rescue_grape_exceptions, true)
157
- subject.rescue_from :grape_exceptions
159
+ expect(subject).to receive(:namespace_inheritable).with(:grape_exceptions_rescue_handler, rescue_handler_proc)
160
+ subject.rescue_from :grape_exceptions, rescue_handler_proc
161
+ end
162
+
163
+ it 'sets given block as rescue handler' do
164
+ rescue_handler_proc = proc {}
165
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
166
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_grape_exceptions, true)
167
+ expect(subject).to receive(:namespace_inheritable).with(:grape_exceptions_rescue_handler, rescue_handler_proc)
168
+ subject.rescue_from :grape_exceptions, &rescue_handler_proc
169
+ end
170
+
171
+ it 'sets a rescue handler declared through :with option' do
172
+ with_block = -> { 'hello' }
173
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
174
+ expect(subject).to receive(:namespace_inheritable).with(:rescue_grape_exceptions, true)
175
+ expect(subject).to receive(:namespace_inheritable).with(:grape_exceptions_rescue_handler, an_instance_of(Proc))
176
+ subject.rescue_from :grape_exceptions, with: with_block
158
177
  end
159
178
  end
160
179
 
@@ -138,10 +138,9 @@ describe Grape::Endpoint do
138
138
 
139
139
  it 'includes request headers' do
140
140
  get '/headers'
141
- expect(JSON.parse(last_response.body)).to eq(
141
+ expect(JSON.parse(last_response.body)).to include(
142
142
  'Host' => 'example.org',
143
- 'Cookie' => '',
144
- 'Version' => 'HTTP/1.0'
143
+ 'Cookie' => ''
145
144
  )
146
145
  end
147
146
 
@@ -174,7 +173,7 @@ describe Grape::Endpoint do
174
173
 
175
174
  get('/get/cookies')
176
175
 
177
- expect(last_response.headers['Set-Cookie'].split("\n").sort).to eql [
176
+ expect(Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.sort).to eql [
178
177
  'cookie3=symbol',
179
178
  'cookie4=secret+code+here',
180
179
  'my-awesome-cookie1=is+cool',
@@ -199,8 +198,9 @@ describe Grape::Endpoint do
199
198
  end
200
199
  get('/username', {}, 'HTTP_COOKIE' => 'username=user; sandbox=false')
201
200
  expect(last_response.body).to eq('user_test')
202
- expect(last_response.headers['Set-Cookie']).to match(/username=user_test/)
203
- expect(last_response.headers['Set-Cookie']).to match(/sandbox=true/)
201
+ cookies = Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }
202
+ expect(cookies.first).to match(/username=user_test/)
203
+ expect(cookies.second).to match(/sandbox=true/)
204
204
  end
205
205
 
206
206
  it 'deletes cookie' do
@@ -214,7 +214,7 @@ describe Grape::Endpoint do
214
214
  end
215
215
  get '/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2'
216
216
  expect(last_response.body).to eq('3')
217
- cookies = last_response.headers['Set-Cookie'].split("\n").to_h do |set_cookie|
217
+ cookies = Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.to_h do |set_cookie|
218
218
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
219
219
  [cookie.name, cookie]
220
220
  end
@@ -238,7 +238,7 @@ describe Grape::Endpoint do
238
238
  end
239
239
  get('/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2')
240
240
  expect(last_response.body).to eq('3')
241
- cookies = last_response.headers['Set-Cookie'].split("\n").to_h do |set_cookie|
241
+ cookies = Array(last_response.headers['Set-Cookie']).flat_map { |h| h.split("\n") }.to_h do |set_cookie|
242
242
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
243
243
  [cookie.name, cookie]
244
244
  end
@@ -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
 
@@ -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
@@ -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