grape 1.3.0 → 1.5.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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +119 -1
  3. data/LICENSE +1 -1
  4. data/README.md +123 -29
  5. data/UPGRADING.md +265 -39
  6. data/lib/grape/api/instance.rb +32 -31
  7. data/lib/grape/api.rb +5 -5
  8. data/lib/grape/content_types.rb +34 -0
  9. data/lib/grape/dsl/callbacks.rb +1 -1
  10. data/lib/grape/dsl/helpers.rb +2 -1
  11. data/lib/grape/dsl/inside_route.rb +77 -43
  12. data/lib/grape/dsl/parameters.rb +12 -8
  13. data/lib/grape/dsl/routing.rb +12 -11
  14. data/lib/grape/dsl/validations.rb +18 -1
  15. data/lib/grape/eager_load.rb +1 -1
  16. data/lib/grape/endpoint.rb +8 -6
  17. data/lib/grape/exceptions/base.rb +0 -4
  18. data/lib/grape/exceptions/validation.rb +1 -1
  19. data/lib/grape/exceptions/validation_errors.rb +12 -13
  20. data/lib/grape/http/headers.rb +26 -0
  21. data/lib/grape/middleware/auth/base.rb +3 -3
  22. data/lib/grape/middleware/base.rb +4 -5
  23. data/lib/grape/middleware/error.rb +11 -13
  24. data/lib/grape/middleware/formatter.rb +3 -3
  25. data/lib/grape/middleware/stack.rb +10 -2
  26. data/lib/grape/middleware/versioner/header.rb +4 -4
  27. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  28. data/lib/grape/middleware/versioner/path.rb +1 -1
  29. data/lib/grape/namespace.rb +12 -2
  30. data/lib/grape/path.rb +13 -3
  31. data/lib/grape/request.rb +13 -8
  32. data/lib/grape/router/attribute_translator.rb +26 -5
  33. data/lib/grape/router/pattern.rb +17 -16
  34. data/lib/grape/router/route.rb +5 -24
  35. data/lib/grape/router.rb +26 -30
  36. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  37. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  38. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  39. data/lib/grape/util/base_inheritable.rb +15 -8
  40. data/lib/grape/util/cache.rb +20 -0
  41. data/lib/grape/util/lazy_object.rb +43 -0
  42. data/lib/grape/util/lazy_value.rb +1 -0
  43. data/lib/grape/util/reverse_stackable_values.rb +2 -0
  44. data/lib/grape/util/stackable_values.rb +7 -20
  45. data/lib/grape/validations/attributes_iterator.rb +8 -0
  46. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  47. data/lib/grape/validations/params_scope.rb +10 -8
  48. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  49. data/lib/grape/validations/types/array_coercer.rb +14 -5
  50. data/lib/grape/validations/types/build_coercer.rb +5 -8
  51. data/lib/grape/validations/types/custom_type_coercer.rb +16 -2
  52. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  53. data/lib/grape/validations/types/file.rb +15 -12
  54. data/lib/grape/validations/types/invalid_value.rb +24 -0
  55. data/lib/grape/validations/types/json.rb +40 -36
  56. data/lib/grape/validations/types/primitive_coercer.rb +15 -6
  57. data/lib/grape/validations/types/set_coercer.rb +6 -4
  58. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  59. data/lib/grape/validations/types.rb +7 -9
  60. data/lib/grape/validations/validator_factory.rb +1 -1
  61. data/lib/grape/validations/validators/as.rb +1 -1
  62. data/lib/grape/validations/validators/base.rb +8 -8
  63. data/lib/grape/validations/validators/coerce.rb +11 -15
  64. data/lib/grape/validations/validators/default.rb +3 -5
  65. data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
  66. data/lib/grape/validations/validators/except_values.rb +1 -1
  67. data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
  68. data/lib/grape/validations/validators/regexp.rb +1 -1
  69. data/lib/grape/validations/validators/values.rb +1 -1
  70. data/lib/grape/version.rb +1 -1
  71. data/lib/grape.rb +5 -5
  72. data/spec/grape/api/instance_spec.rb +50 -0
  73. data/spec/grape/api_remount_spec.rb +9 -4
  74. data/spec/grape/api_spec.rb +82 -6
  75. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  76. data/spec/grape/endpoint/declared_spec.rb +601 -0
  77. data/spec/grape/endpoint_spec.rb +0 -521
  78. data/spec/grape/entity_spec.rb +7 -1
  79. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  80. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  81. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  82. data/spec/grape/middleware/error_spec.rb +1 -1
  83. data/spec/grape/middleware/formatter_spec.rb +3 -3
  84. data/spec/grape/middleware/stack_spec.rb +10 -0
  85. data/spec/grape/path_spec.rb +4 -4
  86. data/spec/grape/request_spec.rb +1 -1
  87. data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
  88. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  89. data/spec/grape/validations/params_scope_spec.rb +26 -0
  90. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  91. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  92. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  93. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  94. data/spec/grape/validations/types_spec.rb +1 -1
  95. data/spec/grape/validations/validators/coerce_spec.rb +366 -86
  96. data/spec/grape/validations/validators/default_spec.rb +170 -0
  97. data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
  98. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  99. data/spec/grape/validations/validators/values_spec.rb +1 -1
  100. data/spec/grape/validations_spec.rb +298 -30
  101. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  102. data/spec/shared/versioning_examples.rb +20 -20
  103. data/spec/spec_helper.rb +3 -10
  104. data/spec/support/chunks.rb +14 -0
  105. data/spec/support/eager_load.rb +19 -0
  106. data/spec/support/versioned_helpers.rb +4 -6
  107. metadata +27 -10
  108. data/lib/grape/util/content_types.rb +0 -28
