grape 1.1.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (306) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +370 -44
  3. data/CONTRIBUTING.md +32 -1
  4. data/LICENSE +1 -1
  5. data/README.md +683 -87
  6. data/UPGRADING.md +481 -17
  7. data/grape.gemspec +15 -4
  8. data/lib/grape/api/helpers.rb +2 -0
  9. data/lib/grape/api/instance.rb +279 -0
  10. data/lib/grape/api.rb +144 -176
  11. data/lib/grape/config.rb +34 -0
  12. data/lib/grape/content_types.rb +34 -0
  13. data/lib/grape/cookies.rb +4 -0
  14. data/lib/grape/dry_types.rb +12 -0
  15. data/lib/grape/dsl/api.rb +1 -1
  16. data/lib/grape/dsl/callbacks.rb +21 -1
  17. data/lib/grape/dsl/configuration.rb +1 -1
  18. data/lib/grape/dsl/desc.rb +41 -23
  19. data/lib/grape/dsl/headers.rb +7 -2
  20. data/lib/grape/dsl/helpers.rb +10 -7
  21. data/lib/grape/dsl/inside_route.rb +118 -62
  22. data/lib/grape/dsl/logger.rb +2 -0
  23. data/lib/grape/dsl/middleware.rb +11 -4
  24. data/lib/grape/dsl/parameters.rb +33 -19
  25. data/lib/grape/dsl/request_response.rb +12 -9
  26. data/lib/grape/dsl/routing.rb +22 -13
  27. data/lib/grape/dsl/settings.rb +10 -6
  28. data/lib/grape/dsl/validations.rb +19 -14
  29. data/lib/grape/eager_load.rb +20 -0
  30. data/lib/grape/endpoint.rb +67 -58
  31. data/lib/grape/error_formatter/base.rb +2 -0
  32. data/lib/grape/error_formatter/json.rb +11 -7
  33. data/lib/grape/error_formatter/txt.rb +2 -0
  34. data/lib/grape/error_formatter/xml.rb +4 -6
  35. data/lib/grape/error_formatter.rb +4 -2
  36. data/lib/grape/exceptions/base.rb +23 -16
  37. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  38. data/lib/grape/exceptions/incompatible_option_values.rb +2 -0
  39. data/lib/grape/exceptions/invalid_accept_header.rb +2 -0
  40. data/lib/grape/exceptions/invalid_formatter.rb +2 -0
  41. data/lib/grape/exceptions/invalid_message_body.rb +2 -0
  42. data/lib/grape/exceptions/invalid_response.rb +11 -0
  43. data/lib/grape/exceptions/invalid_version_header.rb +2 -0
  44. data/lib/grape/exceptions/invalid_versioner_option.rb +2 -0
  45. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +2 -0
  46. data/lib/grape/exceptions/method_not_allowed.rb +2 -0
  47. data/lib/grape/exceptions/missing_group_type.rb +10 -1
  48. data/lib/grape/exceptions/missing_mime_type.rb +2 -0
  49. data/lib/grape/exceptions/missing_option.rb +2 -0
  50. data/lib/grape/exceptions/missing_vendor_option.rb +2 -0
  51. data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
  52. data/lib/grape/exceptions/unknown_options.rb +2 -0
  53. data/lib/grape/exceptions/unknown_parameter.rb +2 -0
  54. data/lib/grape/exceptions/unknown_validator.rb +2 -0
  55. data/lib/grape/exceptions/unsupported_group_type.rb +10 -1
  56. data/lib/grape/exceptions/validation.rb +5 -8
  57. data/lib/grape/exceptions/validation_array_errors.rb +2 -0
  58. data/lib/grape/exceptions/validation_errors.rb +16 -13
  59. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +4 -3
  60. data/lib/grape/extensions/deep_mergeable_hash.rb +2 -0
  61. data/lib/grape/extensions/deep_symbolize_hash.rb +2 -0
  62. data/lib/grape/extensions/hash.rb +2 -0
  63. data/lib/grape/extensions/hashie/mash.rb +2 -0
  64. data/lib/grape/formatter/json.rb +3 -0
  65. data/lib/grape/formatter/serializable_hash.rb +4 -1
  66. data/lib/grape/formatter/txt.rb +2 -0
  67. data/lib/grape/formatter/xml.rb +3 -0
  68. data/lib/grape/formatter.rb +5 -3
  69. data/lib/grape/http/headers.rb +50 -18
  70. data/lib/grape/locale/en.yml +11 -8
  71. data/lib/grape/middleware/auth/base.rb +7 -7
  72. data/lib/grape/middleware/auth/dsl.rb +9 -2
  73. data/lib/grape/middleware/auth/strategies.rb +2 -0
  74. data/lib/grape/middleware/auth/strategy_info.rb +2 -0
  75. data/lib/grape/middleware/base.rb +13 -8
  76. data/lib/grape/middleware/error.rb +22 -17
  77. data/lib/grape/middleware/filter.rb +2 -0
  78. data/lib/grape/middleware/formatter.rb +12 -10
  79. data/lib/grape/middleware/globals.rb +2 -0
  80. data/lib/grape/middleware/helpers.rb +12 -0
  81. data/lib/grape/middleware/stack.rb +16 -6
  82. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -5
  83. data/lib/grape/middleware/versioner/header.rb +13 -9
  84. data/lib/grape/middleware/versioner/param.rb +4 -1
  85. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +5 -1
  86. data/lib/grape/middleware/versioner/path.rb +5 -1
  87. data/lib/grape/middleware/versioner.rb +2 -0
  88. data/lib/grape/namespace.rb +14 -2
  89. data/lib/grape/parser/json.rb +3 -1
  90. data/lib/grape/parser/xml.rb +3 -1
  91. data/lib/grape/parser.rb +4 -2
  92. data/lib/grape/path.rb +16 -3
  93. data/lib/grape/presenters/presenter.rb +2 -0
  94. data/lib/grape/request.rb +21 -9
  95. data/lib/grape/router/attribute_translator.rb +41 -8
  96. data/lib/grape/router/pattern.rb +21 -17
  97. data/lib/grape/router/route.rb +15 -29
  98. data/lib/grape/router.rb +36 -29
  99. data/lib/grape/{serve_file → serve_stream}/file_body.rb +3 -1
  100. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +3 -1
  101. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +10 -8
  102. data/lib/grape/types/invalid_value.rb +8 -0
  103. data/lib/grape/util/base_inheritable.rb +43 -0
  104. data/lib/grape/util/cache.rb +20 -0
  105. data/lib/grape/util/endpoint_configuration.rb +8 -0
  106. data/lib/grape/util/env.rb +19 -17
  107. data/lib/grape/util/inheritable_setting.rb +3 -3
  108. data/lib/grape/util/inheritable_values.rb +7 -25
  109. data/lib/grape/util/json.rb +4 -0
  110. data/lib/grape/util/lazy_block.rb +27 -0
  111. data/lib/grape/util/lazy_object.rb +43 -0
  112. data/lib/grape/util/lazy_value.rb +99 -0
  113. data/lib/grape/util/registrable.rb +2 -0
  114. data/lib/grape/util/reverse_stackable_values.rb +10 -35
  115. data/lib/grape/util/stackable_values.rb +21 -34
  116. data/lib/grape/util/strict_hash_configuration.rb +3 -1
  117. data/lib/grape/util/xml.rb +2 -0
  118. data/lib/grape/validations/attributes_doc.rb +58 -0
  119. data/lib/grape/validations/attributes_iterator.rb +16 -6
  120. data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
  121. data/lib/grape/validations/params_scope.rb +174 -94
  122. data/lib/grape/validations/single_attribute_iterator.rb +24 -0
  123. data/lib/grape/validations/types/array_coercer.rb +63 -0
  124. data/lib/grape/validations/types/build_coercer.rb +47 -49
  125. data/lib/grape/validations/types/custom_type_coercer.rb +30 -51
  126. data/lib/grape/validations/types/custom_type_collection_coercer.rb +10 -25
  127. data/lib/grape/validations/types/dry_type_coercer.rb +72 -0
  128. data/lib/grape/validations/types/file.rb +22 -18
  129. data/lib/grape/validations/types/invalid_value.rb +17 -0
  130. data/lib/grape/validations/types/json.rb +47 -39
  131. data/lib/grape/validations/types/multiple_type_coercer.rb +14 -33
  132. data/lib/grape/validations/types/primitive_coercer.rb +75 -0
  133. data/lib/grape/validations/types/set_coercer.rb +38 -0
  134. data/lib/grape/validations/types/variant_collection_coercer.rb +5 -13
  135. data/lib/grape/validations/types.rb +106 -63
  136. data/lib/grape/validations/validator_factory.rb +8 -11
  137. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  138. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  139. data/lib/grape/validations/validators/as_validator.rb +14 -0
  140. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  141. data/lib/grape/validations/validators/base.rb +84 -68
  142. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  143. data/lib/grape/validations/validators/default_validator.rb +51 -0
  144. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  145. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  146. data/lib/grape/validations/validators/multiple_params_base.rb +27 -16
  147. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  148. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  149. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  150. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  151. data/lib/grape/validations/validators/values_validator.rb +88 -0
  152. data/lib/grape/validations.rb +18 -6
  153. data/lib/grape/version.rb +3 -1
  154. data/lib/grape.rb +175 -94
  155. data/spec/grape/api/custom_validations_spec.rb +117 -44
  156. data/spec/grape/api/deeply_included_options_spec.rb +4 -4
  157. data/spec/grape/api/defines_boolean_in_params_spec.rb +38 -0
  158. data/spec/grape/api/documentation_spec.rb +59 -0
  159. data/spec/grape/api/inherited_helpers_spec.rb +1 -1
  160. data/spec/grape/api/instance_spec.rb +103 -0
  161. data/spec/grape/api/invalid_format_spec.rb +3 -1
  162. data/spec/grape/api/namespace_parameters_in_route_spec.rb +1 -1
  163. data/spec/grape/api/nested_helpers_spec.rb +1 -1
  164. data/spec/grape/api/optional_parameters_in_route_spec.rb +1 -1
  165. data/spec/grape/api/parameters_modification_spec.rb +2 -2
  166. data/spec/grape/api/patch_method_helpers_spec.rb +1 -1
  167. data/spec/grape/api/recognize_path_spec.rb +2 -2
  168. data/spec/grape/api/required_parameters_in_route_spec.rb +1 -1
  169. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +1 -1
  170. data/spec/grape/api/routes_with_requirements_spec.rb +59 -0
  171. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +10 -16
  172. data/spec/grape/api/shared_helpers_spec.rb +1 -1
  173. data/spec/grape/api_remount_spec.rb +473 -0
  174. data/spec/grape/api_spec.rb +995 -231
  175. data/spec/grape/config_spec.rb +17 -0
  176. data/spec/grape/dsl/callbacks_spec.rb +3 -2
  177. data/spec/grape/dsl/desc_spec.rb +43 -17
  178. data/spec/grape/dsl/headers_spec.rb +40 -10
  179. data/spec/grape/dsl/helpers_spec.rb +6 -5
  180. data/spec/grape/dsl/inside_route_spec.rb +189 -38
  181. data/spec/grape/dsl/logger_spec.rb +17 -19
  182. data/spec/grape/dsl/middleware_spec.rb +11 -2
  183. data/spec/grape/dsl/parameters_spec.rb +3 -1
  184. data/spec/grape/dsl/request_response_spec.rb +8 -7
  185. data/spec/grape/dsl/routing_spec.rb +22 -9
  186. data/spec/grape/dsl/settings_spec.rb +1 -1
  187. data/spec/grape/dsl/validations_spec.rb +1 -16
  188. data/spec/grape/endpoint/declared_spec.rb +846 -0
  189. data/spec/grape/endpoint_spec.rb +136 -577
  190. data/spec/grape/entity_spec.rb +31 -24
  191. data/spec/grape/exceptions/base_spec.rb +81 -0
  192. data/spec/grape/exceptions/body_parse_errors_spec.rb +4 -1
  193. data/spec/grape/exceptions/invalid_accept_header_spec.rb +65 -23
  194. data/spec/grape/exceptions/invalid_formatter_spec.rb +1 -1
  195. data/spec/grape/exceptions/invalid_response_spec.rb +11 -0
  196. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +2 -2
  197. data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
  198. data/spec/grape/exceptions/missing_mime_type_spec.rb +1 -1
  199. data/spec/grape/exceptions/missing_option_spec.rb +2 -2
  200. data/spec/grape/exceptions/unknown_options_spec.rb +1 -1
  201. data/spec/grape/exceptions/unknown_validator_spec.rb +1 -1
  202. data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
  203. data/spec/grape/exceptions/validation_errors_spec.rb +21 -15
  204. data/spec/grape/exceptions/validation_spec.rb +6 -4
  205. data/spec/grape/extensions/param_builders/hash_spec.rb +8 -8
  206. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +9 -9
  207. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +9 -9
  208. data/spec/grape/integration/global_namespace_function_spec.rb +2 -2
  209. data/spec/grape/integration/rack_sendfile_spec.rb +14 -10
  210. data/spec/grape/integration/rack_spec.rb +25 -8
  211. data/spec/grape/loading_spec.rb +9 -9
  212. data/spec/grape/middleware/auth/base_spec.rb +2 -1
  213. data/spec/grape/middleware/auth/dsl_spec.rb +19 -10
  214. data/spec/grape/middleware/auth/strategies_spec.rb +62 -22
  215. data/spec/grape/middleware/base_spec.rb +36 -17
  216. data/spec/grape/middleware/error_spec.rb +11 -4
  217. data/spec/grape/middleware/exception_spec.rb +112 -162
  218. data/spec/grape/middleware/formatter_spec.rb +65 -29
  219. data/spec/grape/middleware/globals_spec.rb +8 -5
  220. data/spec/grape/middleware/stack_spec.rb +25 -13
  221. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +3 -2
  222. data/spec/grape/middleware/versioner/header_spec.rb +37 -14
  223. data/spec/grape/middleware/versioner/param_spec.rb +8 -2
  224. data/spec/grape/middleware/versioner/path_spec.rb +6 -2
  225. data/spec/grape/middleware/versioner_spec.rb +2 -2
  226. data/spec/grape/named_api_spec.rb +19 -0
  227. data/spec/grape/parser_spec.rb +10 -6
  228. data/spec/grape/path_spec.rb +53 -53
  229. data/spec/grape/presenters/presenter_spec.rb +8 -7
  230. data/spec/grape/request_spec.rb +29 -3
  231. data/spec/grape/util/inheritable_setting_spec.rb +9 -8
  232. data/spec/grape/util/inheritable_values_spec.rb +5 -3
  233. data/spec/grape/util/reverse_stackable_values_spec.rb +5 -2
  234. data/spec/grape/util/stackable_values_spec.rb +10 -7
  235. data/spec/grape/util/strict_hash_configuration_spec.rb +2 -1
  236. data/spec/grape/validations/attributes_doc_spec.rb +153 -0
  237. data/spec/grape/validations/instance_behaivour_spec.rb +13 -14
  238. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +40 -0
  239. data/spec/grape/validations/params_scope_spec.rb +568 -99
  240. data/spec/grape/validations/single_attribute_iterator_spec.rb +57 -0
  241. data/spec/grape/validations/types/array_coercer_spec.rb +33 -0
  242. data/spec/grape/validations/types/primitive_coercer_spec.rb +150 -0
  243. data/spec/grape/validations/types/set_coercer_spec.rb +32 -0
  244. data/spec/grape/validations/types_spec.rb +44 -45
  245. data/spec/grape/validations/validators/all_or_none_spec.rb +134 -32
  246. data/spec/grape/validations/validators/allow_blank_spec.rb +137 -141
  247. data/spec/grape/validations/validators/at_least_one_of_spec.rb +169 -31
  248. data/spec/grape/validations/validators/coerce_spec.rb +491 -151
  249. data/spec/grape/validations/validators/default_spec.rb +242 -78
  250. data/spec/grape/validations/validators/exactly_one_of_spec.rb +198 -40
  251. data/spec/grape/validations/validators/except_values_spec.rb +6 -5
  252. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +181 -30
  253. data/spec/grape/validations/validators/presence_spec.rb +45 -2
  254. data/spec/grape/validations/validators/regexp_spec.rb +27 -33
  255. data/spec/grape/validations/validators/same_as_spec.rb +57 -0
  256. data/spec/grape/validations/validators/values_spec.rb +227 -180
  257. data/spec/grape/validations_spec.rb +502 -72
  258. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  259. data/spec/integration/multi_json/json_spec.rb +2 -2
  260. data/spec/integration/multi_xml/xml_spec.rb +2 -2
  261. data/spec/shared/versioning_examples.rb +34 -29
  262. data/spec/spec_helper.rb +31 -5
  263. data/spec/support/basic_auth_encode_helpers.rb +3 -1
  264. data/spec/support/chunks.rb +14 -0
  265. data/spec/support/content_type_helpers.rb +2 -0
  266. data/spec/support/endpoint_faker.rb +2 -0
  267. data/spec/support/file_streamer.rb +2 -0
  268. data/spec/support/integer_helpers.rb +2 -0
  269. data/spec/support/versioned_helpers.rb +8 -8
  270. metadata +111 -61
  271. data/Appraisals +0 -32
  272. data/Dangerfile +0 -2
  273. data/Gemfile +0 -33
  274. data/Gemfile.lock +0 -231
  275. data/Guardfile +0 -10
  276. data/RELEASING.md +0 -111
  277. data/Rakefile +0 -25
  278. data/benchmark/simple.rb +0 -27
  279. data/benchmark/simple_with_type_coercer.rb +0 -22
  280. data/gemfiles/multi_json.gemfile +0 -35
  281. data/gemfiles/multi_xml.gemfile +0 -35
  282. data/gemfiles/rack_1.5.2.gemfile +0 -35
  283. data/gemfiles/rack_edge.gemfile +0 -35
  284. data/gemfiles/rails_3.gemfile +0 -36
  285. data/gemfiles/rails_4.gemfile +0 -35
  286. data/gemfiles/rails_5.gemfile +0 -35
  287. data/gemfiles/rails_edge.gemfile +0 -35
  288. data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +0 -18
  289. data/lib/grape/util/content_types.rb +0 -26
  290. data/lib/grape/validations/types/virtus_collection_patch.rb +0 -16
  291. data/lib/grape/validations/validators/all_or_none.rb +0 -20
  292. data/lib/grape/validations/validators/allow_blank.rb +0 -16
  293. data/lib/grape/validations/validators/as.rb +0 -15
  294. data/lib/grape/validations/validators/at_least_one_of.rb +0 -20
  295. data/lib/grape/validations/validators/coerce.rb +0 -74
  296. data/lib/grape/validations/validators/default.rb +0 -48
  297. data/lib/grape/validations/validators/exactly_one_of.rb +0 -29
  298. data/lib/grape/validations/validators/except_values.rb +0 -20
  299. data/lib/grape/validations/validators/mutual_exclusion.rb +0 -25
  300. data/lib/grape/validations/validators/presence.rb +0 -10
  301. data/lib/grape/validations/validators/regexp.rb +0 -11
  302. data/lib/grape/validations/validators/values.rb +0 -71
  303. data/pkg/grape-0.17.0.gem +0 -0
  304. data/pkg/grape-0.19.0.gem +0 -0
  305. data/spec/grape/dsl/configuration_spec.rb +0 -14
  306. data/spec/grape/validations/attributes_iterator_spec.rb +0 -4
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dry_type_coercer'
4
+
5
+ module Grape
6
+ module Validations
7
+ module Types
8
+ # Coerces the given value to a type defined via a +type+ argument during
9
+ # initialization. When +strict+ is true, it doesn't coerce a value but check
10
+ # that it has the proper type.
11
+ 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)
32
+ super
33
+
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
43
+ end
44
+
45
+ def call(val)
46
+ return InvalidValue.new if reject?(val)
47
+ return nil if val.nil? || treat_as_nil?(val)
48
+
49
+ super
50
+ end
51
+
52
+ protected
53
+
54
+ attr_reader :type
55
+
56
+ # This method maintains logic which was defined by Virtus. For example,
57
+ # dry-types is ok to convert an array or a hash to a string, it is supported,
58
+ # but Virtus wouldn't accept it. So, this method only exists to not introduce
59
+ # breaking changes.
60
+ def reject?(val)
61
+ (val.is_a?(Array) && type == String) ||
62
+ (val.is_a?(String) && type == Hash) ||
63
+ (val.is_a?(Hash) && type == String)
64
+ end
65
+
66
+ # Dry-Types treats an empty string as invalid. However, Grape considers an empty string as
67
+ # absence of a value and coerces it into nil. See a discussion there
68
+ # https://github.com/ruby-grape/grape/pull/2045
69
+ def treat_as_nil?(val)
70
+ val == '' && type != String
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative 'array_coercer'
5
+
6
+ module Grape
7
+ module Validations
8
+ module Types
9
+ # Takes the given array and converts it to a set. Every element of the set
10
+ # is also coerced.
11
+ class SetCoercer < ArrayCoercer
12
+ def initialize(type, strict = false)
13
+ super
14
+
15
+ @coercer = nil
16
+ end
17
+
18
+ def call(value)
19
+ return InvalidValue.new unless value.is_a?(Array)
20
+
21
+ coerce_elements(value)
22
+ end
23
+
24
+ protected
25
+
26
+ def coerce_elements(collection)
27
+ collection.each_with_object(Set.new) do |elem, memo|
28
+ coerced_elem = elem_coercer.call(elem)
29
+
30
+ return coerced_elem if coerced_elem.is_a?(InvalidValue)
31
+
32
+ memo.add(coerced_elem)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Validations
3
5
  module Types
