grape 1.2.5 → 1.4.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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -0
  3. data/LICENSE +1 -1
  4. data/README.md +53 -16
  5. data/UPGRADING.md +231 -23
  6. data/grape.gemspec +10 -1
  7. data/lib/grape.rb +6 -7
  8. data/lib/grape/api.rb +4 -2
  9. data/lib/grape/api/helpers.rb +2 -0
  10. data/lib/grape/api/instance.rb +36 -33
  11. data/lib/grape/config.rb +2 -0
  12. data/lib/grape/content_types.rb +34 -0
  13. data/lib/grape/cookies.rb +2 -0
  14. data/lib/grape/dsl/api.rb +2 -0
  15. data/lib/grape/dsl/callbacks.rb +2 -0
  16. data/lib/grape/dsl/configuration.rb +2 -0
  17. data/lib/grape/dsl/desc.rb +2 -0
  18. data/lib/grape/dsl/headers.rb +2 -0
  19. data/lib/grape/dsl/helpers.rb +4 -2
  20. data/lib/grape/dsl/inside_route.rb +83 -34
  21. data/lib/grape/dsl/logger.rb +2 -0
  22. data/lib/grape/dsl/middleware.rb +2 -0
  23. data/lib/grape/dsl/parameters.rb +8 -6
  24. data/lib/grape/dsl/request_response.rb +4 -2
  25. data/lib/grape/dsl/routing.rb +9 -5
  26. data/lib/grape/dsl/settings.rb +7 -1
  27. data/lib/grape/dsl/validations.rb +20 -1
  28. data/lib/grape/eager_load.rb +3 -1
  29. data/lib/grape/endpoint.rb +21 -13
  30. data/lib/grape/error_formatter.rb +3 -1
  31. data/lib/grape/error_formatter/base.rb +2 -0
  32. data/lib/grape/error_formatter/json.rb +2 -0
  33. data/lib/grape/error_formatter/txt.rb +2 -0
  34. data/lib/grape/error_formatter/xml.rb +2 -0
  35. data/lib/grape/exceptions/base.rb +11 -13
  36. data/lib/grape/exceptions/incompatible_option_values.rb +2 -0
  37. data/lib/grape/exceptions/invalid_accept_header.rb +2 -0
  38. data/lib/grape/exceptions/invalid_formatter.rb +2 -0
  39. data/lib/grape/exceptions/invalid_message_body.rb +2 -0
  40. data/lib/grape/exceptions/invalid_response.rb +2 -0
  41. data/lib/grape/exceptions/invalid_version_header.rb +2 -0
  42. data/lib/grape/exceptions/invalid_versioner_option.rb +2 -0
  43. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +2 -0
  44. data/lib/grape/exceptions/method_not_allowed.rb +2 -0
  45. data/lib/grape/exceptions/missing_group_type.rb +2 -0
  46. data/lib/grape/exceptions/missing_mime_type.rb +2 -0
  47. data/lib/grape/exceptions/missing_option.rb +2 -0
  48. data/lib/grape/exceptions/missing_vendor_option.rb +2 -0
  49. data/lib/grape/exceptions/unknown_options.rb +2 -0
  50. data/lib/grape/exceptions/unknown_parameter.rb +2 -0
  51. data/lib/grape/exceptions/unknown_validator.rb +2 -0
  52. data/lib/grape/exceptions/unsupported_group_type.rb +2 -0
  53. data/lib/grape/exceptions/validation.rb +3 -1
  54. data/lib/grape/exceptions/validation_array_errors.rb +2 -0
  55. data/lib/grape/exceptions/validation_errors.rb +13 -12
  56. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +4 -3
  57. data/lib/grape/extensions/deep_mergeable_hash.rb +2 -0
  58. data/lib/grape/extensions/deep_symbolize_hash.rb +2 -0
  59. data/lib/grape/extensions/hash.rb +2 -0
  60. data/lib/grape/extensions/hashie/mash.rb +2 -0
  61. data/lib/grape/formatter.rb +5 -3
  62. data/lib/grape/formatter/json.rb +2 -0
  63. data/lib/grape/formatter/serializable_hash.rb +2 -0
  64. data/lib/grape/formatter/txt.rb +2 -0
  65. data/lib/grape/formatter/xml.rb +2 -0
  66. data/lib/grape/http/headers.rb +50 -18
  67. data/lib/grape/middleware/auth/base.rb +2 -0
  68. data/lib/grape/middleware/auth/dsl.rb +2 -0
  69. data/lib/grape/middleware/auth/strategies.rb +2 -0
  70. data/lib/grape/middleware/auth/strategy_info.rb +2 -0
  71. data/lib/grape/middleware/base.rb +7 -7
  72. data/lib/grape/middleware/error.rb +3 -1
  73. data/lib/grape/middleware/filter.rb +2 -0
  74. data/lib/grape/middleware/formatter.rb +8 -6
  75. data/lib/grape/middleware/globals.rb +2 -0
  76. data/lib/grape/middleware/helpers.rb +2 -0
  77. data/lib/grape/middleware/stack.rb +4 -1
  78. data/lib/grape/middleware/versioner.rb +2 -0
  79. data/lib/grape/middleware/versioner/accept_version_header.rb +2 -0
  80. data/lib/grape/middleware/versioner/header.rb +6 -4
  81. data/lib/grape/middleware/versioner/param.rb +3 -1
  82. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +4 -1
  83. data/lib/grape/middleware/versioner/path.rb +3 -1
  84. data/lib/grape/namespace.rb +14 -2
  85. data/lib/grape/parser.rb +3 -1
  86. data/lib/grape/parser/json.rb +2 -0
  87. data/lib/grape/parser/xml.rb +2 -0
  88. data/lib/grape/path.rb +15 -3
  89. data/lib/grape/presenters/presenter.rb +2 -0
  90. data/lib/grape/request.rb +15 -8
  91. data/lib/grape/router.rb +30 -29
  92. data/lib/grape/router/attribute_translator.rb +39 -8
  93. data/lib/grape/router/pattern.rb +20 -16
  94. data/lib/grape/router/route.rb +12 -26
  95. data/lib/grape/{serve_file → serve_stream}/file_body.rb +3 -1
  96. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +3 -1
  97. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +10 -8
  98. data/lib/grape/util/base_inheritable.rb +15 -6
  99. data/lib/grape/util/cache.rb +20 -0
  100. data/lib/grape/util/endpoint_configuration.rb +2 -0
  101. data/lib/grape/util/env.rb +19 -17
  102. data/lib/grape/util/inheritable_setting.rb +2 -0
  103. data/lib/grape/util/inheritable_values.rb +2 -0
  104. data/lib/grape/util/json.rb +2 -0
  105. data/lib/grape/util/lazy_block.rb +2 -0
  106. data/lib/grape/util/lazy_object.rb +43 -0
  107. data/lib/grape/util/lazy_value.rb +2 -0
  108. data/lib/grape/util/registrable.rb +2 -0
  109. data/lib/grape/util/reverse_stackable_values.rb +4 -0
  110. data/lib/grape/util/stackable_values.rb +10 -20
  111. data/lib/grape/util/strict_hash_configuration.rb +2 -0
  112. data/lib/grape/util/xml.rb +2 -0
  113. data/lib/grape/validations.rb +2 -0
  114. data/lib/grape/validations/attributes_iterator.rb +3 -3
  115. data/lib/grape/validations/multiple_attributes_iterator.rb +2 -0
  116. data/lib/grape/validations/params_scope.rb +27 -14
  117. data/lib/grape/validations/single_attribute_iterator.rb +13 -2
  118. data/lib/grape/validations/types.rb +12 -34
  119. data/lib/grape/validations/types/array_coercer.rb +65 -0
  120. data/lib/grape/validations/types/build_coercer.rb +47 -49
  121. data/lib/grape/validations/types/custom_type_coercer.rb +15 -49
  122. data/lib/grape/validations/types/custom_type_collection_coercer.rb +10 -25
  123. data/lib/grape/validations/types/dry_type_coercer.rb +76 -0
  124. data/lib/grape/validations/types/file.rb +22 -18
  125. data/lib/grape/validations/types/json.rb +46 -39
  126. data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
  127. data/lib/grape/validations/types/primitive_coercer.rb +67 -0
  128. data/lib/grape/validations/types/set_coercer.rb +40 -0
  129. data/lib/grape/validations/types/variant_collection_coercer.rb +5 -13
  130. data/lib/grape/validations/validator_factory.rb +2 -0
  131. data/lib/grape/validations/validators/all_or_none.rb +3 -1
  132. data/lib/grape/validations/validators/allow_blank.rb +3 -1
  133. data/lib/grape/validations/validators/as.rb +2 -0
  134. data/lib/grape/validations/validators/at_least_one_of.rb +3 -1
  135. data/lib/grape/validations/validators/base.rb +8 -5
  136. data/lib/grape/validations/validators/coerce.rb +39 -29
  137. data/lib/grape/validations/validators/default.rb +2 -1
  138. data/lib/grape/validations/validators/exactly_one_of.rb +6 -2
  139. data/lib/grape/validations/validators/except_values.rb +3 -1
  140. data/lib/grape/validations/validators/multiple_params_base.rb +2 -0
  141. data/lib/grape/validations/validators/mutual_exclusion.rb +3 -1
  142. data/lib/grape/validations/validators/presence.rb +3 -1
  143. data/lib/grape/validations/validators/regexp.rb +4 -2
  144. data/lib/grape/validations/validators/same_as.rb +6 -3
  145. data/lib/grape/validations/validators/values.rb +17 -5
  146. data/lib/grape/version.rb +3 -1
  147. data/spec/grape/api/custom_validations_spec.rb +5 -3
  148. data/spec/grape/api/deeply_included_options_spec.rb +2 -0
  149. data/spec/grape/api/defines_boolean_in_params_spec.rb +5 -3
  150. data/spec/grape/api/inherited_helpers_spec.rb +2 -0
  151. data/spec/grape/api/instance_spec.rb +104 -0
  152. data/spec/grape/api/invalid_format_spec.rb +2 -0
  153. data/spec/grape/api/namespace_parameters_in_route_spec.rb +2 -0
  154. data/spec/grape/api/nested_helpers_spec.rb +2 -0
  155. data/spec/grape/api/optional_parameters_in_route_spec.rb +2 -0
  156. data/spec/grape/api/parameters_modification_spec.rb +3 -1
  157. data/spec/grape/api/patch_method_helpers_spec.rb +2 -0
  158. data/spec/grape/api/recognize_path_spec.rb +2 -0
  159. data/spec/grape/api/required_parameters_in_route_spec.rb +2 -0
  160. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +2 -0
  161. data/spec/grape/api/routes_with_requirements_spec.rb +2 -0
  162. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +2 -0
  163. data/spec/grape/api/shared_helpers_spec.rb +2 -0
  164. data/spec/grape/api_remount_spec.rb +2 -0
  165. data/spec/grape/api_spec.rb +99 -11
  166. data/spec/grape/config_spec.rb +2 -0
  167. data/spec/grape/dsl/callbacks_spec.rb +2 -0
  168. data/spec/grape/dsl/configuration_spec.rb +2 -0
  169. data/spec/grape/dsl/desc_spec.rb +2 -0
  170. data/spec/grape/dsl/headers_spec.rb +2 -0
  171. data/spec/grape/dsl/helpers_spec.rb +4 -2
  172. data/spec/grape/dsl/inside_route_spec.rb +177 -33
  173. data/spec/grape/dsl/logger_spec.rb +2 -0
  174. data/spec/grape/dsl/middleware_spec.rb +2 -0
  175. data/spec/grape/dsl/parameters_spec.rb +2 -0
  176. data/spec/grape/dsl/request_response_spec.rb +2 -0
  177. data/spec/grape/dsl/routing_spec.rb +2 -0
  178. data/spec/grape/dsl/settings_spec.rb +2 -0
  179. data/spec/grape/dsl/validations_spec.rb +2 -0
  180. data/spec/grape/endpoint_spec.rb +21 -6
  181. data/spec/grape/entity_spec.rb +2 -0
  182. data/spec/grape/exceptions/base_spec.rb +3 -1
  183. data/spec/grape/exceptions/body_parse_errors_spec.rb +2 -0
  184. data/spec/grape/exceptions/invalid_accept_header_spec.rb +2 -0
  185. data/spec/grape/exceptions/invalid_formatter_spec.rb +2 -0
  186. data/spec/grape/exceptions/invalid_response_spec.rb +2 -0
  187. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +2 -0
  188. data/spec/grape/exceptions/missing_mime_type_spec.rb +2 -0
  189. data/spec/grape/exceptions/missing_option_spec.rb +2 -0
  190. data/spec/grape/exceptions/unknown_options_spec.rb +2 -0
  191. data/spec/grape/exceptions/unknown_validator_spec.rb +2 -0
  192. data/spec/grape/exceptions/validation_errors_spec.rb +4 -2
  193. data/spec/grape/exceptions/validation_spec.rb +3 -1
  194. data/spec/grape/extensions/param_builders/hash_spec.rb +2 -0
  195. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +2 -0
  196. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +2 -0
  197. data/spec/grape/integration/global_namespace_function_spec.rb +2 -0
  198. data/spec/grape/integration/rack_sendfile_spec.rb +14 -8
  199. data/spec/grape/integration/rack_spec.rb +3 -1
  200. data/spec/grape/loading_spec.rb +2 -0
  201. data/spec/grape/middleware/auth/base_spec.rb +2 -0
  202. data/spec/grape/middleware/auth/dsl_spec.rb +2 -0
  203. data/spec/grape/middleware/auth/strategies_spec.rb +3 -1
  204. data/spec/grape/middleware/base_spec.rb +2 -0
  205. data/spec/grape/middleware/error_spec.rb +2 -0
  206. data/spec/grape/middleware/exception_spec.rb +3 -1
  207. data/spec/grape/middleware/formatter_spec.rb +19 -12
  208. data/spec/grape/middleware/globals_spec.rb +2 -0
  209. data/spec/grape/middleware/stack_spec.rb +11 -0
  210. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -1
  211. data/spec/grape/middleware/versioner/header_spec.rb +3 -1
  212. data/spec/grape/middleware/versioner/param_spec.rb +3 -1
  213. data/spec/grape/middleware/versioner/path_spec.rb +3 -1
  214. data/spec/grape/middleware/versioner_spec.rb +2 -0
  215. data/spec/grape/named_api_spec.rb +2 -0
  216. data/spec/grape/parser_spec.rb +7 -5
  217. data/spec/grape/path_spec.rb +6 -4
  218. data/spec/grape/presenters/presenter_spec.rb +2 -0
  219. data/spec/grape/request_spec.rb +2 -0
  220. data/spec/grape/util/inheritable_setting_spec.rb +2 -0
  221. data/spec/grape/util/inheritable_values_spec.rb +2 -0
  222. data/spec/grape/util/reverse_stackable_values_spec.rb +2 -0
  223. data/spec/grape/util/stackable_values_spec.rb +3 -1
  224. data/spec/grape/util/strict_hash_configuration_spec.rb +2 -0
  225. data/spec/grape/validations/attributes_iterator_spec.rb +2 -0
  226. data/spec/grape/validations/instance_behaivour_spec.rb +5 -3
  227. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +2 -0
  228. data/spec/grape/validations/params_scope_spec.rb +3 -1
  229. data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -4
  230. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  231. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  232. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  233. data/spec/grape/validations/types_spec.rb +9 -36
  234. data/spec/grape/validations/validators/all_or_none_spec.rb +2 -0
  235. data/spec/grape/validations/validators/allow_blank_spec.rb +2 -0
  236. data/spec/grape/validations/validators/at_least_one_of_spec.rb +2 -0
  237. data/spec/grape/validations/validators/coerce_spec.rb +341 -136
  238. data/spec/grape/validations/validators/default_spec.rb +123 -0
  239. data/spec/grape/validations/validators/exactly_one_of_spec.rb +14 -12
  240. data/spec/grape/validations/validators/except_values_spec.rb +3 -1
  241. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +2 -0
  242. data/spec/grape/validations/validators/presence_spec.rb +30 -0
  243. data/spec/grape/validations/validators/regexp_spec.rb +2 -0
  244. data/spec/grape/validations/validators/same_as_spec.rb +2 -0
  245. data/spec/grape/validations/validators/values_spec.rb +30 -5
  246. data/spec/grape/validations_spec.rb +91 -33
  247. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  248. data/spec/integration/multi_json/json_spec.rb +2 -0
  249. data/spec/integration/multi_xml/xml_spec.rb +2 -0
  250. data/spec/shared/versioning_examples.rb +2 -0
  251. data/spec/spec_helper.rb +18 -0
  252. data/spec/support/basic_auth_encode_helpers.rb +2 -0
  253. data/spec/support/content_type_helpers.rb +2 -0
  254. data/spec/support/eager_load.rb +19 -0
  255. data/spec/support/endpoint_faker.rb +2 -0
  256. data/spec/support/file_streamer.rb +2 -0
  257. data/spec/support/integer_helpers.rb +2 -0
  258. data/spec/support/versioned_helpers.rb +4 -2
  259. metadata +48 -28
  260. data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +0 -18
  261. data/lib/grape/util/content_types.rb +0 -26
  262. data/lib/grape/validations/types/virtus_collection_patch.rb +0 -16
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::DefaultValidator do
@@ -296,4 +298,125 @@ describe Grape::Validations::DefaultValidator do
296
298
  end