@@ -298,4 +298,174 @@ describe Grape::Validations::DefaultValidator do
298
298
  end
299
299
  end
300
300
  end
301
+
302
+ context 'optional with nil as value' do
303
+ subject do
304
+ Class.new(Grape::API) do
305
+ default_format :json
306
+ end
307
+ end
308
+
309
+ def app
310
+ subject
311
+ end
312
+
313
+ context 'primitive types' do
314
+ [
315
+ [Integer, 0],
316
+ [Integer, 42],
317
+ [Float, 0.0],
318
+ [Float, 4.2],
319
+ [BigDecimal, 0.0],
320
+ [BigDecimal, 4.2],
321
+ [Numeric, 0],
322
+ [Numeric, 42],
323
+ [Date, Date.today],
324
+ [DateTime, DateTime.now],
325
+ [Time, Time.now],
326
+ [Time, Time.at(0)],
327
+ [Grape::API::Boolean, false],
328
+ [String, ''],
329
+ [String, 'non-empty-string'],
330
+ [Symbol, :symbol],
331
+ [TrueClass, true],
332
+ [FalseClass, false]
333
+ ].each do |type, default|
334
+ it 'respects the default value' do
335
+ subject.params do
336
+ optional :param, type: type, default: default
337
+ end
338
+ subject.get '/default_value' do
339
+ params[:param]
340
+ end
341
+
342
+ get '/default_value', param: nil
343
+ expect(last_response.status).to eq(200)
344
+ expect(last_response.body).to eq(default.to_json)
345
+ end
346
+ end
347
+ end
348
+
349
+ context 'structures types' do
350
+ [
351
+ [Hash, {}],
352
+ [Hash, { test: 'non-empty' }],
353
+ [Array, []],
354
+ [Array, ['non-empty']],
355
+ [Array[Integer], []],
356
+ [Set, []],
357
+ [Set, [1]]
358
+ ].each do |type, default|
359
+ it 'respects the default value' do
360
+ subject.params do
361
+ optional :param, type: type, default: default
362
+ end
363
+ subject.get '/default_value' do
364
+ params[:param]
365
+ end
366
+
367
+ get '/default_value', param: nil
368
+ expect(last_response.status).to eq(200)
369
+ expect(last_response.body).to eq(default.to_json)
370
+ end
371
+ end
372
+ end
373
+
374
+ context 'special types' do
375
+ [
376
+ [JSON, ''],
377
+ [JSON, { test: 'non-empty-string' }.to_json],
378
+ [Array[JSON], []],
379
+ [Array[JSON], [{ test: 'non-empty-string' }.to_json]],
380
+ [::File, ''],
381
+ [::File, { test: 'non-empty-string' }.to_json],
382
+ [Rack::Multipart::UploadedFile, ''],
383
+ [Rack::Multipart::UploadedFile, { test: 'non-empty-string' }.to_json]
384
+ ].each do |type, default|
385
+ it 'respects the default value' do
386
+ subject.params do
387
+ optional :param, type: type, default: default
388
+ end
389
+ subject.get '/default_value' do
390
+ params[:param]
391
+ end
392
+
393
+ get '/default_value', param: nil
394
+ expect(last_response.status).to eq(200)
395
+ expect(last_response.body).to eq(default.to_json)
396
+ end
397
+ end
398
+ end
399
+
400
+ context 'variant-member-type collections' do
401
+ [
402
+ [Array[Integer, String], [0, '']],
403
+ [Array[Integer, String], [42, 'non-empty-string']],
404
+ [[Integer, String, Array[Integer, String]], [0, '', [0, '']]],
405
+ [[Integer, String, Array[Integer, String]], [42, 'non-empty-string', [42, 'non-empty-string']]]
406
+ ].each do |type, default|
407
+ it 'respects the default value' do
408
+ subject.params do
409
+ optional :param, type: type, default: default
410
+ end
411
+ subject.get '/default_value' do
412
+ params[:param]
413
+ end
414
+
415
+ get '/default_value', param: nil
416
+ expect(last_response.status).to eq(200)
417
+ expect(last_response.body).to eq(default.to_json)
418
+ end
419
+ end
420
+ end
421
+ end
422
+
423
+ context 'array with default values and given conditions' do
424
+ subject do
425
+ Class.new(Grape::API) do
426
+ default_format :json
427
+ end
428
+ end
429
+
430
+ def app
431
+ subject
432
+ end
433
+
434
+ it 'applies the default values only if the conditions are met' do
435
+ subject.params do
436
+ requires :ary, type: Array do
437
+ requires :has_value, type: Grape::API::Boolean
438
+ given has_value: ->(has_value) { has_value } do
439
+ optional :type, type: String, values: %w[str int], default: 'str'
440
+ given type: ->(type) { type == 'str' } do
441
+ optional :str, type: String, default: 'a'
442
+ end
443
+ given type: ->(type) { type == 'int' } do
444
+ optional :int, type: Integer, default: 1
445
+ end
446
+ end
447
+ end
448
+ end
449
+ subject.post('/nested_given_and_default') { declared(self.params) }
450
+
451
+ params = {
452
+ ary: [
453
+ { has_value: false },
454
+ { has_value: true, type: 'int', int: 123 },
455
+ { has_value: true, type: 'str', str: 'b' }
456
+ ]
457
+ }
458
+ expected = {
459
+ 'ary' => [
460
+ { 'has_value' => false, 'type' => nil, 'int' => nil, 'str' => nil },
461
+ { 'has_value' => true, 'type' => 'int', 'int' => 123, 'str' => nil },
462
+ { 'has_value' => true, 'type' => 'str', 'int' => nil, 'str' => 'b' }
463
+ ]
464
+ }
465
+
466
+ post '/nested_given_and_default', params
467
+ expect(last_response.status).to eq(201)
468
+ expect(JSON.parse(last_response.body)).to eq(expected)
469
+ end
470
+ end
301
471
  end
