grape 2.1.0 → 2.2.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/README.md +4 -3
  4. data/UPGRADING.md +8 -0
  5. data/grape.gemspec +2 -1
  6. data/lib/grape/api/instance.rb +1 -1
  7. data/lib/grape/api.rb +2 -2
  8. data/lib/grape/content_types.rb +13 -8
  9. data/lib/grape/dsl/helpers.rb +7 -3
  10. data/lib/grape/dsl/inside_route.rb +14 -3
  11. data/lib/grape/dsl/parameters.rb +1 -1
  12. data/lib/grape/dsl/request_response.rb +14 -18
  13. data/lib/grape/endpoint.rb +34 -23
  14. data/lib/grape/error_formatter/json.rb +13 -4
  15. data/lib/grape/error_formatter.rb +13 -25
  16. data/lib/grape/exceptions/validation_errors.rb +1 -1
  17. data/lib/grape/formatter.rb +15 -25
  18. data/lib/grape/locale/en.yml +1 -0
  19. data/lib/grape/middleware/base.rb +14 -13
  20. data/lib/grape/middleware/error.rb +13 -11
  21. data/lib/grape/middleware/formatter.rb +2 -2
  22. data/lib/grape/middleware/stack.rb +2 -2
  23. data/lib/grape/middleware/versioner/accept_version_header.rb +8 -31
  24. data/lib/grape/middleware/versioner/header.rb +95 -10
  25. data/lib/grape/middleware/versioner/param.rb +5 -21
  26. data/lib/grape/middleware/versioner/path.rb +11 -31
  27. data/lib/grape/middleware/versioner.rb +5 -14
  28. data/lib/grape/middleware/versioner_helpers.rb +75 -0
  29. data/lib/grape/parser.rb +8 -24
  30. data/lib/grape/router.rb +2 -1
  31. data/lib/grape/validations/attributes_iterator.rb +1 -0
  32. data/lib/grape/validations/params_scope.rb +12 -10
  33. data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
  34. data/lib/grape/validations/validators/length_validator.rb +10 -3
  35. data/lib/grape/version.rb +1 -1
  36. metadata +8 -9
  37. data/lib/grape/util/accept/header.rb +0 -19
  38. data/lib/grape/util/accept_header_handler.rb +0 -105
  39. data/lib/grape/util/registrable.rb +0 -15
@@ -4,18 +4,17 @@ module Grape
4
4
  module Middleware
5
5
  class Base
6
6
  include Helpers
7
+ include Grape::DSL::Headers
7
8
 
8
9
  attr_reader :app, :env, :options
9
10
 
10
11
  TEXT_HTML = 'text/html'
11
12
 
12
- include Grape::DSL::Headers
13
-
14
13
  # @param [Rack Application] app The standard argument for a Rack middleware.
15
14
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
16
15
  def initialize(app, *options)
17
16
  @app = app
18
- @options = options.any? ? default_options.merge(options.shift) : default_options
17
+ @options = options.any? ? default_options.deep_merge(options.shift) : default_options
19
18
  @app_response = nil
20
19
  end
21
20
 
@@ -61,22 +60,20 @@ module Grape
61
60
  @app_response = Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
62
61
  end
63
62
 
64
- def content_type_for(format)
65
- HashWithIndifferentAccess.new(content_types)[format]
63
+ def content_types
64
+ @content_types ||= Grape::ContentTypes.content_types_for(options[:content_types])
66
65
  end
67
66
 
68
- def content_types
69
- ContentTypes.content_types_for(options[:content_types])
67
+ def mime_types
68
+ @mime_types ||= Grape::ContentTypes.mime_types_for(content_types)
70
69
  end
71
70
 
72
- def content_type
73
- content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
71
+ def content_type_for(format)
72
+ content_types_indifferent_access[format]
74
73
  end
75
74
 
76
- def mime_types
77
- @mime_type ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params|
78
- types_without_params[v.split(';').first] = k
79
- end
75
+ def content_type
76
+ content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
80
77
  end
81
78
 
82
79
  private
@@ -89,6 +86,10 @@ module Grape
89
86
  when Array then response[1].merge!(headers)
