grape 1.3.3 → 1.6.2

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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -2
  3. data/CONTRIBUTING.md +2 -1
  4. data/README.md +135 -23
  5. data/UPGRADING.md +237 -46
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +34 -42
  8. data/lib/grape/api.rb +21 -16
  9. data/lib/grape/cookies.rb +2 -0
  10. data/lib/grape/dsl/callbacks.rb +1 -1
  11. data/lib/grape/dsl/desc.rb +3 -5
  12. data/lib/grape/dsl/headers.rb +5 -2
  13. data/lib/grape/dsl/helpers.rb +8 -5
  14. data/lib/grape/dsl/inside_route.rb +72 -53
  15. data/lib/grape/dsl/middleware.rb +4 -4
  16. data/lib/grape/dsl/parameters.rb +11 -7
  17. data/lib/grape/dsl/request_response.rb +9 -6
  18. data/lib/grape/dsl/routing.rb +8 -9
  19. data/lib/grape/dsl/settings.rb +5 -5
  20. data/lib/grape/dsl/validations.rb +18 -1
  21. data/lib/grape/eager_load.rb +1 -1
  22. data/lib/grape/endpoint.rb +29 -42
  23. data/lib/grape/error_formatter/json.rb +2 -6
  24. data/lib/grape/error_formatter/xml.rb +2 -6
  25. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  26. data/lib/grape/exceptions/validation.rb +2 -3
  27. data/lib/grape/exceptions/validation_errors.rb +1 -1
  28. data/lib/grape/formatter/json.rb +1 -0
  29. data/lib/grape/formatter/serializable_hash.rb +2 -1
  30. data/lib/grape/formatter/xml.rb +1 -0
  31. data/lib/grape/locale/en.yml +1 -1
  32. data/lib/grape/middleware/auth/base.rb +3 -3
  33. data/lib/grape/middleware/auth/dsl.rb +7 -1
  34. data/lib/grape/middleware/base.rb +6 -3
  35. data/lib/grape/middleware/error.rb +11 -13
  36. data/lib/grape/middleware/formatter.rb +7 -7
  37. data/lib/grape/middleware/stack.rb +10 -3
  38. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  39. data/lib/grape/middleware/versioner/header.rb +6 -4
  40. data/lib/grape/middleware/versioner/param.rb +1 -0
  41. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  42. data/lib/grape/middleware/versioner/path.rb +2 -0
  43. data/lib/grape/parser/json.rb +1 -1
  44. data/lib/grape/parser/xml.rb +1 -1
  45. data/lib/grape/path.rb +1 -0
  46. data/lib/grape/request.rb +4 -1
  47. data/lib/grape/router/attribute_translator.rb +3 -3
  48. data/lib/grape/router/pattern.rb +1 -1
  49. data/lib/grape/router/route.rb +2 -2
  50. data/lib/grape/router.rb +31 -30
  51. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  52. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  53. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  54. data/lib/grape/util/base_inheritable.rb +2 -2
  55. data/lib/grape/util/inheritable_setting.rb +1 -3
  56. data/lib/grape/util/lazy_value.rb +4 -2
  57. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  58. data/lib/grape/validations/attributes_iterator.rb +8 -0
  59. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  60. data/lib/grape/validations/params_scope.rb +97 -62
  61. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  62. data/lib/grape/validations/types/custom_type_coercer.rb +16 -3
  63. data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
  64. data/lib/grape/validations/types/invalid_value.rb +24 -0
  65. data/lib/grape/validations/types/json.rb +2 -1
  66. data/lib/grape/validations/types/primitive_coercer.rb +4 -5
  67. data/lib/grape/validations/types.rb +1 -4
  68. data/lib/grape/validations/validator_factory.rb +1 -1
  69. data/lib/grape/validations/validators/all_or_none.rb +8 -5
  70. data/lib/grape/validations/validators/allow_blank.rb +9 -7
  71. data/lib/grape/validations/validators/as.rb +6 -8
  72. data/lib/grape/validations/validators/at_least_one_of.rb +7 -4
  73. data/lib/grape/validations/validators/base.rb +74 -69
  74. data/lib/grape/validations/validators/coerce.rb +63 -76
  75. data/lib/grape/validations/validators/default.rb +36 -34
  76. data/lib/grape/validations/validators/exactly_one_of.rb +9 -6
  77. data/lib/grape/validations/validators/except_values.rb +13 -11
  78. data/lib/grape/validations/validators/multiple_params_base.rb +24 -19
  79. data/lib/grape/validations/validators/mutual_exclusion.rb +8 -5
  80. data/lib/grape/validations/validators/presence.rb +7 -4
  81. data/lib/grape/validations/validators/regexp.rb +8 -5
  82. data/lib/grape/validations/validators/same_as.rb +18 -15
  83. data/lib/grape/validations/validators/values.rb +61 -56
  84. data/lib/grape/validations.rb +6 -0
  85. data/lib/grape/version.rb +1 -1
  86. data/lib/grape.rb +7 -3
  87. data/spec/grape/api/custom_validations_spec.rb +77 -45
  88. data/spec/grape/api/deeply_included_options_spec.rb +3 -3
  89. data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -1
  90. data/spec/grape/api/invalid_format_spec.rb +2 -0
  91. data/spec/grape/api/recognize_path_spec.rb +1 -1
  92. data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
  93. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -15
  94. data/spec/grape/api_remount_spec.rb +25 -19
  95. data/spec/grape/api_spec.rb +576 -211
  96. data/spec/grape/dsl/callbacks_spec.rb +2 -1
  97. data/spec/grape/dsl/headers_spec.rb +39 -9
  98. data/spec/grape/dsl/helpers_spec.rb +3 -2
  99. data/spec/grape/dsl/inside_route_spec.rb +185 -34
  100. data/spec/grape/dsl/logger_spec.rb +16 -18
  101. data/spec/grape/dsl/middleware_spec.rb +2 -1
  102. data/spec/grape/dsl/parameters_spec.rb +2 -0
  103. data/spec/grape/dsl/request_response_spec.rb +1 -0
  104. data/spec/grape/dsl/routing_spec.rb +10 -7
  105. data/spec/grape/endpoint/declared_spec.rb +848 -0
  106. data/spec/grape/endpoint_spec.rb +77 -589
  107. data/spec/grape/entity_spec.rb +29 -23
  108. data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -0
  109. data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -22
  110. data/spec/grape/exceptions/validation_errors_spec.rb +13 -10
  111. data/spec/grape/exceptions/validation_spec.rb +5 -3
  112. data/spec/grape/extensions/param_builders/hash_spec.rb +7 -7
  113. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -8
  114. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -8
  115. data/spec/grape/integration/rack_sendfile_spec.rb +13 -9
  116. data/spec/grape/loading_spec.rb +8 -8
  117. data/spec/grape/middleware/auth/dsl_spec.rb +15 -6
  118. data/spec/grape/middleware/auth/strategies_spec.rb +61 -21
  119. data/spec/grape/middleware/base_spec.rb +24 -15
  120. data/spec/grape/middleware/error_spec.rb +3 -3
  121. data/spec/grape/middleware/exception_spec.rb +111 -161
  122. data/spec/grape/middleware/formatter_spec.rb +28 -7
  123. data/spec/grape/middleware/globals_spec.rb +7 -4
  124. data/spec/grape/middleware/stack_spec.rb +15 -12
  125. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -1
  126. data/spec/grape/middleware/versioner/header_spec.rb +14 -13
  127. data/spec/grape/middleware/versioner/param_spec.rb +7 -1
  128. data/spec/grape/middleware/versioner/path_spec.rb +5 -1
  129. data/spec/grape/middleware/versioner_spec.rb +1 -1
  130. data/spec/grape/parser_spec.rb +4 -0
  131. data/spec/grape/path_spec.rb +52 -52
  132. data/spec/grape/presenters/presenter_spec.rb +7 -6
  133. data/spec/grape/request_spec.rb +6 -4
  134. data/spec/grape/util/inheritable_setting_spec.rb +7 -7
  135. data/spec/grape/util/inheritable_values_spec.rb +3 -2
  136. data/spec/grape/util/reverse_stackable_values_spec.rb +3 -1
  137. data/spec/grape/util/stackable_values_spec.rb +7 -5
  138. data/spec/grape/validations/instance_behaivour_spec.rb +9 -10
  139. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +14 -3
  140. data/spec/grape/validations/params_scope_spec.rb +72 -10
  141. data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -6
  142. data/spec/grape/validations/types/primitive_coercer_spec.rb +63 -7
  143. data/spec/grape/validations/types_spec.rb +8 -8
  144. data/spec/grape/validations/validators/all_or_none_spec.rb +50 -56
  145. data/spec/grape/validations/validators/allow_blank_spec.rb +136 -140
  146. data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -56
  147. data/spec/grape/validations/validators/coerce_spec.rb +248 -33
  148. data/spec/grape/validations/validators/default_spec.rb +121 -78
  149. data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -77
  150. data/spec/grape/validations/validators/except_values_spec.rb +4 -3
  151. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -77
  152. data/spec/grape/validations/validators/presence_spec.rb +16 -1
  153. data/spec/grape/validations/validators/regexp_spec.rb +25 -31
  154. data/spec/grape/validations/validators/same_as_spec.rb +14 -20
  155. data/spec/grape/validations/validators/values_spec.rb +183 -178
  156. data/spec/grape/validations_spec.rb +342 -29
  157. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  158. data/spec/integration/multi_json/json_spec.rb +1 -1
  159. data/spec/integration/multi_xml/xml_spec.rb +1 -1
  160. data/spec/shared/versioning_examples.rb +32 -29
  161. data/spec/spec_helper.rb +12 -12
  162. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  163. data/spec/support/chunks.rb +14 -0
  164. data/spec/support/versioned_helpers.rb +4 -6
  165. metadata +110 -102
