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
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'attributes_doc'
4
+
1
5
  module Grape
2
6
  module Validations
3
7
  class ParamsScope
@@ -6,11 +10,42 @@ module Grape
6
10
 
7
11
  include Grape::DSL::Parameters
8
12
 
13
+ class Attr
14
+ attr_accessor :key, :scope
15
+
16
+ # Open up a new ParamsScope::Attr
17
+ # @param key [Hash, Symbol] key of attr
18
+ # @param scope [Grape::Validations::ParamsScope] scope of attr
19
+ def initialize(key, scope)
20
+ @key = key
21
+ @scope = scope
22
+ end
23
+
24
+ # @return Array[Symbol, Hash[Symbol => Array]] declared_params with symbol instead of Attr
25
+ def self.attrs_keys(declared_params)
26
+ declared_params.map do |declared_param_attr|
27
+ attr_key(declared_param_attr)
28
+ end
29
+ end
30
+
31
+ def self.attr_key(declared_param_attr)
32
+ return attr_key(declared_param_attr.key) if declared_param_attr.is_a?(self)
33
+
34
+ if declared_param_attr.is_a?(Hash)
35
+ declared_param_attr.transform_values { |value| attrs_keys(value) }
36
+ else
37
+ declared_param_attr
38
+ end
39
+ end
40
+ end
41
+
9
42
  # Open up a new ParamsScope, allowing parameter definitions per
10
43
  # Grape::DSL::Params.
11
44
  # @param opts [Hash] options for this scope
12
45
  # @option opts :element [Symbol] the element that contains this scope; for
13
46
  # this to be relevant, @parent must be set
47
+ # @option opts :element_renamed [Symbol, nil] whenever this scope should
48
+ # be renamed and to what, given +nil+ no renaming is done
14
49
  # @option opts :parent [ParamsScope] the scope containing this scope
15
50
  # @option opts :api [API] the API endpoint to modify
16
51
  # @option opts :optional [Boolean] whether or not this scope needs to have
@@ -21,38 +56,59 @@ module Grape
21
56
  # validate if this param is present in the parent scope
22
57
  # @yield the instance context, open for parameter definitions
23
58
  def initialize(opts, &block)
24
- @element = opts[:element]
25
- @parent = opts[:parent]
26
- @api = opts[:api]
27
- @optional = opts[:optional] || false
28
- @type = opts[:type]
29
- @group = opts[:group] || {}
30
- @dependent_on = opts[:dependent_on]
59
+ @element = opts[:element]
60
+ @element_renamed = opts[:element_renamed]
61
+ @parent = opts[:parent]
62
+ @api = opts[:api]
63
+ @optional = opts[:optional] || false
64
+ @type = opts[:type]
65
+ @group = opts[:group]
66
+ @dependent_on = opts[:dependent_on]
31
67
  @declared_params = []
32
68
  @index = nil
33
69
 
34
- instance_eval(&block) if block_given?
70
+ instance_eval(&block) if block
35
71
 
36
72
  configure_declared_params
37
73
  end
38
74
 
75
+ def configuration
76
+ @api.configuration.respond_to?(:evaluate) ? @api.configuration.evaluate : @api.configuration
77
+ end
78
+
39
79
  # @return [Boolean] whether or not this entire scope needs to be
40
80
  # validated
41
81
  def should_validate?(parameters)
42
- return false if @optional && (params(parameters).blank? || all_element_blank?(parameters))
43
- return false unless meets_dependency?(params(parameters), parameters)
82
+ scoped_params = params(parameters)
83
+
84
+ return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))
85
+ return false unless meets_dependency?(scoped_params, parameters)
44
86
  return true if parent.nil?
87
+
45
88
  parent.should_validate?(parameters)
46
89
  end
47
90
 
48
91
  def meets_dependency?(params, request_params)
49
- if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
50
- return false
51
- end
52
-
53
92
  return true unless @dependent_on
93
+
94
+ return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
95
+
54
96
  return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