90
87
  end
91
88
  end
89
+
90
+ def content_types_indifferent_access
91
+ @content_types_indifferent_access ||= content_types.with_indifferent_access
92
+ end
92
93
  end
93
94
  end
94
95
  end
@@ -26,7 +26,7 @@ module Grape
26
26
 
27
27
  def initialize(app, *options)
28
28
  super
29
- self.class.send(:include, @options[:helpers]) if @options[:helpers]
29
+ self.class.include(@options[:helpers]) if @options[:helpers]
30
30
  end
31
31
 
32
32
  def call!(env)
@@ -45,7 +45,7 @@ module Grape
45
45
 
46
46
  def format_message(message, backtrace, original_exception = nil)
47
47
  format = env[Grape::Env::API_FORMAT] || options[:format]
48
- formatter = Grape::ErrorFormatter.formatter_for(format, **options)
48
+ formatter = Grape::ErrorFormatter.formatter_for(format, options[:error_formatters], options[:default_error_formatter])
49
49
  return formatter.call(message, backtrace, options, env, original_exception) if formatter
50
50
 
51
51
  throw :error,
@@ -74,12 +74,12 @@ module Grape
74
74
  rack_response(status, headers, format_message(message, backtrace, original_exception))
75
75
  end
76
76
 
77
- def default_rescue_handler(e)
78
- error_response(message: e.message, backtrace: e.backtrace, original_exception: e)
77
+ def default_rescue_handler(exception)
78
+ error_response(message: exception.message, backtrace: exception.backtrace, original_exception: exception)
79
79
  end
80
80
 
81
81
  def rescue_handler_for_base_only_class(klass)
82
- error, handler = options[:base_only_rescue_handlers].find { |err, _handler| klass == err }
82
+ error, handler = options[:base_only_rescue_handlers]&.find { |err, _handler| klass == err }
83
83
 
84
84
  return unless error
85
85
 
@@ -87,7 +87,7 @@ module Grape
87
87
  end
88
88
 
89
89
  def rescue_handler_for_class_or_its_ancestor(klass)
90
- error, handler = options[:rescue_handlers].find { |err, _handler| klass <= err }
90
+ error, handler = options[:rescue_handlers]&.find { |err, _handler| klass <= err }
91
91
 
92
92
  return unless error
93
93
 
@@ -120,12 +120,12 @@ module Grape
120
120
  handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
121
121
  end
122
122
 
123
- response = error!(response[:message], response[:status], response[:headers]) if error?(response)
124
-
125
- if response.is_a?(Rack::Response)
123
+ if error?(response)
124
+ error_response(response)
125
+ elsif response.is_a?(Rack::Response)
126
126
  response
127
127
  else
128
- run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new, endpoint)
128
+ run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InvalidResponse.new, endpoint)
129
129
  end
130
130
  end
131
131
 
@@ -137,7 +137,9 @@ module Grape
137
137
  end
138
138
 
139
139
  def error?(response)
140
- response.is_a?(Hash) && response[:message] && response[:status] && response[:headers]
140
+ return false unless response.is_a?(Hash)
141
+
142
+ response.key?(:message) && response.key?(:status) && response.key?(:headers)
141
143
  end
142
144
  end
143
145
  end
@@ -54,7 +54,7 @@ module Grape
54
54
 
55
55
  def fetch_formatter(headers, options)
