grape 1.8.0 → 3.3.5

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 (312) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +391 -1
  3. data/CONTRIBUTING.md +3 -11
  4. data/README.md +648 -552
  5. data/UPGRADING.md +803 -7
  6. data/grape.gemspec +11 -14
  7. data/lib/grape/api/instance.rb +68 -158
  8. data/lib/grape/api.rb +71 -107
  9. data/lib/grape/content_types.rb +23 -11
  10. data/lib/grape/cookies.rb +28 -25
  11. data/lib/grape/declared_params_handler.rb +116 -0
  12. data/lib/grape/dry_types.rb +48 -6
  13. data/lib/grape/dsl/callbacks.rb +14 -58
  14. data/lib/grape/dsl/declared.rb +35 -0
  15. data/lib/grape/dsl/desc.rb +15 -63
  16. data/lib/grape/dsl/entity.rb +88 -0
  17. data/lib/grape/dsl/headers.rb +2 -2
  18. data/lib/grape/dsl/helpers.rb +83 -64
  19. data/lib/grape/dsl/inside_route.rb +56 -295
  20. data/lib/grape/dsl/logger.rb +4 -9
  21. data/lib/grape/dsl/middleware.rb +22 -40
  22. data/lib/grape/dsl/parameters.rb +50 -82
  23. data/lib/grape/dsl/request_response.rb +144 -146
  24. data/lib/grape/dsl/rescue_options.rb +24 -0
  25. data/lib/grape/dsl/routing.rb +261 -186
  26. data/lib/grape/dsl/settings.rb +29 -135
  27. data/lib/grape/dsl/validations.rb +39 -32
  28. data/lib/grape/dsl/version_options.rb +23 -0
  29. data/lib/grape/endpoint/options.rb +19 -0
  30. data/lib/grape/endpoint.rb +277 -250
  31. data/lib/grape/{util/env.rb → env.rb} +1 -8
  32. data/lib/grape/error_formatter/base.rb +56 -21
  33. data/lib/grape/error_formatter/json.rb +13 -15
  34. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  35. data/lib/grape/error_formatter/txt.rb +12 -18
  36. data/lib/grape/error_formatter/xml.rb +3 -13
  37. data/lib/grape/error_formatter.rb +5 -25
  38. data/lib/grape/exceptions/base.rb +22 -58
  39. data/lib/grape/exceptions/error_response.rb +45 -0
  40. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  41. data/lib/grape/exceptions/internal_server_error.rb +16 -0
  42. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  43. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  44. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  45. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  46. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  47. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  48. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  49. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  50. data/lib/grape/exceptions/request_error.rb +11 -0
  51. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  52. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  53. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  54. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  55. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  56. data/lib/grape/exceptions/validation.rb +25 -9
  57. data/lib/grape/exceptions/validation_array_errors.rb +5 -0
  58. data/lib/grape/exceptions/validation_errors.rb +22 -26
  59. data/lib/grape/formatter/base.rb +16 -0
  60. data/lib/grape/formatter/json.rb +4 -6
  61. data/lib/grape/formatter/serializable_hash.rb +6 -10
  62. data/lib/grape/formatter/txt.rb +3 -5
  63. data/lib/grape/formatter/xml.rb +4 -6
  64. data/lib/grape/formatter.rb +7 -25
  65. data/lib/grape/json.rb +46 -0
  66. data/lib/grape/locale/en.yml +43 -42
  67. data/lib/grape/middleware/auth/base.rb +11 -33
  68. data/lib/grape/middleware/auth/dsl.rb +37 -31
  69. data/lib/grape/middleware/auth/strategies.rb +1 -2
  70. data/lib/grape/middleware/base.rb +56 -33
  71. data/lib/grape/middleware/deprecated_options_hash_access.rb +19 -0
  72. data/lib/grape/middleware/error.rb +183 -91
  73. data/lib/grape/middleware/formatter.rb +94 -105
  74. data/lib/grape/middleware/precomputed_content_types.rb +46 -0
  75. data/lib/grape/middleware/stack.rb +27 -38
  76. data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
  77. data/lib/grape/middleware/versioner/base.rb +72 -0
  78. data/lib/grape/middleware/versioner/header.rb +44 -129
  79. data/lib/grape/middleware/versioner/param.rb +4 -25
  80. data/lib/grape/middleware/versioner/path.rb +13 -32
  81. data/lib/grape/middleware/versioner.rb +7 -14
  82. data/lib/grape/mountable.rb +22 -0
  83. data/lib/grape/namespace.rb +18 -12
  84. data/lib/grape/params_builder/base.rb +20 -0
  85. data/lib/grape/params_builder/hash.rb +11 -0
  86. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  87. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  88. data/lib/grape/params_builder.rb +15 -0
  89. data/lib/grape/parser/base.rb +16 -0
  90. data/lib/grape/parser/json.rb +6 -8
  91. data/lib/grape/parser/xml.rb +6 -8
  92. data/lib/grape/parser.rb +5 -23
  93. data/lib/grape/path.rb +38 -63
  94. data/lib/grape/railtie.rb +9 -0
  95. data/lib/grape/request.rb +168 -26
  96. data/lib/grape/router/base_route.rb +48 -0
  97. data/lib/grape/router/greedy_route.rb +26 -0
  98. data/lib/grape/router/mustermann_pattern.rb +44 -0
  99. data/lib/grape/router/pattern.rb +30 -34
  100. data/lib/grape/router/route.rb +28 -61
  101. data/lib/grape/router.rb +71 -86
  102. data/lib/grape/serve_stream/file_body.rb +1 -0
  103. data/lib/grape/serve_stream/sendfile_response.rb +3 -5
  104. data/lib/grape/serve_stream/stream_response.rb +1 -0
  105. data/lib/grape/testing.rb +33 -0
  106. data/lib/grape/util/api_description.rb +58 -0
  107. data/lib/grape/util/base_inheritable.rb +14 -14
  108. data/lib/grape/util/cache.rb +1 -3
  109. data/lib/grape/util/deep_freeze.rb +35 -0
  110. data/lib/grape/util/endpoint_configuration.rb +1 -1
  111. data/lib/grape/util/header.rb +13 -0
  112. data/lib/grape/util/inheritable_setting.rb +51 -27
  113. data/lib/grape/util/inheritable_values.rb +7 -5
  114. data/lib/grape/util/lazy/base.rb +16 -0
  115. data/lib/grape/util/lazy/block.rb +22 -0
  116. data/lib/grape/util/lazy/value.rb +31 -0
  117. data/lib/grape/util/lazy/value_array.rb +21 -0
  118. data/lib/grape/util/lazy/value_enumerable.rb +31 -0
  119. data/lib/grape/util/lazy/value_hash.rb +21 -0
  120. data/lib/grape/util/media_type.rb +67 -0
  121. data/lib/grape/util/path_normalizer.rb +34 -0
  122. data/lib/grape/util/registry.rb +27 -0
  123. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  124. data/lib/grape/util/stackable_values.rb +12 -14
  125. data/lib/grape/util/translation.rb +42 -0
  126. data/lib/grape/validations/attributes_iterator.rb +43 -27
  127. data/lib/grape/validations/coerce_options.rb +21 -0
  128. data/lib/grape/validations/contract_scope.rb +28 -0
  129. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  130. data/lib/grape/validations/oneof_collector.rb +39 -0
  131. data/lib/grape/validations/param_scope_tracker.rb +62 -0
  132. data/lib/grape/validations/params_documentation.rb +52 -0
  133. data/lib/grape/validations/params_scope.rb +180 -303
  134. data/lib/grape/validations/shared_options.rb +19 -0
  135. data/lib/grape/validations/single_attribute_iterator.rb +2 -2
  136. data/lib/grape/validations/types/array_coercer.rb +4 -7
  137. data/lib/grape/validations/types/custom_type_coercer.rb +41 -85
  138. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  139. data/lib/grape/validations/types/dry_type_coercer.rb +15 -28
  140. data/lib/grape/validations/types/json.rb +0 -2
  141. data/lib/grape/validations/types/primitive_coercer.rb +11 -35
  142. data/lib/grape/validations/types/set_coercer.rb +1 -4
  143. data/lib/grape/validations/types/variant_collection_coercer.rb +11 -0
  144. data/lib/grape/validations/types.rb +29 -54
  145. data/lib/grape/validations/validations_spec.rb +164 -0
  146. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  147. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  148. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  149. data/lib/grape/validations/validators/base.rb +120 -36
  150. data/lib/grape/validations/validators/coerce_validator.rb +18 -36
  151. data/lib/grape/validations/validators/contract_scope_validator.rb +45 -0
  152. data/lib/grape/validations/validators/default_validator.rb +13 -16
  153. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  154. data/lib/grape/validations/validators/except_values_validator.rb +15 -5
  155. data/lib/grape/validations/validators/length_validator.rb +48 -0
  156. data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
  157. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  158. data/lib/grape/validations/validators/oneof_validator.rb +49 -0
  159. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  160. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  161. data/lib/grape/validations/validators/same_as_validator.rb +6 -15
  162. data/lib/grape/validations/validators/values_validator.rb +36 -65
  163. data/lib/grape/validations.rb +8 -21
  164. data/lib/grape/version.rb +1 -2
  165. data/lib/grape/xml.rb +17 -0
  166. data/lib/grape.rb +75 -288
  167. metadata +79 -291
  168. data/lib/grape/api/helpers.rb +0 -9
  169. data/lib/grape/dsl/api.rb +0 -19
  170. data/lib/grape/dsl/configuration.rb +0 -15
  171. data/lib/grape/eager_load.rb +0 -20
  172. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  173. data/lib/grape/exceptions/missing_option.rb +0 -11
  174. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  175. data/lib/grape/exceptions/unknown_options.rb +0 -11
  176. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -27
  177. data/lib/grape/extensions/hash.rb +0 -22
  178. data/lib/grape/extensions/hashie/mash.rb +0 -26
  179. data/lib/grape/http/headers.rb +0 -61
  180. data/lib/grape/middleware/globals.rb +0 -16
  181. data/lib/grape/middleware/helpers.rb +0 -12
  182. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  183. data/lib/grape/router/attribute_translator.rb +0 -63
  184. data/lib/grape/types/invalid_value.rb +0 -8
  185. data/lib/grape/util/json.rb +0 -12
  186. data/lib/grape/util/lazy_block.rb +0 -27
  187. data/lib/grape/util/lazy_object.rb +0 -43
  188. data/lib/grape/util/lazy_value.rb +0 -91
  189. data/lib/grape/util/registrable.rb +0 -15
  190. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  191. data/lib/grape/util/xml.rb +0 -10
  192. data/lib/grape/validations/attributes_doc.rb +0 -58
  193. data/lib/grape/validations/types/build_coercer.rb +0 -94
  194. data/lib/grape/validations/validator_factory.rb +0 -15
  195. data/spec/grape/api/custom_validations_spec.rb +0 -213
  196. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  197. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  198. data/spec/grape/api/documentation_spec.rb +0 -59
  199. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  200. data/spec/grape/api/instance_spec.rb +0 -103
  201. data/spec/grape/api/invalid_format_spec.rb +0 -45
  202. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  203. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  204. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  205. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  206. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  207. data/spec/grape/api/recognize_path_spec.rb +0 -21
  208. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  209. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  210. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  211. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  212. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  213. data/spec/grape/api_remount_spec.rb +0 -509
  214. data/spec/grape/api_spec.rb +0 -4356
  215. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  216. data/spec/grape/dsl/desc_spec.rb +0 -98
  217. data/spec/grape/dsl/headers_spec.rb +0 -62
  218. data/spec/grape/dsl/helpers_spec.rb +0 -100
  219. data/spec/grape/dsl/inside_route_spec.rb +0 -531
  220. data/spec/grape/dsl/logger_spec.rb +0 -24
  221. data/spec/grape/dsl/middleware_spec.rb +0 -60
  222. data/spec/grape/dsl/parameters_spec.rb +0 -180
  223. data/spec/grape/dsl/request_response_spec.rb +0 -225
  224. data/spec/grape/dsl/routing_spec.rb +0 -275
  225. data/spec/grape/dsl/settings_spec.rb +0 -261
  226. data/spec/grape/dsl/validations_spec.rb +0 -55
  227. data/spec/grape/endpoint/declared_spec.rb +0 -846
  228. data/spec/grape/endpoint_spec.rb +0 -1085
  229. data/spec/grape/entity_spec.rb +0 -336
  230. data/spec/grape/exceptions/base_spec.rb +0 -81
  231. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
  232. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  233. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  234. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  235. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  236. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
  237. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  238. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  239. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  240. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  241. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
  242. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  243. data/spec/grape/exceptions/validation_spec.rb +0 -19
  244. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  245. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  246. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  247. data/spec/grape/grape_spec.rb +0 -9
  248. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  249. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  250. data/spec/grape/integration/rack_spec.rb +0 -51
  251. data/spec/grape/loading_spec.rb +0 -44
  252. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  253. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  254. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  255. data/spec/grape/middleware/base_spec.rb +0 -221
  256. data/spec/grape/middleware/error_spec.rb +0 -85
  257. data/spec/grape/middleware/exception_spec.rb +0 -294
  258. data/spec/grape/middleware/formatter_spec.rb +0 -461
  259. data/spec/grape/middleware/globals_spec.rb +0 -30
  260. data/spec/grape/middleware/stack_spec.rb +0 -155
  261. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  262. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  263. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  264. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  265. data/spec/grape/middleware/versioner_spec.rb +0 -21
  266. data/spec/grape/named_api_spec.rb +0 -19
  267. data/spec/grape/parser_spec.rb +0 -86
  268. data/spec/grape/path_spec.rb +0 -252
  269. data/spec/grape/presenters/presenter_spec.rb +0 -71
  270. data/spec/grape/request_spec.rb +0 -126
  271. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  272. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  273. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  274. data/spec/grape/util/stackable_values_spec.rb +0 -128
  275. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  276. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  277. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  278. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
  279. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  280. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
  281. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  282. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  283. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  284. data/spec/grape/validations/types_spec.rb +0 -111
  285. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  286. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  287. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  288. data/spec/grape/validations/validators/base_spec.rb +0 -38
  289. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  290. data/spec/grape/validations/validators/default_spec.rb +0 -463
  291. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  292. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  293. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  294. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  295. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  296. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  297. data/spec/grape/validations/validators/values_spec.rb +0 -733
  298. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  299. data/spec/grape/validations_spec.rb +0 -2030
  300. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  301. data/spec/integration/multi_json/json_spec.rb +0 -7
  302. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  303. data/spec/shared/deprecated_class_examples.rb +0 -16
  304. data/spec/shared/versioning_examples.rb +0 -215
  305. data/spec/spec_helper.rb +0 -52
  306. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  307. data/spec/support/chunks.rb +0 -14
  308. data/spec/support/content_type_helpers.rb +0 -15
  309. data/spec/support/endpoint_faker.rb +0 -25
  310. data/spec/support/file_streamer.rb +0 -13
  311. data/spec/support/integer_helpers.rb +0 -13
  312. data/spec/support/versioned_helpers.rb +0 -55
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ # Immutable value object holding the two options every validator reads at
6
+ # construction time: +allow_blank+ and +fail_fast+. Internal to
7
+ # {Validators::Base}, which builds it from the +opts+ Hash so the public
8
+ # 5th-argument contract stays a plain Hash — not part of any wire contract.
9
+ #
10
+ # Defaults mirror the prior +opts.values_at+ behaviour: +allow_blank+ is
11
+ # +nil+ when the declaration didn't supply it (validators treat nil as
12
+ # "not set"), +fail_fast+ defaults to +false+.
13
+ SharedOptions = Data.define(:allow_blank, :fail_fast) do
14
+ def initialize(allow_blank: nil, fail_fast: false)
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
@@ -5,10 +5,10 @@ module Grape
5
5
  class SingleAttributeIterator < AttributesIterator
