grape 1.5.3 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -0
  3. data/CONTRIBUTING.md +32 -1
  4. data/README.md +176 -25
  5. data/UPGRADING.md +61 -4
  6. data/grape.gemspec +6 -6
  7. data/lib/grape/api/instance.rb +14 -18
  8. data/lib/grape/api.rb +17 -12
  9. data/lib/grape/cookies.rb +2 -0
  10. data/lib/grape/dry_types.rb +12 -0
  11. data/lib/grape/dsl/api.rb +0 -2
  12. data/lib/grape/dsl/callbacks.rb +0 -2
  13. data/lib/grape/dsl/configuration.rb +0 -2
  14. data/lib/grape/dsl/desc.rb +4 -20
  15. data/lib/grape/dsl/headers.rb +5 -2
  16. data/lib/grape/dsl/helpers.rb +7 -7
  17. data/lib/grape/dsl/inside_route.rb +43 -30
  18. data/lib/grape/dsl/middleware.rb +4 -6
  19. data/lib/grape/dsl/parameters.rb +13 -10
  20. data/lib/grape/dsl/request_response.rb +9 -8
  21. data/lib/grape/dsl/routing.rb +6 -4
  22. data/lib/grape/dsl/settings.rb +5 -7
  23. data/lib/grape/dsl/validations.rb +0 -15
  24. data/lib/grape/endpoint.rb +22 -37
  25. data/lib/grape/error_formatter/json.rb +9 -7
  26. data/lib/grape/error_formatter/xml.rb +2 -6
  27. data/lib/grape/exceptions/base.rb +3 -2
  28. data/lib/grape/exceptions/missing_group_type.rb +8 -1
  29. data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
  30. data/lib/grape/exceptions/unsupported_group_type.rb +8 -1
  31. data/lib/grape/exceptions/validation.rb +1 -6
  32. data/lib/grape/formatter/json.rb +1 -0
  33. data/lib/grape/formatter/serializable_hash.rb +2 -1
  34. data/lib/grape/formatter/xml.rb +1 -0
  35. data/lib/grape/locale/en.yml +9 -8
  36. data/lib/grape/middleware/auth/dsl.rb +7 -2
  37. data/lib/grape/middleware/base.rb +3 -1
  38. data/lib/grape/middleware/error.rb +2 -2
  39. data/lib/grape/middleware/formatter.rb +4 -4
  40. data/lib/grape/middleware/stack.rb +3 -3
  41. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  42. data/lib/grape/middleware/versioner/header.rb +6 -4
  43. data/lib/grape/middleware/versioner/param.rb +1 -0
  44. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  45. data/lib/grape/middleware/versioner/path.rb +2 -0
  46. data/lib/grape/path.rb +1 -0
  47. data/lib/grape/request.rb +4 -1
  48. data/lib/grape/router/attribute_translator.rb +1 -1
  49. data/lib/grape/router/pattern.rb +1 -1
  50. data/lib/grape/router/route.rb +2 -2
  51. data/lib/grape/router.rb +6 -0
  52. data/lib/grape/types/invalid_value.rb +8 -0
  53. data/lib/grape/util/cache.rb +1 -1
  54. data/lib/grape/util/inheritable_setting.rb +1 -3
  55. data/lib/grape/util/json.rb +2 -0
  56. data/lib/grape/util/lazy_value.rb +3 -2
  57. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  58. data/lib/grape/validations/attributes_doc.rb +58 -0
  59. data/lib/grape/validations/params_scope.rb +138 -79
  60. data/lib/grape/validations/types/array_coercer.rb +0 -2
  61. data/lib/grape/validations/types/custom_type_coercer.rb +1 -0
  62. data/lib/grape/validations/types/dry_type_coercer.rb +4 -8
  63. data/lib/grape/validations/types/invalid_value.rb +0 -7
  64. data/lib/grape/validations/types/json.rb +2 -1
  65. data/lib/grape/validations/types/primitive_coercer.rb +16 -8
  66. data/lib/grape/validations/types/set_coercer.rb +0 -2
  67. data/lib/grape/validations/types.rb +98 -30
  68. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  69. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  70. data/lib/grape/validations/validators/as_validator.rb +14 -0
  71. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  72. data/lib/grape/validations/validators/base.rb +82 -70
  73. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  74. data/lib/grape/validations/validators/default_validator.rb +51 -0
  75. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  76. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  77. data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
  78. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  79. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  80. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  81. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  82. data/lib/grape/validations/validators/values_validator.rb +88 -0
  83. data/lib/grape/validations.rb +16 -6
  84. data/lib/grape/version.rb +1 -1
  85. data/lib/grape.rb +77 -29
  86. data/spec/grape/api/custom_validations_spec.rb +116 -45
  87. data/spec/grape/api/deeply_included_options_spec.rb +3 -5
  88. data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -3
  89. data/spec/grape/api/documentation_spec.rb +59 -0
  90. data/spec/grape/api/inherited_helpers_spec.rb +0 -2
  91. data/spec/grape/api/instance_spec.rb +0 -1
  92. data/spec/grape/api/invalid_format_spec.rb +2 -2
  93. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -2
  94. data/spec/grape/api/nested_helpers_spec.rb +0 -2
  95. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -2
  96. data/spec/grape/api/parameters_modification_spec.rb +0 -2
  97. data/spec/grape/api/patch_method_helpers_spec.rb +0 -2
  98. data/spec/grape/api/recognize_path_spec.rb +1 -3
  99. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -2
  100. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -2
  101. data/spec/grape/api/routes_with_requirements_spec.rb +8 -10
  102. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -17
  103. data/spec/grape/api/shared_helpers_spec.rb +0 -2
  104. data/spec/grape/api_remount_spec.rb +16 -16
  105. data/spec/grape/api_spec.rb +462 -251
  106. data/spec/grape/config_spec.rb +0 -2
  107. data/spec/grape/dsl/callbacks_spec.rb +2 -3
  108. data/spec/grape/dsl/desc_spec.rb +2 -2
  109. data/spec/grape/dsl/headers_spec.rb +39 -11
  110. data/spec/grape/dsl/helpers_spec.rb +3 -4
  111. data/spec/grape/dsl/inside_route_spec.rb +16 -16
  112. data/spec/grape/dsl/logger_spec.rb +15 -19
  113. data/spec/grape/dsl/middleware_spec.rb +2 -3
  114. data/spec/grape/dsl/parameters_spec.rb +2 -2
  115. data/spec/grape/dsl/request_response_spec.rb +7 -8
  116. data/spec/grape/dsl/routing_spec.rb +11 -10
  117. data/spec/grape/dsl/settings_spec.rb +0 -2
  118. data/spec/grape/dsl/validations_spec.rb +0 -17
  119. data/spec/grape/endpoint/declared_spec.rb +261 -16
  120. data/spec/grape/endpoint_spec.rb +88 -59
  121. data/spec/grape/entity_spec.rb +22 -23
  122. data/spec/grape/exceptions/base_spec.rb +16 -2
  123. data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -2
  124. data/spec/grape/exceptions/invalid_accept_header_spec.rb +64 -24
  125. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -2
  126. data/spec/grape/exceptions/invalid_response_spec.rb +0 -2
  127. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +1 -3
  128. data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
  129. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -2
  130. data/spec/grape/exceptions/missing_option_spec.rb +1 -3
  131. data/spec/grape/exceptions/unknown_options_spec.rb +0 -2
  132. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -2
  133. data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
  134. data/spec/grape/exceptions/validation_errors_spec.rb +13 -11
  135. data/spec/grape/exceptions/validation_spec.rb +5 -5
  136. data/spec/grape/extensions/param_builders/hash_spec.rb +7 -9
  137. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -10
  138. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -10
  139. data/spec/grape/integration/global_namespace_function_spec.rb +0 -2
  140. data/spec/grape/integration/rack_sendfile_spec.rb +1 -3
  141. data/spec/grape/integration/rack_spec.rb +6 -7
  142. data/spec/grape/loading_spec.rb +8 -10
  143. data/spec/grape/middleware/auth/base_spec.rb +0 -1
  144. data/spec/grape/middleware/auth/dsl_spec.rb +15 -8
  145. data/spec/grape/middleware/auth/strategies_spec.rb +60 -22
  146. data/spec/grape/middleware/base_spec.rb +28 -19
  147. data/spec/grape/middleware/error_spec.rb +8 -3
  148. data/spec/grape/middleware/exception_spec.rb +111 -163
  149. data/spec/grape/middleware/formatter_spec.rb +33 -14
  150. data/spec/grape/middleware/globals_spec.rb +7 -6
  151. data/spec/grape/middleware/stack_spec.rb +14 -14
  152. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -3
  153. data/spec/grape/middleware/versioner/header_spec.rb +30 -15
  154. data/spec/grape/middleware/versioner/param_spec.rb +7 -3
  155. data/spec/grape/middleware/versioner/path_spec.rb +5 -3
  156. data/spec/grape/middleware/versioner_spec.rb +1 -3
  157. data/spec/grape/named_api_spec.rb +0 -2
  158. data/spec/grape/parser_spec.rb +4 -2
  159. data/spec/grape/path_spec.rb +52 -54
  160. data/spec/grape/presenters/presenter_spec.rb +7 -8
  161. data/spec/grape/request_spec.rb +6 -6
  162. data/spec/grape/util/inheritable_setting_spec.rb +7 -8
  163. data/spec/grape/util/inheritable_values_spec.rb +3 -3
  164. data/spec/grape/util/reverse_stackable_values_spec.rb +3 -2
  165. data/spec/grape/util/stackable_values_spec.rb +7 -6
  166. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -1
  167. data/spec/grape/validations/attributes_doc_spec.rb +153 -0
  168. data/spec/grape/validations/instance_behaivour_spec.rb +9 -12
  169. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -2
  170. data/spec/grape/validations/params_scope_spec.rb +361 -96
  171. data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -3
  172. data/spec/grape/validations/types/array_coercer_spec.rb +0 -2
  173. data/spec/grape/validations/types/primitive_coercer_spec.rb +24 -9
  174. data/spec/grape/validations/types/set_coercer_spec.rb +0 -2
  175. data/spec/grape/validations/types_spec.rb +36 -10
  176. data/spec/grape/validations/validators/all_or_none_spec.rb +50 -58
  177. data/spec/grape/validations/validators/allow_blank_spec.rb +135 -141
  178. data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -58
  179. data/spec/grape/validations/validators/coerce_spec.rb +23 -24
  180. data/spec/grape/validations/validators/default_spec.rb +72 -80
  181. data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -79
  182. data/spec/grape/validations/validators/except_values_spec.rb +3 -5
  183. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -79
  184. data/spec/grape/validations/validators/presence_spec.rb +16 -3
  185. data/spec/grape/validations/validators/regexp_spec.rb +25 -33
  186. data/spec/grape/validations/validators/same_as_spec.rb +14 -22
  187. data/spec/grape/validations/validators/values_spec.rb +201 -179
  188. data/spec/grape/validations_spec.rb +171 -79
  189. data/spec/integration/eager_load/eager_load_spec.rb +2 -2
  190. data/spec/integration/multi_json/json_spec.rb +1 -3
  191. data/spec/integration/multi_xml/xml_spec.rb +1 -3
  192. data/spec/shared/versioning_examples.rb +12 -9
  193. data/spec/spec_helper.rb +21 -6
  194. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  195. metadata +41 -29
  196. data/lib/grape/validations/validators/all_or_none.rb +0 -15
  197. data/lib/grape/validations/validators/allow_blank.rb +0 -18
  198. data/lib/grape/validations/validators/as.rb +0 -16
  199. data/lib/grape/validations/validators/at_least_one_of.rb +0 -14
  200. data/lib/grape/validations/validators/coerce.rb +0 -91
  201. data/lib/grape/validations/validators/default.rb +0 -48
  202. data/lib/grape/validations/validators/exactly_one_of.rb +0 -16
  203. data/lib/grape/validations/validators/except_values.rb +0 -22
  204. data/lib/grape/validations/validators/mutual_exclusion.rb +0 -15
  205. data/lib/grape/validations/validators/presence.rb +0 -12
  206. data/lib/grape/validations/validators/regexp.rb +0 -13
  207. data/lib/grape/validations/validators/same_as.rb +0 -26
  208. data/lib/grape/validations/validators/values.rb +0 -83
  209. data/spec/grape/dsl/configuration_spec.rb +0 -16
  210. data/spec/grape/validations/attributes_iterator_spec.rb +0 -6
  211. data/spec/support/eager_load.rb +0 -19