4
6
  # This class wraps {MultipleTypeCoercer}, for use with collections
5
7
  # that allow members of more than one type.
6
- class VariantCollectionCoercer < Virtus::Attribute
8
+ class VariantCollectionCoercer
7
9
  # Construct a new coercer that will attempt to coerce
8
10
  # a list of values such that all members are of one of
9
11
  # the given types. The container may also optionally be
@@ -30,8 +32,8 @@ module Grape
30
32
  # @return [Array<Object>,Set<Object>,InvalidValue]
31
33
  # the coerced result, or an instance
32
34
  # of {InvalidValue} if the value could not be coerced.
33
- def coerce(value)
34
- return InvalidValue.new unless value.is_a? Array
35
+ def call(value)
36
+ return unless value.is_a? Array
35
37
 
36
38
  value =
37
39
  if @method
@@ -43,16 +45,6 @@ module Grape
43
45
 
44
46
  value
45
47
  end
46
-
47
- # Assert that the value has been coerced successfully.
48
- #
49
- # @param value [Object] a coerced result returned from {#coerce}
50
- # @return [true,false] whether or not the coerced value
51
- # satisfies type requirements.
52
- def value_coerced?(value)
53
- value.is_a?(@types.class) &&
54
- value.all? { |v| @member_coercer.success?(@types, v) }
55
- end
56
48
  end
