grape 1.2.5 → 1.3.1

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 (258) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +7 -6
  4. data/UPGRADING.md +43 -0
  5. data/grape.gemspec +10 -1
  6. data/lib/grape/api/helpers.rb +2 -0
  7. data/lib/grape/api/instance.rb +8 -6
  8. data/lib/grape/api.rb +4 -2
  9. data/lib/grape/config.rb +2 -0
  10. data/lib/grape/content_types.rb +34 -0
  11. data/lib/grape/cookies.rb +2 -0
  12. data/lib/grape/dsl/api.rb +2 -0
  13. data/lib/grape/dsl/callbacks.rb +2 -0
  14. data/lib/grape/dsl/configuration.rb +2 -0
  15. data/lib/grape/dsl/desc.rb +2 -0
  16. data/lib/grape/dsl/headers.rb +2 -0
  17. data/lib/grape/dsl/helpers.rb +4 -2
  18. data/lib/grape/dsl/inside_route.rb +15 -11
  19. data/lib/grape/dsl/logger.rb +2 -0
  20. data/lib/grape/dsl/middleware.rb +2 -0
  21. data/lib/grape/dsl/parameters.rb +8 -6
  22. data/lib/grape/dsl/request_response.rb +4 -2
  23. data/lib/grape/dsl/routing.rb +9 -5
  24. data/lib/grape/dsl/settings.rb +7 -1
  25. data/lib/grape/dsl/validations.rb +2 -0
  26. data/lib/grape/eager_load.rb +2 -0
  27. data/lib/grape/endpoint.rb +13 -7
  28. data/lib/grape/error_formatter/base.rb +2 -0
  29. data/lib/grape/error_formatter/json.rb +2 -0
  30. data/lib/grape/error_formatter/txt.rb +2 -0
  31. data/lib/grape/error_formatter/xml.rb +2 -0
  32. data/lib/grape/error_formatter.rb +3 -1
  33. data/lib/grape/exceptions/base.rb +11 -13
  34. data/lib/grape/exceptions/incompatible_option_values.rb +2 -0
  35. data/lib/grape/exceptions/invalid_accept_header.rb +2 -0
  36. data/lib/grape/exceptions/invalid_formatter.rb +2 -0
  37. data/lib/grape/exceptions/invalid_message_body.rb +2 -0
  38. data/lib/grape/exceptions/invalid_response.rb +2 -0
  39. data/lib/grape/exceptions/invalid_version_header.rb +2 -0
  40. data/lib/grape/exceptions/invalid_versioner_option.rb +2 -0
  41. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +2 -0
  42. data/lib/grape/exceptions/method_not_allowed.rb +2 -0
  43. data/lib/grape/exceptions/missing_group_type.rb +2 -0
  44. data/lib/grape/exceptions/missing_mime_type.rb +2 -0
  45. data/lib/grape/exceptions/missing_option.rb +2 -0
  46. data/lib/grape/exceptions/missing_vendor_option.rb +2 -0
  47. data/lib/grape/exceptions/unknown_options.rb +2 -0
  48. data/lib/grape/exceptions/unknown_parameter.rb +2 -0
  49. data/lib/grape/exceptions/unknown_validator.rb +2 -0
  50. data/lib/grape/exceptions/unsupported_group_type.rb +2 -0
  51. data/lib/grape/exceptions/validation.rb +3 -1
  52. data/lib/grape/exceptions/validation_array_errors.rb +2 -0
  53. data/lib/grape/exceptions/validation_errors.rb +13 -12
  54. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +4 -3
  55. data/lib/grape/extensions/deep_mergeable_hash.rb +2 -0
  56. data/lib/grape/extensions/deep_symbolize_hash.rb +2 -0
  57. data/lib/grape/extensions/hash.rb +2 -0
  58. data/lib/grape/extensions/hashie/mash.rb +2 -0
  59. data/lib/grape/formatter/json.rb +2 -0
  60. data/lib/grape/formatter/serializable_hash.rb +2 -0
  61. data/lib/grape/formatter/txt.rb +2 -0
  62. data/lib/grape/formatter/xml.rb +2 -0
  63. data/lib/grape/formatter.rb +5 -3
  64. data/lib/grape/http/headers.rb +49 -18
  65. data/lib/grape/middleware/auth/base.rb +2 -0
  66. data/lib/grape/middleware/auth/dsl.rb +2 -0
  67. data/lib/grape/middleware/auth/strategies.rb +2 -0
  68. data/lib/grape/middleware/auth/strategy_info.rb +2 -0
  69. data/lib/grape/middleware/base.rb +5 -5
  70. data/lib/grape/middleware/error.rb +3 -1
  71. data/lib/grape/middleware/filter.rb +2 -0
  72. data/lib/grape/middleware/formatter.rb +5 -3
  73. data/lib/grape/middleware/globals.rb +2 -0
  74. data/lib/grape/middleware/helpers.rb +2 -0
  75. data/lib/grape/middleware/stack.rb +4 -1
  76. data/lib/grape/middleware/versioner/accept_version_header.rb +2 -0
  77. data/lib/grape/middleware/versioner/header.rb +5 -3
  78. data/lib/grape/middleware/versioner/param.rb +3 -1
  79. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -0
  80. data/lib/grape/middleware/versioner/path.rb +3 -1
  81. data/lib/grape/middleware/versioner.rb +2 -0
  82. data/lib/grape/namespace.rb +14 -2
  83. data/lib/grape/parser/json.rb +2 -0
  84. data/lib/grape/parser/xml.rb +2 -0
  85. data/lib/grape/parser.rb +3 -1
  86. data/lib/grape/path.rb +13 -1
  87. data/lib/grape/presenters/presenter.rb +2 -0
  88. data/lib/grape/request.rb +15 -8
  89. data/lib/grape/router/attribute_translator.rb +18 -8
  90. data/lib/grape/router/pattern.rb +20 -16
  91. data/lib/grape/router/route.rb +9 -4
  92. data/lib/grape/router.rb +26 -12
  93. data/lib/grape/serve_file/file_body.rb +2 -0
  94. data/lib/grape/serve_file/file_response.rb +2 -0
  95. data/lib/grape/serve_file/sendfile_response.rb +2 -0
  96. data/lib/grape/util/base_inheritable.rb +6 -0
  97. data/lib/grape/util/cache.rb +20 -0
  98. data/lib/grape/util/endpoint_configuration.rb +2 -0
  99. data/lib/grape/util/env.rb +19 -17
  100. data/lib/grape/util/inheritable_setting.rb +2 -0
  101. data/lib/grape/util/inheritable_values.rb +2 -0
  102. data/lib/grape/util/json.rb +2 -0
  103. data/lib/grape/util/lazy_block.rb +2 -0
  104. data/lib/grape/util/lazy_object.rb +43 -0
  105. data/lib/grape/util/lazy_value.rb +2 -0
  106. data/lib/grape/util/registrable.rb +2 -0
  107. data/lib/grape/util/reverse_stackable_values.rb +3 -1
  108. data/lib/grape/util/stackable_values.rb +9 -21
  109. data/lib/grape/util/strict_hash_configuration.rb +2 -0
  110. data/lib/grape/util/xml.rb +2 -0
  111. data/lib/grape/validations/attributes_iterator.rb +3 -3
  112. data/lib/grape/validations/multiple_attributes_iterator.rb +2 -0
  113. data/lib/grape/validations/params_scope.rb +24 -11
  114. data/lib/grape/validations/single_attribute_iterator.rb +13 -2
  115. data/lib/grape/validations/types/array_coercer.rb +56 -0
  116. data/lib/grape/validations/types/build_coercer.rb +49 -48
  117. data/lib/grape/validations/types/custom_type_coercer.rb +15 -49
  118. data/lib/grape/validations/types/custom_type_collection_coercer.rb +10 -25
  119. data/lib/grape/validations/types/dry_type_coercer.rb +41 -0
  120. data/lib/grape/validations/types/file.rb +11 -9
  121. data/lib/grape/validations/types/json.rb +11 -8
  122. data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
  123. data/lib/grape/validations/types/primitive_coercer.rb +61 -0
  124. data/lib/grape/validations/types/set_coercer.rb +38 -0
  125. data/lib/grape/validations/types/variant_collection_coercer.rb +4 -12
  126. data/lib/grape/validations/types.rb +7 -30
  127. data/lib/grape/validations/validator_factory.rb +2 -0
  128. data/lib/grape/validations/validators/all_or_none.rb +3 -1
  129. data/lib/grape/validations/validators/allow_blank.rb +3 -1
  130. data/lib/grape/validations/validators/as.rb +2 -0
  131. data/lib/grape/validations/validators/at_least_one_of.rb +3 -1
  132. data/lib/grape/validations/validators/base.rb +8 -5
  133. data/lib/grape/validations/validators/coerce.rb +44 -27
  134. data/lib/grape/validations/validators/default.rb +2 -0
  135. data/lib/grape/validations/validators/exactly_one_of.rb +6 -2
  136. data/lib/grape/validations/validators/except_values.rb +3 -1
  137. data/lib/grape/validations/validators/multiple_params_base.rb +2 -0
  138. data/lib/grape/validations/validators/mutual_exclusion.rb +3 -1
  139. data/lib/grape/validations/validators/presence.rb +3 -1
  140. data/lib/grape/validations/validators/regexp.rb +3 -1
  141. data/lib/grape/validations/validators/same_as.rb +6 -3
  142. data/lib/grape/validations/validators/values.rb +17 -5
  143. data/lib/grape/validations.rb +2 -0
  144. data/lib/grape/version.rb +3 -1
  145. data/lib/grape.rb +4 -5
  146. data/spec/grape/api/custom_validations_spec.rb +5 -3
  147. data/spec/grape/api/deeply_included_options_spec.rb +2 -0
  148. data/spec/grape/api/defines_boolean_in_params_spec.rb +5 -3
  149. data/spec/grape/api/inherited_helpers_spec.rb +2 -0
  150. data/spec/grape/api/instance_spec.rb +54 -0
  151. data/spec/grape/api/invalid_format_spec.rb +2 -0
  152. data/spec/grape/api/namespace_parameters_in_route_spec.rb +2 -0
  153. data/spec/grape/api/nested_helpers_spec.rb +2 -0
  154. data/spec/grape/api/optional_parameters_in_route_spec.rb +2 -0
  155. data/spec/grape/api/parameters_modification_spec.rb +3 -1
  156. data/spec/grape/api/patch_method_helpers_spec.rb +2 -0
  157. data/spec/grape/api/recognize_path_spec.rb +2 -0
  158. data/spec/grape/api/required_parameters_in_route_spec.rb +2 -0
  159. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +2 -0
  160. data/spec/grape/api/routes_with_requirements_spec.rb +2 -0
  161. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +2 -0
  162. data/spec/grape/api/shared_helpers_spec.rb +2 -0
  163. data/spec/grape/api_remount_spec.rb +2 -0
  164. data/spec/grape/api_spec.rb +34 -11
  165. data/spec/grape/config_spec.rb +2 -0
  166. data/spec/grape/dsl/callbacks_spec.rb +2 -0
  167. data/spec/grape/dsl/configuration_spec.rb +2 -0
  168. data/spec/grape/dsl/desc_spec.rb +2 -0
  169. data/spec/grape/dsl/headers_spec.rb +2 -0
  170. data/spec/grape/dsl/helpers_spec.rb +4 -2
  171. data/spec/grape/dsl/inside_route_spec.rb +2 -0
  172. data/spec/grape/dsl/logger_spec.rb +2 -0
  173. data/spec/grape/dsl/middleware_spec.rb +2 -0
  174. data/spec/grape/dsl/parameters_spec.rb +2 -0
  175. data/spec/grape/dsl/request_response_spec.rb +2 -0
  176. data/spec/grape/dsl/routing_spec.rb +2 -0
  177. data/spec/grape/dsl/settings_spec.rb +2 -0
  178. data/spec/grape/dsl/validations_spec.rb +2 -0
  179. data/spec/grape/endpoint_spec.rb +3 -1
  180. data/spec/grape/entity_spec.rb +2 -0
  181. data/spec/grape/exceptions/base_spec.rb +3 -1
  182. data/spec/grape/exceptions/body_parse_errors_spec.rb +2 -0
  183. data/spec/grape/exceptions/invalid_accept_header_spec.rb +2 -0
  184. data/spec/grape/exceptions/invalid_formatter_spec.rb +2 -0
  185. data/spec/grape/exceptions/invalid_response_spec.rb +2 -0
  186. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +2 -0
  187. data/spec/grape/exceptions/missing_mime_type_spec.rb +2 -0
  188. data/spec/grape/exceptions/missing_option_spec.rb +2 -0
  189. data/spec/grape/exceptions/unknown_options_spec.rb +2 -0
  190. data/spec/grape/exceptions/unknown_validator_spec.rb +2 -0
  191. data/spec/grape/exceptions/validation_errors_spec.rb +4 -2
  192. data/spec/grape/exceptions/validation_spec.rb +3 -1
  193. data/spec/grape/extensions/param_builders/hash_spec.rb +2 -0
  194. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +2 -0
  195. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +2 -0
  196. data/spec/grape/integration/global_namespace_function_spec.rb +2 -0
  197. data/spec/grape/integration/rack_sendfile_spec.rb +2 -0
  198. data/spec/grape/integration/rack_spec.rb +3 -1
  199. data/spec/grape/loading_spec.rb +2 -0
  200. data/spec/grape/middleware/auth/base_spec.rb +2 -0
  201. data/spec/grape/middleware/auth/dsl_spec.rb +2 -0
  202. data/spec/grape/middleware/auth/strategies_spec.rb +2 -0
  203. data/spec/grape/middleware/base_spec.rb +2 -0
  204. data/spec/grape/middleware/error_spec.rb +2 -0
  205. data/spec/grape/middleware/exception_spec.rb +3 -1
  206. data/spec/grape/middleware/formatter_spec.rb +19 -12
  207. data/spec/grape/middleware/globals_spec.rb +2 -0
  208. data/spec/grape/middleware/stack_spec.rb +11 -0
  209. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -1
  210. data/spec/grape/middleware/versioner/header_spec.rb +3 -1
  211. data/spec/grape/middleware/versioner/param_spec.rb +3 -1
  212. data/spec/grape/middleware/versioner/path_spec.rb +3 -1
  213. data/spec/grape/middleware/versioner_spec.rb +2 -0
  214. data/spec/grape/named_api_spec.rb +2 -0
  215. data/spec/grape/parser_spec.rb +7 -5
  216. data/spec/grape/path_spec.rb +2 -0
  217. data/spec/grape/presenters/presenter_spec.rb +2 -0
  218. data/spec/grape/request_spec.rb +2 -0
  219. data/spec/grape/util/inheritable_setting_spec.rb +2 -0
  220. data/spec/grape/util/inheritable_values_spec.rb +2 -0
  221. data/spec/grape/util/reverse_stackable_values_spec.rb +2 -0
  222. data/spec/grape/util/stackable_values_spec.rb +3 -1
  223. data/spec/grape/util/strict_hash_configuration_spec.rb +2 -0
  224. data/spec/grape/validations/attributes_iterator_spec.rb +2 -0
  225. data/spec/grape/validations/instance_behaivour_spec.rb +5 -3
  226. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +2 -0
  227. data/spec/grape/validations/params_scope_spec.rb +3 -1
  228. data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -4
  229. data/spec/grape/validations/types/primitive_coercer_spec.rb +75 -0
  230. data/spec/grape/validations/types_spec.rb +8 -35
  231. data/spec/grape/validations/validators/all_or_none_spec.rb +2 -0
  232. data/spec/grape/validations/validators/allow_blank_spec.rb +2 -0
  233. data/spec/grape/validations/validators/at_least_one_of_spec.rb +2 -0
  234. data/spec/grape/validations/validators/coerce_spec.rb +51 -110
  235. data/spec/grape/validations/validators/default_spec.rb +2 -0
  236. data/spec/grape/validations/validators/exactly_one_of_spec.rb +14 -12
  237. data/spec/grape/validations/validators/except_values_spec.rb +3 -1
  238. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +2 -0
  239. data/spec/grape/validations/validators/presence_spec.rb +30 -0
  240. data/spec/grape/validations/validators/regexp_spec.rb +2 -0
  241. data/spec/grape/validations/validators/same_as_spec.rb +2 -0
  242. data/spec/grape/validations/validators/values_spec.rb +29 -4
  243. data/spec/grape/validations_spec.rb +69 -15
  244. data/spec/integration/multi_json/json_spec.rb +2 -0
  245. data/spec/integration/multi_xml/xml_spec.rb +2 -0
  246. data/spec/shared/versioning_examples.rb +2 -0
  247. data/spec/spec_helper.rb +18 -0
  248. data/spec/support/basic_auth_encode_helpers.rb +2 -0
  249. data/spec/support/content_type_helpers.rb +2 -0
  250. data/spec/support/eager_load.rb +19 -0
  251. data/spec/support/endpoint_faker.rb +2 -0
  252. data/spec/support/file_streamer.rb +2 -0
  253. data/spec/support/integer_helpers.rb +2 -0
  254. data/spec/support/versioned_helpers.rb +4 -2
  255. metadata +126 -112
  256. data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +0 -18
  257. data/lib/grape/util/content_types.rb +0 -26
  258. 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
  class LazyValue
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Util
3
5
  module Registrable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'stackable_values'