data/lib/grape/path.rb CHANGED
@@ -91,6 +91,7 @@ module Grape
91
91
 
92
92
  def split_setting(key)
93
93
  return if settings[key].nil?
94
+
94
95
  settings[key].to_s.split('/')
95
96
  end
96
97
  end
data/lib/grape/request.rb CHANGED
@@ -17,6 +17,8 @@ module Grape
17
17
  @params ||= build_params
18
18
  rescue EOFError
19
19
  raise Grape::Exceptions::EmptyMessageBody.new(content_type)
20
+ rescue Rack::Multipart::MultipartPartLimitError
21
+ raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
20
22
  end
21
23
 
22
24
  def headers
@@ -37,6 +39,7 @@ module Grape
37
39
  Grape::Util::LazyObject.new do
38
40
  env.each_pair.with_object({}) do |(k, v), headers|
39
41
  next unless k.to_s.start_with? HTTP_PREFIX
42
+
40
43
  transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
41
44
  headers[transformed_header] = v
42
45
  end
@@ -44,7 +47,7 @@ module Grape
44
47
  end
45
48
 
46
49
  def transform_header(header)
47
- -header[5..-1].split('_').each(&:capitalize!).join('-')
50
+ -header[5..].split('_').each(&:capitalize!).join('-')
48
51
  end