297
299
  end
298
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
299
422
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::ExactlyOneOfValidator do
@@ -98,7 +100,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
98
100
  validate
99
101
  expect(last_response.status).to eq 400
100
102
  expect(JSON.parse(last_response.body)).to eq(
101
- 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided']
103
+ 'beer,wine,grapefruit' => ['are mutually exclusive']
102
104
  )
103
105
  end
104
106
 
@@ -110,7 +112,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
110
112
  validate
111
113
  expect(last_response.status).to eq 400
112
114
  expect(JSON.parse(last_response.body)).to eq(
113
- 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided']
115
+ 'beer,wine,grapefruit' => ['are mutually exclusive']
114
116
  )
115
117
  end
116
118
  end
@@ -124,7 +126,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
124
126
  validate
125
127
  expect(last_response.status).to eq 400
126
128
  expect(JSON.parse(last_response.body)).to eq(
127
- 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided']
129
+ 'beer,grapefruit' => ['are mutually exclusive']
128
130
  )
129
131
  end
130
132
  end
@@ -137,7 +139,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
137
139
  validate
138
140
  expect(last_response.status).to eq 400
139
141
  expect(JSON.parse(last_response.body)).to eq(
140
- 'beer,wine,grapefruit' => ['you should choose one']
142
+ 'beer,wine' => ['you should choose one']
141
143
  )