@@ -10,32 +10,32 @@ describe Grape::Endpoint do
10
10
  end
11
11
 
12
12
  describe '.before_each' do
13
- after { Grape::Endpoint.before_each.clear }
13
+ after { described_class.before_each.clear }
14
14
 
15
15
  it 'is settable via block' do
16
16
  block = ->(_endpoint) { 'noop' }
17
- Grape::Endpoint.before_each(&block)
18
- expect(Grape::Endpoint.before_each.first).to eq(block)
17
+ described_class.before_each(&block)
18
+ expect(described_class.before_each.first).to eq(block)
19
19
  end
20
20
 
21
21
  it 'is settable via reference' do
22
22
  block = ->(_endpoint) { 'noop' }
23
- Grape::Endpoint.before_each block
24
- expect(Grape::Endpoint.before_each.first).to eq(block)
23
+ described_class.before_each block
24
+ expect(described_class.before_each.first).to eq(block)
25
25
  end
26
26
 
27
27
  it 'is able to override a helper' do
28
28
  subject.get('/') { current_user }
29
29
  expect { get '/' }.to raise_error(NameError)
30
30
 
31
- Grape::Endpoint.before_each do |endpoint|
31
+ described_class.before_each do |endpoint|
32
32
  allow(endpoint).to receive(:current_user).and_return('Bob')
33
33
  end
34
34
 
35
35
  get '/'