6
6
  private
7
7
 
8
- def yield_attributes(val, attrs)
8
+ def yield_attributes(val)
9
9
  return if skip?(val)
10
10
 
11
- attrs.each do |attr_name|
11
+ @attrs.each do |attr_name|
12
12
  yield val, attr_name, empty?(val)
13
13
  end
14
14
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'dry_type_coercer'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  module Types
@@ -9,15 +7,14 @@ module Grape
9
7
  # an array of arrays of integers.
10
8
  #
11
9
  # It could've been possible to use an +of+
12
- # method (https://dry-rb.org/gems/dry-types/1.2/array-with-member/)
10
+ # method (https://dry-rb.org/gems/dry-types/main/array-with-member/)
13
11
  # provided by dry-types. Unfortunately, it doesn't work for Grape because of
14
12
  # behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
15
13
  # maintains Virtus behavior in coercing.
16
14
  class ArrayCoercer < DryTypeCoercer
17
- def initialize(type, strict = false)
15
+ def initialize(type, strict: false)
18
16
  super
19
-
20
- @coercer = scope::Array
17
+ @coercer = strict ? DryTypes::Strict::Array : DryTypes::Params::Array
21
18
  @subtype = type.first
22
19
  end
23
20
 
@@ -55,7 +52,7 @@ module Grape
55
52
  end