55
- params = params.with_indifferent_access
97
+
98
+ meets_hash_dependency?(params)
99
+ end
100
+
101
+ def attr_meets_dependency?(params)
102
+ return true unless @dependent_on
103
+
104
+ return false if @parent.present? && !@parent.attr_meets_dependency?(params)
105
+
106
+ meets_hash_dependency?(params)
107
+ end
108
+
109
+ def meets_hash_dependency?(params)
110
+ # params might be anything what looks like a hash, so it must implement a `key?` method
111
+ return false unless params.respond_to?(:key?)
56
112
 
57
113
  @dependent_on.each do |dependency|
58
114
  if dependency.is_a?(Hash)
@@ -71,7 +127,7 @@ module Grape
71
127
  def full_name(name, index: nil)
72
128
  if nested?
73
129
  # Find our containing element's name, and append ours.
74
- [@parent.full_name(@element), [@index || index, name].map(&method(:brackets))].compact.join
130
+ "#{@parent.full_name(@element)}#{brackets(@index || index)}#{brackets(name)}"
75
131
  elsif lateral?
76
132
  # Find the name of the element as if it was at the same nesting level
77
133
  # as our parent. We need to forward our index upward to achieve this.
@@ -115,20 +171,39 @@ module Grape
115
171
  # Adds a parameter declaration to our list of validations.
116
172
  # @param attrs [Array] (see Grape::DSL::Parameters#requires)
117
173
  def push_declared_params(attrs, **opts)
174
+ opts = opts.merge(declared_params_scope: self) unless opts.key?(:declared_params_scope)
118
175
  if lateral?
119
- @parent.push_declared_params(attrs, opts)
176
+ @parent.push_declared_params(attrs, **opts)
120
177
  else
121
- if opts && opts[:as]
122
- @api.route_setting(:aliased_params, @api.route_setting(:aliased_params) || [])
123
- @api.route_setting(:aliased_params) << { attrs.first => opts[:as] }
124
- end
178
+ push_renamed_param(full_path + [attrs.first], opts[:as]) \
179
+ if opts && opts[:as]
125
180
 
126
- @declared_params.concat attrs
181
+ @declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })
127
182
  end
128
183
  end
129
184
 
185
+ # Get the full path of the parameter scope in the hierarchy.
186
+ #
187
+ # @return [Array<Symbol>] the nesting/path of the current parameter scope
188
+ def full_path
189
+ nested? ? @parent.full_path + [@element] : []
190
+ end
191
+
130
192
  private
131
193
 
194
+ # Add a new parameter which should be renamed when using the +#declared+
195
+ # method.
196
+ #
197
+ # @param path [Array<String, Symbol>] the full path of the parameter
198
+ # (including the parameter name as last array element)
199
+ # @param new_name [String, Symbol] the new name of the parameter (the
200
+ # renamed name, with the +as: ...+ semantic)
201
+ def push_renamed_param(path, new_name)
202
+ base = @api.route_setting(:renamed_params) || {}
203
+ base[Array(path).map(&:to_s)] = new_name.to_s
204
+ @api.route_setting(:renamed_params, base)
205
+ end
206
+
132
207
  def require_required_and_optional_fields(context, opts)
133
208
  if context == :all
134
209
  optional_fields = Array(opts[:except])
@@ -140,6 +215,7 @@ module Grape
140
215
  required_fields.each do |field|
141
216
  field_opts = opts[:using][field]
142
217
  raise ArgumentError, "required field not exist: #{field}" unless field_opts
218
+
143
219
  requires(field, field_opts)
144
220
  end
145
221
  optional_fields.each do |field|
@@ -174,18 +250,17 @@ module Grape
174
250
  # if required params are grouped and no type or unsupported type is provided, raise an error
175
251
  type = attrs[1] ? attrs[1][:type] : nil
176
252
  if attrs.first && !optional
177
- raise Grape::Exceptions::MissingGroupTypeError.new if type.nil?
178
- raise Grape::Exceptions::UnsupportedGroupTypeError.new unless Grape::Validations::Types.group?(type)
253
+ raise Grape::Exceptions::MissingGroupType if type.nil?
254
+ raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)
179
255
  end
180
256
 