49
52
  end
50
53
  end
@@ -39,7 +39,7 @@ module Grape
39
39
 
40
40
  def method_missing(method_name, *args)
41
41
  if setter?(method_name[-1])
42
- attributes[method_name[0..-1]] = *args
42
+ attributes[method_name[0..]] = *args
43
43
  else
44
44
  attributes[method_name]
45
45
  end
@@ -41,7 +41,7 @@ module Grape
41
41
  end
42
42
 
43
43
  pattern = -pattern.split('/').tap do |parts|
44
- parts[parts.length - 1] = '?' + parts.last
44
+ parts[parts.length - 1] = "?#{parts.last}"
45
45
  end.join('/') if pattern.end_with?('*path')
46
46
 
47
47
  PatternCache[[pattern, suffix]]
@@ -84,8 +84,8 @@ module Grape
84
84
  path, line = *location.scan(SOURCE_LOCATION_REGEXP).first
85
85
  path = File.realpath(path) if Pathname.new(path).relative?
86
86
  expected ||= name
87
- warn <<-WARNING
88
- #{path}:#{line}: The route_xxx methods such as route_#{name} have been deprecated, please use #{expected}.
87
+ warn <<~WARNING
88
+ #{path}:#{line}: The route_xxx methods such as route_#{name} have been deprecated, please use #{expected}.
89
89
  WARNING