2
4
 
3
5
  module Grape
@@ -7,7 +9,7 @@ module Grape
7
9
 
8
10
  def concat_values(inherited_value, new_value)
9
11
  [].tap do |value|
10
- value.concat(new_value)
12
+ value.concat(new_value) if new_value
11
13
  value.concat(inherited_value)
12
14
  end
13
15
  end
@@ -1,31 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'base_inheritable'
2
4
 
3
5
  module Grape
4
6
  module Util
5
7
  class StackableValues < BaseInheritable
6
- attr_reader :frozen_values
7
-
8
- def initialize(*_args)
9
- super
10
-
11
- @frozen_values = {}
12
- end
13
-
8
+ # Even if there is no value, an empty array will be returned.
14
9
  def [](name)
15
- return @frozen_values[name] if @frozen_values.key? name
16
-
17
- inherited_value = @inherited_values[name]
18
- new_value = @new_values[name] || []
10
+ inherited_value = inherited_values[name]
11
+ new_value = new_values[name]
19
12
 
20
- return new_value unless inherited_value
13
+ return new_value || [] unless inherited_value
21
14
 
22
15
  concat_values(inherited_value, new_value)
23
16
  end
24
17
 
25
18
  def []=(name, value)
26
- raise if @frozen_values.key? name
27
- @new_values[name] ||= []
28
- @new_values[name].push value
19
+ new_values[name] ||= []
20
+ new_values[name].push value
29
21
  end