36
36
  expect(last_response.body).to eq('Bob')
37
37
 
38
- Grape::Endpoint.before_each(nil)
38
+ described_class.before_each(nil)
39
39
  expect { get '/' }.to raise_error(NameError)
40
40
  end
41
41
 
@@ -46,18 +46,18 @@ describe Grape::Endpoint do
46
46
  end
47
47
  expect { get '/' }.to raise_error(NameError)
48
48
 
49
- Grape::Endpoint.before_each do |endpoint|
49
+ described_class.before_each do |endpoint|
50
50
  allow(endpoint).to receive(:current_user).and_return('Bob')
51
51
  end
52
52
 
53
- Grape::Endpoint.before_each do |endpoint|
53
+ described_class.before_each do |endpoint|
54
54
  allow(endpoint).to receive(:authenticate_user!).and_return(true)
55
55
  end
56
56
 
57
57
  get '/'
58
58
  expect(last_response.body).to eq('Bob')
59
59
 
60
- Grape::Endpoint.before_each(nil)
60
+ described_class.before_each(nil)
61
61
  expect { get '/' }.to raise_error(NameError)
62
62
  end
63
63
  end
@@ -66,7 +66,7 @@ describe Grape::Endpoint do
66
66
  it 'takes a settings stack, options, and a block' do
67
67
  p = proc {}
68
68
  expect do