@@ -100,7 +100,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
100
100
  validate
101
101
  expect(last_response.status).to eq 400
102
102
  expect(JSON.parse(last_response.body)).to eq(
103
- 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided']
103
+ 'beer,wine,grapefruit' => ['are mutually exclusive']
104
104
  )
105
105
  end
106
106
 
@@ -112,7 +112,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
112
112
  validate
113
113
  expect(last_response.status).to eq 400
114
114
  expect(JSON.parse(last_response.body)).to eq(
115
- 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided']
115
+ 'beer,wine,grapefruit' => ['are mutually exclusive']
116
116
  )
117
117
  end
118
118
  end
@@ -126,7 +126,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
126
126
  validate
127
127
  expect(last_response.status).to eq 400
128
128
  expect(JSON.parse(last_response.body)).to eq(
129
- 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided']
129
+ 'beer,grapefruit' => ['are mutually exclusive']
130
130
  )
131
131
  end
132
132
  end
@@ -139,7 +139,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
139
139
  validate
140
140
  expect(last_response.status).to eq 400
141
141
  expect(JSON.parse(last_response.body)).to eq(
142
- 'beer,wine,grapefruit' => ['you should choose one']
142
+ 'beer,wine' => ['you should choose one']
143
143
  )
144
144
  end