57
49
  end
58
50
  end
@@ -1,14 +1,7 @@
1
- require_relative 'types/build_coercer'
2
- require_relative 'types/custom_type_coercer'
3
- require_relative 'types/custom_type_collection_coercer'
4
- require_relative 'types/multiple_type_coercer'
5
- require_relative 'types/variant_collection_coercer'
6
- require_relative 'types/json'
7
- require_relative 'types/file'
8
-
9
- # Patch for Virtus::Attribute::Collection
10
- # See the file for more details
11
- require_relative 'types/virtus_collection_patch'
1
+ # frozen_string_literal: true
2
+
3
+ require 'grape/validations/types/json'
4
+ require 'grape/validations/types/file'
12
5
 
13
6
  module Grape
14
7
  module Validations
@@ -23,12 +16,8 @@ module Grape
23
16
  # and {Grape::Dsl::Parameters#optional}. The main
24
17
  # entry point for this process is {Types.build_coercer}.
25
18
  module Types
26
- # Instances of this class may be used as tokens to denote that
27
- # a parameter value could not be coerced.
28
- class InvalidValue; end
19
+ module_function
29
20
 
30
- # Types representing a single value, which are coerced through Virtus
31
- # or special logic in Grape.
32
21
  PRIMITIVES = [
33
22
  # Numerical
34
23
  Integer,
@@ -42,41 +31,31 @@ module Grape
42
31
  Time,
43
32
 
44
33
  # Misc
45
- Virtus::Attribute::Boolean,
34
+ Grape::API::Boolean,
46
35
  String,
47
36
  Symbol,
48
- Rack::Multipart::UploadedFile
37
+ TrueClass,
38
+ FalseClass
49
39
  ].freeze
