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
  module Grape
2
4
  module Util
3
5
  module StrictHashConfiguration
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  if Object.const_defined? :MultiXml
3
5
  Xml = ::MultiXml
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  # Registry to store and locate known Validators.
3
5
  module Validations
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Validations
3
5
  class AttributesIterator
@@ -29,9 +31,7 @@ module Grape
29
31
 
30
32
  if @scope.type == Array
31
33
  next unless @original_params.is_a?(Array) # do not validate content of array if it isn't array
32
- inside_array = true
33
- end
34
- if inside_array
34
+
35
35
  # fill current and parent scopes with correct array indicies
36
36
  parent_scope = @scope.parent
37
37
  parent_indicies.each do |parent_index|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Validations
3
5
  class MultipleAttributesIterator < AttributesIterator
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Validations
3
5
  class ParamsScope
@@ -43,8 +45,10 @@ module Grape
43
45
  # @return [Boolean] whether or not this entire scope needs to be
44
46
  # validated
45
47
  def should_validate?(parameters)
46
- return false if @optional && (params(parameters).blank? || all_element_blank?(parameters))
47
- return false unless meets_dependency?(params(parameters), parameters)
48
+ scoped_params = params(parameters)
49
+
50
+ return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))
51
+ return false unless meets_dependency?(scoped_params, parameters)
48
52
  return true if parent.nil?
49
53
  parent.should_validate?(parameters)
50
54
  end
@@ -121,7 +125,7 @@ module Grape
121
125
  # @param attrs [Array] (see Grape::DSL::Parameters#requires)
122
126
  def push_declared_params(attrs, **opts)
123
127
  if lateral?
124
- @parent.push_declared_params(attrs, opts)
128
+ @parent.push_declared_params(attrs, **opts)
125
129
  else
126
130
  if opts && opts[:as]
127
131
  @api.route_setting(:renamed_params, @api.route_setting(:renamed_params) || [])
@@ -233,14 +237,14 @@ module Grape
233
237
  @parent.push_declared_params [element => @declared_params]
234
238
  else
235
239
  @api.namespace_stackable(:declared_params, @declared_params)
236
-
237
- @api.route_setting(:declared_params, []) unless @api.route_setting(:declared_params)
238
- @api.route_setting(:declared_params, @api.namespace_stackable(:declared_params).flatten)
239
240
  end
241
+
242
+ # params were stored in settings, it can be cleaned from the params scope
243
+ @declared_params = nil
240
244
  end
241
245
 
242
246
  def validates(attrs, validations)
243
- doc_attrs = { required: validations.keys.include?(:presence) }
247
+ doc_attrs = { required: validations.key?(:presence) }
244
248
 
245
249
  coerce_type = infer_coercion(validations)
246
250
 
@@ -280,9 +284,7 @@ module Grape
280
284
  full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
281
285
  @api.document_attribute(full_attrs, doc_attrs)
282
286
 
283
- # slice out fail_fast attribute
284
- opts = {}
285
- opts[:fail_fast] = validations.delete(:fail_fast) || false
287
+ opts = derive_validator_options(validations)
286
288
 
287
289
  # Validate for presence before any other validators
288
290
  if validations.key?(:presence) && validations[:presence]
@@ -427,8 +429,8 @@ module Grape
427
429
  values_list.each do |values|
428
430
  next if !values || values.is_a?(Proc)
429
431
  value_types = values.is_a?(Range) ? [values.begin, values.end] : values
430
- if coerce_type == Virtus::Attribute::Boolean
431
- value_types = value_types.map { |type| Virtus::Attribute.build(type) }
432
+ if coerce_type == Grape::API::Boolean
433
+ value_types = value_types.map { |type| Grape::API::Boolean.build(type) }
432
434
  end
433
435
  unless value_types.all? { |v| v.is_a? coerce_type }
434
436
  raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
@@ -446,8 +448,19 @@ module Grape
446
448
  validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil?
447
449
  end
448
450
 