90
90
  end
91
91
  end
data/lib/grape/router.rb CHANGED
@@ -28,6 +28,7 @@ module Grape
28
28
 
29
29
  def compile!
30
30
  return if compiled
31
+
31
32
  @union = Regexp.union(@neutral_regexes)
32
33
  @neutral_regexes = nil
33
34
  self.class.supported_methods.each do |method|
@@ -60,6 +61,7 @@ module Grape
60
61
  def recognize_path(input)
61
62
  any = with_optimization { greedy_match?(input) }
62
63
  return if any == default_response
64
+
63
65
  any.endpoint
64
66
  end
65
67
 
@@ -80,6 +82,7 @@ module Grape
80
82
  map[method].each do |route|
81
83
  next if exact_route == route
82
84
  next unless route.match?(input)
85
+
83
86
  response = process_route(route, env)
84
87
  break unless cascade?(response)
85
88
  end
@@ -91,6 +94,7 @@ module Grape
91
94
  response = yield(input, method)
92
95
 
93
96
  return response if response && !(cascade = cascade?(response))
97
+
94
98
  last_neighbor_route = greedy_match?(input)
95
99
 
96
100
  # If last_neighbor_route exists and request method is OPTIONS,
@@ -139,12 +143,14 @@ module Grape
139
143
  def match?(input, method)
140
144
  current_regexp = @optimized_map[method]
141
145
  return unless current_regexp.match(input)
146
+
142
147
  last_match = Regexp.last_match
143
148
  @map[method].detect { |route| last_match["_#{route.index}"] }
