grape 1.1.0 → 1.5.3

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 (286) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +278 -44
  3. data/LICENSE +1 -1
  4. data/README.md +514 -69
  5. data/UPGRADING.md +424 -17
  6. data/grape.gemspec +13 -2
  7. data/lib/grape.rb +104 -71
  8. data/lib/grape/api.rb +138 -175
  9. data/lib/grape/api/helpers.rb +2 -0
  10. data/lib/grape/api/instance.rb +283 -0
  11. data/lib/grape/config.rb +34 -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 +22 -0
  16. data/lib/grape/dsl/configuration.rb +2 -0
  17. data/lib/grape/dsl/desc.rb +41 -7
  18. data/lib/grape/dsl/headers.rb +2 -0
  19. data/lib/grape/dsl/helpers.rb +5 -2
  20. data/lib/grape/dsl/inside_route.rb +92 -49
  21. data/lib/grape/dsl/logger.rb +2 -0
  22. data/lib/grape/dsl/middleware.rb +9 -0
  23. data/lib/grape/dsl/parameters.rb +25 -14
  24. data/lib/grape/dsl/request_response.rb +4 -2
  25. data/lib/grape/dsl/routing.rb +17 -10
  26. data/lib/grape/dsl/settings.rb +7 -1
  27. data/lib/grape/dsl/validations.rb +24 -4
  28. data/lib/grape/eager_load.rb +20 -0
  29. data/lib/grape/endpoint.rb +59 -35
  30. data/lib/grape/error_formatter.rb +4 -2
  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 +20 -14
  36. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  37. data/lib/grape/exceptions/incompatible_option_values.rb +2 -0
  38. data/lib/grape/exceptions/invalid_accept_header.rb +2 -0
  39. data/lib/grape/exceptions/invalid_formatter.rb +2 -0
  40. data/lib/grape/exceptions/invalid_message_body.rb +2 -0
  41. data/lib/grape/exceptions/invalid_response.rb +11 -0
  42. data/lib/grape/exceptions/invalid_version_header.rb +2 -0
  43. data/lib/grape/exceptions/invalid_versioner_option.rb +2 -0
  44. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +2 -0
  45. data/lib/grape/exceptions/method_not_allowed.rb +2 -0
  46. data/lib/grape/exceptions/missing_group_type.rb +2 -0
  47. data/lib/grape/exceptions/missing_mime_type.rb +2 -0
  48. data/lib/grape/exceptions/missing_option.rb +2 -0
  49. data/lib/grape/exceptions/missing_vendor_option.rb +2 -0
  50. data/lib/grape/exceptions/unknown_options.rb +2 -0
  51. data/lib/grape/exceptions/unknown_parameter.rb +2 -0
  52. data/lib/grape/exceptions/unknown_validator.rb +2 -0
  53. data/lib/grape/exceptions/unsupported_group_type.rb +2 -0
  54. data/lib/grape/exceptions/validation.rb +4 -2
  55. data/lib/grape/exceptions/validation_array_errors.rb +2 -0
  56. data/lib/grape/exceptions/validation_errors.rb +16 -13
  57. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +4 -3
  58. data/lib/grape/extensions/deep_mergeable_hash.rb +2 -0
  59. data/lib/grape/extensions/deep_symbolize_hash.rb +2 -0
  60. data/lib/grape/extensions/hash.rb +2 -0
  61. data/lib/grape/extensions/hashie/mash.rb +2 -0
  62. data/lib/grape/formatter.rb +5 -3
  63. data/lib/grape/formatter/json.rb +2 -0
  64. data/lib/grape/formatter/serializable_hash.rb +2 -0
  65. data/lib/grape/formatter/txt.rb +2 -0
  66. data/lib/grape/formatter/xml.rb +2 -0
  67. data/lib/grape/http/headers.rb +50 -18
  68. data/lib/grape/locale/en.yml +3 -1
  69. data/lib/grape/middleware/auth/base.rb +7 -7
  70. data/lib/grape/middleware/auth/dsl.rb +2 -0
  71. data/lib/grape/middleware/auth/strategies.rb +2 -0
  72. data/lib/grape/middleware/auth/strategy_info.rb +2 -0
  73. data/lib/grape/middleware/base.rb +10 -7
  74. data/lib/grape/middleware/error.rb +21 -16
  75. data/lib/grape/middleware/filter.rb +2 -0
  76. data/lib/grape/middleware/formatter.rb +8 -6
  77. data/lib/grape/middleware/globals.rb +2 -0
  78. data/lib/grape/middleware/helpers.rb +12 -0
  79. data/lib/grape/middleware/stack.rb +13 -3
  80. data/lib/grape/middleware/versioner.rb +2 -0
  81. data/lib/grape/middleware/versioner/accept_version_header.rb +2 -0
  82. data/lib/grape/middleware/versioner/header.rb +10 -8
  83. data/lib/grape/middleware/versioner/param.rb +3 -1
  84. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +4 -1
  85. data/lib/grape/middleware/versioner/path.rb +3 -1
  86. data/lib/grape/namespace.rb +14 -2
  87. data/lib/grape/parser.rb +4 -2
  88. data/lib/grape/parser/json.rb +3 -1
  89. data/lib/grape/parser/xml.rb +3 -1
  90. data/lib/grape/path.rb +15 -3
  91. data/lib/grape/presenters/presenter.rb +2 -0
  92. data/lib/grape/request.rb +19 -10
  93. data/lib/grape/router.rb +30 -29
  94. data/lib/grape/router/attribute_translator.rb +41 -8
  95. data/lib/grape/router/pattern.rb +20 -16
  96. data/lib/grape/router/route.rb +14 -28
  97. data/lib/grape/{serve_file → serve_stream}/file_body.rb +3 -1
  98. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +3 -1
  99. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +10 -8
  100. data/lib/grape/util/base_inheritable.rb +43 -0
  101. data/lib/grape/util/cache.rb +20 -0
  102. data/lib/grape/util/endpoint_configuration.rb +8 -0
  103. data/lib/grape/util/env.rb +19 -17
  104. data/lib/grape/util/inheritable_setting.rb +2 -0
  105. data/lib/grape/util/inheritable_values.rb +7 -25
  106. data/lib/grape/util/json.rb +2 -0
  107. data/lib/grape/util/lazy_block.rb +27 -0
  108. data/lib/grape/util/lazy_object.rb +43 -0
  109. data/lib/grape/util/lazy_value.rb +98 -0
  110. data/lib/grape/util/registrable.rb +2 -0
  111. data/lib/grape/util/reverse_stackable_values.rb +10 -35
  112. data/lib/grape/util/stackable_values.rb +21 -34
  113. data/lib/grape/util/strict_hash_configuration.rb +2 -0
  114. data/lib/grape/util/xml.rb +2 -0
  115. data/lib/grape/validations.rb +2 -0
  116. data/lib/grape/validations/attributes_iterator.rb +16 -6
  117. data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
  118. data/lib/grape/validations/params_scope.rb +51 -30
  119. data/lib/grape/validations/single_attribute_iterator.rb +24 -0
  120. data/lib/grape/validations/types.rb +13 -38
  121. data/lib/grape/validations/types/array_coercer.rb +65 -0
  122. data/lib/grape/validations/types/build_coercer.rb +47 -49
  123. data/lib/grape/validations/types/custom_type_coercer.rb +29 -51
  124. data/lib/grape/validations/types/custom_type_collection_coercer.rb +10 -25
  125. data/lib/grape/validations/types/dry_type_coercer.rb +76 -0
  126. data/lib/grape/validations/types/file.rb +22 -18
  127. data/lib/grape/validations/types/invalid_value.rb +24 -0
  128. data/lib/grape/validations/types/json.rb +46 -39
  129. data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
  130. data/lib/grape/validations/types/primitive_coercer.rb +67 -0
  131. data/lib/grape/validations/types/set_coercer.rb +40 -0
  132. data/lib/grape/validations/types/variant_collection_coercer.rb +5 -13
  133. data/lib/grape/validations/validator_factory.rb +8 -11
  134. data/lib/grape/validations/validators/all_or_none.rb +8 -13
  135. data/lib/grape/validations/validators/allow_blank.rb +3 -1
  136. data/lib/grape/validations/validators/as.rb +5 -4
  137. data/lib/grape/validations/validators/at_least_one_of.rb +7 -13
  138. data/lib/grape/validations/validators/base.rb +20 -16
  139. data/lib/grape/validations/validators/coerce.rb +46 -29
  140. data/lib/grape/validations/validators/default.rb +6 -6
  141. data/lib/grape/validations/validators/exactly_one_of.rb +10 -23
  142. data/lib/grape/validations/validators/except_values.rb +4 -2
  143. data/lib/grape/validations/validators/multiple_params_base.rb +17 -10
  144. data/lib/grape/validations/validators/mutual_exclusion.rb +8 -18
  145. data/lib/grape/validations/validators/presence.rb +3 -1
  146. data/lib/grape/validations/validators/regexp.rb +4 -2
  147. data/lib/grape/validations/validators/same_as.rb +26 -0
  148. data/lib/grape/validations/validators/values.rb +18 -6
  149. data/lib/grape/version.rb +3 -1
  150. data/spec/grape/api/custom_validations_spec.rb +5 -3
  151. data/spec/grape/api/deeply_included_options_spec.rb +2 -0
  152. data/spec/grape/api/defines_boolean_in_params_spec.rb +39 -0
  153. data/spec/grape/api/inherited_helpers_spec.rb +2 -0
  154. data/spec/grape/api/instance_spec.rb +104 -0
  155. data/spec/grape/api/invalid_format_spec.rb +2 -0
  156. data/spec/grape/api/namespace_parameters_in_route_spec.rb +2 -0
  157. data/spec/grape/api/nested_helpers_spec.rb +2 -0
  158. data/spec/grape/api/optional_parameters_in_route_spec.rb +2 -0
  159. data/spec/grape/api/parameters_modification_spec.rb +3 -1
  160. data/spec/grape/api/patch_method_helpers_spec.rb +2 -0
  161. data/spec/grape/api/recognize_path_spec.rb +2 -0
  162. data/spec/grape/api/required_parameters_in_route_spec.rb +2 -0
  163. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +2 -0
  164. data/spec/grape/api/routes_with_requirements_spec.rb +61 -0
  165. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +2 -0
  166. data/spec/grape/api/shared_helpers_spec.rb +2 -0
  167. data/spec/grape/api_remount_spec.rb +473 -0
  168. data/spec/grape/api_spec.rb +565 -12
  169. data/spec/grape/config_spec.rb +19 -0
  170. data/spec/grape/dsl/callbacks_spec.rb +2 -0
  171. data/spec/grape/dsl/configuration_spec.rb +2 -0
  172. data/spec/grape/dsl/desc_spec.rb +42 -16
  173. data/spec/grape/dsl/headers_spec.rb +2 -0
  174. data/spec/grape/dsl/helpers_spec.rb +4 -2
  175. data/spec/grape/dsl/inside_route_spec.rb +184 -33
  176. data/spec/grape/dsl/logger_spec.rb +2 -0
  177. data/spec/grape/dsl/middleware_spec.rb +10 -0
  178. data/spec/grape/dsl/parameters_spec.rb +2 -0
  179. data/spec/grape/dsl/request_response_spec.rb +2 -0
  180. data/spec/grape/dsl/routing_spec.rb +12 -0
  181. data/spec/grape/dsl/settings_spec.rb +2 -0
  182. data/spec/grape/dsl/validations_spec.rb +2 -0
  183. data/spec/grape/endpoint/declared_spec.rb +601 -0
  184. data/spec/grape/endpoint_spec.rb +53 -523
  185. data/spec/grape/entity_spec.rb +9 -1
  186. data/spec/grape/exceptions/base_spec.rb +67 -0
  187. data/spec/grape/exceptions/body_parse_errors_spec.rb +2 -0
  188. data/spec/grape/exceptions/invalid_accept_header_spec.rb +2 -0
  189. data/spec/grape/exceptions/invalid_formatter_spec.rb +2 -0
  190. data/spec/grape/exceptions/invalid_response_spec.rb +13 -0
  191. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +2 -0
  192. data/spec/grape/exceptions/missing_mime_type_spec.rb +2 -0
  193. data/spec/grape/exceptions/missing_option_spec.rb +2 -0
  194. data/spec/grape/exceptions/unknown_options_spec.rb +2 -0
  195. data/spec/grape/exceptions/unknown_validator_spec.rb +2 -0
  196. data/spec/grape/exceptions/validation_errors_spec.rb +8 -4
  197. data/spec/grape/exceptions/validation_spec.rb +3 -1
  198. data/spec/grape/extensions/param_builders/hash_spec.rb +2 -0
  199. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +2 -0
  200. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +2 -0
  201. data/spec/grape/integration/global_namespace_function_spec.rb +2 -0
  202. data/spec/grape/integration/rack_sendfile_spec.rb +14 -8
  203. data/spec/grape/integration/rack_spec.rb +25 -7
  204. data/spec/grape/loading_spec.rb +2 -0
  205. data/spec/grape/middleware/auth/base_spec.rb +2 -0
  206. data/spec/grape/middleware/auth/dsl_spec.rb +5 -3
  207. data/spec/grape/middleware/auth/strategies_spec.rb +3 -1
  208. data/spec/grape/middleware/base_spec.rb +10 -0
  209. data/spec/grape/middleware/error_spec.rb +3 -1
  210. data/spec/grape/middleware/exception_spec.rb +4 -2
  211. data/spec/grape/middleware/formatter_spec.rb +33 -16
  212. data/spec/grape/middleware/globals_spec.rb +2 -0
  213. data/spec/grape/middleware/stack_spec.rb +12 -0
  214. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -1
  215. data/spec/grape/middleware/versioner/header_spec.rb +9 -1
  216. data/spec/grape/middleware/versioner/param_spec.rb +3 -1
  217. data/spec/grape/middleware/versioner/path_spec.rb +3 -1
  218. data/spec/grape/middleware/versioner_spec.rb +2 -0
  219. data/spec/grape/named_api_spec.rb +21 -0
  220. data/spec/grape/parser_spec.rb +7 -5
  221. data/spec/grape/path_spec.rb +6 -4
  222. data/spec/grape/presenters/presenter_spec.rb +2 -0
  223. data/spec/grape/request_spec.rb +26 -0
  224. data/spec/grape/util/inheritable_setting_spec.rb +2 -0
  225. data/spec/grape/util/inheritable_values_spec.rb +2 -0
  226. data/spec/grape/util/reverse_stackable_values_spec.rb +2 -0
  227. data/spec/grape/util/stackable_values_spec.rb +3 -1
  228. data/spec/grape/util/strict_hash_configuration_spec.rb +2 -0
  229. data/spec/grape/validations/attributes_iterator_spec.rb +2 -0
  230. data/spec/grape/validations/instance_behaivour_spec.rb +5 -3
  231. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +41 -0
  232. data/spec/grape/validations/params_scope_spec.rb +213 -9
  233. data/spec/grape/validations/single_attribute_iterator_spec.rb +58 -0
  234. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  235. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  236. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  237. data/spec/grape/validations/types_spec.rb +9 -36
  238. data/spec/grape/validations/validators/all_or_none_spec.rb +140 -30
  239. data/spec/grape/validations/validators/allow_blank_spec.rb +2 -0
  240. data/spec/grape/validations/validators/at_least_one_of_spec.rb +175 -29
  241. data/spec/grape/validations/validators/coerce_spec.rb +476 -135
  242. data/spec/grape/validations/validators/default_spec.rb +172 -0
  243. data/spec/grape/validations/validators/exactly_one_of_spec.rb +204 -38
  244. data/spec/grape/validations/validators/except_values_spec.rb +4 -1
  245. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +186 -27
  246. data/spec/grape/validations/validators/presence_spec.rb +30 -0
  247. data/spec/grape/validations/validators/regexp_spec.rb +2 -0
  248. data/spec/grape/validations/validators/same_as_spec.rb +65 -0
  249. data/spec/grape/validations/validators/values_spec.rb +30 -5
  250. data/spec/grape/validations_spec.rb +388 -50
  251. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  252. data/spec/integration/multi_json/json_spec.rb +2 -0
  253. data/spec/integration/multi_xml/xml_spec.rb +2 -0
  254. data/spec/shared/versioning_examples.rb +22 -20
  255. data/spec/spec_helper.rb +12 -1
  256. data/spec/support/basic_auth_encode_helpers.rb +2 -0
  257. data/spec/support/chunks.rb +14 -0
  258. data/spec/support/content_type_helpers.rb +2 -0
  259. data/spec/support/eager_load.rb +19 -0
  260. data/spec/support/endpoint_faker.rb +2 -0
  261. data/spec/support/file_streamer.rb +2 -0
  262. data/spec/support/integer_helpers.rb +2 -0
  263. data/spec/support/versioned_helpers.rb +8 -8
  264. metadata +86 -48
  265. data/Appraisals +0 -32
  266. data/Dangerfile +0 -2
  267. data/Gemfile +0 -33
  268. data/Gemfile.lock +0 -231
  269. data/Guardfile +0 -10
  270. data/RELEASING.md +0 -111
  271. data/Rakefile +0 -25
  272. data/benchmark/simple.rb +0 -27
  273. data/benchmark/simple_with_type_coercer.rb +0 -22
  274. data/gemfiles/multi_json.gemfile +0 -35
  275. data/gemfiles/multi_xml.gemfile +0 -35
  276. data/gemfiles/rack_1.5.2.gemfile +0 -35
  277. data/gemfiles/rack_edge.gemfile +0 -35
  278. data/gemfiles/rails_3.gemfile +0 -36
  279. data/gemfiles/rails_4.gemfile +0 -35
  280. data/gemfiles/rails_5.gemfile +0 -35
  281. data/gemfiles/rails_edge.gemfile +0 -35
  282. data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +0 -18
  283. data/lib/grape/util/content_types.rb +0 -26
  284. data/lib/grape/validations/types/virtus_collection_patch.rb +0 -16
  285. data/pkg/grape-0.17.0.gem +0 -0
  286. data/pkg/grape-0.19.0.gem +0 -0