56
53
 
57
54
  def elem_coercer
58
- @elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict)
55
+ @elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict:)
59
56
  end
60
57
  end
61
58
  end
@@ -33,6 +33,10 @@ module Grape
33
33
  # contract as +coerced?+, and must be supplied with a coercion
34
34
  # +method+.
35
35
  class CustomTypeCoercer
36
+ TYPE_CHECK_METHODS = %i[coerced? parsed?].freeze
37
+ COLLECTION_TYPES = [Array, Set].freeze
38
+ private_constant :TYPE_CHECK_METHODS, :COLLECTION_TYPES
39
+
36
40
  # A new coercer for the given type specification
37
41
  # and coercion method.
38
42
  #
@@ -41,8 +45,7 @@ module Grape
41
45
  # @param method [#parse,#call]
42
46
  # optional coercion method. See class docs.
43
47
  def initialize(type, method = nil)
44
- coercion_method = infer_coercion_method type, method
45
- @method = enforce_symbolized_keys type, coercion_method
48
+ @method = build_coercion_method(type, method)
46
49
  @type_check = infer_type_check(type)
47
50
  end
48
51
 
@@ -66,97 +69,50 @@ module Grape
66
69
 
67
70
  private
68
71
 
69
- # Determine the coercion method we're expected to use
70
- # based on the parameters given.
71
- #
72
- # @param type see #new
73
- # @param method see #new
74
- # @return [#call] coercion method
72
+ def build_coercion_method(type, method)
73
+ coercion_method = infer_coercion_method(type, method)
74
+ return hash_symbolizer(coercion_method) if type == Hash
75
+ return collection_symbolizer(coercion_method) if COLLECTION_TYPES.include?(type)
76
+
77
+ coercion_method
78
+ end
79
+
75
80
  def infer_coercion_method(type, method)