50
40
 
51
41
  # Types representing data structures.
52
- STRUCTURES = [
53
- Hash,
54
- Array,
55
- Set
56
- ].freeze
42
+ STRUCTURES = [Hash, Array, Set].freeze
57
43
 
58
- # Types for which Grape provides special coercion
59
- # and type-checking logic.
60
44
  SPECIAL = {
61
- JSON => Json,
45
+ ::JSON => Json,
62
46
  Array[JSON] => JsonArray,
63
47
  ::File => File,
64
48
  Rack::Multipart::UploadedFile => File
65
49
  }.freeze
66
50
 
67
- GROUPS = [
68
- Array,
69
- Hash,
70
- JSON,
71
- Array[JSON]
72
- ].freeze
51
+ GROUPS = [Array, Hash, JSON, Array[JSON]].freeze
73
52
 
74
53
  # Is the given class a primitive type as recognized by Grape?
75
54
  #
76
55
  # @param type [Class] type to check
77
56
  # @return [Boolean] whether or not the type is known by Grape as a valid
78
57
  # type for a single value
79
- def self.primitive?(type)
58
+ def primitive?(type)
80
59
  PRIMITIVES.include?(type)
81
60
  end
82
61
 
@@ -86,9 +65,7 @@ module Grape
86
65
  # @param type [Class] type to check
