grape 3.0.0 → 3.1.0

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/CONTRIBUTING.md +2 -2
  4. data/README.md +2 -2
  5. data/UPGRADING.md +14 -0
  6. data/grape.gemspec +2 -2
  7. data/lib/grape/api/instance.rb +17 -47
  8. data/lib/grape/api.rb +7 -10
  9. data/lib/grape/content_types.rb +1 -4
  10. data/lib/grape/declared_params_handler.rb +118 -0
  11. data/lib/grape/dsl/declared.rb +35 -0
  12. data/lib/grape/dsl/helpers.rb +2 -2
  13. data/lib/grape/dsl/inside_route.rb +3 -141
  14. data/lib/grape/dsl/parameters.rb +8 -26
  15. data/lib/grape/dsl/routing.rb +41 -29
  16. data/lib/grape/dsl/settings.rb +1 -1
  17. data/lib/grape/dsl/validations.rb +22 -20
  18. data/lib/grape/endpoint.rb +84 -83
  19. data/lib/grape/exceptions/base.rb +1 -1
  20. data/lib/grape/middleware/auth/dsl.rb +4 -4
  21. data/lib/grape/middleware/base.rb +4 -0
  22. data/lib/grape/middleware/error.rb +1 -1
  23. data/lib/grape/middleware/formatter.rb +6 -4
  24. data/lib/grape/middleware/stack.rb +1 -0
  25. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
  26. data/lib/grape/middleware/versioner/base.rb +20 -0
  27. data/lib/grape/middleware/versioner/header.rb +1 -17
  28. data/lib/grape/middleware/versioner/path.rb +1 -1
  29. data/lib/grape/namespace.rb +5 -9
  30. data/lib/grape/params_builder.rb +2 -19
  31. data/lib/grape/router/base_route.rb +14 -5
  32. data/lib/grape/router/greedy_route.rb +11 -5
  33. data/lib/grape/router/pattern.rb +6 -20
  34. data/lib/grape/router/route.rb +7 -11
  35. data/lib/grape/router.rb +35 -61
  36. data/lib/grape/util/api_description.rb +10 -8
  37. data/lib/grape/util/cache.rb +1 -0
  38. data/lib/grape/validations/attributes_iterator.rb +2 -2
  39. data/lib/grape/validations/params_scope.rb +12 -10
  40. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  41. data/lib/grape/validations/validators/base.rb +2 -2
  42. data/lib/grape/validations/validators/except_values_validator.rb +1 -1
  43. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +1 -1
  44. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  45. data/lib/grape/validations/validators/regexp_validator.rb +12 -2
  46. data/lib/grape/validations/validators/values_validator.rb +1 -1
  47. data/lib/grape/version.rb +1 -1
  48. data/lib/grape.rb +5 -13
  49. metadata +11 -14
  50. data/lib/grape/exceptions/missing_option.rb +0 -11
  51. data/lib/grape/exceptions/unknown_options.rb +0 -11
  52. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
  53. data/lib/grape/extensions/hash.rb +0 -27
  54. data/lib/grape/extensions/hashie/mash.rb +0 -24
@@ -22,7 +22,7 @@ module Grape
22
22
  return if path_info == '/'
23
23
 
24
24
  [mount_path, Grape::Router.normalize_path(prefix)].each do |path|
25
- path_info.delete_prefix!(path) if path.present? && path != '/' && path_info.start_with?(path)
25
+ path_info = path_info.delete_prefix(path) if path.present? && path != '/' && path_info.start_with?(path)
26
26
  end
27
27
 
28
28
  slash_position = path_info.index('/', 1) # omit the first one
@@ -5,24 +5,19 @@ module Grape
5
5
  # logical grouping of endpoints as well as sharing common configuration.
6
6
  # May also be referred to as group, segment, or resource.
7
7
  class Namespace
8
- attr_reader :space, :options
8
+ attr_reader :space, :requirements, :options
9
9
 
10
10
  # @param space [String] the name of this namespace
11
11
  # @param options [Hash] options hash