76
- if method
77
- if method.respond_to? :parse
78
- method.method :parse
79
- else
80
- method
81
- end
82
- else
83
- # Try to use parse() declared on the target type.
84
- # This may raise an exception, but we are out of ideas anyway.
85
- type.method :parse
86
- end
81
+ return type.method(:parse) unless method
82
+ return method unless method.respond_to?(:parse)
83
+
84
+ method.method(:parse)
85
+ end
86
+
87
+ def hash_symbolizer(method)
88
+ ->(val) { method.call(val).deep_symbolize_keys }
89
+ end
90
+
91
+ def collection_symbolizer(method)
92
+ ->(val) { method.call(val).map! { |item| symbolize_if_hash(item) } }
93
+ end
94
+
95
+ def symbolize_if_hash(item)
96
+ item.is_a?(Hash) ? item.deep_symbolize_keys : item
87
97
  end
88
98
 
89
- # Determine how the type validity of a coerced
90
- # value should be decided.
91
- #
92
- # @param type see #new
93
- # @return [#call] a procedure which accepts a single parameter
94
- # and returns +true+ if the passed object is of the correct type.
95
99
  def infer_type_check(type)
96
- # First check for special class methods
97
- if type.respond_to? :coerced?
98
- type.method :coerced?
99
- elsif type.respond_to? :parsed?
100
- type.method :parsed?
101
- elsif type.respond_to? :call
102
- # Arbitrary proc passed for type validation.
103
- # Note that this will fail unless a method is also
104
- # passed, or if the type also implements a parse() method.
105
- type
106
- elsif type.is_a?(Enumerable)
107
- lambda do |value|
108
- value.is_a?(Enumerable) && value.all? do |val|
109
- recursive_type_check(type.first, val)
110
- end
111
- end
112
- else
113
- # By default, do a simple type check
114
- ->(value) { value.is_a? type }
115
- end
100
+ method_name = TYPE_CHECK_METHODS.detect { |m| type.respond_to?(m) }
101
+ return type.method(method_name) if method_name
102
+ return type if type.respond_to?(:call)
103
+ return enumerable_type_check(type) if type.is_a?(Enumerable)
104
+
105
+ ->(value) { value.is_a? type }
116
106
  end