144
149
  end
145
150
 
146
151
  def greedy_match?(input)
147
152
  return unless @union.match(input)
153
+
148
154
  last_match = Regexp.last_match
149
155
  @neutral_map.detect { |route| last_match["_#{route.index}"] }
150
156
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # only exists to make it shorter for external use
4
+ module Grape
5
+ module Types
6
+ InvalidValue = Class.new(Grape::Validations::Types::InvalidValue)
7
+ end
8
+ end
@@ -1,4 +1,4 @@
1
- # frozen_String_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'singleton'
4
4
  require 'forwardable'
@@ -5,9 +5,7 @@ module Grape
5
5
  # A branchable, inheritable settings object which can store both stackable
6
6
  # and inheritable values (see InheritableValues and StackableValues).
7
7
  class InheritableSetting
8
- attr_accessor :route, :api_class, :namespace
9
- attr_accessor :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable
10
- attr_accessor :parent, :point_in_time_copies
8
+ attr_accessor :route, :api_class, :namespace, :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable, :parent, :point_in_time_copies
11
9
 
12
10
  # Retrieve global settings.
13
11
  def self.global
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module Grape
4
6
  if Object.const_defined? :MultiJson
5
7
  Json = ::MultiJson
@@ -49,9 +49,10 @@ module Grape
49
49
  end
50
50
 
51
51
  def []=(key, value)
52
- @value_hash[key] = if value.is_a?(Hash)
52
+ @value_hash[key] = case value
53
+ when Hash
53
54
  LazyValueHash.new(value)
54
- elsif value.is_a?(Array)
55
+ when Array
55
56
  LazyValueArray.new(value)
56
57
  else
57
58
  LazyValue.new(value)
@@ -65,7 +65,7 @@ module Grape
65
65
  end
66
66
  end
67
67
 
68
- define_method 'to_hash' do
68
+ define_method :to_hash do
69
69
  merge_hash = {}
70
70
  setting_name.each_key { |k| merge_hash[k] = send("#{k}_context").to_hash }
71
71
 
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ class ParamsScope
6
+ # Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an
7
+ # internal API), the class only cleans up attributes to avoid junk in RAM.
8
+ class AttributesDoc
9
+ attr_accessor :type, :values
10
+
11
+ # @param api [Grape::API::Instance]
12
+ # @param scope [Validations::ParamsScope]
13
+ def initialize(api, scope)
14
+ @api = api
15
+ @scope = scope
16
+ @type = type
17
+ end
18
+
19
+ def extract_details(validations)
20
+ details[:required] = validations.key?(:presence)
21
+
22
+ desc = validations.delete(:desc) || validations.delete(:description)
23
+
24
+ details[:desc] = desc if desc
25
+
26
+ documentation = validations.delete(:documentation)
27
+
28
+ details[:documentation] = documentation if documentation
29
+
30
+ details[:default] = validations[:default] if validations.key?(:default)
31
+ end
32
+
33
+ def document(attrs)
34
+ return if @api.namespace_inheritable(:do_not_document)
35
+
36
+ details[:type] = type.to_s if type
37
+ details[:values] = values if values
38
+
39
+ documented_attrs = attrs.each_with_object({}) do |name, memo|
40
+ memo[@scope.full_name(name)] = details
41
+ end
42
+
43
+ @api.namespace_stackable(:params, documented_attrs)
44
+ end
45
+
46
+ def required
47
+ details[:required]
48
+ end
49
+
50
+ protected
51
+
52
+ def details
53
+ @details ||= {}
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'attributes_doc'
4
+
3
5
  module Grape
4
6
  module Validations
5
7
  class ParamsScope
@@ -8,11 +10,42 @@ module Grape
8
10
 
9
11
  include Grape::DSL::Parameters
10
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
+
11
42
  # Open up a new ParamsScope, allowing parameter definitions per
12
43
  # Grape::DSL::Params.
13
44
  # @param opts [Hash] options for this scope
14
45
  # @option opts :element [Symbol] the element that contains this scope; for
15
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
16
49
  # @option opts :parent [ParamsScope] the scope containing this scope
17
50
  # @option opts :api [API] the API endpoint to modify
18
51
  # @option opts :optional [Boolean] whether or not this scope needs to have