12
12
  # @option options :requirements [Hash] param-regex pairs, all of which must
13
13
  # be met by a request's params for all endpoints in this namespace, or
14
14
  # validation will fail and return a 422.
15
- def initialize(space, options)
15
+ def initialize(space, requirements: nil, **options)
16
16
  @space = space.to_s
17
+ @requirements = requirements
17
18
  @options = options
18
19
  end
19
20
 
20
- # Retrieves the requirements from the options hash, if given.
21
- # @return [Hash]
22
- def requirements
23
- options[:requirements] || {}
24
- end
25
-
26
21
  # (see ::joined_space_path)
27
22
  def self.joined_space(settings)
28
23
  settings&.map(&:space)
@@ -31,12 +26,13 @@ module Grape
31
26
  def eql?(other)
32
27
  other.class == self.class &&
33
28
  other.space == space &&
29
+ other.requirements == requirements &&
34
30
  other.options == options
35
31
  end
36
32
  alias == eql?
37
33
 
38
34
  def hash
39
- [self.class, space, options].hash
35
+ [self.class, space, requirements, options].hash
40
36
  end
41
37
 
42
38
  # Join the namespaces from a list of settings to create a path prefix.
@@ -4,29 +4,12 @@ module Grape
4
4
  module ParamsBuilder
5
5
  extend Grape::Util::Registry
6
6
 
7
- SHORT_NAME_LOOKUP = {
8
- 'Grape::Extensions::Hash::ParamBuilder' => :hash,
9
- 'Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder' => :hash_with_indifferent_access,
10
- 'Grape::Extensions::Hashie::Mash::ParamBuilder' => :hashie_mash
11
- }.freeze
12
-
13
7
  module_function
14
8
 
15
9
  def params_builder_for(short_name)
16
- verified_short_name = verify_short_name!(short_name)
17
-
18
- raise Grape::Exceptions::UnknownParamsBuilder, verified_short_name unless registry.key?(verified_short_name)
19
-
20
- registry[verified_short_name]
21
- end
22
-
23
- def verify_short_name!(short_name)
24
- return short_name if short_name.is_a?(Symbol)
10
+ raise Grape::Exceptions::UnknownParamsBuilder, short_name unless registry.key?(short_name)
25
11
 
26
- class_name = short_name.name
27
- SHORT_NAME_LOOKUP[class_name].tap do |real_short_name|
28
- Grape.deprecator.warn "#{class_name} has been deprecated. Use short name :#{real_short_name} instead."
29
- end
12
+ registry[short_name]
30
13
  end
31
14
  end
32
15
  end
@@ -3,22 +3,31 @@
3
3
  module Grape
4
4
  class Router
5
5
  class BaseRoute
6
+ extend Forwardable
7
+
6
8
  delegate_missing_to :@options
7
9
 
8
- attr_reader :index, :pattern, :options
10
+ attr_reader :options, :pattern
11
+
12
+ def_delegators :@pattern, :path, :origin
13
+ def_delegators :@options, :description, :version, :requirements, :prefix, :anchor, :settings, :forward_match, *Grape::Util::ApiDescription::DSL_METHODS
9
14
 
10
- def initialize(options)
15
+ def initialize(pattern, options = {})
16
+ @pattern = pattern
11
17
  @options = options.is_a?(ActiveSupport::OrderedOptions) ? options : ActiveSupport::OrderedOptions.new.update(options)
12
18
  end
13
19
 
14
- alias attributes options
20
+ # see https://github.com/ruby-grape/grape/issues/1348
21
+ def namespace
22
+ @namespace ||= @options[:namespace]
23
+ end
15
24
 
16
25
  def regexp_capture_index
17
- CaptureIndexCache[index]
26
+ @regexp_capture_index ||= CaptureIndexCache[@index]
18
27
  end
19
28
 
20
29
  def pattern_regexp
21
- pattern.to_regexp
30
+ @pattern.to_regexp
22
31
  end
23
32
 
24
33
  def to_regexp(index)
@@ -6,14 +6,20 @@
6
6
  module Grape
7
7
  class Router