117
107
 
118
- def recursive_type_check(type, value)
119
- if type.is_a?(Enumerable) && value.is_a?(Enumerable)
120
- value.all? { |val| recursive_type_check(type.first, val) }
121
- else
122
- !type.is_a?(Enumerable) && value.is_a?(type)
123
- end
108
+ def enumerable_type_check(type)
109
+ ->(value) { value.is_a?(Enumerable) && value.all? { |val| recursive_type_check(type.first, val) } }
124
110
  end
125
111
 
126
- # Enforce symbolized keys for complex types
127
- # by wrapping the coercion method such that
128
- # any Hash objects in the immediate heirarchy
129
- # have their keys recursively symbolized.
130
- # This helps common libs such as JSON to work easily.
131
- #
132
- # @param type see #new
133
- # @param method see #infer_coercion_method
134
- # @return [#call] +method+ wrapped in an additional
135
- # key-conversion step, or just returns +method+
136
- # itself if no conversion is deemed to be
137
- # necessary.
138
- def enforce_symbolized_keys(type, method)
139
- # Collections have all values processed individually
140
- if [Array, Set].include?(type)
141
- lambda do |val|
142
- method.call(val).tap do |new_val|
143
- new_val.map do |item|
144
- item.is_a?(Hash) ? item.deep_symbolize_keys : item
145
- end
146
- end
147
- end
148
-
149
- # Hash objects are processed directly
150
- elsif type == Hash
151
- lambda do |val|
152
- method.call(val).deep_symbolize_keys
153
- end
154
-
155
- # Simple types are not processed.
156
- # This includes Array<primitive> types.
157
- else
158
- method
159
- end
112
+ def recursive_type_check(type, value)
113
+ return value.all? { |val| recursive_type_check(type.first, val) } if type.is_a?(Enumerable) && value.is_a?(Enumerable)
114
+
115
+ !type.is_a?(Enumerable) && value.is_a?(type)
160
116
  end