56
56
  api_format = mime_types[headers[Rack::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT]
57
- Grape::Formatter.formatter_for(api_format, **options)
57
+ Grape::Formatter.formatter_for(api_format, options[:formatters])
58
58
  end
59
59
 
60
60
  # Set the content type header for the API format if it is not already present.
@@ -97,7 +97,7 @@ module Grape
97
97
  fmt = request.media_type ? mime_types[request.media_type] : options[:default_format]
98
98
 
99
99
  throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported." unless content_type_for(fmt)
100
- parser = Grape::Parser.parser_for fmt, **options
100
+ parser = Grape::Parser.parser_for fmt, options[:parsers]
101
101
  if parser
102
102
  begin
103
103
  body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
@@ -57,8 +57,8 @@ module Grape
57
57
  middlewares.last
58
58
  end
59
59
 
60
- def [](i)
61
- middlewares[i]
60
+ def [](index)
61
+ middlewares[index]
62
62
  end
63
63
 
64
64
  def insert(index, *args, &block)
@@ -17,45 +17,22 @@ module Grape
17
17
  # X-Cascade header to alert Grape::Router to attempt the next matched
18
18
  # route.
19
19
  class AcceptVersionHeader < Base
20
- def before
21
- potential_version = (env[Grape::Http::Headers::HTTP_ACCEPT_VERSION] || '').strip
22
-
23
- if strict? && potential_version.empty?
24
- # If no Accept-Version header:
25
- throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
26
- end
20
+ include VersionerHelpers
27
21
 
28
- return if potential_version.empty?
22
+ def before
23
+ potential_version = env[Grape::Http::Headers::HTTP_ACCEPT_VERSION]&.strip
24
+ not_acceptable!('Accept-Version header must be set.') if strict? && potential_version.blank?
29
25
 
30
- # If the requested version is not supported:
31
- throw :error, status: 406, headers: error_headers, message: 'The requested version is not supported.' unless versions.any? { |v| v.to_s == potential_version }
26
+ return if potential_version.blank?
32
27
 
28
+ not_acceptable!('The requested version is not supported.') unless potential_version_match?(potential_version)
33
29
  env[Grape::Env::API_VERSION] = potential_version
34
30
  end
35
31
 
36
32
  private
37
33
 
38
- def versions
39
- options[:versions] || []
40
- end
41
-
42
- def strict?
43
- options[:version_options] && options[:version_options][:strict]
44
- end
45
-
46
- # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
47
- # of routes (see Grape::Router) for more information). To prevent
48
- # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
49
- def cascade?
50
- if options[:version_options]&.key?(:cascade)
51
- options[:version_options][:cascade]
52
- else
53
- true
54
- end
55
- end
56
-
57
- def error_headers
58
- cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
34
+ def not_acceptable!(message)
35
+ throw :error, status: 406, headers: error_headers, message: message
59
36
  end
60
37
  end
61
38
  end
@@ -22,17 +22,10 @@ module Grape
22
22
  # X-Cascade header to alert Grape::Router to attempt the next matched
23
23
  # route.
24
24
  class Header < Base
25
+ include VersionerHelpers
26
+
25
27
  def before
26
- handler = Grape::Util::AcceptHeaderHandler.new(
27
- accept_header: env[Grape::Http::Headers::HTTP_ACCEPT],
28
- versions: options[:versions],
29
- **options.fetch(:version_options) { {} }
30
- )
31
-
32
- handler.match_best_quality_media_type!(
33
- content_types: content_types,
34
- allowed_methods: env[Grape::Env::GRAPE_ALLOWED_METHODS]
35
- ) do |media_type|
28
+ match_best_quality_media_type! do |media_type|
36
29
  env.update(
37
30
  Grape::Env::API_TYPE => media_type.type,
38
31
  Grape::Env::API_SUBTYPE => media_type.subtype,
@@ -42,6 +35,98 @@ module Grape
42
35
  )
43
36
  end
44
37
  end
38
+
39
+ private
40
+
41
+ def match_best_quality_media_type!
42
+ return unless vendor
43
+
44
+ strict_header_checks!
45
+ media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types)
46
+ if media_type
47
+ yield media_type
48
+ else
49
+ fail!(allowed_methods)
50
+ end
51
+ end
52
+
53
+ def allowed_methods
54
+ env[Grape::Env::GRAPE_ALLOWED_METHODS]
55
+ end
56
+
57
+ def accept_header
58
+ env[Grape::Http::Headers::HTTP_ACCEPT]
59
+ end
60
+
61
+ def strict_header_checks!
62
+ return unless strict?
63
+
64
+ accept_header_check!
65
+ version_and_vendor_check!
66
+ end
67
+
68
+ def accept_header_check!
69
+ return if accept_header.present?
70
+
71
+ invalid_accept_header!('Accept header must be set.')
72
+ end
73
+
74
+ def version_and_vendor_check!
75
+ return if versions.blank? || version_and_vendor?
76
+
77
+ invalid_accept_header!('API vendor or version not found.')
78
+ end
79
+
80
+ def q_values_mime_types
81
+ @q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
82
+ end
83
+
84
+ def version_and_vendor?
85
+ q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
86
+ end
87
+
88
+ def invalid_accept_header!(message)
89
+ raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
90
+ end
91
+
92
+ def invalid_version_header!(message)
93
+ raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
94
+ end
95
+
96
+ def fail!(grape_allowed_methods)
97
+ return grape_allowed_methods if grape_allowed_methods.present?
98
+
99
+ media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
100
+ vendor_not_found!(media_types) || version_not_found!(media_types)
101
+ end
102
+
103
+ def vendor_not_found!(media_types)
104
+ return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
105
+
106
+ invalid_accept_header!('API vendor not found.')
107
+ end
108
+
109
+ def version_not_found!(media_types)
110
+ return unless media_types.all? { |media_type| media_type&.version && versions&.exclude?(media_type.version) }
111
+
112
+ invalid_version_header!('API version not found.')
113
+ end
114
+
115
+ def available_media_types
116
+ [].tap do |available_media_types|
117
+ base_media_type = "application/vnd.#{vendor}"
118
+ content_types.each_key do |extension|
119
+ versions&.reverse_each do |version|
120
+ available_media_types << "#{base_media_type}-#{version}+#{extension}"
121
+ available_media_types << "#{base_media_type}-#{version}"
122
+ end
123
+ available_media_types << "#{base_media_type}+#{extension}"
124
+ end
125
+
126
+ available_media_types << base_media_type
127
+ available_media_types.concat(content_types.values.flatten)
128
+ end
129
+ end
45
130
  end