8
8
  class GreedyRoute < BaseRoute
9
- def initialize(pattern, options)
10
- @pattern = pattern
11
- super(options)
9
+ extend Forwardable
10
+
11
+ def_delegators :@endpoint, :call
12
+
13
+ attr_reader :endpoint, :allow_header
14
+
15
+ def initialize(pattern, endpoint:, allow_header:)
16
+ super(pattern)
17
+ @endpoint = endpoint
18
+ @allow_header = allow_header
12
19
  end
13
20
 
14
- # Grape::Router:Route defines params as a function
15
21
  def params(_input = nil)
16
- options[:params] || {}
22
+ nil
17
23
  end
18
24
  end
19
25
  end
@@ -13,10 +13,10 @@ module Grape
13
13
  def_delegators :to_regexp, :===
14
14
  alias match? ===
15
15
 
16
- def initialize(origin, suffix, options)
16
+ def initialize(origin:, suffix:, anchor:, params:, format:, version:, requirements:)
17
17
  @origin = origin
18
- @path = build_path(origin, options[:anchor], suffix)
19
- @pattern = build_pattern(@path, options[:params], options[:format], options[:version], options[:requirements])
18
+ @path = PatternCache[[build_path_from_pattern(@origin, anchor), suffix]]
19
+ @pattern = Mustermann::Grape.new(@path, uri_decode: true, params: params, capture: extract_capture(format, version, requirements))
20
20
  @to_regexp = @pattern.to_regexp
21
21
  end
22
22
 
@@ -28,24 +28,10 @@ module Grape
28
28
 
29
29
  private
30
30
 
31
- def build_pattern(path, params, format, version, requirements)
32
- Mustermann::Grape.new(
33
- path,
34
- uri_decode: true,
35
- params: params,
36
- capture: extract_capture(format, version, requirements)
37
- )
38
- end
39
-
40
- def build_path(pattern, anchor, suffix)
41
- PatternCache[[build_path_from_pattern(pattern, anchor), suffix]]
42
- end
43
-
44
31
  def extract_capture(format, version, requirements)
45
- capture = {}.tap do |h|
46
- h[:format] = map_str(format) if format.present?
47
- h[:version] = map_str(version) if version.present?
48
- end
32
+ capture = {}
33
+ capture[:format] = map_str(format) if format.present?
34
+ capture[:version] = map_str(version) if version.present?
49
35
 
50
36
  return capture if requirements.blank?
51
37
 
@@ -8,25 +8,21 @@ module Grape
8
8
  FORWARD_MATCH_METHOD = ->(input, pattern) { input.start_with?(pattern.origin) }
9
9
  NON_FORWARD_MATCH_METHOD = ->(input, pattern) { pattern.match?(input) }
10
10
 
11
- attr_reader :app, :request_method
11
+ attr_reader :app, :request_method, :index
12
12
 
13
- def_delegators :pattern, :path, :origin
13
+ def_delegators :@app, :call
14
14
 
15
- def initialize(method, origin, path, options)
15
+ def initialize(endpoint, method, pattern, options)
16
+ super(pattern, options)
17
+ @app = endpoint
16
18
  @request_method = upcase_method(method)
17
- @pattern = Grape::Router::Pattern.new(origin, path, options)
18
19
  @match_function = options[:forward_match] ? FORWARD_MATCH_METHOD : NON_FORWARD_MATCH_METHOD
19
- super(options)
20
20
  end
21
21
 
22
22
  def convert_to_head_request!
23
23
  @request_method = Rack::HEAD
24
24
  end
25
25
 
26
- def exec(env)
27
- @app.call(env)
28
- end
29
-
30
26
  def apply(app)
31
27
  @app = app
32
28
  self
@@ -42,7 +38,7 @@ module Grape
42
38
  return params_without_input if input.blank?
43
39
 
44
40
  parsed = pattern.params(input)
45
- return {} unless parsed
41
+ return unless parsed
46
42
 
47
43
  parsed.compact.symbolize_keys
48
44
  end
@@ -50,7 +46,7 @@ module Grape
50
46
  private