87
66
  # @return [Boolean] whether or not the type is known by Grape as a valid
88
67
  # data structure type
89
- # @note This method does not yet consider 'complex types', which inherit
90
- # Virtus.model.
91
- def self.structure?(type)
68
+ def structure?(type)
92
69
  STRUCTURES.include?(type)
93
70
  end
94
71
 
@@ -100,29 +77,10 @@ module Grape
100
77
  # @param type [Array<Class>,Set<Class>] type (or type list!) to check
101
78
  # @return [Boolean] +true+ if the given value will be treated as
102
79
  # a list of types.
103
- def self.multiple?(type)
80
+ def multiple?(type)
104
81
  (type.is_a?(Array) || type.is_a?(Set)) && type.size > 1
105
82
  end
106
83
 
107
- # Does the given class implement a type system that Grape
108
- # (i.e. the underlying virtus attribute system) supports
109
- # out-of-the-box? Currently supported are +axiom-types+
110
- # and +virtus+.
111
- #
112
- # The type will be passed to +Virtus::Attribute.build+,
113
- # and the resulting attribute object will be expected to
114
- # respond correctly to +coerce+ and +value_coerced?+.
115
- #
116
- # @param type [Class] type to check
117
- # @return [Boolean] +true+ where the type is recognized
118
- def self.recognized?(type)
119
- return false if type.is_a?(Array) || type.is_a?(Set)
120
-
121
- type.is_a?(Virtus::Attribute) ||
122
- type.ancestors.include?(Axiom::Types::Type) ||
123
- type.include?(Virtus::Model::Core)
124
- end
125
-
126
84
  # Does Grape provide special coercion and validation