46
131
  end
47
132
  end
@@ -19,31 +19,15 @@ module Grape
19
19
  #
20
20
  # env['api.version'] => 'v1'
21
21
  class Param < Base
22
- def default_options
23
- {
24
- version_options: {
25
- parameter: 'apiver'
26
- }
27
- }
28
- end
22
+ include VersionerHelpers
29
23
 
30
24
  def before
31
- potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[paramkey]
32
- return if potential_version.nil?
25
+ potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[parameter_key]
26
+ return if potential_version.blank?
33
27
 
34
- throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
28
+ version_not_found! unless potential_version_match?(potential_version)
35
29
  env[Grape::Env::API_VERSION] = potential_version
36
- env[Rack::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Rack::RACK_REQUEST_QUERY_HASH
37
- end
38
-
39
- private
40
-
41
- def paramkey
42
- version_options[:parameter] || default_options[:version_options][:parameter]
43
- end
44
-
45
- def version_options
46
- options[:version_options]
30
+ env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key) if env.key? Rack::RACK_REQUEST_QUERY_HASH
47
31
  end
48
32
  end
49
33
  end
@@ -17,44 +17,24 @@ module Grape
17
17
  # env['api.version'] => 'v1'
18
18
  #
19
19
  class Path < Base
20
- def default_options
21
- {
22
- pattern: /.*/i
23
- }
24
- end
20
+ include VersionerHelpers
25
21
 
26
22
  def before
27
- path = env[Rack::PATH_INFO].dup
28
- path.sub!(mount_path, '') if mounted_path?(path)
23
+ path_info = Grape::Router.normalize_path(env[Rack::PATH_INFO])
24
+ return if path_info == '/'
29
25
 
30
- if prefix && path.index(prefix) == 0 # rubocop:disable all
31
- path.sub!(prefix, '')
32
- path = Grape::Router.normalize_path(path)
26
+ [mount_path, Grape::Router.normalize_path(prefix)].each do |path|
27
+ path_info.delete_prefix!(path) if path.present? && path != '/' && path_info.start_with?(path)
33
28
  end
34
29
 
35
- pieces = path.split('/')
36
- potential_version = pieces[1]
37
- return unless potential_version&.match?(options[:pattern])
38
-
39
- throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
40
- env[Grape::Env::API_VERSION] = potential_version
41
- end
42
-
43
- private
30
+ slash_position = path_info.index('/', 1) # omit the first one
31
+ return unless slash_position
44
32
 