69
- Grape::Endpoint.new(Grape::Util::InheritableSetting.new, {
69
+ described_class.new(Grape::Util::InheritableSetting.new, {
70
70
  path: '/',
71
71
  method: :get
72
72
  }, &p)
@@ -77,7 +77,7 @@ describe Grape::Endpoint do
77
77
  it 'sets itself in the env upon call' do
78
78
  subject.get('/') { 'Hello world.' }
79
79
  get '/'
80
- expect(last_request.env['api.endpoint']).to be_kind_of(Grape::Endpoint)
80
+ expect(last_request.env['api.endpoint']).to be_kind_of(described_class)
81
81
  end
82
82
 
83
83
  describe '#status' do
@@ -137,6 +137,7 @@ describe Grape::Endpoint do
137
137
  headers.to_json
138
138
  end
139
139
  end
140
+
140
141
  it 'includes request headers' do
141
142
  get '/headers'
142
143
  expect(JSON.parse(last_response.body)).to eq(
@@ -144,13 +145,15 @@ describe Grape::Endpoint do
144
145
  'Cookie' => ''
145
146
  )
146
147
  end
148
+
147
149
  it 'includes additional request headers' do
148
150
  get '/headers', nil, 'HTTP_X_GRAPE_CLIENT' => '1'
149
151
  expect(JSON.parse(last_response.body)['X-Grape-Client']).to eq('1')
150
152
  end
153
+
151
154
  it 'includes headers passed as symbols' do
152
155
  env = Rack::MockRequest.env_for('/headers')
153
- env['HTTP_SYMBOL_HEADER'.to_sym] = 'Goliath passes symbols'
156
+ env[:HTTP_SYMBOL_HEADER] = 'Goliath passes symbols'
154
157
  body = read_chunks(subject.call(env)[2]).join
155
158
  expect(JSON.parse(body)['Symbol-Header']).to eq('Goliath passes symbols')
156
159
  end
@@ -212,10 +215,10 @@ describe Grape::Endpoint do
212
215
  end
213
216
  get '/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2'
214
217
  expect(last_response.body).to eq('3')
215
- cookies = Hash[last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
218
+ cookies = last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
216
219
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
217
220
  [cookie.name, cookie]
218
- end]
221
+ end.to_h
219
222
  expect(cookies.size).to eq(2)
220
223
  %w[and_this delete_this_cookie].each do |cookie_name|
221
224
  cookie = cookies[cookie_name]
@@ -236,10 +239,10 @@ describe Grape::Endpoint do
236
239
  end
237
240
  get('/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2')
238
241
  expect(last_response.body).to eq('3')
239
- cookies = Hash[last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
242
+ cookies = last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
240
243
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
241
244
  [cookie.name, cookie]
242
- end]
245
+ end.to_h
243
246
  expect(cookies.size).to eq(2)
244
247
  %w[and_this delete_this_cookie].each do |cookie_name|
245
248
  cookie = cookies[cookie_name]
@@ -253,7 +256,7 @@ describe Grape::Endpoint do
253
256
 
254
257
  describe '#params' do
255
258
  context 'default class' do
256
- it 'should be a ActiveSupport::HashWithIndifferentAccess' do
259
+ it 'is a ActiveSupport::HashWithIndifferentAccess' do
257
260
  subject.get '/foo' do
258
261
  params.class
259
262
  end
@@ -280,540 +283,6 @@ describe Grape::Endpoint do
280
283
  end
281
284
  end
282
285
 
283
- describe '#declared' do
284
- before do
285
- subject.format :json
286
- subject.params do
287
- requires :first
288
- optional :second
289
- optional :third, default: 'third-default'
290
- optional :nested, type: Hash do
291
- optional :fourth
292
- optional :fifth
293
- optional :nested_two, type: Hash do
294
- optional :sixth
295
- optional :nested_three, type: Hash do
296
- optional :seventh
297
- end
298
- end
299
- optional :nested_arr, type: Array do
300
- optional :eighth
301
- end
302
- end
303
- optional :arr, type: Array do
304
- optional :nineth
305
- end
306
- end
307
- end
308
-
309
- context 'when params are not built with default class' do
310
- it 'returns an object that corresponds with the params class - hash with indifferent access' do
311
- subject.params do
312
- build_with Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
313
- end
314
- subject.get '/declared' do
315
- d = declared(params, include_missing: true)
316
- { declared_class: d.class.to_s }
317
- end
318
-
319
- get '/declared?first=present'
320
- expect(JSON.parse(last_response.body)['declared_class']).to eq('ActiveSupport::HashWithIndifferentAccess')
321
- end
322
-
323
- it 'returns an object that corresponds with the params class - hashie mash' do
324
- subject.params do
325
- build_with Grape::Extensions::Hashie::Mash::ParamBuilder
326
- end
327
- subject.get '/declared' do
328
- d = declared(params, include_missing: true)
329
- { declared_class: d.class.to_s }
330
- end
331
-
332
- get '/declared?first=present'
333
- expect(JSON.parse(last_response.body)['declared_class']).to eq('Hashie::Mash')
334
- end
335
-
336
- it 'returns an object that corresponds with the params class - hash' do
337
- subject.params do
338
- build_with Grape::Extensions::Hash::ParamBuilder
339
- end
340
- subject.get '/declared' do
341
- d = declared(params, include_missing: true)
342
- { declared_class: d.class.to_s }
343
- end
344
-
345
- get '/declared?first=present'
346
- expect(JSON.parse(last_response.body)['declared_class']).to eq('Hash')
347
- end
348
- end
349
-
350
- it 'should show nil for nested params if include_missing is true' do
351
- subject.get '/declared' do
352
- declared(params, include_missing: true)
353
- end
354
-
355
- get '/declared?first=present'
356
- expect(last_response.status).to eq(200)
357
- expect(JSON.parse(last_response.body)['nested']['fourth']).to be_nil
358
- end
359
-
360
- it 'does not work in a before filter' do
361
- subject.before do
362
- declared(params)
363
- end
364
- subject.get('/declared') { declared(params) }
365
-
366
- expect { get('/declared') }.to raise_error(
367
- Grape::DSL::InsideRoute::MethodNotYetAvailable
368
- )
369
- end
370
-
371
- it 'has as many keys as there are declared params' do
372
- subject.get '/declared' do
373
- declared(params)
374
- end
375
- get '/declared?first=present'
376
- expect(last_response.status).to eq(200)
377
- expect(JSON.parse(last_response.body).keys.size).to eq(5)
378
- end
379
-
380
- it 'has a optional param with default value all the time' do
381
- subject.get '/declared' do
382
- declared(params)
383
- end
384
- get '/declared?first=one'
385
- expect(last_response.status).to eq(200)
386
- expect(JSON.parse(last_response.body)['third']).to eql('third-default')
387
- end
388
-
389
- it 'builds nested params' do
390
- subject.get '/declared' do
391
- declared(params)
392
- end
393
-
394
- get '/declared?first=present&nested[fourth]=1'
395
- expect(last_response.status).to eq(200)
396
- expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 4
397
- end
398
-
399
- it 'builds nested params when given array' do
400
- subject.get '/dummy' do
401
- end
402
- subject.params do
403
- requires :first
404
- optional :second
405
- optional :third, default: 'third-default'
406
- optional :nested, type: Array do
407
- optional :fourth
408
- end
409
- end
410
- subject.get '/declared' do
411
- declared(params)
412
- end
413
-
414
- get '/declared?first=present&nested[][fourth]=1&nested[][fourth]=2'
415
- expect(last_response.status).to eq(200)
416
- expect(JSON.parse(last_response.body)['nested'].size).to eq 2
417
- end
418
-
419
- context 'sets nested objects when the param is missing' do
420
- it 'to be a hash when include_missing is true' do
421
- subject.get '/declared' do
422
- declared(params, include_missing: true)
423
- end
424
-
425
- get '/declared?first=present'
426
- expect(last_response.status).to eq(200)
427
- expect(JSON.parse(last_response.body)['nested']).to eq({})
428
- end
429
-
430
- it 'to be an array when include_missing is true' do
431
- subject.get '/declared' do
432
- declared(params, include_missing: true)
433
- end
434
-
435
- get '/declared?first=present'
436
- expect(last_response.status).to eq(200)
437
- expect(JSON.parse(last_response.body)['arr']).to be_a(Array)
438
- end
439
-
440
- it 'to be an array when nested and include_missing is true' do
441
- subject.get '/declared' do
442
- declared(params, include_missing: true)
443
- end
444
-
445
- get '/declared?first=present&nested[fourth]=1'
446
- expect(last_response.status).to eq(200)
447
- expect(JSON.parse(last_response.body)['nested']['nested_arr']).to be_a(Array)
448
- end
449
-
450
- it 'to be nil when include_missing is false' do
451
- subject.get '/declared' do
452
- declared(params, include_missing: false)
453
- end
454
-
455
- get '/declared?first=present'
456
- expect(last_response.status).to eq(200)
457
- expect(JSON.parse(last_response.body)['nested']).to be_nil
458
- end
459
- end
460
-
461
- it 'filters out any additional params that are given' do
462
- subject.get '/declared' do
463
- declared(params)
464
- end
465
- get '/declared?first=one&other=two'
466
- expect(last_response.status).to eq(200)
467
- expect(JSON.parse(last_response.body).key?(:other)).to eq false
468
- end
469
-
470
- it 'stringifies if that option is passed' do
471
- subject.get '/declared' do
472
- declared(params, stringify: true)
473
- end
474
-
475
- get '/declared?first=one&other=two'
476
- expect(last_response.status).to eq(200)
477
- expect(JSON.parse(last_response.body)['first']).to eq 'one'
478
- end
479
-
480
- it 'does not include missing attributes if that option is passed' do
481
- subject.get '/declared' do
482
- error! 'expected nil', 400 if declared(params, include_missing: false).key?(:second)
483
- ''
484
- end
485
-
486
- get '/declared?first=one&other=two'
487
- expect(last_response.status).to eq(200)
488
- end
489
-
490
- it 'does not include renamed missing attributes if that option is passed' do
491
- subject.params do
492
- optional :renamed_original, as: :renamed
493
- end
494
- subject.get '/declared' do
495
- error! 'expected nil', 400 if declared(params, include_missing: false).key?(:renamed)
496
- ''
497
- end
498
-
499
- get '/declared?first=one&other=two'
500
- expect(last_response.status).to eq(200)
501
- end
502
-
503
- it 'includes attributes with value that evaluates to false' do
504
- subject.params do
505
- requires :first
506
- optional :boolean
507
- end
508
-
509
- subject.post '/declared' do
510
- error!('expected false', 400) if declared(params, include_missing: false)[:boolean] != false
511
- ''
512
- end
513
-
514
- post '/declared', ::Grape::Json.dump(first: 'one', boolean: false), 'CONTENT_TYPE' => 'application/json'
515
- expect(last_response.status).to eq(201)
516
- end
517
-
518
- it 'includes attributes with value that evaluates to nil' do
519
- subject.params do
520
- requires :first
521
- optional :second
522
- end
523
-
524
- subject.post '/declared' do
525
- error!('expected nil', 400) unless declared(params, include_missing: false)[:second].nil?
526
- ''
527
- end
528
-
529
- post '/declared', ::Grape::Json.dump(first: 'one', second: nil), 'CONTENT_TYPE' => 'application/json'
530
- expect(last_response.status).to eq(201)
531
- end
532
-
533
- it 'includes missing attributes with defaults when there are nested hashes' do
534
- subject.get '/dummy' do
535
- end
536
-
537
- subject.params do
538
- requires :first
539
- optional :second
540
- optional :third, default: nil
541
- optional :nested, type: Hash do
542
- optional :fourth, default: nil
543
- optional :fifth, default: nil
544
- requires :nested_nested, type: Hash do
545
- optional :sixth, default: 'sixth-default'
546
- optional :seven, default: nil
547
- end
548
- end
549
- end
550
-
551
- subject.get '/declared' do
552
- declared(params, include_missing: false)
553
- end
554
-
555
- get '/declared?first=present&nested[fourth]=&nested[nested_nested][sixth]=sixth'
556
- json = JSON.parse(last_response.body)
557
- expect(last_response.status).to eq(200)
558
- expect(json['first']).to eq 'present'
559
- expect(json['nested'].keys).to eq %w[fourth fifth nested_nested]
560
- expect(json['nested']['fourth']).to eq ''
561
- expect(json['nested']['nested_nested'].keys).to eq %w[sixth seven]
562
- expect(json['nested']['nested_nested']['sixth']).to eq 'sixth'
563
- end
564
-
565
- it 'does not include missing attributes when there are nested hashes' do
566
- subject.get '/dummy' do
567
- end
568
-
569
- subject.params do
570
- requires :first
571
- optional :second
572
- optional :third
573
- optional :nested, type: Hash do
574
- optional :fourth
575
- optional :fifth
576
- end
577
- end
578
-
579
- subject.get '/declared' do
580
- declared(params, include_missing: false)
581
- end
582
-
583
- get '/declared?first=present&nested[fourth]=4'
584
- json = JSON.parse(last_response.body)
585
- expect(last_response.status).to eq(200)
586
- expect(json['first']).to eq 'present'
587
- expect(json['nested'].keys).to eq %w[fourth]
588
- expect(json['nested']['fourth']).to eq '4'
589
- end
590
- end
591
-
592
- describe '#declared; call from child namespace' do
593
- before do
594
- subject.format :json
595
- subject.namespace :parent do
596
- params do
597
- requires :parent_name, type: String
598
- end
599
-
600
- namespace ':parent_name' do
601
- params do
602
- requires :child_name, type: String
603
- requires :child_age, type: Integer
604
- end
605
-
606
- namespace ':child_name' do
607
- params do
608
- requires :grandchild_name, type: String
609
- end
610
-
611
- get ':grandchild_name' do
612
- {
613
- 'params' => params,
614
- 'without_parent_namespaces' => declared(params, include_parent_namespaces: false),
615
- 'with_parent_namespaces' => declared(params, include_parent_namespaces: true)
616
- }
617
- end
618
- end
619
- end
620
- end
621
-
622
- get '/parent/foo/bar/baz', child_age: 5, extra: 'hello'
623
- end
624
-
625
- let(:parsed_response) { JSON.parse(last_response.body, symbolize_names: true) }
626
-
627
- it { expect(last_response.status).to eq 200 }
628
-
629
- context 'with include_parent_namespaces: false' do
630
- it 'returns declared parameters only from current namespace' do
631
- expect(parsed_response[:without_parent_namespaces]).to eq(
632
- grandchild_name: 'baz'
633
- )
634
- end
635
- end
636
-
637
- context 'with include_parent_namespaces: true' do
638
- it 'returns declared parameters from every parent namespace' do
639
- expect(parsed_response[:with_parent_namespaces]).to eq(
640
- parent_name: 'foo',
641
- child_name: 'bar',
642
- grandchild_name: 'baz',
643
- child_age: 5
644
- )
645
- end
646
- end
647
-
648
- context 'without declaration' do
649
- it 'returns all requested parameters' do
650
- expect(parsed_response[:params]).to eq(
651
- parent_name: 'foo',
652
- child_name: 'bar',
653
- grandchild_name: 'baz',
654
- child_age: 5,
655
- extra: 'hello'
656
- )
657
- end
658
- end
659
- end
660
-
661
- describe '#declared; from a nested mounted endpoint' do
662
- before do
663
- doubly_mounted = Class.new(Grape::API)
664
- doubly_mounted.namespace :more do
665
- params do
666
- requires :y, type: Integer
667
- end
668
- route_param :y do
669
- get do
670
- {
671
- params: params,
672
- declared_params: declared(params)
673
- }
674
- end
675
- end
676
- end
677
-
678
- mounted = Class.new(Grape::API)
679
- mounted.namespace :another do
680
- params do
681
- requires :mount_space, type: Integer
682
- end
683
- route_param :mount_space do
684
- mount doubly_mounted
685
- end
686
- end
687
-
688
- subject.format :json
689
- subject.namespace :something do
690
- params do
691
- requires :id, type: Integer
692
- end
693
- resource ':id' do
694
- mount mounted
695
- end
696
- end
697
- end
698
-
699
- it 'can access parent attributes' do
700
- get '/something/123/another/456/more/789'
701
- expect(last_response.status).to eq 200
702
- json = JSON.parse(last_response.body, symbolize_names: true)
703
-
704
- # test all three levels of params
705
- expect(json[:declared_params][:y]).to eq 789
706
- expect(json[:declared_params][:mount_space]).to eq 456
707
- expect(json[:declared_params][:id]).to eq 123
708
- end
709
- end
710
-
711
- describe '#declared; mixed nesting' do
712
- before do
713
- subject.format :json
714
- subject.resource :users do
715
- route_param :id, type: Integer, desc: 'ID desc' do
716
- # Adding this causes route_setting(:declared_params) to be nil for the
717
- # get block in namespace 'foo' below
718
- get do
719
- end
720
-
721
- namespace 'foo' do
722
- get do
723
- {
724
- params: params,
725
- declared_params: declared(params),
726
- declared_params_no_parent: declared(params, include_parent_namespaces: false)
727
- }
728
- end
729
- end
730
- end
731
- end
732
- end
733
-
734
- it 'can access parent route_param' do
735
- get '/users/123/foo', bar: 'bar'
736
- expect(last_response.status).to eq 200
737
- json = JSON.parse(last_response.body, symbolize_names: true)
738
-
739
- expect(json[:declared_params][:id]).to eq 123
740
- expect(json[:declared_params_no_parent][:id]).to eq nil
741
- end
742
- end
743
-
744
- describe '#declared; with multiple route_param' do
745
- before do
746
- mounted = Class.new(Grape::API)
747
- mounted.namespace :albums do
748
- get do
749
- declared(params)
750
- end
751
- end
752
-
753
- subject.format :json
754
- subject.namespace :artists do
755
- route_param :id, type: Integer do
756
- get do
757
- declared(params)
758
- end
759
-
760
- params do
761
- requires :filter, type: String
762
- end
763
- get :some_route do
764
- declared(params)
765
- end
766
- end
767
-
768
- route_param :artist_id, type: Integer do
769
- namespace :compositions do
770
- get do
771
- declared(params)
772
- end
773
- end
774
- end
775
-
776
- route_param :compositor_id, type: Integer do
777
- mount mounted
778
- end
779
- end
780
- end
781
-
782
- it 'return only :id without :artist_id' do
783
- get '/artists/1'
784
- json = JSON.parse(last_response.body, symbolize_names: true)
785
-
786
- expect(json.key?(:id)).to be_truthy
787
- expect(json.key?(:artist_id)).not_to be_truthy
788
- end
789
-
790
- it 'return only :artist_id without :id' do
791
- get '/artists/1/compositions'
792
- json = JSON.parse(last_response.body, symbolize_names: true)
793
-
794
- expect(json.key?(:artist_id)).to be_truthy
795
- expect(json.key?(:id)).not_to be_truthy
796
- end
797
-
798
- it 'return :filter and :id parameters in declared for second enpoint inside route_param' do
799
- get '/artists/1/some_route', filter: 'some_filter'
800
- json = JSON.parse(last_response.body, symbolize_names: true)
801
-
802
- expect(json.key?(:filter)).to be_truthy
803
- expect(json.key?(:id)).to be_truthy
804
- expect(json.key?(:artist_id)).not_to be_truthy
805
- end
806
-
807
- it 'return :compositor_id for mounter in route_param' do
808
- get '/artists/1/albums'
809
- json = JSON.parse(last_response.body, symbolize_names: true)
810
-
811
- expect(json.key?(:compositor_id)).to be_truthy
812
- expect(json.key?(:id)).not_to be_truthy
813
- expect(json.key?(:artist_id)).not_to be_truthy
814
- end
815
- end
816
-
817
286
  describe '#params' do
818
287
  it 'is available to the caller' do
819
288
  subject.get('/hey') do
@@ -873,7 +342,7 @@ describe Grape::Endpoint do
873
342
  end
874
343
 
875
344
  context 'namespace requirements' do
876
- before :each do
345
+ before do
877
346
  subject.namespace :outer, requirements: { person_email: /abc@(.*).com/ } do
878
347
  get('/:person_email') do
879
348
  params[:person_email]
@@ -892,7 +361,7 @@ describe Grape::Endpoint do
892
361
  expect(last_response.body).to eq('abc@example.com')
893
362
  end
894
363
 
895
- it "should override outer namespace's requirements" do
364
+ it "overrides outer namespace's requirements" do
896
365
  get '/outer/inner/someone@testing.wrong/test/1'
897
366
  expect(last_response.status).to eq(404)
898
367
 
@@ -904,7 +373,7 @@ describe Grape::Endpoint do
904
373
  end
905
374
 
906
375
  context 'from body parameters' do
907
- before(:each) do
376
+ before do
908
377
  subject.post '/request_body' do
909
378
  params[:user]
910
379
  end
@@ -954,6 +423,19 @@ describe Grape::Endpoint do
954
423
  expect(last_response.status).to eq(201)
955
424
  expect(last_response.body).to eq('Bob')
956
425
  end
426
+
427
+ # Rack swallowed this error until v2.2.0
428
+ it 'returns a 400 if given an invalid multipart body', if: Gem::Version.new(Rack.release) >= Gem::Version.new('2.2.0') do
429
+ subject.params do
430
+ requires :file, type: Rack::Multipart::UploadedFile
431
+ end
432
+ subject.post '/upload' do
433
+ params[:file][:filename]
434
+ end
435
+ post '/upload', { file: '' }, 'CONTENT_TYPE' => 'multipart/form-data; boundary=foobar'
436
+ expect(last_response.status).to eq(400)
437
+ expect(last_response.body).to eq('Empty message body supplied with multipart/form-data; boundary=foobar content-type')
438
+ end
957
439
  end
958
440
 
959
441
  it 'responds with a 415 for an unsupported content-type' do
@@ -990,11 +472,11 @@ describe Grape::Endpoint do
990
472
  post '/', ::Grape::Json.dump(data: { some: 'payload' }), 'CONTENT_TYPE' => 'application/json'
991
473
  end
992
474
 
993
- it 'should not response with 406 for same type without params' do
475
+ it 'does not response with 406 for same type without params' do
994
476
  expect(last_response.status).not_to be 406
995
477
  end
996
478
 
997
- it 'should response with given content type in headers' do
479
+ it 'responses with given content type in headers' do
998
480
  expect(last_response.headers['Content-Type']).to eq 'application/json; charset=utf-8'
999
481
  end
1000
482
  end
@@ -1230,16 +712,18 @@ describe Grape::Endpoint do
1230
712
  describe '.generate_api_method' do
1231
713
  it 'raises NameError if the method name is already in use' do
1232
714
  expect do
1233
- Grape::Endpoint.generate_api_method('version', &proc {})
715
+ described_class.generate_api_method('version', &proc {})
1234
716
  end.to raise_error(NameError)
1235
717
  end
718
+
1236
719
  it 'raises ArgumentError if a block is not given' do
1237
720
  expect do
1238
- Grape::Endpoint.generate_api_method('GET without a block method')
721
+ described_class.generate_api_method('GET without a block method')
1239
722
  end.to raise_error(ArgumentError)
1240
723
  end
724
+
1241
725
  it 'returns a Proc' do
1242
- expect(Grape::Endpoint.generate_api_method('GET test for a proc', &proc {})).to be_a Proc
726
+ expect(described_class.generate_api_method('GET test for a proc', &proc {})).to be_a Proc
1243
727
  end
1244
728
  end
1245
729
 
@@ -1298,7 +782,7 @@ describe Grape::Endpoint do
1298
782
  end
1299
783
 
1300
784
  get '/error_filters'
1301
- expect(last_response.status).to eql 500
785
+ expect(last_response.status).to be 500
1302
786
  expect(called).to match_array %w[before before_validation]
1303
787
  end
1304
788
 
@@ -1307,8 +791,11 @@ describe Grape::Endpoint do
1307
791
  subject.before { called << 'parent' }
1308
792
  subject.namespace :parent do
1309
793
  before { called << 'prior' }
794
+
1310
795
  before { error! :oops, 500 }
796
+
1311
797
  before { called << 'subsequent' }
798
+
1312
799
  get :hello do
1313
800
  called << :endpoint
1314
801
  'Hello!'
@@ -1316,7 +803,7 @@ describe Grape::Endpoint do
1316
803
  end
1317
804
 
1318
805
  get '/parent/hello'
1319
- expect(last_response.status).to eql 500
806
+ expect(last_response.status).to be 500
1320
807
  expect(called).to match_array %w[parent prior]
1321
808
  end
1322
809
  end
@@ -1327,19 +814,19 @@ describe Grape::Endpoint do
1327
814
  it 'allows for the anchoring option with a delete method' do
1328
815
  subject.send(:delete, '/example', anchor: true) {}
1329
816
  send(:delete, '/example/and/some/more')
1330
- expect(last_response.status).to eql 404
817
+ expect(last_response.status).to be 404
1331
818
  end
1332
819
 
1333
820
  it 'anchors paths by default for the delete method' do
1334
821
  subject.send(:delete, '/example') {}
1335
822
  send(:delete, '/example/and/some/more')
1336
- expect(last_response.status).to eql 404
823
+ expect(last_response.status).to be 404
1337
824
  end
1338
825
 
1339
826
  it 'responds to /example/and/some/more for the non-anchored delete method' do
1340
827
  subject.send(:delete, '/example', anchor: false) {}
1341
828
  send(:delete, '/example/and/some/more')
1342
- expect(last_response.status).to eql 204
829
+ expect(last_response.status).to be 204
1343
830
  expect(last_response.body).to be_empty
1344
831
  end
1345
832
  end
@@ -1351,7 +838,7 @@ describe Grape::Endpoint do
1351
838
  body 'deleted'
1352
839
  end
1353
840
  send(:delete, '/example/and/some/more')
1354
- expect(last_response.status).to eql 200
841
+ expect(last_response.status).to be 200
1355
842
  expect(last_response.body).not_to be_empty
1356
843
  end
1357
844
  end
@@ -1360,7 +847,7 @@ describe Grape::Endpoint do
1360
847
  it 'responds to /example delete method' do
1361
848
  subject.delete(:example) { 'deleted' }
1362
849
  delete '/example'
1363
- expect(last_response.status).to eql 200
850
+ expect(last_response.status).to be 200
1364
851
  expect(last_response.body).not_to be_empty
1365
852
  end
1366
853
  end
@@ -1369,7 +856,7 @@ describe Grape::Endpoint do
1369
856
  it 'responds to /example delete method' do
1370
857
  subject.delete(:example) { nil }
1371
858
  delete '/example'
1372
- expect(last_response.status).to eql 204
859
+ expect(last_response.status).to be 204
1373
860
  expect(last_response.body).to be_empty
1374
861
  end
1375
862
  end
@@ -1378,7 +865,7 @@ describe Grape::Endpoint do
1378
865
  it 'responds to /example delete method' do
1379
866
  subject.delete(:example) { '' }
1380
867
  delete '/example'
1381
- expect(last_response.status).to eql 204
868
+ expect(last_response.status).to be 204
1382
869
  expect(last_response.body).to be_empty
1383
870
  end
1384
871
  end
@@ -1390,7 +877,7 @@ describe Grape::Endpoint do
1390
877
  verb
1391
878
  end
1392
879
  send(verb, '/example/and/some/more')
1393
- expect(last_response.status).to eql 404
880
+ expect(last_response.status).to be 404
1394
881
  end
1395
882
 
1396
883
  it "anchors paths by default for the #{verb.upcase} method" do
@@ -1398,7 +885,7 @@ describe Grape::Endpoint do
1398
885
  verb
1399
886
  end
1400
887
  send(verb, '/example/and/some/more')
1401
- expect(last_response.status).to eql 404
888
+ expect(last_response.status).to be 404
1402
889
  end
1403
890
 
1404
891
  it "responds to /example/and/some/more for the non-anchored #{verb.upcase} method" do
@@ -1421,8 +908,9 @@ describe Grape::Endpoint do
1421
908
  get '/url'
1422
909
  expect(last_response.body).to eq('http://example.org/url')
1423
910
  end
911
+
1424
912
  ['v1', :v1].each do |version|
1425
- it "should include version #{version}" do
913
+ it "includes version #{version}" do
1426
914
  subject.version version, using: :path
1427
915
  subject.get('/url') do
1428
916
  request.url
@@ -1431,7 +919,7 @@ describe Grape::Endpoint do
1431
919
  expect(last_response.body).to eq("http://example.org/#{version}/url")
1432
920
  end
1433
921
  end
1434
- it 'should include prefix' do
922
+ it 'includes prefix' do
1435
923
  subject.version 'v1', using: :path
1436
924
  subject.prefix 'api'
1437
925
  subject.get('/url') do
@@ -1521,26 +1009,26 @@ describe Grape::Endpoint do
1521
1009
 
1522
1010
  # In order that the events finalized (time each block ended)
1523
1011
  expect(@events).to contain_exactly(
1524
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1012
+ have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1525
1013
  filters: a_collection_containing_exactly(an_instance_of(Proc)),
1526
1014
  type: :before }),
1527
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1015
+ have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1528
1016
  filters: [],
1529
1017
  type: :before_validation }),