127
85
  # routines for the given class? This does not include
128
86
  # automatic handling for primitives, structures and
@@ -130,7 +88,7 @@ module Grape
130
88
  #
131
89
  # @param type [Class] type to check
132
90
  # @return [Boolean] +true+ if special routines are available
133
- def self.special?(type)
91
+ def special?(type)
134
92
  SPECIAL.key? type
135
93
  end
136
94
 
@@ -139,7 +97,7 @@ module Grape
139
97
  #
140
98
  # @param type [Array<Class>,Class] type to check
141
99
  # @return [Boolean] +true+ if the type is a supported group type
142
- def self.group?(type)
100
+ def group?(type)
143
101
  GROUPS.include? type
144
102
  end
145
103
 
@@ -148,12 +106,10 @@ module Grape
148
106
  #
149
107
  # @param type [Class] type to check
150
108
  # @return [Boolean] whether or not the type can be used as a custom type
151
- def self.custom?(type)
109
+ def custom?(type)
152
110
  !primitive?(type) &&
153
111
  !structure?(type) &&
154
112
  !multiple?(type) &&
155
- !recognized?(type) &&
156
- !special?(type) &&
157
113
  type.respond_to?(:parse) &&
158
114
  type.method(:parse).arity == 1
159
115
  end
@@ -163,11 +119,98 @@ module Grape
163
119
  # @param type [Array<Class>,Class] type to check
164
120
  # @return [Boolean] true if +type+ is a collection of a type that implements
165
121
  # its own +#parse+ method.
166
- def self.collection_of_custom?(type)
122
+ def collection_of_custom?(type)
167
123
  (type.is_a?(Array) || type.is_a?(Set)) &&
168
124
  type.length == 1 &&
169
- custom?(type.first)
125
+ (custom?(type.first) || special?(type.first))
126
+ end
127
+
128
+ def map_special(type)
129
+ SPECIAL.fetch(type, type)
170
130
  end