142
144
  end
143
145
  end
@@ -173,7 +175,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
173
175
  validate
174
176
  expect(last_response.status).to eq 400
175
177
  expect(JSON.parse(last_response.body)).to eq(
176
- 'item[beer],item[wine],item[grapefruit]' => ['are missing, exactly one parameter must be provided']
178
+ 'item[beer],item[wine]' => ['are mutually exclusive']
177
179
  )
178
180
  end
179
181
  end
@@ -188,7 +190,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
188
190
  validate
189
191
  expect(last_response.status).to eq 400
190
192
  expect(JSON.parse(last_response.body)).to eq(
191
- 'item[beer],item[wine],item[grapefruit]' => ['are missing, exactly one parameter must be provided']
193
+ 'item[beer],item[wine]' => ['are mutually exclusive']
192
194
  )
193
195
  end
194
196
  end
@@ -211,11 +213,11 @@ describe Grape::Validations::ExactlyOneOfValidator do
211
213
  validate
212
214
  expect(last_response.status).to eq 400
213
215
  expect(JSON.parse(last_response.body)).to eq(
214
- 'items[0][beer],items[0][wine],items[0][grapefruit]' => [
215
- 'are missing, exactly one parameter must be provided'
216
+ 'items[0][beer],items[0][wine]' => [
217
+ 'are mutually exclusive'
216
218
  ],
217
- 'items[1][beer],items[1][wine],items[1][grapefruit]' => [
218
- 'are missing, exactly one parameter must be provided'
219
+ 'items[1][wine],items[1][grapefruit]' => [
220
+ 'are mutually exclusive'
219
221
  ]
220
222
  )
221
223
  end
@@ -229,8 +231,8 @@ describe Grape::Validations::ExactlyOneOfValidator do
229
231
  validate
230
232
  expect(last_response.status).to eq 400
231
233
  expect(JSON.parse(last_response.body)).to eq(
232
- 'items[0][nested_items][0][beer],items[0][nested_items][0][wine],items[0][nested_items][0][grapefruit]' => [
233
- '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'
234
236
  ]
235
237
  )
236
238
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::ExceptValuesValidator do
@@ -110,7 +112,7 @@ describe Grape::Validations::ExceptValuesValidator do
110
112
  optional: { type: Array[Integer], except_values: [10, 11], default: 12 },
111
113
  tests: [
112
114
  { value: 'invalid-type1', rc: 400, body: { error: 'type is invalid' }.to_json },
113
- { value: 10, rc: 400, body: { error: 'type has a value not allowed' }.to_json },
115
+ { value: 10, rc: 400, body: { error: 'type is invalid' }.to_json },
114
116
  { value: [10], rc: 400, body: { error: 'type has a value not allowed' }.to_json },
115
117
  { value: ['3'], rc: 200, body: { type: [3] }.to_json },
116
118
  { value: [3], rc: 200, body: { type: [3] }.to_json },
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::MutualExclusionValidator do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::PresenceValidator do
@@ -269,4 +271,32 @@ describe Grape::Validations::PresenceValidator do
269
271
  expect(last_response.body).to eq('Hello optional'.to_json)
270
272
  end
271
273
  end
274
+
275
+ context 'with a custom type' do
276
+ it 'does not validate their type when it is missing' do
277
+ class CustomType
278
+ def self.parse(value)
279
+ return if value.blank?
280
+
281
+ new
282
+ end
283
+ end
284
+
285
+ subject.params do
286
+ requires :custom, type: CustomType
287
+ end
288
+ subject.get '/custom' do
289
+ 'custom'
290
+ end
291
+
292
+ get 'custom'
293
+
294
+ expect(last_response.status).to eq(400)
295
+ expect(last_response.body).to eq('{"error":"custom is missing"}')
296
+
297
+ get 'custom', custom: 'filled'
298
+
299
+ expect(last_response.status).to eq(200)
300
+ end
301
+ end
272
302
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::RegexpValidator do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::SameAsValidator do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::ValuesValidator do
@@ -222,6 +224,11 @@ describe Grape::Validations::ValuesValidator do
222
224
  requires :type, values: { proc: ->(v) { ValuesModel.values.include? v }, message: 'failed check' }
223
225
  end
224
226
  get '/proc/message'
227
+
228
+ params do
229
+ optional :name, type: String, values: %w[a b], allow_blank: true
230
+ end
231
+ get '/allow_blank'
225
232
  end
226
233
  end
227
234
  end
@@ -312,7 +319,7 @@ describe Grape::Validations::ValuesValidator do
312
319
  expect(last_response.status).to eq 200
313
320
  end
314
321
 
315
- 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
316
323
  put('/optional_with_array_of_string_values', optional: nil)
317
324
  expect(last_response.status).to eq 200
318
325
  end
@@ -431,11 +438,21 @@ describe Grape::Validations::ValuesValidator do
431
438
  end.to raise_error Grape::Exceptions::IncompatibleOptionValues
432
439
  end
433
440
 
434
- it 'allows values to be true or false when setting the type to boolean' do
435
- get('/values/optional_boolean', type: true)
436
- expect(last_response.status).to eq 200
437
- expect(last_response.body).to eq({ type: true }.to_json)
441
+ context 'boolean values' do
442
+ it 'allows a value from the list' do
443
+ get('/values/optional_boolean', type: true)
444
+
445
+ expect(last_response.status).to eq 200
446
+ expect(last_response.body).to eq({ type: true }.to_json)
447
+ end
448
+
449
+ it 'rejects a value which is not in the list' do
450
+ get('/values/optional_boolean', type: false)
451
+
452
+ expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)
453
+ end
438
454
  end
455
+
439
456
  it 'allows values to be a kind of the coerced type not just an instance of it' do
440
457
  get('/values/coercion', type: 10)
441
458
  expect(last_response.status).to eq 200
@@ -462,6 +479,14 @@ describe Grape::Validations::ValuesValidator do
462
479
  end.to raise_error Grape::Exceptions::IncompatibleOptionValues
463
480
  end
464
481
 
482
+ it 'allows a blank value when the allow_blank option is true' do
483
+ get 'allow_blank', name: nil
484
+ expect(last_response.status).to eq(200)
485
+
486
+ get 'allow_blank', name: ''
487
+ expect(last_response.status).to eq(200)
488
+ end
489
+
465
490
  context 'with a lambda values' do
466
491
  subject do
467
492
  Class.new(Grape::API) do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations do
@@ -7,16 +9,23 @@ describe Grape::Validations do
7
9
  subject
8
10
  end
9
11
 
12
+ def declared_params
13
+ subject.namespace_stackable(:declared_params).flatten
14
+ end
15
+
10
16
  describe 'params' do
11
17
  context 'optional' do
12
- it 'validates when params is present' do
18
+ before do
13
19
  subject.params do
14
20
  optional :a_number, regexp: /^[0-9]+$/
21
+ optional :attachment, type: File
15
22
  end
16
23
  subject.get '/optional' do
17
24
  'optional works!'
18
25
  end
26
+ end
19
27
 
28
+ it 'validates when params is present' do
20
29
  get '/optional', a_number: 'string'
21
30
  expect(last_response.status).to eq(400)
22
31
  expect(last_response.body).to eq('a_number is invalid')
@@ -27,14 +36,7 @@ describe Grape::Validations do
27
36
  end
28
37
 
29
38
  it "doesn't validate when param not present" do
30
- subject.params do
31
- optional :a_number, regexp: /^[0-9]+$/
32
- end
33
- subject.get '/optional' do
34
- 'optional works!'
35
- end
36
-
37
- get '/optional'
39
+ get '/optional', a_number: nil, attachment: nil
38
40
  expect(last_response.status).to eq(200)
39
41
  expect(last_response.body).to eq('optional works!')
40
42
  end
@@ -43,7 +45,7 @@ describe Grape::Validations do
43
45
  subject.params do
44
46
  optional :some_param
45
47
  end
46
- expect(subject.route_setting(:declared_params)).to eq([:some_param])
48
+ expect(declared_params).to eq([:some_param])
47
49
  end
48
50
  end
49
51
 
@@ -63,7 +65,7 @@ describe Grape::Validations do
63
65
 
64
66
  it 'adds entity documentation to declared params' do
65
67
  define_optional_using
66
- expect(subject.route_setting(:declared_params)).to eq(%i[field_a field_b])
68
+ expect(declared_params).to eq(%i[field_a field_b])
67
69
  end
68
70
 
69
71
  it 'works when field_a and field_b are not present' do
@@ -110,7 +112,7 @@ describe Grape::Validations do
110
112
  subject.params do
111
113
  requires :some_param
112
114
  end
113
- expect(subject.route_setting(:declared_params)).to eq([:some_param])
115
+ expect(declared_params).to eq([:some_param])
114
116
  end
115
117
 
116
118
  it 'works when required field is present but nil' do
@@ -120,6 +122,62 @@ describe Grape::Validations do
120
122
  end
121
123
  end
122
124
 
125
+ context 'requires with nested params' do
126
+ before do
127
+ subject.params do
128
+ requires :first_level, type: Hash do
129
+ optional :second_level, type: Array do
130
+ requires :value, type: Integer
131
+ optional :name, type: String
132
+ optional :third_level, type: Array do
133
+ requires :value, type: Integer
134
+ optional :name, type: String
135
+ optional :fourth_level, type: Array do
136
+ requires :value, type: Integer
137
+ optional :name, type: String
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ subject.put('/required') { 'required works' }
144
+ end
145
+
146
+ let(:request_params) do
147
+ {
148
+ first_level: {
149
+ second_level: [
150
+ { value: 1, name: 'Lisa' },
151
+ {
152
+ value: 2,
153
+ name: 'James',
154
+ third_level: [
155
+ { value: 'three', name: 'Sophie' },
156
+ {
157
+ value: 4,
158
+ name: 'Jenny',
159
+ fourth_level: [
160
+ { name: 'Samuel' }, { value: 6, name: 'Jane' }
161
+ ]
162
+ }
163
+ ]
164
+ }
165
+ ]
166
+ }
167
+ }
168
+ end
169
+
170
+ it 'validates correctly in deep nested params' do
171
+ put '/required', request_params.to_json, 'CONTENT_TYPE' => 'application/json'
172
+
173
+ expect(last_response.status).to eq(400)
174
+ expect(last_response.body).to eq(
175
+ 'first_level[second_level][1][third_level][0][value] is invalid, ' \
176
+ 'first_level[second_level][1][third_level][1][fourth_level][0][value] is missing'
177
+ )
178
+ end
179
+ end
180
+
123
181
  context 'requires :all using Grape::Entity documentation' do
124
182
  def define_requires_all
125
183
  documentation = {
@@ -139,7 +197,7 @@ describe Grape::Validations do
139
197
 
140
198
  it 'adds entity documentation to declared params' do
141
199
  define_requires_all
142
- expect(subject.route_setting(:declared_params)).to eq(%i[required_field optional_field])
200
+ expect(declared_params).to eq(%i[required_field optional_field])
143
201
  end
144
202
 
145
203
  it 'errors when required_field is not present' do
@@ -174,7 +232,7 @@ describe Grape::Validations do
174
232
 
175
233
  it 'adds entity documentation to declared params' do
176
234
  define_requires_none
177
- expect(subject.route_setting(:declared_params)).to eq(%i[required_field optional_field])
235
+ expect(declared_params).to eq(%i[required_field optional_field])
178
236
  end
179
237
 
180
238
  it 'errors when required_field is not present' do
@@ -204,7 +262,7 @@ describe Grape::Validations do
204
262
 
205
263
  it 'adds only the entity documentation to declared params, nothing more' do
206
264
  define_requires_all
207
- expect(subject.route_setting(:declared_params)).to eq(%i[required_field optional_field])
265
+ expect(declared_params).to eq(%i[required_field optional_field])
208
266
  end
209
267
  end
210
268
 
@@ -270,7 +328,7 @@ describe Grape::Validations do
270
328
  requires :key
271
329
  end
272
330
  end
273
- expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
331
+ expect(declared_params).to eq([items: [:key]])
274
332
  end
275
333
  end
276
334
 
@@ -342,7 +400,7 @@ describe Grape::Validations do
342
400
  requires :key
343
401
  end
344
402
  end
345
- expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
403
+ expect(declared_params).to eq([items: [:key]])
346
404
  end
347
405
  end
348
406
 
@@ -405,7 +463,7 @@ describe Grape::Validations do
405
463
  requires :key
406
464
  end
407
465
  end
408
- expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
466
+ expect(declared_params).to eq([items: [:key]])
409
467
  end
410
468
  end
411
469
 
@@ -436,7 +494,7 @@ describe Grape::Validations do
436
494
  class DateRangeValidator < Grape::Validations::Base
437
495
  def validate_param!(attr_name, params)
438
496
  return if params[attr_name][:from] <= params[attr_name][:to]
439
- raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "'from' must be lower or equal to 'to'"
497
+ raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "'from' must be lower or equal to 'to'")
440
498
  end
441
499
  end
442
500
  end
@@ -520,7 +578,7 @@ describe Grape::Validations do
520
578
  # NOTE: with body parameters in json or XML or similar this
521
579
  # should actually fail with: children[parents][name] is missing.
522
580
  expect(last_response.status).to eq(400)
523
- expect(last_response.body).to eq('children[1][parents] is missing')
581
+ expect(last_response.body).to eq('children[1][parents] is missing, children[0][parents][1][name] is missing, children[0][parents][1][name] is empty')
524
582
  end
525
583
 
526
584
  it 'errors when a parameter is not present in array within array' do
@@ -561,7 +619,7 @@ describe Grape::Validations do
561
619
 
562
620
  get '/within_array', children: [name: 'Jay']
563
621
  expect(last_response.status).to eq(400)
564
- expect(last_response.body).to eq('children[0][parents] is missing')
622
+ expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing, children[0][parents][0][name] is empty')
565
623
  end
566
624
 
567
625
  it 'errors when param is not an Array' do
@@ -709,7 +767,7 @@ describe Grape::Validations do
709
767
  expect(last_response.status).to eq(200)
710
768
  put_with_json '/within_array', children: [name: 'Jay']
711
769
  expect(last_response.status).to eq(400)
712
- expect(last_response.body).to eq('children[0][parents] is missing')
770
+ expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing')
713
771
  end
714
772
  end
715
773
 
@@ -759,7 +817,7 @@ describe Grape::Validations do
759
817
  requires :key
760
818
  end
761
819
  end
762
- expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
820
+ expect(declared_params).to eq([items: [:key]])
763
821
  end
764
822
  end
765
823
 
@@ -784,7 +842,7 @@ describe Grape::Validations do
784
842
  it 'does internal validations if the outer group is present' do
785
843
  get '/nested_optional_group', items: [{ key: 'foo' }]
786
844
  expect(last_response.status).to eq(400)
787
- expect(last_response.body).to eq('items[0][required_subitems] is missing')
845
+ expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')
788
846
 
789
847
  get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
790
848
  expect(last_response.status).to eq(200)
@@ -804,7 +862,7 @@ describe Grape::Validations do
804
862
  it 'handles validation within arrays' do
805
863
  get '/nested_optional_group', items: [{ key: 'foo' }]
806
864
  expect(last_response.status).to eq(400)
807
- expect(last_response.body).to eq('items[0][required_subitems] is missing')
865
+ expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')
808
866
 
809
867
  get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
810
868
  expect(last_response.status).to eq(200)
@@ -823,7 +881,7 @@ describe Grape::Validations do
823
881
  requires(:required_subitems, type: Array) { requires :value }
824
882
  end
825
883
  end
826
- expect(subject.route_setting(:declared_params)).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]])
884
+ expect(declared_params).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]])
827
885
  end