@@ -23,17 +56,18 @@ module Grape
23
56
  # validate if this param is present in the parent scope
24
57
  # @yield the instance context, open for parameter definitions
25
58
  def initialize(opts, &block)
26
- @element = opts[:element]
27
- @parent = opts[:parent]
28
- @api = opts[:api]
29
- @optional = opts[:optional] || false
30
- @type = opts[:type]
31
- @group = opts[:group] || {}
32
- @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]
33
67
  @declared_params = []
34
68
  @index = nil
35
69
 
36
- instance_eval(&block) if block_given?
70
+ instance_eval(&block) if block
37
71
 
38
72
  configure_declared_params
39
73
  end
@@ -50,18 +84,29 @@ module Grape
50
84
  return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))
51
85
  return false unless meets_dependency?(scoped_params, parameters)
52
86
  return true if parent.nil?
87
+
53
88
  parent.should_validate?(parameters)
54
89
  end
55
90
 
56
91
  def meets_dependency?(params, request_params)
57
92
  return true unless @dependent_on
58
93
 
59
- if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
60
- return false
61
- end
94
+ return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
62
95
 
63
96
  return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
64
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)
65
110
  # params might be anything what looks like a hash, so it must implement a `key?` method
66
111
  return false unless params.respond_to?(:key?)
67
112
 
@@ -126,21 +171,39 @@ module Grape
126
171
  # Adds a parameter declaration to our list of validations.
127
172
  # @param attrs [Array] (see Grape::DSL::Parameters#requires)
128
173
  def push_declared_params(attrs, **opts)
174
+ opts = opts.merge(declared_params_scope: self) unless opts.key?(:declared_params_scope)
129
175
  if lateral?
130
176
  @parent.push_declared_params(attrs, **opts)
131
177
  else
132
- if opts && opts[:as]
133
- @api.route_setting(:renamed_params, @api.route_setting(:renamed_params) || [])
134
- @api.route_setting(:renamed_params) << { attrs.first => opts[:as] }
135
- attrs = [opts[:as]]
136
- end
178
+ push_renamed_param(full_path + [attrs.first], opts[:as]) \
179
+ if opts && opts[:as]
137
180
 
138
- @declared_params.concat attrs
181
+ @declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })
139
182
  end
140
183
  end
141
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
+
142
192
  private
143
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
+
144
207
  def require_required_and_optional_fields(context, opts)
145
208
  if context == :all
146
209
  optional_fields = Array(opts[:except])
@@ -152,6 +215,7 @@ module Grape
152
215
  required_fields.each do |field|
153
216
  field_opts = opts[:using][field]
154
217
  raise ArgumentError, "required field not exist: #{field}" unless field_opts
218
+
155
219
  requires(field, field_opts)
156
220
  end
157
221
  optional_fields.each do |field|
@@ -186,16 +250,17 @@ module Grape
186
250
  # if required params are grouped and no type or unsupported type is provided, raise an error
187
251
  type = attrs[1] ? attrs[1][:type] : nil
188
252
  if attrs.first && !optional
189
- raise Grape::Exceptions::MissingGroupTypeError.new if type.nil?
190
- 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)
191
255
  end
192
256
 
193
257
  self.class.new(
194
- api: @api,
195
- element: attrs[1][:as] || attrs.first,
196
- parent: self,
258
+ api: @api,
259
+ element: attrs.first,
260
+ element_renamed: attrs[1][:as],
261
+ parent: self,
197
262
  optional: optional,
198
- type: type || Array,
263
+ type: type || Array,
199
264
  &block
200
265
  )
201
266
  end
@@ -209,11 +274,11 @@ module Grape
209
274
  # @yield parameter scope
210
275
  def new_lateral_scope(options, &block)
211
276
  self.class.new(
212
- api: @api,
213
- element: nil,
214
- parent: self,
215
- options: @optional,
216
- type: type == Array ? Array : Hash,
277
+ api: @api,
278
+ element: nil,
279
+ parent: self,
280
+ options: @optional,
281
+ type: type == Array ? Array : Hash,
217
282
  dependent_on: options[:dependent_on],
218
283
  &block
219
284
  )
@@ -226,15 +291,17 @@ module Grape
226
291
  # @yield parameter scope
227
292
  def new_group_scope(attrs, &block)