51
47
 
52
48
  def params_without_input
53
- @params_without_input ||= pattern.captures_default.merge(attributes.params)
49
+ @params_without_input ||= pattern.captures_default.merge(options[:params])
54
50
  end
55
51
 
56
52
  def upcase_method(method)
data/lib/grape/router.rb CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Grape
4
4
  class Router
5
- attr_reader :map, :compiled
6
-
7
5
  # Taken from Rails
8
6
  # normalize_path("/foo") # => "/foo"
9
7
  # normalize_path("/foo/") # => "/foo"
@@ -16,7 +14,7 @@ module Grape
16
14
  return path if path == '/'
17
15
 
18
16
  # Fast path for the overwhelming majority of paths that don't need to be normalized
19
- return path.dup if path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//}))
17
+ return path if path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//}))
20
18
 
21
19
  # Slow path
22
20
  encoding = path.encoding
@@ -39,14 +37,14 @@ module Grape
39
37
  end
40
38
 
41
39
  def compile!
42
- return if compiled
40
+ return if @compiled
43
41
 
44
42
  @union = Regexp.union(@neutral_regexes)
45
43
  @neutral_regexes = nil
46
44
  (Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
47
- next unless map.key?(method)
45
+ next unless @map.key?(method)
48
46
 
49
- routes = map[method]
47
+ routes = @map[method]
50
48
  optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
51
49
  @optimized_map[method] = Regexp.union(optimized_map)
52
50
  end
@@ -54,20 +52,20 @@ module Grape
54
52
  end
55
53
 
56
54
  def append(route)
57
- map[route.request_method] << route
55
+ @map[route.request_method] << route
58
56
  end
59
57
 
60
- def associate_routes(pattern, options)
61
- Grape::Router::GreedyRoute.new(pattern, options).then do |greedy_route|
62
- @neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
63
- @neutral_map << greedy_route
64
- end
58
+ def associate_routes(greedy_route)
59
+ @neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
60
+ @neutral_map << greedy_route
65
61
  end
66
62
 
67
63
  def call(env)
68
64
  with_optimization do
69
- response, route = identity(env)
70
- response || rotation(env, route)
65
+ input = Router.normalize_path(env[Rack::PATH_INFO])
66
+ method = env[Rack::REQUEST_METHOD]
67
+ response, route = identity(input, method, env)
68
+ response || rotation(input, method, env, route)
71
69
  end
72
70
  end
73
71
 
@@ -80,31 +78,28 @@ module Grape
80
78
 
81
79
  private
82
80
 
83
- def identity(env)
81
+ def identity(input, method, env)
84
82
  route = nil
85
- response = transaction(env) do |input, method|
83
+ response = transaction(input, method, env) do
86
84
  route = match?(input, method)
87
- process_route(route, env) if route
85
+ process_route(route, input, env) if route
88
86
  end
89
87
  [response, route]
90
88
  end
91
89
 
92
- def rotation(env, exact_route = nil)
90
+ def rotation(input, method, env, exact_route)
93
91
  response = nil
94
- input, method = *extract_input_and_method(env)
95
- map[method].each do |route|
92
+ @map[method].each do |route|
96
93
  next if exact_route == route
97
94
  next unless route.match?(input)
98
95
 
99
- response = process_route(route, env)
96
+ response = process_route(route, input, env)
100
97
  break unless cascade?(response)
101
98
  end
102
99
  response
103
100
  end
104
101
 
105
- def transaction(env)
106
- input, method = *extract_input_and_method(env)
107
-
102
+ def transaction(input, method, env)
108
103
  # using a Proc is important since `return` will exit the enclosing function
109
104
  cascade_or_return_response = proc do |response|
110
105
  if response
@@ -112,47 +107,41 @@ module Grape
112
107
  return response unless cascade
113
108
 
114
109
  # we need to close the body if possible before dismissing
115
- response[2].try(:close)
110
+ response[2].close if response[2].respond_to?(:close)
116
111
  end
117
112
  end
118
113
  end
119
114
 
120
- last_response_cascade = cascade_or_return_response.call(yield(input, method))
115
+ response = yield
116
+ last_response_cascade = cascade_or_return_response.call(response)
121
117
  last_neighbor_route = greedy_match?(input)
122
118
 
123
119
  # If last_neighbor_route exists and request method is OPTIONS,
124
- # return response by using #call_with_allow_headers.
125
- return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Rack::OPTIONS && !last_response_cascade
120
+ # return response by using #include_allow_header.
121
+ return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && method == Rack::OPTIONS && last_neighbor_route
126
122
 
127
123
  route = match?(input, '*')
128
124
 
129
- return last_neighbor_route.options[:endpoint].call(env) if last_neighbor_route && last_response_cascade && route
125
+ return last_neighbor_route.call(env) if last_neighbor_route && last_response_cascade && route
130
126
 
131
- last_response_cascade = cascade_or_return_response.call(process_route(route, env)) if route
127
+ last_response_cascade = cascade_or_return_response.call(process_route(route, input, env)) if route
132
128
 
133
- return call_with_allow_headers(env, last_neighbor_route) if !last_response_cascade && last_neighbor_route
129
+ return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && last_neighbor_route
134
130
 
135
131
  nil
136
132
  end
137
133
 
138
- def process_route(route, env)
139
- prepare_env_from_route(env, route)
140
- route.exec(env)
141
- end
142
-
143
- def make_routing_args(default_args, route, input)
144
- args = default_args || { route_info: route }
145
- args.merge(route.params(input))
146
- end
147
-
148
- def extract_input_and_method(env)
149
- input = string_for(env[Rack::PATH_INFO])
150
- method = env[Rack::REQUEST_METHOD]
151
- [input, method]
134
+ def process_route(route, input, env, include_allow_header: false)
135
+ args = env[Grape::Env::GRAPE_ROUTING_ARGS] || { route_info: route }
136
+ route_params = route.params(input)
137
+ routing_args = args.merge(route_params || {})
138
+ env[Grape::Env::GRAPE_ROUTING_ARGS] = routing_args
139
+ env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header if include_allow_header
140
+ route.call(env)
152
141
  end
153
142
 
154
143
  def with_optimization
155
- compile! unless compiled
144
+ compile!
156
145
  yield || default_response
157
146
  end
158
147
 
@@ -169,23 +158,8 @@ module Grape
169
158
  @union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
170
159
  end
171
160
 
172
- def call_with_allow_headers(env, route)
173
- prepare_env_from_route(env, route)
174
- env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.options[:allow_header]
175
- route.options[:endpoint].call(env)
176
- end
177
-
178
- def prepare_env_from_route(env, route)
179
- input, = *extract_input_and_method(env)
180
- env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
181
- end
182
-
183
161
  def cascade?(response)
184
162
  response && response[1]['X-Cascade'] == 'pass'
185
163
  end
186
-
187
- def string_for(input)
188
- self.class.normalize_path(input)
189
- end
190
164
  end
191
165
  end
@@ -3,13 +3,7 @@
3
3
  module Grape
4
4
  module Util
5
5
  class ApiDescription
6
- def initialize(description, endpoint_configuration, &block)
7
- @endpoint_configuration = endpoint_configuration
8
- @attributes = { description: description }
9
- instance_eval(&block)
10
- end
11
-
12
- %i[
6
+ DSL_METHODS = %i[
13
7
  body_name
14
8
  consumes
15
9
  default
@@ -27,7 +21,15 @@ module Grape
27
21
  security
28
22
  summary
29
23
  tags
30
- ].each do |attribute|
24
+ ].freeze
25
+
26
+ def initialize(description, endpoint_configuration, &)
27
+ @endpoint_configuration = endpoint_configuration
28
+ @attributes = { description: description }
29
+ instance_eval(&)
30
+ end
31
+
32
+ DSL_METHODS.each do |attribute|
31
33
  define_method attribute do |value|
32
34
  @attributes[attribute] = value
33
35
  end
@@ -9,6 +9,7 @@ module Grape
9
9
 
10
10
  class << self
11
11
  extend Forwardable
12
+
12
13
  def_delegators :cache, :[]
13
14
  def_delegators :instance, :cache
14
15
  end
@@ -14,8 +14,8 @@ module Grape
14
14
  @params = Array.wrap(@original_params)
15
15
  end
16
16
 
17
- def each(&block)
18
- do_each(@params, &block) # because we need recursion for nested arrays
17
+ def each(&)
18
+ do_each(@params, &) # because we need recursion for nested arrays
19
19
  end
20
20
 
21
21
  private
@@ -14,6 +14,8 @@ module Grape
14
14
  # we maintain a list of them here and skip looking up validators for them.
15
15
  RESERVED_DOCUMENTATION_KEYWORDS = %i[as required param_type is_array format example].freeze
16
16
 
17
+ SPECIAL_JSON = [JSON, Array[JSON]].freeze
18
+
17
19
  class Attr
18
20
  attr_accessor :key, :scope
19
21
 
@@ -78,7 +80,7 @@ module Grape
78
80
  end
79
81
 
80
82
  def configuration
81
- @api.configuration.respond_to?(:evaluate) ? @api.configuration.evaluate : @api.configuration
83
+ (@api.configuration.respond_to?(:evaluate) && @api.configuration.evaluate) || @api.configuration
82
84
  end
83
85
 
84
86
  # @return [Boolean] whether or not this entire scope needs to be
@@ -120,8 +122,8 @@ module Grape
120
122
  if dependency.is_a?(Hash)
121
123
  dependency_key = dependency.keys[0]
122
124
  proc = dependency.values[0]
123
- return false unless proc.call(params.try(:[], dependency_key))
124
- elsif params.respond_to?(:key?) && params.try(:[], dependency).blank?
125
+ return false unless proc.call(params[dependency_key])
126
+ elsif params[dependency].blank?
125
127
  return false
126
128
  end
127
129
  end
@@ -265,7 +267,7 @@ module Grape
265
267
  # @param optional [Boolean] whether the parameter this are nested under
266
268
  # is optional or not (and hence, whether this block's params will be).
267
269
  # @yield parameter scope
268
- def new_scope(attrs, opts, optional = false, &block)
270
+ def new_scope(attrs, opts, optional = false, &)
269
271
  # if required params are grouped and no type or unsupported type is provided, raise an error
270
272
  type = opts[:type]
271
273
  if attrs.first && !optional
@@ -281,7 +283,7 @@ module Grape
281
283
  optional: optional,
282
284
  type: type || Array,
283
285
  group: @group,
284
- &block
286
+ &
285
287
  )