181
- opts = attrs[1] || { type: Array }
182
-
183
257
  self.class.new(
184
- api: @api,
185
- element: attrs.first,
186
- parent: self,
258
+ api: @api,
259
+ element: attrs.first,
260
+ element_renamed: attrs[1][:as],
261
+ parent: self,
187
262
  optional: optional,
188
- type: opts[:type],
263
+ type: type || Array,
189
264
  &block
190
265
  )
191
266
  end
@@ -199,11 +274,11 @@ module Grape
199
274
  # @yield parameter scope
200
275
  def new_lateral_scope(options, &block)
201
276
  self.class.new(
202
- api: @api,
203
- element: nil,
204
- parent: self,
205
- options: @optional,
206
- type: type == Array ? Array : Hash,
277
+ api: @api,
278
+ element: nil,
279
+ parent: self,
280
+ options: @optional,
281
+ type: type == Array ? Array : Hash,
207
282
  dependent_on: options[:dependent_on],
208
283
  &block
209
284
  )
@@ -216,37 +291,36 @@ module Grape
216
291
  # @yield parameter scope
217
292
  def new_group_scope(attrs, &block)
218
293
  self.class.new(
219
- api: @api,
220
- parent: self,
221
- group: attrs.first,
294
+ api: @api,
295
+ parent: self,
296
+ group: attrs.first,
222
297
  &block
223
298
  )
224
299
  end
225
300
 
226
301
  # Pushes declared params to parent or settings
227
302
  def configure_declared_params
303
+ push_renamed_param(full_path, @element_renamed) if @element_renamed
304
+
228
305
  if nested?
229
306
  @parent.push_declared_params [element => @declared_params]
230
307
  else
231
308
  @api.namespace_stackable(:declared_params, @declared_params)
232
-
233
- @api.route_setting(:declared_params, []) unless @api.route_setting(:declared_params)
234
- @api.route_setting(:declared_params, @api.namespace_stackable(:declared_params).flatten)
235
309
  end
310
+
311
+ # params were stored in settings, it can be cleaned from the params scope
312
+ @declared_params = nil
236
313
  end
237
314
 
238
315
  def validates(attrs, validations)
239
- doc_attrs = { required: validations.keys.include?(:presence) }
316
+ doc = AttributesDoc.new @api, self
317
+ doc.extract_details validations
240
318
 
241
319
  coerce_type = infer_coercion(validations)
242
320
 
243
- doc_attrs[:type] = coerce_type.to_s if coerce_type
244
-
245
- desc = validations.delete(:desc) || validations.delete(:description)
246
- doc_attrs[:desc] = desc if desc
321
+ doc.type = coerce_type
247
322
 
248
323
  default = validations[:default]
249
- doc_attrs[:default] = default if validations.key?(:default)
250
324
 
251
325
  if (values_hash = validations[:values]).is_a? Hash
252
326
  values = values_hash[:value]
@@ -255,7 +329,8 @@ module Grape
255
329
  else
256
330
  values = validations[:values]
257
331
  end
258
- doc_attrs[:values] = values if values
332
+
333
+ doc.values = values
259
334
 
260
335
  except_values = options_key?(:except_values, :value, validations) ? validations[:except_values][:value] : validations[:except_values]
261
336
 
@@ -271,29 +346,22 @@ module Grape
271
346
  # type should be compatible with values array, if both exist
272
347
  validate_value_coercion(coerce_type, values, except_values, excepts)
273
348
 
274
- doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
275
-
276
- full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
277
- @api.document_attribute(full_attrs, doc_attrs)
349
+ doc.document attrs
278
350
 
279
- # slice out fail_fast attribute
280
- opts = {}
281
- opts[:fail_fast] = validations.delete(:fail_fast) || false
351
+ opts = derive_validator_options(validations)
282
352
 
283
353
  # Validate for presence before any other validators
284
- if validations.key?(:presence) && validations[:presence]
285
- validate('presence', validations[:presence], attrs, doc_attrs, opts)
286
- validations.delete(:presence)
287
- validations.delete(:message) if validations.key?(:message)
288
- end
354
+ validates_presence(validations, attrs, doc, opts)
289
355
 
290
356
  # Before we run the rest of the validators, let's handle