1530
- have_attributes(name: 'endpoint_run_validators.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1018
+ have_attributes(name: 'endpoint_run_validators.grape', payload: { endpoint: a_kind_of(described_class),
1531
1019
  validators: [],
1532
1020
  request: a_kind_of(Grape::Request) }),
1533
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1021
+ have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1534
1022
  filters: [],
1535
1023
  type: :after_validation }),
1536
- have_attributes(name: 'endpoint_render.grape', payload: { endpoint: a_kind_of(Grape::Endpoint) }),
1537
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1024
+ have_attributes(name: 'endpoint_render.grape', payload: { endpoint: a_kind_of(described_class) }),
1025
+ have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1538
1026
  filters: [],
1539
1027
  type: :after }),
1540
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1028
+ have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1541
1029
  filters: [],
1542
1030
  type: :finally }),
1543
- have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1031
+ have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(described_class),
1544
1032
  env: an_instance_of(Hash) }),
1545
1033
  have_attributes(name: 'format_response.grape', payload: { env: an_instance_of(Hash),
1546
1034
  formatter: a_kind_of(Module) })
@@ -1548,25 +1036,25 @@ describe Grape::Endpoint do
1548
1036
 
1549
1037
  # In order that events were initialized
1550
1038
  expect(@events.sort_by(&:time)).to contain_exactly(
1551
- have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1039
+ have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(described_class),
1552
1040
  env: an_instance_of(Hash) }),
1553
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1041
+ have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1554
1042
  filters: a_collection_containing_exactly(an_instance_of(Proc)),
1555
1043
  type: :before }),
1556
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1044
+ have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1557
1045
  filters: [],
1558
1046
  type: :before_validation }),
1559
- have_attributes(name: 'endpoint_run_validators.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1047
+ have_attributes(name: 'endpoint_run_validators.grape', payload: { endpoint: a_kind_of(described_class),
1560
1048
  validators: [],
1561
1049
  request: a_kind_of(Grape::Request) }),
1562
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1050
+ have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1563
1051
  filters: [],
1564
1052
  type: :after_validation }),
1565
- have_attributes(name: 'endpoint_render.grape', payload: { endpoint: a_kind_of(Grape::Endpoint) }),
1566
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1053
+ have_attributes(name: 'endpoint_render.grape', payload: { endpoint: a_kind_of(described_class) }),
1054
+ have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1567
1055
  filters: [],
1568
1056
  type: :after }),
1569
- have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint),
1057
+ have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),
1570
1058
  filters: [],
1571
1059
  type: :finally }),
1572
1060
  have_attributes(name: 'format_response.grape', payload: { env: an_instance_of(Hash),