286
288
  end
287
289
 
@@ -292,7 +294,7 @@ module Grape
292
294
  # scope should only validate if this parameter from the above scope is
293
295
  # present
294
296
  # @yield parameter scope
295
- def new_lateral_scope(options, &block)
297
+ def new_lateral_scope(options, &)
296
298
  self.class.new(
297
299
  api: @api,
298
300
  element: nil,
@@ -300,7 +302,7 @@ module Grape
300
302
  options: @optional,
301
303
  type: type == Array ? Array : Hash,
302
304
  dependent_on: options[:dependent_on],
303
- &block
305
+ &
304
306
  )
305
307
  end
306
308
 
@@ -309,8 +311,8 @@ module Grape
309
311
  # @param attrs [Array] the attributes passed to the `requires` or
310
312
  # `optional` invocation that opened this scope.
311
313
  # @yield parameter scope
312
- def new_group_scope(attrs, &block)
313
- self.class.new(api: @api, parent: self, group: attrs.first, &block)
314
+ def new_group_scope(attrs, &)
315
+ self.class.new(api: @api, parent: self, group: attrs.first, &)
314
316
  end
315
317
 
316
318
  # Pushes declared params to parent or settings
@@ -414,7 +416,7 @@ module Grape
414
416
 
415
417
  # but not special JSON types, which