449
- def all_element_blank?(parameters)
450
- params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
451
+ def all_element_blank?(scoped_params)
452
+ scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?)
453
+ end
454
+
455
+ # Validators don't have access to each other and they don't need, however,
456
+ # some validators might influence others, so their options should be shared
457
+ def derive_validator_options(validations)
458
+ allow_blank = validations[:allow_blank]
459
+
460
+ {
461
+ allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,
462
+ fail_fast: validations.delete(:fail_fast) || false
463
+ }
451
464
  end
452
465
  end
453
466
  end
@@ -1,13 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Validations
3
5
  class SingleAttributeIterator < AttributesIterator
4
6
  private
5
7
 
6
- def yield_attributes(resource_params, attrs)
8
+ def yield_attributes(val, attrs)
7
9
  attrs.each do |attr_name|
8
- yield resource_params, attr_name
10
+ yield val, attr_name, empty?(val)
9
11
  end
10
12
  end
13
+
14
+ # Primitives like Integers and Booleans don't respond to +empty?+.
15
+ # It could be possible to use +blank?+ instead, but
16
+ #
17
+ # false.blank?
18
+ # => true
19
+ def empty?(val)
20
+ val.respond_to?(:empty?) ? val.empty? : val.nil?
21
+ end
11
22
  end
12
23
  end
13
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'types/build_coercer'
2
4
  require_relative 'types/custom_type_coercer'
3
5
  require_relative 'types/custom_type_collection_coercer'
@@ -6,10 +8,6 @@ require_relative 'types/variant_collection_coercer'
6
8
  require_relative 'types/json'
7
9
  require_relative 'types/file'
8
10
 
9
- # Patch for Virtus::Attribute::Collection
10
- # See the file for more details
11
- require_relative 'types/virtus_collection_patch'
12
-
13
11
  module Grape
14
12
  module Validations
15
13
  # Module for code related to grape's system for
@@ -27,8 +25,7 @@ module Grape
27
25
  # a parameter value could not be coerced.
28
26
  class InvalidValue; end
29
27
 
30
- # Types representing a single value, which are coerced through Virtus
31
- # or special logic in Grape.
28
+ # Types representing a single value, which are coerced.
32
29
  PRIMITIVES = [
33
30
  # Numerical
34
31
  Integer,
@@ -42,10 +39,11 @@ module Grape
42
39
  Time,
43
40
 
44
41
  # Misc
45
- Virtus::Attribute::Boolean,
42
+ Grape::API::Boolean,
46
43
  String,
47
44
  Symbol,
48
- Rack::Multipart::UploadedFile
45
+ TrueClass,
46
+ FalseClass
49
47
  ].freeze
50
48
 
51
49
  # Types representing data structures.
@@ -55,8 +53,7 @@ module Grape
55
53
  Set
56
54
  ].freeze
57
55
 