45
- def mounted_path?(path)
46
- return false unless mount_path && path.start_with?(mount_path)
33
+ potential_version = path_info[1..slash_position - 1]
34
+ return unless potential_version.match?(pattern)
47
35
 
48
- rest = path.slice(mount_path.length..-1)
49
- rest.start_with?('/') || rest.empty?
50
- end
51
-
52
- def mount_path
53
- @mount_path ||= options[:mount_path] && options[:mount_path] != '/' ? options[:mount_path] : ''
54
- end
55
-
56
- def prefix
57
- Grape::Router.normalize_path(options[:prefix].to_s) if options[:prefix]
36
+ version_not_found! unless potential_version_match?(potential_version)
37
+ env[Grape::Env::API_VERSION] = potential_version
58
38
  end
59
39
  end
60
40
  end
@@ -4,30 +4,21 @@
4
4
  # on the requests. The current methods for determining version are:
5
5
  #
6
6
  # :header - version from HTTP Accept header.
7
+ # :accept_version_header - version from HTTP Accept-Version header
7
8
  # :path - version from uri. e.g. /v1/resource
8
9
  # :param - version from uri query string, e.g. /v1/resource?apiver=v1
9
- #
10
10
  # See individual classes for details.
11
11
  module Grape
12
12
  module Middleware
13
13
  module Versioner
14
14
  module_function
15
15
 
16
- # @param strategy [Symbol] :path, :header or :param
16
+ # @param strategy [Symbol] :path, :header, :accept_version_header or :param
17
17
  # @return a middleware class based on strategy
18
18
  def using(strategy)
19
- case strategy
20
- when :path
21
- Path
22
- when :header
23
- Header
24
- when :param
25
- Param
26
- when :accept_version_header
27
- AcceptVersionHeader
28
- else
29
- raise Grape::Exceptions::InvalidVersionerOption.new(strategy)
30
- end
19
+ Grape::Middleware::Versioner.const_get(:"#{strategy.to_s.camelize}")
20
+ rescue NameError
21
+ raise Grape::Exceptions::InvalidVersionerOption, strategy
31
22
  end
32
23
  end
33
24
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Middleware
5
+ module VersionerHelpers
6
+ DEFAULT_PATTERN = /.*/i.freeze
7
+ DEFAULT_PARAMETER = 'apiver'
8
+
9
+ def default_options
10
+ {
11
+ versions: nil,
12
+ prefix: nil,
13
+ mount_path: nil,
14
+ pattern: DEFAULT_PATTERN,
15
+ version_options: {
16
+ strict: false,
17
+ cascade: true,
18
+ parameter: DEFAULT_PARAMETER
19
+ }
20
+ }
21
+ end
22
+
23
+ def versions
24
+ options[:versions]
25
+ end
26
+
27
+ def prefix
28
+ options[:prefix]
29
+ end
30
+
31
+ def mount_path
32
+ options[:mount_path]
33
+ end
34
+
35
+ def pattern
36
+ options[:pattern]
37
+ end
38
+
39
+ def version_options
40
+ options[:version_options]
41
+ end
42
+
43
+ def strict?
44
+ version_options[:strict]
45
+ end
46
+
47
+ # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
48
+ # of routes (see Grape::Router) for more information). To prevent
49
+ # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
50
+ def cascade?
51
+ version_options[:cascade]
52
+ end
53
+
54
+ def parameter_key
55
+ version_options[:parameter]
56
+ end
57
+
58
+ def vendor
59
+ version_options[:vendor]
60
+ end
61
+
62
+ def error_headers
63
+ cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
64
+ end
65
+
66
+ def potential_version_match?(potential_version)
67
+ versions.blank? || versions.any? { |v| v.to_s == potential_version }
68
+ end
69
+
70
+ def version_not_found!
71
+ throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' }
72
+ end
73
+ end
74
+ end
75
+ end
data/lib/grape/parser.rb CHANGED
@@ -2,32 +2,16 @@
2
2
 
3
3
  module Grape
4
4
  module Parser