161
117
  end
162
118
  end
@@ -29,7 +29,7 @@ module Grape
29
29
  # @param set [Boolean]
30
30
  # when true, a +Set+ will be returned by {#call} instead
31
31
  # of an +Array+ and duplicate items will be discarded.
32
- def initialize(type, set = false)
32
+ def initialize(type, set: false)
33
33
  super(type)
34
34
  @set = set
35
35
  end
@@ -1,21 +1,12 @@
1
1
  # frozen_string_literal: true
2
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
3
  module Grape
13
4
  module Validations
14
5
  module Types
15
6
  # A base class for classes which must identify a coercer to be used.
16
7
  # If the +strict+ argument is true, it won't coerce the given value
17
8
  # but check its type. More information there
18
- # https://dry-rb.org/gems/dry-types/1.2/built-in-types/
9
+ # https://dry-rb.org/gems/dry-types/main/built-in-types/
19
10
  class DryTypeCoercer
20
11
  class << self
21
12
  # Returns a collection coercer which corresponds to a given type.
@@ -24,31 +15,27 @@ module Grape
24
15
  # collection_coercer_for(Array)
25
16
  # #=> Grape::Validations::Types::ArrayCoercer
26
17
  def collection_coercer_for(type)
27
- collection_coercers.fetch(type) do
28
- DryTypeCoercer.collection_coercers[type] = Grape::Validations::Types.const_get("#{type.name.camelize}Coercer")
18
+ case type
19
+ when Array
20
+ ArrayCoercer
21
+ when Set
22
+ SetCoercer
23
+ else
24
+ raise ArgumentError, "Unknown type: #{type}"
29
25
  end
30
26
  end
31
27
 
32
28
  # Returns an instance of a coercer for a given type
33
- def coercer_instance_for(type, strict = false)
34
- return PrimitiveCoercer.new(type, strict) if type.instance_of?(Class)
35
-
36
- # in case of a collection (Array[Integer]) the type is an instance of a collection,
37
- # so we need to figure out the actual type
38
- collection_coercer_for(type.class).new(type, strict)
39
- end
40
-
41
- protected
42
-
43
- def collection_coercers
44
- @collection_coercers ||= {}
29
+ def coercer_instance_for(type, strict: false)
30
+ klass = type.instance_of?(Class) ? PrimitiveCoercer : collection_coercer_for(type)
31
+ klass.new(type, strict:)
45
32
  end
46
33
  end
47
34
 
48
- def initialize(type, strict = false)
35
+ def initialize(type, strict: false)
49
36
  @type = type
50
37
  @strict = strict
51
- @scope = strict ? DryTypes::Strict : DryTypes::Params
38
+ @cache_coercer = strict ? DryTypes::StrictCache : DryTypes::ParamsCache
52
39
  end
53
40
 
54
41
  # Coerces the given value to a type which was specified during
@@ -59,13 +46,13 @@ module Grape
59
46
  return if val.nil?
60
47
 
61
48
  @coercer[val]
62
- rescue Dry::Types::CoercionError => _e
49
+ rescue Dry::Types::CoercionError
63
50
  InvalidValue.new
64
51
  end
65
52
 
66
53
  protected
67
54
 
68
- attr_reader :scope, :type, :strict
55
+ attr_reader :type, :strict, :cache_coercer
69
56
  end
70
57
  end
71
58
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  module Types
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'dry_type_coercer'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  module Types
@@ -9,42 +7,15 @@ module Grape
9
7
  # initialization. When +strict+ is true, it doesn't coerce a value but check
10
8
  # that it has the proper type.
11
9
  class PrimitiveCoercer < DryTypeCoercer