291
357
  # whatever coercion so that we are working with correctly
292
358
  # type casted values
293
- coerce_type validations, attrs, doc_attrs, opts
359
+ coerce_type validations, attrs, doc, opts
294
360
 
295
361
  validations.each do |type, options|
296
- validate(type, options, attrs, doc_attrs, opts)
362
+ next if type == :as
363
+
364
+ validate(type, options, attrs, doc, opts)
297
365
  end
298
366
  end
299
367
 
@@ -311,9 +379,7 @@ module Grape
311
379
  # @return [class-like] type to which the parameter will be coerced
312
380
  # @raise [ArgumentError] if the given type options are invalid
313
381
  def infer_coercion(validations)
314
- if validations.key?(:type) && validations.key?(:types)
315
- raise ArgumentError, ':type may not be supplied with :types'
316
- end
382
+ raise ArgumentError, ':type may not be supplied with :types' if validations.key?(:type) && validations.key?(:types)
317
383
 
318
384
  validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type)
319
385
  validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type)
@@ -349,6 +415,7 @@ module Grape
349
415
  # but not special JSON types, which
350
416
  # already imply coercion method
351
417
  return unless [JSON, Array[JSON]].include? validations[:coerce]
418
+
352
419
  raise ArgumentError, 'coerce_with disallowed for type: JSON'
353
420
  end
354
421
 
@@ -358,7 +425,7 @@ module Grape
358
425
  # composited from more than one +requires+/+optional+
359
426
  # parameter, and needs to be run before most other
360
427
  # validations.
361
- def coerce_type(validations, attrs, doc_attrs, opts)
428
+ def coerce_type(validations, attrs, doc, opts)
362
429
  check_coerce_with(validations)
363
430
 
364
431
  return unless validations.key?(:coerce)
@@ -368,7 +435,7 @@ module Grape
368
435
  method: validations[:coerce_with],
369
436
  message: validations[:coerce_message]
370
437
  }
371
- validate('coerce', coerce_options, attrs, doc_attrs, opts)
438
+ validate('coerce', coerce_options, attrs, doc, opts)
372
439
  validations.delete(:coerce_with)
373
440
  validations.delete(:coerce)
374
441
  validations.delete(:coerce_message)
@@ -376,6 +443,7 @@ module Grape
376
443
 
377
444
  def guess_coerce_type(coerce_type, *values_list)
378
445
  return coerce_type unless coerce_type == Array
446
+
379
447
  values_list.each do |values|
380
448
  next if !values || values.is_a?(Proc)
381
449
  return values.first.class if values.is_a?(Range) || !values.empty?
@@ -386,14 +454,11 @@ module Grape
386
454
  def check_incompatible_option_values(default, values, except_values, excepts)
387
455
  return unless default && !default.is_a?(Proc)
388
456
 
389
- if values && !values.is_a?(Proc)
390
- raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) \
391
- unless Array(default).all? { |def_val| values.include?(def_val) }
392
- end
457
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) if values && !values.is_a?(Proc) && !Array(default).all? { |def_val| values.include?(def_val) }
393
458
 
394
- if except_values && !except_values.is_a?(Proc)
459
+ if except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
395
460
  raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) \
396
- unless Array(default).none? { |def_val| except_values.include?(def_val) }
461
+
397
462
  end
398
463
 
399
464
  return unless excepts && !excepts.is_a?(Proc)
@@ -401,37 +466,34 @@ module Grape
401
466
  unless Array(default).none? { |def_val| excepts.include?(def_val) }
402
467
  end
403
468
 
404
- def validate(type, options, attrs, doc_attrs, opts)
405
- validator_class = Validations.validators[type.to_s]
406
-
407
- raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
408
-
409
- factory = Grape::Validations::ValidatorFactory.new(attributes: attrs,
410
- options: options,
411
- required: doc_attrs[:required],
412
- params_scope: self,
413
- opts: opts,
414
- validator_class: validator_class)
415
- @api.namespace_stackable(:validations, factory)
469
+ def validate(type, options, attrs, doc, opts)
470
+ validator_options = {
471
+ attributes: attrs,
472
+ options: options,
473
+ required: doc.required,
474
+ params_scope: self,
475
+ opts: opts,
476
+ validator_class: Validations.require_validator(type)
477
+ }
478
+ @api.namespace_stackable(:validations, validator_options)
416
479
  end
