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::AllOrNoneOfValidator do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::AllowBlankValidator do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::AtLeastOneOfValidator do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Grape::Validations::CoerceValidator do
@@ -10,11 +12,13 @@ describe Grape::Validations::CoerceValidator do
10
12
  end
11
13
 
12
14
  describe 'coerce' do
13
- module CoerceValidatorSpec
14
- class User
15
- include Virtus.model
16
- attribute :id, Integer
17
- attribute :name, String
15
+ class SecureURIOnly
16
+ def self.parse(value)
17
+ URI.parse(value)
18
+ end
19
+
20
+ def self.parsed?(value)
21
+ value.is_a? URI::HTTPS
18
22
  end
19
23
  end
20
24
 
@@ -96,6 +100,7 @@ describe Grape::Validations::CoerceValidator do
96
100
 
97
101
  it 'respects :coerce_with' do
98
102
  get '/', a: 'yup'
103
+
99
104
  expect(last_response.status).to eq(200)
100
105
  expect(last_response.body).to eq('TrueClass')
101
106
  end
@@ -148,26 +153,50 @@ describe Grape::Validations::CoerceValidator do
148
153
  expect(last_response.body).to eq('array int works')
149
154
  end
150
155
 
151
- context 'complex objects' do
152
- it 'error on malformed input for complex objects' do
153
- subject.params do
154
- requires :user, type: CoerceValidatorSpec::User
156
+ context 'coerces' do
157
+ context 'json' do
158
+ let(:headers) { { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' } }
159
+
160
+ it 'BigDecimal' do
161
+ subject.params do
162
+ requires :bigdecimal, type: BigDecimal
163
+ end
164
+ subject.post '/bigdecimal' do
165
+ "#{params[:bigdecimal].class} #{params[:bigdecimal].to_f}"
166
+ end
167
+
168
+ post '/bigdecimal', { bigdecimal: 45.1 }.to_json, headers
169
+ expect(last_response.status).to eq(201)
170
+ expect(last_response.body).to eq('BigDecimal 45.1')
155
171
  end
156
- subject.get '/user' do
157
- 'complex works'
172
+
173
+ it 'Boolean' do
174
+ subject.params do
175
+ requires :boolean, type: Boolean
176
+ end
177
+ subject.post '/boolean' do
178
+ params[:boolean]
179
+ end
180
+
181
+ post '/boolean', { boolean: 'true' }.to_json, headers
182
+ expect(last_response.status).to eq(201)
183
+ expect(last_response.body).to eq('true')
158
184
  end
185
+ end
159
186
 
160
- get '/user', user: '32'
161
- expect(last_response.status).to eq(400)
162
- expect(last_response.body).to eq('user is invalid')
187
+ it 'BigDecimal' do
188
+ subject.params do
189
+ requires :bigdecimal, coerce: BigDecimal
190
+ end
191
+ subject.get '/bigdecimal' do
192
+ params[:bigdecimal].class
193
+ end
163
194
 
164
- get '/user', user: { id: 32, name: 'Bob' }
195
+ get '/bigdecimal', bigdecimal: '45'
165
196
  expect(last_response.status).to eq(200)
166
- expect(last_response.body).to eq('complex works')
197
+ expect(last_response.body).to eq('BigDecimal')
167
198
  end
168
- end
169
199
 
170
- context 'coerces' do
171
200
  it 'Integer' do
172
201
  subject.params do
173
202
  requires :int, coerce: Integer
@@ -181,6 +210,42 @@ describe Grape::Validations::CoerceValidator do
181
210
  expect(last_response.body).to eq(integer_class_name)
182
211
  end
183
212
 
213
+ it 'String' do
214
+ subject.params do
215
+ requires :string, coerce: String
216
+ end
217
+ subject.get '/string' do
218
+ params[:string].class
219
+ end
220
+
221
+ get '/string', string: 45
222
+ expect(last_response.status).to eq(200)
223
+ expect(last_response.body).to eq('String')
224
+
225
+ get '/string', string: nil
226
+ expect(last_response.status).to eq(200)
227
+ expect(last_response.body).to eq('NilClass')
228
+ end
229
+
230
+ it 'is a custom type' do
231
+ subject.params do
232
+ requires :uri, coerce: SecureURIOnly
233
+ end
234
+ subject.get '/secure_uri' do
235
+ params[:uri].class
236
+ end
237
+
238
+ get 'secure_uri', uri: 'https://www.example.com'
239
+
240
+ expect(last_response.status).to eq(200)
241
+ expect(last_response.body).to eq('URI::HTTPS')
242
+
243
+ get 'secure_uri', uri: 'http://www.example.com'
244
+
245
+ expect(last_response.status).to eq(400)
246
+ expect(last_response.body).to eq('uri is invalid')
247
+ end
248
+
184
249
  context 'Array' do
185
250
  it 'Array of Integers' do
186
251
  subject.params do
@@ -197,7 +262,7 @@ describe Grape::Validations::CoerceValidator do
197
262
 
198
263
  it 'Array of Bools' do
199
264
  subject.params do
200
- requires :arry, coerce: Array[Virtus::Attribute::Boolean]
265
+ requires :arry, coerce: Array[Grape::API::Boolean]
201
266
  end
202
267
  subject.get '/array' do
203
268
  params[:arry][0].class
@@ -208,27 +273,6 @@ describe Grape::Validations::CoerceValidator do
208
273
  expect(last_response.body).to eq('TrueClass')
209
274
  end
210
275
 
211
- it 'Array of Complex' do
212
- subject.params do
213
- requires :arry, coerce: Array[CoerceValidatorSpec::User]
214
- end
215
- subject.get '/array' do
216
- params[:arry].size
217
- end
218
-
219
- get 'array', arry: [31]
220
- expect(last_response.status).to eq(400)
221
- expect(last_response.body).to eq('arry is invalid')
222
-
223
- get 'array', arry: { id: 31, name: 'Alice' }
224
- expect(last_response.status).to eq(400)
225
- expect(last_response.body).to eq('arry is invalid')
226
-
227
- get 'array', arry: [{ id: 31, name: 'Alice' }]
228
- expect(last_response.status).to eq(200)
229
- expect(last_response.body).to eq('1')
230
- end
231
-
232
276
  it 'Array of type implementing parse' do
233
277
  subject.params do
234
278
  requires :uri, type: Array[URI]
@@ -253,17 +297,7 @@ describe Grape::Validations::CoerceValidator do
253
297
  expect(last_response.body).to eq('Set,URI::HTTP,1')
254
298
  end
255
299
 
256
- it 'Array of class implementing parse and parsed?' do
257
- class SecureURIOnly
258
- def self.parse(value)
259
- URI.parse(value)
260
- end
261
-
262
- def self.parsed?(value)
263
- value.is_a? URI::HTTPS
264
- end
265
- end
266
-
300
+ it 'Array of a custom type' do
267
301
  subject.params do
268
302
  requires :uri, type: Array[SecureURIOnly]
269
303
  end
@@ -295,7 +329,7 @@ describe Grape::Validations::CoerceValidator do
295
329
 
296
330
  it 'Set of Bools' do
297
331
  subject.params do
298
- requires :set, coerce: Set[Virtus::Attribute::Boolean]
332
+ requires :set, coerce: Set[Grape::API::Boolean]
299
333
  end
300
334
  subject.get '/set' do
301
335
  params[:set].first.class
@@ -307,119 +341,247 @@ describe Grape::Validations::CoerceValidator do
307
341
  end
308
342
  end
309
343
 
310
- it 'Bool' do
344
+ it 'Boolean' do
311
345
  subject.params do
312
- requires :bool, coerce: Virtus::Attribute::Boolean
346
+ requires :boolean, type: Boolean
313
347
  end
314
- subject.get '/bool' do
315
- params[:bool].class
348
+ subject.get '/boolean' do
349
+ params[:boolean].class
316
350
  end
317
351
 
318
- get '/bool', bool: 1
352
+ get '/boolean', boolean: 1
319
353
  expect(last_response.status).to eq(200)
320
354
  expect(last_response.body).to eq('TrueClass')
355
+ end
321
356
 
322
- get '/bool', bool: 0
323
- expect(last_response.status).to eq(200)
324
- expect(last_response.body).to eq('FalseClass')
357
+ context 'File' do
358
+ let(:file) { Rack::Test::UploadedFile.new(__FILE__) }
359
+ let(:filename) { File.basename(__FILE__).to_s }
325
360
 
326
- get '/bool', bool: 'false'
327
- expect(last_response.status).to eq(200)
328
- expect(last_response.body).to eq('FalseClass')
361
+ it 'Rack::Multipart::UploadedFile' do
362
+ subject.params do
363
+ requires :file, type: Rack::Multipart::UploadedFile
364
+ end
365
+ subject.post '/upload' do
366
+ params[:file][:filename]
367
+ end
329
368
 
330
- get '/bool', bool: 'true'
331
- expect(last_response.status).to eq(200)
332
- expect(last_response.body).to eq('TrueClass')
333
- end
369
+ post '/upload', file: file
370
+ expect(last_response.status).to eq(201)
371
+ expect(last_response.body).to eq(filename)
334
372
 
335
- it 'Boolean' do
336
- subject.params do
337
- optional :boolean, type: Boolean, default: true
338
- end
339
- subject.get '/boolean' do
340
- params[:boolean].class
373
+ post '/upload', file: 'not a file'
374
+ expect(last_response.status).to eq(400)
375
+ expect(last_response.body).to eq('file is invalid')
341
376
  end
342
377
 
343
- get '/boolean'
344
- expect(last_response.status).to eq(200)
345
- expect(last_response.body).to eq('TrueClass')
346
-
347
- get '/boolean', boolean: true
348
- expect(last_response.status).to eq(200)
349
- expect(last_response.body).to eq('TrueClass')
378
+ it 'File' do
379
+ subject.params do
380
+ requires :file, coerce: File
381
+ end
382
+ subject.post '/upload' do
383
+ params[:file][:filename]
384
+ end
350
385
 
351
- get '/boolean', boolean: false
352
- expect(last_response.status).to eq(200)
353
- expect(last_response.body).to eq('FalseClass')
386
+ post '/upload', file: file
387
+ expect(last_response.status).to eq(201)
388
+ expect(last_response.body).to eq(filename)
354
389
 
355
- get '/boolean', boolean: 'true'
356
- expect(last_response.status).to eq(200)
357
- expect(last_response.body).to eq('TrueClass')
390
+ post '/upload', file: 'not a file'
391
+ expect(last_response.status).to eq(400)
392
+ expect(last_response.body).to eq('file is invalid')
358
393
 
359
- get '/boolean', boolean: 'false'
360
- expect(last_response.status).to eq(200)
361
- expect(last_response.body).to eq('FalseClass')
394
+ post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' }
395
+ expect(last_response.status).to eq(400)
396
+ expect(last_response.body).to eq('file is invalid')
397
+ end
362
398
 
363
- get '/boolean', boolean: 123
364
- expect(last_response.status).to eq(400)
365
- expect(last_response.body).to eq('boolean is invalid')
399
+ it 'collection' do
400
+ subject.params do
401
+ requires :files, type: Array[File]
402
+ end
403
+ subject.post '/upload' do
404
+ params[:files].first[:filename]
405
+ end
366
406
 
367
- get '/boolean', boolean: '123'
368
- expect(last_response.status).to eq(400)
369
- expect(last_response.body).to eq('boolean is invalid')
407
+ post '/upload', files: [file]
408
+ expect(last_response.status).to eq(201)
409
+ expect(last_response.body).to eq(filename)
410
+ end
370
411
  end
371
412
 
372
- it 'Rack::Multipart::UploadedFile' do
413
+ it 'Nests integers' do
373
414
  subject.params do
374
- requires :file, type: Rack::Multipart::UploadedFile
415
+ requires :integers, type: Hash do
416
+ requires :int, coerce: Integer
417
+ end
375
418
  end
376
- subject.post '/upload' do
377
- params[:file][:filename]
419
+ subject.get '/int' do
420
+ params[:integers][:int].class
378
421
  end
379
422
 
380
- post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
381
- expect(last_response.status).to eq(201)
382
- expect(last_response.body).to eq(File.basename(__FILE__).to_s)
383
-
384
- post '/upload', file: 'not a file'
385
- expect(last_response.status).to eq(400)
386
- expect(last_response.body).to eq('file is invalid')
423
+ get '/int', integers: { int: '45' }
424
+ expect(last_response.status).to eq(200)
425
+ expect(last_response.body).to eq(integer_class_name)
387
426
  end
388
427
 
389
- it 'File' do
390
- subject.params do
391
- requires :file, coerce: File
428
+ context 'nil values' do
429
+ context 'primitive types' do
430
+ Grape::Validations::Types::PRIMITIVES.each do |type|
431
+ it 'respects the nil value' do
432
+ subject.params do
433
+ requires :param, type: type
434
+ end
435
+ subject.get '/nil_value' do
436
+ params[:param].class
437
+ end
438
+
439
+ get '/nil_value', param: nil
440
+ expect(last_response.status).to eq(200)
441
+ expect(last_response.body).to eq('NilClass')
442
+ end
443
+ end
392
444
  end
393
- subject.post '/upload' do
394
- params[:file][:filename]
445
+
446
+ context 'structures types' do
447
+ Grape::Validations::Types::STRUCTURES.each do |type|
448
+ it 'respects the nil value' do
449
+ subject.params do
450
+ requires :param, type: type
451
+ end
452
+ subject.get '/nil_value' do
453
+ params[:param].class
454
+ end
455
+
456
+ get '/nil_value', param: nil
457
+ expect(last_response.status).to eq(200)
458
+ expect(last_response.body).to eq('NilClass')
459
+ end
460
+ end
395
461
  end
396
462
 
397
- post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
398
- expect(last_response.status).to eq(201)
399
- expect(last_response.body).to eq(File.basename(__FILE__).to_s)
463
+ context 'special types' do
464
+ Grape::Validations::Types::SPECIAL.each_key do |type|
465
+ it 'respects the nil value' do
466
+ subject.params do
467
+ requires :param, type: type
468
+ end
469
+ subject.get '/nil_value' do
470
+ params[:param].class
471
+ end
400
472
 
401
- post '/upload', file: 'not a file'
402
- expect(last_response.status).to eq(400)
403
- expect(last_response.body).to eq('file is invalid')
473
+ get '/nil_value', param: nil
474
+ expect(last_response.status).to eq(200)
475
+ expect(last_response.body).to eq('NilClass')
476
+ end
477
+ end
404
478
 
405
- post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' }
406
- expect(last_response.status).to eq(400)
407
- expect(last_response.body).to eq('file is invalid')
479
+ context 'variant-member-type collections' do
480
+ [
481
+ Array[Integer, String],
482
+ [Integer, String, Array[Integer, String]]
483
+ ].each do |type|
484
+ it 'respects the nil value' do
485
+ subject.params do
486
+ requires :param, type: type
487
+ end
488
+ subject.get '/nil_value' do
489
+ params[:param].class
490
+ end
491
+
492
+ get '/nil_value', param: nil
493
+ expect(last_response.status).to eq(200)
494
+ expect(last_response.body).to eq('NilClass')
495
+ end
496
+ end
497
+ end
498
+ end
408
499
  end
409
500
 
410
- it 'Nests integers' do
411
- subject.params do
412
- requires :integers, type: Hash do
413
- requires :int, coerce: Integer
501
+ context 'empty string' do
502
+ context 'primitive types' do
503
+ (Grape::Validations::Types::PRIMITIVES - [String]).each do |type|
504
+ it "is coerced to nil for type #{type}" do
505
+ subject.params do
506
+ requires :param, type: type
507
+ end
508
+ subject.get '/empty_string' do
509
+ params[:param].class
510
+ end
511
+
512
+ get '/empty_string', param: ''
513
+ expect(last_response.status).to eq(200)
514
+ expect(last_response.body).to eq('NilClass')
515
+ end
516
+ end
517
+
518
+ it 'is not coerced to nil for type String' do
519
+ subject.params do
520
+ requires :param, type: String
521
+ end
522
+ subject.get '/empty_string' do
523
+ params[:param].class
524
+ end
525
+
526
+ get '/empty_string', param: ''
527
+ expect(last_response.status).to eq(200)
528
+ expect(last_response.body).to eq('String')
414
529
  end
415
530
  end
416
- subject.get '/int' do
417
- params[:integers][:int].class
531
+
532
+ context 'structures types' do
533
+ (Grape::Validations::Types::STRUCTURES - [Hash]).each do |type|
534
+ it "is coerced to nil for type #{type}" do
535
+ subject.params do
536
+ requires :param, type: type
537
+ end
538
+ subject.get '/empty_string' do
539
+ params[:param].class
540
+ end
541
+
542
+ get '/empty_string', param: ''
543
+ expect(last_response.status).to eq(200)
544
+ expect(last_response.body).to eq('NilClass')
545
+ end
546
+ end
418
547
  end
419
548
 
420
- get '/int', integers: { int: '45' }
421
- expect(last_response.status).to eq(200)
422
- expect(last_response.body).to eq(integer_class_name)
549
+ context 'special types' do
550
+ (Grape::Validations::Types::SPECIAL.keys - [File, Rack::Multipart::UploadedFile]).each do |type|
551
+ it "is coerced to nil for type #{type}" do
552
+ subject.params do
553
+ requires :param, type: type
554
+ end
555
+ subject.get '/empty_string' do
556
+ params[:param].class
557
+ end
558
+
559
+ get '/empty_string', param: ''
560
+ expect(last_response.status).to eq(200)
561
+ expect(last_response.body).to eq('NilClass')
562
+ end
563
+ end
564
+
565
+ context 'variant-member-type collections' do
566
+ [
567
+ Array[Integer, String],
568
+ [Integer, String, Array[Integer, String]]
569
+ ].each do |type|
570
+ it "is coerced to nil for type #{type}" do
571
+ subject.params do
572
+ requires :param, type: type
573
+ end
574
+ subject.get '/empty_string' do
575
+ params[:param].class
576
+ end
577
+
578
+ get '/empty_string', param: ''
579
+ expect(last_response.status).to eq(200)
580
+ expect(last_response.body).to eq('NilClass')
581
+ end
582
+ end
583
+ end
584
+ end
423
585
  end
424
586
  end
425
587
 
@@ -443,19 +605,19 @@ describe Grape::Validations::CoerceValidator do
443
605
 
444
606
  it 'parses parameters with Array[String] type' do
445
607
  subject.params do
446
- requires :values, type: Array[String], coerce_with: ->(val) { val.split(/\s+/).map(&:to_i) }
608
+ requires :values, type: Array[String], coerce_with: ->(val) { val.split(/\s+/) }
447
609
  end
448
- subject.get '/ints' do
610
+ subject.get '/strings' do
449
611
  params[:values]
450
612
  end
451
613
 
452
- get '/ints', values: '1 2 3 4'
614
+ get '/strings', values: '1 2 3 4'
453
615
  expect(last_response.status).to eq(200)
454
616
  expect(JSON.parse(last_response.body)).to eq(%w[1 2 3 4])
455
617
 
456
- get '/ints', values: 'a b c d'
618
+ get '/strings', values: 'a b c d'
457
619
  expect(last_response.status).to eq(200)
458
- expect(JSON.parse(last_response.body)).to eq(%w[0 0 0 0])
620
+ expect(JSON.parse(last_response.body)).to eq(%w[a b c d])
459
621
  end
460
622
 
461
623
  it 'parses parameters with Array[Integer] type' do
@@ -540,6 +702,46 @@ describe Grape::Validations::CoerceValidator do
540
702
  expect(last_response.body).to eq('3')
541
703
  end
542
704
 
705
+ context 'Integer type and coerce_with potentially returning nil' do
706
+ before do
707
+ subject.params do
708
+ requires :int, type: Integer, coerce_with: (lambda do |val|
709
+ if val == '0'
710
+ nil
711
+ elsif val.match?(/^-?\d+$/)
712
+ val.to_i
713
+ else
714
+ val
715
+ end
716
+ end)
717
+ end
718
+ subject.get '/' do
719
+ params[:int].class.to_s
720
+ end
721
+ end
722
+
723
+ it 'accepts value that coerces to nil' do
724
+ get '/', int: '0'
725
+
726
+ expect(last_response.status).to eq(200)
727
+ expect(last_response.body).to eq('NilClass')
728
+ end
729
+
730
+ it 'coerces to Integer' do
731
+ get '/', int: '1'
732
+
733
+ expect(last_response.status).to eq(200)
734
+ expect(last_response.body).to eq('Integer')
735
+ end
736
+
737
+ it 'returns invalid value if coercion returns a wrong type' do
738
+ get '/', int: 'lol'
739
+
740
+ expect(last_response.status).to eq(400)
741
+ expect(last_response.body).to eq('int is invalid')
742
+ end
743
+ end
744
+
543
745
  it 'must be supplied with :type or :coerce' do
544
746
  expect do
545
747
  subject.params do
@@ -914,14 +1116,17 @@ describe Grape::Validations::CoerceValidator do
914
1116
  end
915
1117
 
916
1118
  context 'converter' do
917
- it 'does not build Virtus::Attribute multiple times' do
1119
+ it 'does not build a coercer multiple times' do
918
1120
  subject.params do
919
1121
  requires :something, type: Array[String]
920
1122
  end
921
1123
  subject.get do
922
1124
  end
923
1125
 
924
- expect(Virtus::Attribute).to receive(:build).at_most(2).times.and_call_original
1126
+ expect(Grape::Validations::Types::ArrayCoercer).to(
1127
+ receive(:new).at_most(:once).and_call_original
1128
+ )
1129
+
925
1130
  10.times { get '/' }
926
1131
  end
927
1132
  end