30
22
 
31
23
  def to_hash
@@ -34,16 +26,12 @@ module Grape
34
26
  end
35
27
  end
36
28
 
37
- def freeze_value(key)
38
- @frozen_values[key] = self[key].freeze
39
- end
40
-
41
29
  protected
42
30
 
43
31
  def concat_values(inherited_value, new_value)
44
32
  [].tap do |value|
45
33
  value.concat(inherited_value)
46
- value.concat(new_value)
34
+ value.concat(new_value) if new_value
47
35
  end
48
36
  end
49
37
  end
@@ -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
  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) || [])
@@ -240,7 +244,7 @@ module Grape
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
@@ -0,0 +1,56 @@
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
+ # anything else.
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
+ def initialize(type, strict = false)
18
+ super
19
+
20
+ @coercer = scope::Array
21
+ @elem_coercer = PrimitiveCoercer.new(type.first, strict)
22
+ end
23
+
24
+ def call(_val)
25
+ collection = super
26
+
27
+ return collection if collection.is_a?(InvalidValue)
28
+
29
+ coerce_elements collection
30
+ end
31
+
32
+ protected
33
+
34
+ def coerce_elements(collection)
35
+ collection.each_with_index do |elem, index|
36
+ return InvalidValue.new if reject?(elem)
37
+
38
+ coerced_elem = @elem_coercer.call(elem)
39
+
40
+ return coerced_elem if coerced_elem.is_a?(InvalidValue)
41
+
42
+ collection[index] = coerced_elem
43
+ end
44
+
45
+ collection
46
+ end
47
+
48
+ # This method maintaine logic which was defined by Virtus for arrays.
49
+ # Virtus doesn't allow nil in arrays.
50
+ def reject?(val)
51
+ val.nil?
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,78 +1,75 @@
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.
8
15
  #
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.
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.
13
20
  #