12
- MAPPING = {
13
- Grape::API::Boolean => DryTypes::Params::Bool,
14
- BigDecimal => DryTypes::Params::Decimal,
15
- Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,
16
- TrueClass => DryTypes::Params::Bool.constrained(eql: true),
17
- FalseClass => DryTypes::Params::Bool.constrained(eql: false),
18
-
19
- # unfortunately, a +Params+ scope doesn't contain String
20
- String => DryTypes::Coercible::String
21
- }.freeze
22
-
23
- STRICT_MAPPING = {
24
- Grape::API::Boolean => DryTypes::Strict::Bool,
25
- BigDecimal => DryTypes::Strict::Decimal,
26
- Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,
27
- TrueClass => DryTypes::Strict::Bool.constrained(eql: true),
28
- FalseClass => DryTypes::Strict::Bool.constrained(eql: false)
29
- }.freeze
30
-
31
- def initialize(type, strict = false)
10
+ def initialize(type, strict: false)
32
11
  super
33
12
 
34
- @type = type
35
-
36
- @coercer = (strict ? STRICT_MAPPING : MAPPING).fetch(type) do
37
- scope.const_get(type.name, false)
38
- rescue NameError
39
- raise ArgumentError, "type #{type} should support coercion via `[]`" unless type.respond_to?(:[])
40
-
41
- type
42
- end
13
+ @coercer = cache_coercer[type]
43
14
  end
44
15
 
45
16
  def call(val)
46
17
  return InvalidValue.new if reject?(val)
47
- return nil if val.nil? || treat_as_nil?(val)
18
+ return if val.nil? || treat_as_nil?(val)
48
19
 
49
20
  super
50
21
  end
@@ -58,9 +29,14 @@ module Grape
58
29
  # but Virtus wouldn't accept it. So, this method only exists to not introduce
59
30
  # breaking changes.
60
31
  def reject?(val)
61
- (val.is_a?(Array) && type == String) ||
62
- (val.is_a?(String) && type == Hash) ||
63
- (val.is_a?(Hash) && type == String)
32
+ case val
33
+ when Array, Hash
34
+ type == String
35
+ when String
36
+ type == Hash
37
+ else
38
+ false
39
+ end
64
40
  end
65
41
 
66
42
  # Dry-Types treats an empty string as invalid. However, Grape considers an empty string as
@@ -1,15 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
- require_relative 'array_coercer'
5
-
6
3
  module Grape
7
4
  module Validations
8
5
  module Types
9
6
  # Takes the given array and converts it to a set. Every element of the set
10
7
  # is also coerced.
11
8
  class SetCoercer < ArrayCoercer
12
- def initialize(type, strict = false)
9
+ def initialize(type, strict: false)
13
10
  super
14
11
 
15
12
  @coercer = nil
@@ -6,6 +6,9 @@ module Grape
6
6
  # This class wraps {MultipleTypeCoercer}, for use with collections
7
7
  # that allow members of more than one type.
8
8
  class VariantCollectionCoercer
9
+ # @return [Array<Class>,Set<Class>] the member types as declared in the DSL
10
+ attr_reader :types
11
+
9
12
  # Construct a new coercer that will attempt to coerce
10
13
  # a list of values such that all members are of one of
11
14
  # the given types. The container may also optionally be
@@ -26,6 +29,14 @@ module Grape
26
29
  @member_coercer = MultipleTypeCoercer.new types, method
27
30
  end
28
31
 
32
+ # Returns the Grape DSL notation for this coercer, e.g. "Array[Integer, String]".
33
+ # Distinct from the plain-array string "[Integer, String]" produced by the
34
+ # +types:+ keyword, which lets documentation tools tell the two apart.
35
+ def to_s
36
+ container = @types.is_a?(Set) ? 'Set' : 'Array'
37
+ "#{container}[#{@types.join(', ')}]"
38
+ end
39
+
29
40
  # Coerce the given value.
30
41
  #
31
42
  # @param value [Array<String>] collection of values to be coerced
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/validations/types/json'
4
- require 'grape/validations/types/file'
5
-
6
3
  module Grape
7
4
  module Validations
8
5
  # Module for code related to grape's system for
@@ -78,7 +75,7 @@ module Grape
78
75
  # @return [Boolean] +true+ if the given value will be treated as
79
76
  # a list of types.
80
77
  def multiple?(type)
81
- (type.is_a?(Array) || type.is_a?(Set)) && type.size > 1
78
+ array_or_set?(type) && type.size > 1
82
79
  end
83
80
 
84
81
  # Does Grape provide special coercion and validation
@@ -101,17 +98,18 @@ module Grape
101
98
  GROUPS.include? type
102
99
  end
103
100
 
101
+ def array_or_set?(type)
102
+ type.is_a?(Array) || type.is_a?(Set)
103
+ end
104
+
104
105
  # A valid custom type must implement a class-level `parse` method, taking
105
106
  # one String argument and returning the parsed value in its correct type.