@@ -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, {})
@@ -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
+ coerced_val = @method.call(val)
56
+
57
+ return coerced_val if coerced_val.is_a?(InvalidValue)
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
+ val.nil? || @type_check.call(val)
98
64
  end
99
65
 
100
66
  private
@@ -137,13 +103,25 @@ module Grape
137
103
  # passed, or if the type also implements a parse() method.
138
104
  type
139
105
  elsif type.is_a?(Enumerable)
140
- ->(value) { value.respond_to?(:all?) && value.all? { |item| item.is_a? type[0] } }
106
+ lambda do |value|
107
+ value.is_a?(Enumerable) && value.all? do |val|
108
+ recursive_type_check(type.first, val)
109
+ end
110
+ end
141
111
  else
142
112
  # By default, do a simple type check
143
113
  ->(value) { value.is_a? type }
144
114
  end
145
115
  end
146
116
 
117
+ def recursive_type_check(type, value)
118
+ if type.is_a?(Enumerable) && value.is_a?(Enumerable)
119
+ value.all? { |val| recursive_type_check(type.first, val) }
120
+ else
121
+ !type.is_a?(Enumerable) && value.is_a?(type)
122
+ end
123
+ end
124
+
147
125
  # Enforce symbolized keys for complex types
148
126
  # by wrapping the coercion method such that