58
- # Types for which Grape provides special coercion
59
- # and type-checking logic.
56
+ # Special custom types provided by Grape.
60
57
  SPECIAL = {
61
58
  JSON => Json,
62
59
  Array[JSON] => JsonArray,
@@ -86,8 +83,6 @@ module Grape
86
83
  # @param type [Class] type to check
87
84
  # @return [Boolean] whether or not the type is known by Grape as a valid
88
85
  # data structure type
89
- # @note This method does not yet consider 'complex types', which inherit
90
- # Virtus.model.
91
86
  def self.structure?(type)
92
87
  STRUCTURES.include?(type)
93
88
  end
@@ -104,25 +99,6 @@ module Grape
104
99
  (type.is_a?(Array) || type.is_a?(Set)) && type.size > 1
105
100
  end
106
101
 
107
- # Does the given class implement a type system that Grape
108
- # (i.e. the underlying virtus attribute system) supports
109
- # out-of-the-box? Currently supported are +axiom-types+
110
- # and +virtus+.
111
- #
112
- # The type will be passed to +Virtus::Attribute.build+,
113
- # and the resulting attribute object will be expected to
114
- # respond correctly to +coerce+ and +value_coerced?+.
115
- #
116
- # @param type [Class] type to check
117
- # @return [Boolean] +true+ where the type is recognized
118
- def self.recognized?(type)
119
- return false if type.is_a?(Array) || type.is_a?(Set)
120
-
121
- type.is_a?(Virtus::Attribute) ||
122
- type.ancestors.include?(Axiom::Types::Type) ||
123
- type.include?(Virtus::Model::Core)
124
- end
125
-
126
102
  # Does Grape provide special coercion and validation
127
103
  # routines for the given class? This does not include
128
104
  # automatic handling for primitives, structures and
@@ -152,8 +128,6 @@ module Grape
152
128
  !primitive?(type) &&
153
129
  !structure?(type) &&
154
130
  !multiple?(type) &&
155
- !recognized?(type) &&
156
- !special?(type) &&
157
131
  type.respond_to?(:parse) &&
158
132
  type.method(:parse).arity == 1
159
133
  end
@@ -166,7 +140,11 @@ module Grape
166
140
  def self.collection_of_custom?(type)
167
141
  (type.is_a?(Array) || type.is_a?(Set)) &&
168
142
  type.length == 1 &&
169
- custom?(type.first)
143
+ (custom?(type.first) || special?(type.first))
144
+ end
145
+
146
+ def self.map_special(type)
147
+ SPECIAL.fetch(type, type)
170
148
  end
171
149
  end
172
150
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dry_type_coercer'
4
+
5
+ module Grape
6
+ module Validations
7
+ module Types
8
+ # Coerces elements in an array. It might be an array of strings or integers or
9
+ # an array of arrays of integers.
10
+ #
11
+ # It could've been possible to use an +of+
12
+ # method (https://dry-rb.org/gems/dry-types/1.2/array-with-member/)
13
+ # provided by dry-types. Unfortunately, it doesn't work for Grape because of
14
+ # behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
15
+ # maintains Virtus behavior in coercing.
16
+ class ArrayCoercer < DryTypeCoercer
17
+ register_collection Array
18
+
19
+ def initialize(type, strict = false)
20
+ super
21
+
22
+ @coercer = scope::Array
23
+ @subtype = type.first
24
+ end
25
+
26
+ def call(_val)
27
+ collection = super
28
+ return collection if collection.is_a?(InvalidValue)
29
+
30
+ coerce_elements collection
31
+ end
32
+
33
+ protected
34
+
35
+ attr_reader :subtype
36
+
37
+ def coerce_elements(collection)
38
+ return if collection.nil?
39
+
40
+ collection.each_with_index do |elem, index|
41
+ return InvalidValue.new if reject?(elem)
42
+
43
+ coerced_elem = elem_coercer.call(elem)
44
+
45
+ return coerced_elem if coerced_elem.is_a?(InvalidValue)
46
+
47
+ collection[index] = coerced_elem
48
+ end
49
+
50
+ collection
51
+ end
52
+
53
+ # This method maintains logic which was defined by Virtus for arrays.
54
+ # Virtus doesn't allow nil in arrays.
55
+ def reject?(val)
56
+ val.nil?
57
+ end
58
+
59
+ def elem_coercer
60
+ @elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,78 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'array_coercer'
4
+ require_relative 'set_coercer'
5
+ require_relative 'primitive_coercer'
6
+
1
7
  module Grape
2
8
  module Validations
3
9
  module Types
4
- # Work out the +Virtus::Attribute+ object to
5
- # use for coercing strings to the given +type+.
6
- # Coercion +method+ will be inferred if none is
7
- # supplied.
10
+ # Chooses the best coercer for the given type. For example, if the type
11
+ # is Integer, it will return a coercer which will be able to coerce a value
12
+ # to the integer.
13
+ #
14
+ # There are a few very special coercers which might be returned.
15
+ #
16
+ # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
17
+ # the given type implies values in an array with different types.
18
+ # For example, +[Integer, String]+ allows integer and string values in
19
+ # an array.
20
+ #
21
+ # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
22
+ # a method is specified by a user with +coerce_with+ option or the user
23
+ # specifies a custom type which implements requirments of
24
+ # +Grape::Types::CustomTypeCoercer+.
8
25
  #
9
- # If a +Virtus::Attribute+ object already built
10
- # with +Virtus::Attribute.build+ is supplied as
11
- # the +type+ it will be returned and +method+
12
- # will be ignored.
26
+ # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
27
+ # previous one, but it expects an array or set of values having a custom
28
+ # type implemented by the user.
13
29
  #
14
- # See {CustomTypeCoercer} for further details
15
- # about coercion and type-checking inference.
30
+ # There is also a group of custom types implemented by Grape, check
31
+ # +Grape::Validations::Types::SPECIAL+ to get the full list.
16
32
  #
17
33
  # @param type [Class] the type to which input strings
18
34
  # should be coerced
19
35
  # @param method [Class,#call] the coercion method to use
20
- # @return [Virtus::Attribute] object to be used
36
+ # @return [Object] object to be used
21
37
  # for coercion and type validation
22
- def self.build_coercer(type, method = nil)
23
- cache_instance(type, method) do
24
- create_coercer_instance(type, method)
38
+ def self.build_coercer(type, method: nil, strict: false)
39
+ cache_instance(type, method, strict) do
40
+ create_coercer_instance(type, method, strict)
25
41
  end
26
42
  end
27
43
 
28
- def self.create_coercer_instance(type, method = nil)
29
- # Accept pre-rolled virtus attributes without interference
30
- return type if type.is_a? Virtus::Attribute
31
-
32
- converter_options = {
33
- nullify_blank: true
34
- }
35
- conversion_type = if method == JSON
36
- Object
37
- # because we want just parsed JSON content:
38
- # if type is Array and data is `"{}"`
39
- # result will be [] because Virtus converts hashes
40
- # to arrays
41
- else
42
- type
43
- end
44
+ def self.create_coercer_instance(type, method, strict)
45
+ # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
46
+ type = Types.map_special(type)
44
47
 
45
48
  # Use a special coercer for multiply-typed parameters.
46
49
  if Types.multiple?(type)
47
- converter_options[:coercer] = Types::MultipleTypeCoercer.new(type, method)
48
- conversion_type = Object
50
+ MultipleTypeCoercer.new(type, method)
49
51
 
50
52
  # Use a special coercer for custom types and coercion methods.
51
53
  elsif method || Types.custom?(type)
52
- converter_options[:coercer] = Types::CustomTypeCoercer.new(type, method)
54
+ CustomTypeCoercer.new(type, method)
53
55
 
54
56
  # Special coercer for collections of types that implement a parse method.
55
57
  # CustomTypeCoercer (above) already handles such types when an explicit coercion
56
58
  # method is supplied.
57
59
  elsif Types.collection_of_custom?(type)
58
- converter_options[:coercer] = Types::CustomTypeCollectionCoercer.new(
59
- type.first, type.is_a?(Set)
60
+ Types::CustomTypeCollectionCoercer.new(
61
+ Types.map_special(type.first), type.is_a?(Set)
60
62
  )
61
-
62
- # Grape swaps in its own Virtus::Attribute implementations
63
- # for certain special types that merit first-class support
64
- # (but not if a custom coercion method has been supplied).
65
- elsif Types.special?(type)
66
- conversion_type = Types::SPECIAL[type]
63
+ else
64
+ DryTypeCoercer.coercer_instance_for(type, strict)
67
65
  end
68
-
69
- # Virtus will infer coercion and validation rules
70
- # for many common ruby types.
71
- Virtus::Attribute.build(conversion_type, converter_options)
72
66
  end
73
67
 
74
- def self.cache_instance(type, method, &_block)
75
- key = cache_key(type, method)
68
+ def self.cache_instance(type, method, strict, &_block)
69
+ key = cache_key(type, method, strict)
76
70
 
77
71
  return @__cache[key] if @__cache.key?(key)
78
72
 
@@ -85,8 +79,12 @@ module Grape
85
79
  instance
86
80
  end
87
81
 
88
- def self.cache_key(type, method)
89
- [type, method].compact.map(&:to_s).join('_')
82
+ def self.cache_key(type, method, strict)
83
+ [type, method, strict].each_with_object(+'_') do |val, memo|
84
+ next if val.nil?
85
+
86
+ memo << '_' << val.to_s
87
+ end
90
88
  end
91
89
 
92
90
  instance_variable_set(:@__cache, {})