106
107
  #
107
108
  # @param type [Class] type to check
108
109
  # @return [Boolean] whether or not the type can be used as a custom type
109
110
  def custom?(type)
110
- !primitive?(type) &&
111
- !structure?(type) &&
112
- !multiple?(type) &&
113
- type.respond_to?(:parse) &&
114
- type.method(:parse).arity == 1
111
+ !(primitive?(type) || structure?(type) || multiple?(type)) &&
112
+ type.respond_to?(:parse) && type.method(:parse).arity == 1
115
113
  end
116
114
 
117
115
  # Is the declared type an +Array+ or +Set+ of a {#custom?} type?
@@ -120,9 +118,7 @@ module Grape
120
118
  # @return [Boolean] true if +type+ is a collection of a type that implements
121
119
  # its own +#parse+ method.
122
120
  def collection_of_custom?(type)
123
- (type.is_a?(Array) || type.is_a?(Set)) &&
124
- type.length == 1 &&
125
- (custom?(type.first) || special?(type.first))
121
+ array_or_set?(type) && type.length == 1 && (custom?(type.first) || special?(type.first))
126
122
  end
127
123
 
128
124
  def map_special(type)
@@ -158,59 +154,38 @@ module Grape
158
154
  # @return [Object] object to be used
159
155
  # for coercion and type validation
160
156
  def build_coercer(type, method: nil, strict: false)
161
- cache_instance(type, method, strict) do
162
- create_coercer_instance(type, method, strict)
163
- end
164
- end
157
+ # no cache since unique
158
+ return create_coercer_instance(type, method, strict) if method.respond_to?(:call)
165
159
 
166
- def create_coercer_instance(type, method, strict)
167
- # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
168
- type = Types.map_special(type)
169
-
170
- # Use a special coercer for multiply-typed parameters.
171
- if Types.multiple?(type)
172
- MultipleTypeCoercer.new(type, method)
173
-
174
- # Use a special coercer for custom types and coercion methods.
175
- elsif method || Types.custom?(type)
176
- CustomTypeCoercer.new(type, method)
177
-
178
- # Special coercer for collections of types that implement a parse method.
179
- # CustomTypeCoercer (above) already handles such types when an explicit coercion
180
- # method is supplied.
181
- elsif Types.collection_of_custom?(type)
182
- Types::CustomTypeCollectionCoercer.new(
183
- Types.map_special(type.first), type.is_a?(Set)
184
- )
185
- else
186
- DryTypeCoercer.coercer_instance_for(type, strict)
187
- end
160
+ CoercerCache[[type, method, strict]]
188
161
  end
189
162
 
190
- def cache_instance(type, method, strict, &_block)
191
- key = cache_key(type, method, strict)
163
+ def create_coercer_instance(type, method, strict)
164
+ # map_special doesn't recurse into collections — applied only to the top-level type here.
165
+ type = map_special(type)
192
166
 
193
- return @__cache[key] if @__cache.key?(key)
167
+ # Multiply-typed parameters, e.g. types: [Integer, String].
168
+ return MultipleTypeCoercer.new(type, method) if multiple?(type)
194
169
 
195
- instance = yield
170
+ # User-supplied coercion method, or a custom type with its own #parse.
171
+ return CustomTypeCoercer.new(type, method) if method || custom?(type)
196
172
 
197
- @__cache_write_lock.synchronize do
198
- @__cache[key] = instance
199
- end
173
+ # Array/Set of a custom type — CustomTypeCoercer already handles single
174
+ # custom types when an explicit coercion method is supplied.
175
+ return CustomTypeCollectionCoercer.new(map_special(type.first), set: type.is_a?(Set)) if collection_of_custom?(type)
200
176
 
201
- instance
177
+ # Fallback: let dry-types handle primitives, structures, and known specials.
178
+ DryTypeCoercer.coercer_instance_for(type, strict:)
202
179
  end
203
180
 
204
- def cache_key(type, method, strict)
205
- [type, method, strict].each_with_object(+'_') do |val, memo|
206
- next if val.nil?
207
-
208
- memo << '_' << val.to_s
181
+ class CoercerCache < Grape::Util::Cache
182
+ def initialize
183
+ super
184
+ @cache = Hash.new do |h, (type, method, strict)|
185
+ h[[type, method, strict]] = Grape::Validations::Types.create_coercer_instance(type, method, strict)
186
+ end
209
187
  end
210
188
  end
211
-
212
- instance_variable_set(:@__cache, {})
213
- instance_variable_set(:@__cache_write_lock, Mutex.new)
214
189
  end
215
190
  end
216
191
  end