131
+
132
+ # Chooses the best coercer for the given type. For example, if the type
133
+ # is Integer, it will return a coercer which will be able to coerce a value
134
+ # to the integer.
135
+ #
136
+ # There are a few very special coercers which might be returned.
137
+ #
138
+ # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
139
+ # the given type implies values in an array with different types.
140
+ # For example, +[Integer, String]+ allows integer and string values in
141
+ # an array.
142
+ #
143
+ # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
144
+ # a method is specified by a user with +coerce_with+ option or the user
145
+ # specifies a custom type which implements requirments of
146
+ # +Grape::Types::CustomTypeCoercer+.
147
+ #
148
+ # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
149
+ # previous one, but it expects an array or set of values having a custom
150
+ # type implemented by the user.
151
+ #
152
+ # There is also a group of custom types implemented by Grape, check
153
+ # +Grape::Validations::Types::SPECIAL+ to get the full list.
154
+ #
155
+ # @param type [Class] the type to which input strings
156
+ # should be coerced
157
+ # @param method [Class,#call] the coercion method to use
158
+ # @return [Object] object to be used
159
+ # for coercion and type validation
160
+ 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
165
+
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
188
+ end
189
+
190
+ def cache_instance(type, method, strict, &_block)
191
+ key = cache_key(type, method, strict)
192
+
193
+ return @__cache[key] if @__cache.key?(key)
194
+
195
+ instance = yield
196
+
197
+ @__cache_write_lock.synchronize do
198
+ @__cache[key] = instance
199
+ end
200
+
201
+ instance
202
+ end
203
+
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
209
+ end
210
+ end
211
+
212
+ instance_variable_set(:@__cache, {})
213
+ instance_variable_set(:@__cache_write_lock, Mutex.new)
171
214
  end
172
215
  end
173
216
  end
@@ -1,17 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Validations
3
5
  class ValidatorFactory
4
- def initialize(**options)
5
- @validator_class = options.delete(:validator_class)
6
- @options = options
7
- end
8
-
9
- def create_validator
10
- @validator_class.new(@options[:attributes],
11
- @options[:options],
12
- @options[:required],
13
- @options[:params_scope],
14
- @options[:opts])
6
+ def self.create_validator(**options)
7
+ options[:validator_class].new(options[:attributes],
8
+ options[:options],
9
+ options[:required],
10
+ options[:params_scope],
11
+ **options[:opts])
15
12
  end
16
13
  end
17
14
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AllOrNoneOfValidator < MultipleParamsBase
7
+ def validate_params!(params)
8
+ keys = keys_in_common(params)
9
+ return if keys.empty? || keys.length == all_keys.length
10
+
11
+ raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:all_or_none))
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AllowBlankValidator < Base
7
+ def validate_param!(attr_name, params)
8
+ return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash)
9
+
10
+ value = params[attr_name]
11
+ value = value.strip if value.respond_to?(:strip)
12
+
13
+ return if value == false || value.present?
14
+
15
+ raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:blank))
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AsValidator < Base
7
+ # We use a validator for renaming parameters. This is just a marker for
8
+ # the parameter scope to handle the renaming. No actual validation
9
+ # happens here.
10
+ def validate_param!(*); end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AtLeastOneOfValidator < MultipleParamsBase
7
+ def validate_params!(params)
8
+ return unless keys_in_common(params).empty?
9
+
10
+ raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:at_least_one))
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,86 +1,102 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  module Validations
3
- class Base
4
- attr_reader :attrs
5
+ module Validators
6
+ class Base
7
+ attr_reader :attrs
5
8
 
6
- # Creates a new Validator from options specified
7
- # by a +requires+ or +optional+ directive during
8
- # parameter definition.
9
- # @param attrs [Array] names of attributes to which the Validator applies
10
- # @param options [Object] implementation-dependent Validator options
11
- # @param required [Boolean] attribute(s) are required or optional
12
- # @param scope [ParamsScope] parent scope for this Validator
13
- # @param opts [Hash] additional validation options
14
- def initialize(attrs, options, required, scope, opts = {})
15
- @attrs = Array(attrs)
16
- @option = options
17
- @required = required
18
- @scope = scope
19
- @fail_fast = opts[:fail_fast] || false
20
- end
9
+ # Creates a new Validator from options specified
10
+ # by a +requires+ or +optional+ directive during
11
+ # parameter definition.
12
+ # @param attrs [Array] names of attributes to which the Validator applies
13
+ # @param options [Object] implementation-dependent Validator options
14
+ # @param required [Boolean] attribute(s) are required or optional
15
+ # @param scope [ParamsScope] parent scope for this Validator
16
+ # @param opts [Array] additional validation options
17
+ def initialize(attrs, options, required, scope, *opts)
18
+ @attrs = Array(attrs)
19
+ @option = options
20
+ @required = required
21
+ @scope = scope
22
+ opts = opts.any? ? opts.shift : {}
23
+ @fail_fast = opts.fetch(:fail_fast, false)
24
+ @allow_blank = opts.fetch(:allow_blank, false)
25
+ end
21
26
 