145
145
  end
@@ -175,7 +175,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
175
175
  validate
176
176
  expect(last_response.status).to eq 400
177
177
  expect(JSON.parse(last_response.body)).to eq(
178
- 'item[beer],item[wine],item[grapefruit]' => ['are missing, exactly one parameter must be provided']
178
+ 'item[beer],item[wine]' => ['are mutually exclusive']
179
179
  )
180
180
  end
181
181
  end
@@ -190,7 +190,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
190
190
  validate
191
191
  expect(last_response.status).to eq 400
192
192
  expect(JSON.parse(last_response.body)).to eq(
193
- 'item[beer],item[wine],item[grapefruit]' => ['are missing, exactly one parameter must be provided']
193
+ 'item[beer],item[wine]' => ['are mutually exclusive']
194
194
  )
195
195
  end
196
196
  end
@@ -213,11 +213,11 @@ describe Grape::Validations::ExactlyOneOfValidator do
213
213
  validate
214
214
  expect(last_response.status).to eq 400
215
215
  expect(JSON.parse(last_response.body)).to eq(
216
- 'items[0][beer],items[0][wine],items[0][grapefruit]' => [
217
- 'are missing, exactly one parameter must be provided'
216
+ 'items[0][beer],items[0][wine]' => [
217
+ 'are mutually exclusive'
218
218
  ],
219
- 'items[1][beer],items[1][wine],items[1][grapefruit]' => [
220
- 'are missing, exactly one parameter must be provided'
219
+ 'items[1][wine],items[1][grapefruit]' => [
220
+ 'are mutually exclusive'
221
221
  ]
222
222
  )
223
223
  end
@@ -231,8 +231,8 @@ describe Grape::Validations::ExactlyOneOfValidator do
231
231
  validate
232
232
  expect(last_response.status).to eq 400
233
233
  expect(JSON.parse(last_response.body)).to eq(
234
- 'items[0][nested_items][0][beer],items[0][nested_items][0][wine],items[0][nested_items][0][grapefruit]' => [
235
- 'are missing, exactly one parameter must be provided'
234
+ 'items[0][nested_items][0][beer],items[0][nested_items][0][wine]' => [
235
+ 'are mutually exclusive'
236
236
  ]
237
237
  )
238
238
  end
@@ -8,6 +8,7 @@ describe Grape::Validations::ExceptValuesValidator do
8
8
  DEFAULT_EXCEPTS = ['invalid-type1', 'invalid-type2', 'invalid-type3'].freeze
9
9
  class << self
10
10
  attr_accessor :excepts
11
+
11
12
  def excepts
12
13
  @excepts ||= []
13
14
  [DEFAULT_EXCEPTS + @excepts].flatten.uniq
@@ -319,7 +319,7 @@ describe Grape::Validations::ValuesValidator do
319
319
  expect(last_response.status).to eq 200
320
320
  end
321
321
 
322
- it 'allows for an optional param with a list of values' do
322
+ it 'accepts for an optional param with a list of values' do
323
323
  put('/optional_with_array_of_string_values', optional: nil)
324
324
  expect(last_response.status).to eq 200
325
325
  end