228
293
  self.class.new(
229
- api: @api,
230
- parent: self,
231
- group: attrs.first,
294
+ api: @api,
295
+ parent: self,
296
+ group: attrs.first,
232
297
  &block
233
298
  )
234
299
  end
235
300
 
236
301
  # Pushes declared params to parent or settings
237
302
  def configure_declared_params
303
+ push_renamed_param(full_path, @element_renamed) if @element_renamed
304
+
238
305
  if nested?
239
306
  @parent.push_declared_params [element => @declared_params]
240
307
  else
@@ -246,17 +313,14 @@ module Grape
246
313
  end
247
314
 
248
315
  def validates(attrs, validations)
249
- doc_attrs = { required: validations.key?(:presence) }
316
+ doc = AttributesDoc.new @api, self
317
+ doc.extract_details validations
250
318
 
251
319
  coerce_type = infer_coercion(validations)
252
320
 
253
- doc_attrs[:type] = coerce_type.to_s if coerce_type
254
-
255
- desc = validations.delete(:desc) || validations.delete(:description)
256
- doc_attrs[:desc] = desc if desc
321
+ doc.type = coerce_type
257
322
 
258
323
  default = validations[:default]
259
- doc_attrs[:default] = default if validations.key?(:default)
260
324
 
261
325
  if (values_hash = validations[:values]).is_a? Hash
262
326
  values = values_hash[:value]
@@ -265,7 +329,8 @@ module Grape
265
329
  else
266
330
  values = validations[:values]
267
331
  end
268
- doc_attrs[:values] = values if values
332
+
333
+ doc.values = values
269
334
 
270
335
  except_values = options_key?(:except_values, :value, validations) ? validations[:except_values][:value] : validations[:except_values]
271
336
 
@@ -281,27 +346,22 @@ module Grape
281
346
  # type should be compatible with values array, if both exist
282
347
  validate_value_coercion(coerce_type, values, except_values, excepts)
283
348
 
284
- doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
285
-
286
- full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
287
- @api.document_attribute(full_attrs, doc_attrs)
349
+ doc.document attrs
288
350
 
289
351
  opts = derive_validator_options(validations)
290
352
 
291
353
  # Validate for presence before any other validators
292
- if validations.key?(:presence) && validations[:presence]
293
- validate('presence', validations[:presence], attrs, doc_attrs, opts)
294
- validations.delete(:presence)
295
- validations.delete(:message) if validations.key?(:message)
296
- end
354
+ validates_presence(validations, attrs, doc, opts)
297
355
 
298
356
  # Before we run the rest of the validators, let's handle
299
357
  # whatever coercion so that we are working with correctly
300
358
  # type casted values
301
- coerce_type validations, attrs, doc_attrs, opts
359
+ coerce_type validations, attrs, doc, opts
302
360
 
303
361
  validations.each do |type, options|
304
- validate(type, options, attrs, doc_attrs, opts)
362
+ next if type == :as
363
+
364
+ validate(type, options, attrs, doc, opts)
305
365
  end
306
366
  end
307
367
 
@@ -319,9 +379,7 @@ module Grape
319
379
  # @return [class-like] type to which the parameter will be coerced
320
380
  # @raise [ArgumentError] if the given type options are invalid
321
381
  def infer_coercion(validations)
322
- if validations.key?(:type) && validations.key?(:types)
323
- raise ArgumentError, ':type may not be supplied with :types'
324
- end
382
+ raise ArgumentError, ':type may not be supplied with :types' if validations.key?(:type) && validations.key?(:types)
325
383
 
326
384
  validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type)
327
385
  validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type)
@@ -357,6 +415,7 @@ module Grape
357
415
  # but not special JSON types, which
358
416
  # already imply coercion method
359
417
  return unless [JSON, Array[JSON]].include? validations[:coerce]
418
+
360
419
  raise ArgumentError, 'coerce_with disallowed for type: JSON'
361
420
  end
362
421
 
@@ -366,7 +425,7 @@ module Grape
366
425
  # composited from more than one +requires+/+optional+
367
426
  # parameter, and needs to be run before most other
368
427
  # validations.
369
- def coerce_type(validations, attrs, doc_attrs, opts)
428
+ def coerce_type(validations, attrs, doc, opts)
370
429
  check_coerce_with(validations)