22
- # Validates a given request.
23
- # @note Override #validate! unless you need to access the entire request.
24
- # @param request [Grape::Request] the request currently being handled
25
- # @raise [Grape::Exceptions::Validation] if validation failed
26
- # @return [void]
27
- def validate(request)
28
- return unless @scope.should_validate?(request.params)
29
- validate!(request.params)
30
- end
27
+ # Validates a given request.
28
+ # @note Override #validate! unless you need to access the entire request.
29
+ # @param request [Grape::Request] the request currently being handled
30
+ # @raise [Grape::Exceptions::Validation] if validation failed
31
+ # @return [void]
32
+ def validate(request)
33
+ return unless @scope.should_validate?(request.params)
34
+
35
+ validate!(request.params)
36
+ end
31
37
 
32
- # Validates a given parameter hash.
33
- # @note Override #validate if you need to access the entire request.
34
- # @param params [Hash] parameters to validate
35
- # @raise [Grape::Exceptions::Validation] if validation failed
36
- # @return [void]
37
- def validate!(params)
38
- attributes = AttributesIterator.new(self, @scope, params)
39
- array_errors = []
40
- attributes.each do |resource_params, attr_name|
41
- next if !@scope.required? && resource_params.empty?
42
- next unless @required || (resource_params.respond_to?(:key?) && resource_params.key?(attr_name))
43
- next unless @scope.meets_dependency?(resource_params, params)
38
+ # Validates a given parameter hash.
39
+ # @note Override #validate if you need to access the entire request.
40
+ # @param params [Hash] parameters to validate
41
+ # @raise [Grape::Exceptions::Validation] if validation failed
42
+ # @return [void]
43
+ def validate!(params)
44
+ attributes = SingleAttributeIterator.new(self, @scope, params)
45
+ # we collect errors inside array because
46
+ # there may be more than one error per field
47
+ array_errors = []
44
48
 
45
- begin
46
- validate_param!(attr_name, resource_params)
47
- rescue Grape::Exceptions::Validation => e
48
- # we collect errors inside array because
49
- # there may be more than one error per field
50
- array_errors << e
49
+ attributes.each do |val, attr_name, empty_val, skip_value|
50
+ next if skip_value
51
+ next if !@scope.required? && empty_val
52
+ next unless @scope.meets_dependency?(val, params)
53
+
54
+ begin
55
+ validate_param!(attr_name, val) if @required || (val.respond_to?(:key?) && val.key?(attr_name))
56
+ rescue Grape::Exceptions::Validation => e
57
+ array_errors << e
58
+ end
51
59
  end
60
+
61
+ raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any?
52
62
  end
53
63
 
54
- raise Grape::Exceptions::ValidationArrayErrors, array_errors if array_errors.any?
55
- end
64
+ def self.convert_to_short_name(klass)
65
+ ret = klass.name.gsub(/::/, '/')
66
+ ret.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
67
+ ret.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
68
+ ret.tr!('-', '_')
69
+ ret.downcase!
70
+ File.basename(ret, '_validator')
71
+ end
56
72
 
57
- def self.convert_to_short_name(klass)
58
- ret = klass.name.gsub(/::/, '/')
59
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
60
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
61
- .tr('-', '_')
62
- .downcase
63
- File.basename(ret, '_validator')
64
- end
73
+ def self.inherited(klass)
74
+ return unless klass.name.present?
65
75
 
66
- def self.inherited(klass)
67
- return unless klass.name.present?
68
- Validations.register_validator(convert_to_short_name(klass), klass)
69
- end
76
+ Validations.register_validator(convert_to_short_name(klass), klass)
77
+ end
70
78
 
71
- def message(default_key = nil)
72
- options = instance_variable_get(:@option)
73
- options_key?(:message) ? options[:message] : default_key
74
- end
79
+ def message(default_key = nil)
80
+ options = instance_variable_get(:@option)
81
+ options_key?(:message) ? options[:message] : default_key
82
+ end
75
83
 
76
- def options_key?(key, options = nil)
77
- options = instance_variable_get(:@option) if options.nil?
78
- options.respond_to?(:key?) && options.key?(key) && !options[key].nil?
79
- end
84
+ def options_key?(key, options = nil)
85
+ options = instance_variable_get(:@option) if options.nil?
86
+ options.respond_to?(:key?) && options.key?(key) && !options[key].nil?
87
+ end
80
88
 
81
- def fail_fast?
82
- @fail_fast
89
+ def fail_fast?
90
+ @fail_fast
91
+ end
83
92
  end
84
93
  end
85
94
  end
86
95
  end
96
+
97
+ Grape::Validations::Base = Class.new(Grape::Validations::Validators::Base) do
98
+ def initialize(*)
99
+ super
100
+ warn '[DEPRECATION] `Grape::Validations::Base` is deprecated. Use `Grape::Validations::Validators::Base` instead.'
101
+ end
102
+ end