417
480
 
418
481
  def validate_value_coercion(coerce_type, *values_list)
419
482
  return unless coerce_type
483
+
420
484
  coerce_type = coerce_type.first if coerce_type.is_a?(Array)
421
485
  values_list.each do |values|
422
486
  next if !values || values.is_a?(Proc)
423
- value_types = values.is_a?(Range) ? [values.begin, values.end] : values
424
- if coerce_type == Virtus::Attribute::Boolean
425
- value_types = value_types.map { |type| Virtus::Attribute.build(type) }
426
- end
427
- unless value_types.all? { |v| v.is_a? coerce_type }
428
- raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
429
- end
487
+
488
+ value_types = values.is_a?(Range) ? [values.begin, values.end].compact : values
489
+ value_types = value_types.map { |type| Grape::API::Boolean.build(type) } if coerce_type == Grape::API::Boolean
490
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values) unless value_types.all?(coerce_type)
430
491
  end
431
492
  end
432
493
 
433
494
  def extract_message_option(attrs)
434
495
  return nil unless attrs.is_a?(Array)
496
+
435
497
  opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
436
498
  opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil
437
499
  end
@@ -440,8 +502,26 @@ module Grape
440
502
  validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil?
441
503
  end
442
504
 
443
- def all_element_blank?(parameters)
444
- params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
505
+ def all_element_blank?(scoped_params)
506
+ scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?)
507
+ end
508
+
509
+ # Validators don't have access to each other and they don't need, however,
510
+ # some validators might influence others, so their options should be shared
511
+ def derive_validator_options(validations)
512
+ allow_blank = validations[:allow_blank]
513
+
514
+ {
515
+ allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,
516
+ fail_fast: validations.delete(:fail_fast) || false
517
+ }
518
+ end
519
+
520
+ def validates_presence(validations, attrs, doc, opts)
521
+ return unless validations.key?(:presence) && validations[:presence]
522
+
523
+ validate(:presence, validations.delete(:presence), attrs, doc, opts)
524
+ validations.delete(:message) if validations.key?(:message)
445
525
  end
446
526
  end
447
527
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ class SingleAttributeIterator < AttributesIterator
6
+ private
7
+
8
+ def yield_attributes(val, attrs)
9
+ attrs.each do |attr_name|
10
+ yield val, attr_name, empty?(val), skip?(val)
11
+ end
12
+ end
13
+
14
+ # Primitives like Integers and Booleans don't respond to +empty?+.
15
+ # It could be possible to use +blank?+ instead, but
16
+ #
17
+ # false.blank?
18
+ # => true
19
+ def empty?(val)
20
+ val.respond_to?(:empty?) ? val.empty? : val.nil?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,63 @@
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
+ def initialize(type, strict = false)
18
+ super
19
+
20
+ @coercer = scope::Array
21
+ @subtype = type.first
22
+ end
23
+
24
+ def call(_val)
25
+ collection = super
26
+ return collection if collection.is_a?(InvalidValue)
27
+
28
+ coerce_elements collection
29
+ end
30
+
31
+ protected
32
+
33
+ attr_reader :subtype
34
+
35
+ def coerce_elements(collection)
36
+ return if collection.nil?
37
+
38
+ collection.each_with_index do |elem, index|
39
+ return InvalidValue.new if reject?(elem)
40
+
41
+ coerced_elem = elem_coercer.call(elem)
42
+
43
+ return coerced_elem if coerced_elem.is_a?(InvalidValue)
44
+
45
+ collection[index] = coerced_elem
46
+ end
47
+
48
+ collection
49
+ end
50
+
51
+ # This method maintains logic which was defined by Virtus for arrays.
52
+ # Virtus doesn't allow nil in arrays.
53
+ def reject?(val)
54
+ val.nil?
55
+ end
56
+
57
+ def elem_coercer
58
+ @elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end