371
430
 
372
431
  return unless validations.key?(:coerce)
@@ -376,7 +435,7 @@ module Grape
376
435
  method: validations[:coerce_with],
377
436
  message: validations[:coerce_message]
378
437
  }
379
- validate('coerce', coerce_options, attrs, doc_attrs, opts)
438
+ validate('coerce', coerce_options, attrs, doc, opts)
380
439
  validations.delete(:coerce_with)
381
440
  validations.delete(:coerce)
382
441
  validations.delete(:coerce_message)
@@ -384,6 +443,7 @@ module Grape
384
443
 
385
444
  def guess_coerce_type(coerce_type, *values_list)
386
445
  return coerce_type unless coerce_type == Array
446
+
387
447
  values_list.each do |values|
388
448
  next if !values || values.is_a?(Proc)
389
449
  return values.first.class if values.is_a?(Range) || !values.empty?
@@ -394,14 +454,11 @@ module Grape
394
454
  def check_incompatible_option_values(default, values, except_values, excepts)
395
455
  return unless default && !default.is_a?(Proc)
396
456
 
397
- if values && !values.is_a?(Proc)
398
- raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) \
399
- unless Array(default).all? { |def_val| values.include?(def_val) }
400
- 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) }
401
458
 
402
- 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) }
403
460
  raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) \
404
- unless Array(default).none? { |def_val| except_values.include?(def_val) }
461
+
405
462
  end
406
463
 
407
464
  return unless excepts && !excepts.is_a?(Proc)
@@ -409,39 +466,34 @@ module Grape
409
466
  unless Array(default).none? { |def_val| excepts.include?(def_val) }
410
467
  end
411
468
 
412
- def validate(type, options, attrs, doc_attrs, opts)
413
- validator_class = Validations.validators[type.to_s]
414
-
415
- raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
416
-
469
+ def validate(type, options, attrs, doc, opts)
417
470
  validator_options = {
418
- attributes: attrs,
419
- options: options,
420
- required: doc_attrs[:required],
421
- params_scope: self,
422
- opts: opts,
423
- validator_class: validator_class
471
+ attributes: attrs,
472
+ options: options,
473
+ required: doc.required,
474
+ params_scope: self,
475
+ opts: opts,
476
+ validator_class: Validations.require_validator(type)
424
477
  }
425
478
  @api.namespace_stackable(:validations, validator_options)
426
479
  end
427
480
 
428
481
  def validate_value_coercion(coerce_type, *values_list)
429
482
  return unless coerce_type
483
+
430
484
  coerce_type = coerce_type.first if coerce_type.is_a?(Array)
431
485
  values_list.each do |values|
432
486
  next if !values || values.is_a?(Proc)
433
- value_types = values.is_a?(Range) ? [values.begin, values.end] : values
434
- if coerce_type == Grape::API::Boolean
435
- value_types = value_types.map { |type| Grape::API::Boolean.build(type) }
436
- end
437
- unless value_types.all? { |v| v.is_a? coerce_type }
438
- raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
439
- 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)
440
491
  end
441
492
  end
442
493
 
443
494
  def extract_message_option(attrs)
444
495
  return nil unless attrs.is_a?(Array)
496
+
445
497
  opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
446
498
  opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil
447
499
  end
@@ -461,9 +513,16 @@ module Grape
461
513
 
462
514
  {
463
515
  allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,
464
- fail_fast: validations.delete(:fail_fast) || false
516
+ fail_fast: validations.delete(:fail_fast) || false
465
517
  }
466
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)
525
+ end
467
526
  end
468
527
  end
469
528
  end
@@ -14,8 +14,6 @@ module Grape
14
14
  # behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
15
15
  # maintains Virtus behavior in coercing.
16
16
  class ArrayCoercer < DryTypeCoercer
17
- register_collection Array
18
-
19
17
  def initialize(type, strict = false)
20
18
  super
21
19
 
@@ -56,6 +56,7 @@ module Grape
56
56
 
57
57
  return coerced_val if coerced_val.is_a?(InvalidValue)
58
58
  return InvalidValue.new unless coerced?(coerced_val)
59
+
59
60
  coerced_val
60
61
  end
61
62