149
127
  # any Hash objects in the immediate heirarchy
@@ -158,10 +136,10 @@ module Grape
158
136
  # necessary.
159
137
  def enforce_symbolized_keys(type, method)
160
138
  # Collections have all values processed individually
161
- if type == Array || type == Set
139
+ if [Array, Set].include?(type)
162
140
  lambda do |val|
163
- method.call(val).tap do |new_value|
164
- new_value.map do |item|
141
+ method.call(val).tap do |new_val|
142
+ new_val.map do |item|
165
143
  item.is_a?(Hash) ? symbolize_keys(item) : item
166
144
  end
167
145
  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,76 @@
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
+ class << self
21
+ # Registers a collection coercer which could be found by a type,
22
+ # see +collection_coercer_for+ method below. This method is meant for inheritors.
23
+ def register_collection(type)
24
+ DryTypeCoercer.collection_coercers[type] = self
25
+ end
26
+
27
+ # Returns a collection coercer which corresponds to a given type.
28
+ # Example:
29
+ #
30
+ # collection_coercer_for(Array)
31
+ # #=> Grape::Validations::Types::ArrayCoercer
32
+ def collection_coercer_for(type)
33
+ collection_coercers[type]
34
+ end
35
+
36
+ # Returns an instance of a coercer for a given type
37
+ def coercer_instance_for(type, strict = false)
38
+ return PrimitiveCoercer.new(type, strict) if type.class == Class
39
+
40
+ # in case of a collection (Array[Integer]) the type is an instance of a collection,
41
+ # so we need to figure out the actual type
42
+ collection_coercer_for(type.class).new(type, strict)
43
+ end
44
+
45
+ protected
46
+
47
+ def collection_coercers
48
+ @collection_coercers ||= {}
49
+ end
50
+ end
51
+
52
+ def initialize(type, strict = false)
53
+ @type = type
54
+ @strict = strict
55
+ @scope = strict ? DryTypes::Strict : DryTypes::Params
56
+ end
57
+
58
+ # Coerces the given value to a type which was specified during
59
+ # initialization as a type argument.
60
+ #
61
+ # @param val [Object]
62
+ def call(val)
63
+ return if val.nil?
64
+
65
+ @coercer[val]
66
+ rescue Dry::Types::CoercionError => _e
67
+ InvalidValue.new
68
+ end
69
+
70
+ protected
71
+
72
+ attr_reader :scope, :type, :strict
73
+ end
74
+ end
75
+ end
76
+ end