416
418
  # already imply coercion method
417
- return if [JSON, Array[JSON]].exclude? validations[:coerce]
419
+ return unless SPECIAL_JSON.include?(validations[:coerce])
418
420
 
419
421
  raise ArgumentError, 'coerce_with disallowed for type: JSON'
420
422
  end
@@ -8,7 +8,7 @@ module Grape
8
8
  return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash)
9
9
 
10
10
  value = params[attr_name]
11
- value = value.scrub if value.respond_to?(:scrub)
11
+ value = value.scrub if value.respond_to?(:valid_encoding?) && !value.valid_encoding?
12
12
 
13
13
  return if value == false || value.present?
14
14
 
@@ -49,7 +49,7 @@ module Grape
49
49
  next if !@scope.required? && empty_val
50
50
  next unless @scope.meets_dependency?(val, params)
51
51
 
52
- validate_param!(attr_name, val) if @required || val.try(:key?, attr_name)
52
+ validate_param!(attr_name, val) if @required || (val.respond_to?(:key?) && val.key?(attr_name))
53
53
  rescue Grape::Exceptions::Validation => e
54
54
  array_errors << e
55
55
  end
@@ -69,7 +69,7 @@ module Grape
69
69
 
70
70
  def options_key?(key, options = nil)
71
71
  options = instance_variable_get(:@option) if options.nil?