14
- # See {CustomTypeCoercer} for further details
15
- # about coercion and type-checking inference.
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+.
25
+ #
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.
29
+ #
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
-
44
+ def self.create_coercer_instance(type, method, strict)
45
45
  # Use a special coercer for multiply-typed parameters.
46
46
  if Types.multiple?(type)
47
- converter_options[:coercer] = Types::MultipleTypeCoercer.new(type, method)
48
- conversion_type = Object
47
+ MultipleTypeCoercer.new(type, method)
49
48
 
50
49
  # Use a special coercer for custom types and coercion methods.
51
50
  elsif method || Types.custom?(type)
52
- converter_options[:coercer] = Types::CustomTypeCoercer.new(type, method)
51
+ CustomTypeCoercer.new(type, method)
53
52
 
54
53
  # Special coercer for collections of types that implement a parse method.
55
54
  # CustomTypeCoercer (above) already handles such types when an explicit coercion
56
55
  # method is supplied.
57
56
  elsif Types.collection_of_custom?(type)
58
- converter_options[:coercer] = Types::CustomTypeCollectionCoercer.new(
57
+ Types::CustomTypeCollectionCoercer.new(
59
58
  type.first, type.is_a?(Set)
60
59
  )
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
60
  elsif Types.special?(type)
66
- conversion_type = Types::SPECIAL[type]
61
+ Types::SPECIAL[type].new
62
+ elsif type.is_a?(Array)
63
+ ArrayCoercer.new type, strict
64
+ elsif type.is_a?(Set)
65
+ SetCoercer.new type, strict
66
+ else
67
+ PrimitiveCoercer.new type, strict
67
68
  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
69
  end
73
70
 
74
- def self.cache_instance(type, method, &_block)
75
- key = cache_key(type, method)
71
+ def self.cache_instance(type, method, strict, &_block)
72
+ key = cache_key(type, method, strict)
76
73
 
77
74
  return @__cache[key] if @__cache.key?(key)
78
75
 
@@ -85,8 +82,12 @@ module Grape
85
82
  instance
86
83
  end
87
84
 
88
- def self.cache_key(type, method)
89
- [type, method].compact.map(&:to_s).join('_')
85
+ def self.cache_key(type, method, strict)
86
+ [type, method, strict].each_with_object(+'_') do |val, memo|
87
+ next if val.nil?
88
+
89
+ memo << '_' << val.to_s
90
+ end
90
91
  end
91
92
 
92
93
  instance_variable_set(:@__cache, {})
@@ -1,21 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Validations
3
5
  module Types
4
- # Instances of this class may be passed to
5
- # +Virtus::Attribute.build+ as the +:coercer+
6
- # option for custom types that do not otherwise
7
- # satisfy the requirements for +Virtus::Attribute::coerce+
8
- # and +Virtus::Attribute::value_coerced?+ to work
9
- # as expected.
10
- #
11
- # Subclasses of +Virtus::Attribute+ or +Axiom::Types::Type+
12
- # (or for which an axiom type can be inferred, i.e. the
13
- # primitives, +Date+, +Time+, etc.) do not need any such
14
- # coercer to be passed with them.
15
- #
16
- # Coercion
17
- # --------
18
- #
19
6
  # This class will detect type classes that implement
20
7
  # a class-level +parse+ method. The method should accept one
21
8
  # +String+ argument and should return the value coerced to
@@ -30,14 +17,14 @@ module Grape
30
17
  # Type Checking
31
18
  # -------------
32
19
  #
33
- # Calls to +value_coerced?+ will consult this class to check
20
+ # Calls to +coerced?+ will consult this class to check
34
21
  # that the coerced value produced above is in fact of the
35
22
  # expected type. By default this class performs a basic check
36
23
  # against the type supplied, but this behaviour will be
37
24
  # overridden if the class implements a class-level
38
25
  # +coerced?+ or +parsed?+ method. This method
39
26
  # will receive a single parameter that is the coerced value
40
- # and should return +true+ iff the value meets type expectations.
27
+ # and should return +true+ if the value meets type expectations.
41
28
  # Arbitrary assertions may be made here but the grape validation
42
29
  # system should be preferred.
43
30
  #
@@ -46,15 +33,6 @@ module Grape
46
33
  # contract as +coerced?+, and must be supplied with a coercion
47
34
  # +method+.
48
35
  class CustomTypeCoercer
49
- # Uses +Virtus::Attribute.build+ to build a new
50
- # attribute that makes use of this class for
51
- # coercion and type validation logic.
52
- #
53
- # @return [Virtus::Attribute]
54
- def self.build(type, method = nil)
55
- Virtus::Attribute.build(type, coercer: new(type, method))
56
- end
57
-
58
36
  # A new coercer for the given type specification
59
37
  # and coercion method.
60
38
  #
@@ -64,37 +42,25 @@ module Grape
64
42
  # optional coercion method. See class docs.
65
43
  def initialize(type, method = nil)
66
44
  coercion_method = infer_coercion_method type, method
67
-
68
45
  @method = enforce_symbolized_keys type, coercion_method
69
-
70
46
  @type_check = infer_type_check(type)
71
47
  end
72
48
 
73
- # This method is called from somewhere within
74
- # +Virtus::Attribute::coerce+ in order to coerce
75
- # the given value.
49
+ # Coerces the given value.
76
50
  #
77
51
  # @param value [String] value to be coerced, in grape
78
52
  # this should always be a string.
79
53
  # @return [Object] the coerced result
80
- def call(value)
81
- @method.call value
54
+ def call(val)
55
+ return if val.nil?
56
+
57
+ coerced_val = @method.call(val)
58
+ return InvalidValue.new unless coerced?(coerced_val)
59
+ coerced_val
82
60
  end
83
61
 
84
- # This method is called from somewhere within
85
- # +Virtus::Attribute::value_coerced?+ in order to
86
- # assert that the value has been coerced successfully.
87
- #
88
- # @param _primitive [Axiom::Types::Type] primitive type
89
- # for the coercion as detected by axiom-types' inference
90
- # system. For custom types this is typically not much use
91
- # (i.e. it is +Axiom::Types::Object+) unless special
92
- # inference rules have been declared for the type.
93
- # @param value [Object] a coerced result returned from {#call}
94
- # @return [true,false] whether or not the coerced value
95
- # satisfies type requirements.
96
- def success?(_primitive, value)
97
- @type_check.call value
62
+ def coerced?(val)
63
+ @type_check.call val
98
64
  end
99
65
 
100
66
  private
@@ -160,8 +126,8 @@ module Grape
160
126
  # Collections have all values processed individually
161
127
  if [Array, Set].include?(type)
162
128
  lambda do |val|
163
- method.call(val).tap do |new_value|
164
- new_value.map do |item|
129
+ method.call(val).tap do |new_val|
130
+ new_val.map do |item|
165
131
  item.is_a?(Hash) ? symbolize_keys(item) : item
166
132
  end
167
133
  end
@@ -1,12 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Validations
3
5
  module Types
4
- # Instances of this class may be passed to
5
- # +Virtus::Attribute.build+ as the +:coercer+
6
- # option, to handle collections of types that
7
- # provide their own parsing (and optionally,
8
- # type-checking) functionality.
9
- #
10
6
  # See {CustomTypeCoercer} for details on types
11
7
  # that will be supported by this by this coercer.
12
8
  # This coercer works in the same way as +CustomTypeCoercer+
@@ -38,32 +34,21 @@ module Grape
38
34
  @set = set
39
35
  end
40
36
 
41
- # This method is called from somewhere within
42
- # +Virtus::Attribute::coerce+ in order to coerce
43
- # the given value.
37
+ # Coerces the given value.
44
38
  #
45
39
  # @param value [Array<String>] an array of values to be coerced
46
40
  # @return [Array,Set] the coerced result. May be an +Array+ or a
47
41
  # +Set+ depending on the setting given to the constructor
48
42
  def call(value)
49
- coerced = value.map { |item| super(item) }
43
+ coerced = value.map do |item|
44
+ coerced_item = super(item)
50
45
 
51
- @set ? Set.new(coerced) : coerced
52
- end
46
+ return coerced_item if coerced_item.is_a?(InvalidValue)
53
47
 
54
- # This method is called from somewhere within
55
- # +Virtus::Attribute::value_coerced?+ in order to assert
56
- # that the all of the values in the array have been coerced
57
- # successfully.
58
- #
59
- # @param primitive [Axiom::Types::Type] primitive type for
60
- # the coercion as deteced by axiom-types' inference system.
61
- # @param value [Enumerable] a coerced result returned from {#call}
62
- # @return [true,false] whether or not all of the coerced values in
63
- # the collection satisfy type requirements.
64
- def success?(primitive, value)
65
- value.is_a?(@set ? Set : Array) &&
66
- value.all? { |item| super(primitive, item) }
48
+ coerced_item
49
+ end
50
+
51
+ @set ? Set.new(coerced) : coerced
67
52
  end
68
53
  end
69
54
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-types'
4
+
5
+ module DryTypes
6
+ # Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
7
+ # a container in this case. Check documentation for more information
8
+ # https://dry-rb.org/gems/dry-types/1.2/getting-started/
9
+ include Dry.Types()
10
+ end
11
+
12
+ module Grape
13
+ module Validations
14
+ module Types
15
+ # A base class for classes which must identify a coercer to be used.
16
+ # If the +strict+ argument is true, it won't coerce the given value
17
+ # but check its type. More information there
18
+ # https://dry-rb.org/gems/dry-types/1.2/built-in-types/
19
+ class DryTypeCoercer
20
+ def initialize(type, strict = false)
21
+ @type = type
22
+ @scope = strict ? DryTypes::Strict : DryTypes::Params
23
+ end
24
+
25
+ # Coerces the given value to a type which was specified during
26
+ # initialization as a type argument.
27
+ #
28
+ # @param val [Object]
29
+ def call(val)
30
+ @coercer[val]
31
+ rescue Dry::Types::CoercionError => _e
32
+ InvalidValue.new
33
+ end
34
+
35
+ protected
36
+
37
+ attr_reader :scope, :type
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,21 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Validations
3
5
  module Types
4
- # +Virtus::Attribute+ implementation for parameters
5
- # that are multipart file objects. Actual handling
6
- # of these objects is provided by +Rack::Request+;
7
- # this class is here only to assert that rack's
8
- # handling has succeeded, and to prevent virtus
9
- # from interfering.
10
- class File < Virtus::Attribute
11
- def coerce(input)
6
+ # Implementation for parameters that are multipart file objects.
7
+ # Actual handling of these objects is provided by +Rack::Request+;
8
+ # this class is here only to assert that rack's handling has succeeded.
9
+ class File
10
+ def call(input)
11
+ return if input.nil?
12
+ return InvalidValue.new unless coerced?(input)
13
+
12
14
  # Processing of multipart file objects
13
15
  # is already taken care of by Rack::Request.
14
16
  # Nothing to do here.
15
17
  input
16
18
  end
17
19
 
18
- def value_coerced?(value)
20
+ def coerced?(value)
19
21
  # Rack::Request creates a Hash with filename,
20
22
  # content type and an IO object. Do a bit of basic
21
23
  # duck-typing.