828
886
  end
829
887
 
@@ -851,7 +909,7 @@ describe Grape::Validations do
851
909
  class Customvalidator < Grape::Validations::Base
852
910
  def validate_param!(attr_name, params)
853
911
  return if params[attr_name] == 'im custom'
854
- raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: 'is not custom!'
912
+ raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: 'is not custom!')
855
913
  end
856
914
  end
857
915
  end
@@ -999,7 +1057,7 @@ describe Grape::Validations do
999
1057
  class CustomvalidatorWithOptions < Grape::Validations::Base
1000
1058
  def validate_param!(attr_name, params)
1001
1059
  return if params[attr_name] == @option[:text]
1002
- raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message
1060
+ raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message)
1003
1061
  end
1004
1062
  end
1005
1063
  end
@@ -1068,14 +1126,14 @@ describe Grape::Validations do
1068
1126
  subject.params do
1069
1127
  use :pagination
1070
1128
  end
1071
- expect(subject.route_setting(:declared_params)).to eq %i[page per_page]
1129
+ expect(declared_params).to eq %i[page per_page]
1072
1130
  end
1073
1131
 
1074
1132
  it 'by #use with multiple params' do
1075
1133
  subject.params do
1076
1134
  use :pagination, :period
1077
1135
  end