72
- options.try(:key?, key) && !options[key].nil?
72
+ options.respond_to?(:key?) && options.key?(key) && !options[key].nil?
73
73
  end
74
74
 
75
75
  def fail_fast?
@@ -10,7 +10,7 @@ module Grape
10
10
  end
11
11
 
12
12
  def validate_param!(attr_name, params)
13
- return unless params.try(:key?, attr_name)
13
+ return unless params.respond_to?(:key?) && params.key?(attr_name)
14
14
 
15
15
  excepts = @except.is_a?(Proc) ? @except.call : @except
16
16
  return if excepts.nil?
@@ -3,7 +3,7 @@
3
3
  module Grape
4
4
  module Validations
5
5
  module Validators
6
- class MutualExclusionValidator < MultipleParamsBase
6
+ class MutuallyExclusiveValidator < MultipleParamsBase
7
7
  def validate_params!(params)
8
8
  keys = keys_in_common(params)
9
9
  return if keys.length <= 1
@@ -5,7 +5,7 @@ module Grape
5
5
  module Validators
6
6
  class PresenceValidator < Base
7
7
  def validate_param!(attr_name, params)
8
- return if params.try(:key?, attr_name)
8
+ return if params.respond_to?(:key?) && params.key?(attr_name)
9
9
 
10
10
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:presence))
11
11
  end
@@ -5,11 +5,21 @@ module Grape
5
5
  module Validators
6
6
  class RegexpValidator < Base
7
7
  def validate_param!(attr_name, params)
8
- return unless params.try(:key?, attr_name)
9
- return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.scrub.match?((options_key?(:value) ? @option[:value] : @option)) }
8
+ return unless params.respond_to?(:key) && params.key?(attr_name)
9
+
10
+ value = options_key?(:value) ? @option[:value] : @option
11
+ return if Array.wrap(params[attr_name]).all? { |param| param.nil? || scrub(param.to_s).match?(value) }
10
12
 
11
13
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp))
12
14
  end
15
+
16
+ private
17
+
18
+ def scrub(param)
19
+ return param if param.valid_encoding?
20
+
21
+ param.scrub
22
+ end
13
23
  end
14
24
  end
15
25
  end
@@ -16,7 +16,7 @@ module Grape
16
16
 
17
17
  return if val.nil? && !required_for_root_scope?
18
18
 
19
- val = val.scrub if val.respond_to?(:scrub)
19
+ val = val.scrub if val.respond_to?(:valid_encoding?) && !val.valid_encoding?
20
20
 
21
21
  # don't forget that +false.blank?+ is true
22
22
  return if val != false && val.blank? && @allow_blank
data/lib/grape/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Grape
4
4
  # The current version of Grape.
5
- VERSION = '3.0.0'
5
+ VERSION = '3.1.0'
6
6
  end