5
- extend Util::Registrable
5
+ module_function
6
6
 
7
- class << self
8
- def builtin_parsers
9
- @builtin_parsers ||= {
10
- json: Grape::Parser::Json,
11
- jsonapi: Grape::Parser::Json,
12
- xml: Grape::Parser::Xml
13
- }
14
- end
7
+ DEFAULTS = {
8
+ json: Grape::Parser::Json,
9
+ jsonapi: Grape::Parser::Json,
10
+ xml: Grape::Parser::Xml
11
+ }.freeze
15
12
 
16
- def parsers(**options)
17
- builtin_parsers.merge(default_elements).merge!(options[:parsers] || {})
18
- end
19
-
20
- def parser_for(api_format, **options)
21
- spec = parsers(**options)[api_format]
22
- case spec
23
- when nil
24
- nil
25
- when Symbol
26
- method(spec)
27
- else
28
- spec
29
- end
30
- end
13
+ def parser_for(format, parsers = nil)
14
+ parsers&.key?(format) ? parsers[format] : DEFAULTS[format]
31
15
  end
32
16
  end
33
17
  end
data/lib/grape/router.rb CHANGED
@@ -138,7 +138,8 @@ module Grape
138
138
  end
139
139
 
140
140
  def default_response
141
- [404, { Grape::Http::Headers::X_CASCADE => 'pass' }, ['404 Not Found']]
141
+ headers = Grape::Util::Header.new.merge(Grape::Http::Headers::X_CASCADE => 'pass')
142
+ [404, headers, ['404 Not Found']]
142
143
  end
143
144
 
144
145
  def match?(input, method)
@@ -21,6 +21,7 @@ module Grape
21
21
  private
22
22
 
23
23
  def do_each(params_to_process, parent_indicies = [], &block)
24
+ @scope.reset_index # gets updated depending on the size of params_to_process
24
25
  params_to_process.each_with_index do |resource_params, index|
25
26
  # when we get arrays of arrays it means that target element located inside array
26
27
  # we need this because we want to know parent arrays indicies
@@ -93,9 +93,7 @@ module Grape
93
93
 
94
94
  def meets_dependency?(params, request_params)
95
95
  return true unless @dependent_on
96
-
97
96
  return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
98
-
99
97
  return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
100
98
 
101
99
  meets_hash_dependency?(params)
@@ -103,7 +101,6 @@ module Grape
103
101
 
104
102
  def attr_meets_dependency?(params)
105
103
  return true unless @dependent_on
106
-
107
104
  return false if @parent.present? && !@parent.attr_meets_dependency?(params)
108
105
 
109
106
  meets_hash_dependency?(params)
@@ -169,6 +166,10 @@ module Grape
169
166
  !@optional
170
167
  end
171
168
 
169
+ def reset_index
170
+ @index = nil
171
+ end
172
+
172
173
  protected
173
174
 
174
175
  # Adds a parameter declaration to our list of validations.
@@ -189,7 +190,13 @@ module Grape
189
190
  #
190
191
  # @return [Array<Symbol>] the nesting/path of the current parameter scope
191
192
  def full_path
192
- nested? ? @parent.full_path + [@element] : []
193
+ if nested?
194
+ (@parent.full_path + [@element])
195
+ elsif lateral?
196
+ @parent.full_path
197
+ else
198
+ []
199
+ end
193
200
  end
194
201
 
195
202
  private
@@ -297,12 +304,7 @@ module Grape
297
304
  # `optional` invocation that opened this scope.
298
305
  # @yield parameter scope
299
306
  def new_group_scope(attrs, &block)
300
- self.class.new(
301
- api: @api,
302
- parent: self,
303
- group: attrs.first,
304
- &block
305
- )
307
+ self.class.new(api: @api, parent: self, group: attrs.first, &block)
306
308
  end
307
309
 
308
310
  # Pushes declared params to parent or settings
@@ -7,7 +7,7 @@ module Grape
7
7
  def validate_params!(params)
8
8
  keys = keys_in_common(params)
9
9
  return if keys.length == 1
10
- raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero?
10
+ raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.empty?
11
11
 
12
12
  raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))
13
13
  end