1078
- expect(subject.route_setting(:declared_params)).to eq %i[page per_page start_date end_date]
1136
+ expect(declared_params).to eq %i[page per_page start_date end_date]
1079
1137
  end
1080
1138
  end
1081
1139
 
@@ -1364,7 +1422,7 @@ describe Grape::Validations do
1364
1422
  it 'errors when two or more are present' do
1365
1423
  get '/custom_message/exactly_one_of', beer: 'string', wine: 'anotherstring'
1366
1424
  expect(last_response.status).to eq(400)
1367
- expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter is required'
1425
+ expect(last_response.body).to eq 'beer, wine are missing, exactly one parameter is required'
1368
1426
  end
1369
1427
  end
1370
1428
 
@@ -1383,7 +1441,7 @@ describe Grape::Validations do
1383
1441
  it 'errors when two or more are present' do
1384
1442
  get '/exactly_one_of', beer: 'string', wine: 'anotherstring'
1385
1443
  expect(last_response.status).to eq(400)
1386
- expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter must be provided'
1444
+ expect(last_response.body).to eq 'beer, wine are mutually exclusive'
1387
1445
  end
1388
1446
  end
1389
1447
 
@@ -1423,7 +1481,7 @@ describe Grape::Validations do
1423
1481
  it 'errors when two or more are present' do
1424
1482
  get '/exactly_one_of_nested', nested: { beer_nested: 'string' }, nested2: [{ beer_nested2: 'string', wine_nested2: 'anotherstring' }]
1425
1483
  expect(last_response.status).to eq(400)
1426
- expect(last_response.body).to eq 'nested2[0][beer_nested2], nested2[0][wine_nested2], nested2[0][juice_nested2] are missing, exactly one parameter must be provided'
1484
+ expect(last_response.body).to eq 'nested2[0][beer_nested2], nested2[0][wine_nested2] are mutually exclusive'
1427
1485
  end
1428
1486
  end
1429
1487
  end