grape 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -3
  3. data/Dangerfile +1 -80
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +69 -51
  6. data/README.md +42 -1
  7. data/UPGRADING.md +2 -2
  8. data/grape.gemspec +1 -1
  9. data/lib/grape/api.rb +5 -15
  10. data/lib/grape/cookies.rb +1 -1
  11. data/lib/grape/dsl/inside_route.rb +2 -3
  12. data/lib/grape/dsl/request_response.rb +1 -1
  13. data/lib/grape/endpoint.rb +19 -7
  14. data/lib/grape/error_formatter.rb +2 -2
  15. data/lib/grape/exceptions/base.rb +18 -18
  16. data/lib/grape/exceptions/validation.rb +6 -7
  17. data/lib/grape/exceptions/validation_errors.rb +5 -5
  18. data/lib/grape/formatter.rb +2 -2
  19. data/lib/grape/locale/en.yml +1 -0
  20. data/lib/grape/middleware/auth/base.rb +2 -2
  21. data/lib/grape/middleware/base.rb +2 -2
  22. data/lib/grape/middleware/error.rb +1 -1
  23. data/lib/grape/middleware/stack.rb +1 -1
  24. data/lib/grape/middleware/versioner/header.rb +1 -1
  25. data/lib/grape/namespace.rb +1 -1
  26. data/lib/grape/parser.rb +2 -2
  27. data/lib/grape/presenters/presenter.rb +1 -1
  28. data/lib/grape/router.rb +6 -6
  29. data/lib/grape/router/pattern.rb +7 -7
  30. data/lib/grape/router/route.rb +3 -3
  31. data/lib/grape/util/env.rb +1 -1
  32. data/lib/grape/validations/params_scope.rb +15 -7
  33. data/lib/grape/validations/validators/allow_blank.rb +0 -13
  34. data/lib/grape/validations/validators/base.rb +8 -1
  35. data/lib/grape/validations/validators/default.rb +1 -3
  36. data/lib/grape/validations/validators/presence.rb +0 -5
  37. data/lib/grape/validations/validators/values.rb +17 -3
  38. data/lib/grape/version.rb +1 -1
  39. data/pkg/grape-0.18.0.gem +0 -0
  40. data/spec/grape/api_spec.rb +41 -0
  41. data/spec/grape/exceptions/validation_spec.rb +1 -1
  42. data/spec/grape/middleware/stack_spec.rb +20 -0
  43. data/spec/grape/validations/params_scope_spec.rb +190 -58
  44. data/spec/grape/validations/validators/allow_blank_spec.rb +22 -7
  45. data/spec/grape/validations/validators/coerce_spec.rb +6 -6
  46. data/spec/grape/validations/validators/values_spec.rb +146 -0
  47. data/spec/grape/validations_spec.rb +28 -0
  48. metadata +6 -6
data/UPGRADING.md CHANGED
@@ -526,7 +526,7 @@ Permitted and default parameter values are now only evaluated lazily for each re
526
526
 
527
527
  ```ruby
528
528
  params do
529
- optional :v, values: -> { [:x, :y] }, default: -> { :z } }
529
+ optional :v, values: -> { [:x, :y] }, default: -> { :z }
530
530
  end
531
531
  ```
532
532
 
@@ -534,7 +534,7 @@ Remove the proc to get the previous behavior.
534
534
 
535
535
  ```ruby
536
536
  params do
537
- optional :v, values: [:x, :y], default: :z }
537
+ optional :v, values: [:x, :y], default: :z
538
538
  end
539
539
  ```
540
540
 
data/grape.gemspec CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
13
13
  s.license = 'MIT'
14
14
 
15
15
  s.add_runtime_dependency 'rack', '>= 1.3.0'
16
- s.add_runtime_dependency 'mustermann19', '~> 0.4.3'
16
+ s.add_runtime_dependency 'mustermann-grape', '~> 0.4.0'
17
17
  s.add_runtime_dependency 'rack-accept'
18
18
  s.add_runtime_dependency 'activesupport'
19
19
  s.add_runtime_dependency 'multi_json', '>= 1.3.2'
data/lib/grape/api.rb CHANGED
@@ -173,7 +173,6 @@ module Grape
173
173
  without_versioning do
174
174
  routes_map.each do |_, config|
175
175
  methods = config[:methods]
176
- path = config[:path]
177
176
  allowed_methods = methods.dup
178
177
 
179
178
  unless self.class.namespace_inheritable(:do_not_route_head)
@@ -182,8 +181,8 @@ module Grape
182
181
 
183
182
  allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
184
183
 
185
- unless self.class.namespace_inheritable(:do_not_route_options)
186
- generate_options_method(path, allow_header, config) unless allowed_methods.include?(Grape::Http::Headers::OPTIONS)
184
+ unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
185
+ config[:endpoint].options[:options_route_enabled] = true
187
186
  end
188
187
 
189
188
  attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
@@ -193,24 +192,15 @@ module Grape
193
192
  end
194
193
  end
195
194
 
196
- # Generate an 'OPTIONS' route for a pre-exisiting user defined route
197
- def generate_options_method(path, allow_header, options = {})
198
- self.class.options(path, options) do
199
- header 'Allow', allow_header
200
- status 204
201
- ''
202
- end
203
- end
204
-
205
195
  # Generate a route that returns an HTTP 405 response for a user defined
206
196
  # path on methods not specified
207
- def generate_not_allowed_method(pattern, attributes = {})
208
- not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - attributes[:allowed_methods]
197
+ def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
198
+ not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - allowed_methods
209
199
  not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
210
200
 
211
201
  return if not_allowed_methods.empty?
212
202
 
213
- @router.associate_routes(pattern, attributes.merge(not_allowed_methods: not_allowed_methods))
203
+ @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
214
204
  end
215
205
 
216
206
  # Allows definition of endpoints that ignore the versioning configuration
data/lib/grape/cookies.rb CHANGED
@@ -31,7 +31,7 @@ module Grape
31
31
  @cookies.each(&block)
32
32
  end
33
33
 
34
- def delete(name, opts = {})
34
+ def delete(name, **opts)
35
35
  options = opts.merge(value: 'deleted', expires: Time.at(0))
36
36
  self.[]=(name, options)
37
37
  end
@@ -97,9 +97,8 @@ module Grape
97
97
  # @param options [Hash] The options used when redirect.
98
98
  # :permanent, default false.
99
99
  # :body, default a short message including the URL.
100
- def redirect(url, options = {})
101
- permanent = options.fetch(:permanent, false)
102
- body_message = options.fetch(:body, nil)
100
+ def redirect(url, permanent: false, body: nil, **_options)
101
+ body_message = body
103
102
  if permanent
104
103
  status 301
105
104
  body_message ||= "This resource has been moved permanently to #{url}."
@@ -88,7 +88,7 @@ module Grape
88
88
  # rescue_from CustomError
89
89
  # end
90
90
  #
91
- # @overload rescue_from(*exception_classes, options = {})
91
+ # @overload rescue_from(*exception_classes, **options)
92
92
  # @param [Array] exception_classes A list of classes that you want to rescue, or
93
93
  # the symbol :all to rescue from all exceptions.
94
94
  # @param [Block] block Execution block to handle the given exception.
@@ -194,8 +194,8 @@ module Grape
194
194
  version.length == 1 ? version.first.to_s : version
195
195
  end
196
196
 
197
- def merge_route_options(default = {})
198
- options[:route_options].clone.reverse_merge(default)
197
+ def merge_route_options(**default)
198
+ options[:route_options].clone.reverse_merge(**default)
199
199
  end
200
200
 
201
201
  def map_routes
@@ -248,16 +248,21 @@ module Grape
248
248
 
249
249
  run_filters befores, :before
250
250
 
251
- allowed_methods = env[Grape::Env::GRAPE_METHOD_NOT_ALLOWED]
252
- raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) if allowed_methods
251
+ allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS]
252
+ raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) if !options? && allowed_methods
253
253
 
254
254
  run_filters before_validations, :before_validation
255
-
256
255
  run_validators validations, request
257
-
258
256
  run_filters after_validations, :after_validation
259
257
 
260
- response_object = @block ? @block.call(self) : nil
258
+ response_object =
259
+ if options?
260
+ header 'Allow', allowed_methods
261
+ status 204
262
+ ''
263
+ else
264
+ @block ? @block.call(self) : nil
265
+ end
261
266
  run_filters afters, :after
262
267
  cookies.write(header)
263
268
 
@@ -338,8 +343,10 @@ module Grape
338
343
  validator.validate(request)
339
344
  rescue Grape::Exceptions::Validation => e
340
345
  validation_errors << e
346
+ break if validator.fail_fast?
341
347
  rescue Grape::Exceptions::ValidationArrayErrors => e
342
348
  validation_errors += e.errors
349
+ break if validator.fail_fast?
343
350
  end
344
351
  end
345
352
 
@@ -373,5 +380,10 @@ module Grape
373
380
  def validations
374
381
  route_setting(:saved_validations) || []
375
382
  end
383
+
384
+ def options?
385
+ options[:options_route_enabled] &&
386
+ env[Grape::Http::Headers::REQUEST_METHOD] == Grape::Http::Headers::OPTIONS
387
+ end
376
388
  end
377
389
  end
@@ -17,8 +17,8 @@ module Grape
17
17
  builtin_formatters.merge(default_elements).merge(options[:error_formatters] || {})
18
18
  end
19
19
 
20
- def formatter_for(api_format, options = {})
21
- spec = formatters(options)[api_format]
20
+ def formatter_for(api_format, **options)
21
+ spec = formatters(**options)[api_format]
22
22
  case spec
23
23
  when nil
24
24
  options[:default_error_formatter] || Grape::ErrorFormatter::Txt
@@ -7,10 +7,10 @@ module Grape
7
7
 
8
8
  attr_reader :status, :message, :headers
9
9
 
10
- def initialize(args = {})
11
- @status = args[:status] || nil
12
- @message = args[:message] || nil
13
- @headers = args[:headers] || nil
10
+ def initialize(status: nil, message: nil, headers: nil, **_options)
11
+ @status = status
12
+ @message = message
13
+ @headers = headers
14
14
  end
15
15
 
16
16
  def [](index)
@@ -22,12 +22,12 @@ module Grape
22
22
  # TODO: translate attribute first
23
23
  # if BASE_ATTRIBUTES_KEY.key respond to a string message, then short_message is returned
24
24
  # if BASE_ATTRIBUTES_KEY.key respond to a Hash, means it may have problem , summary and resolution
25
- def compose_message(key, attributes = {})
26
- short_message = translate_message(key, attributes)
25
+ def compose_message(key, **attributes)
26
+ short_message = translate_message(key, **attributes)
27
27
  if short_message.is_a? Hash
28
- @problem = problem(key, attributes)
29
- @summary = summary(key, attributes)
30
- @resolution = resolution(key, attributes)
28
+ @problem = problem(key, **attributes)
29
+ @summary = summary(key, **attributes)
30
+ @resolution = resolution(key, **attributes)
31
31
  [['Problem', @problem], ['Summary', @summary], ['Resolution', @resolution]].reduce('') do |message, detail_array|
32
32
  message << "\n#{detail_array[0]}:\n #{detail_array[1]}" unless detail_array[1].blank?
33
33
  message
@@ -49,20 +49,20 @@ module Grape
49
49
  translate_message("#{key}.resolution".to_sym, attributes)
50
50
  end
51
51
 
52
- def translate_attributes(keys, options = {})
52
+ def translate_attributes(keys, **options)
53
53
  keys.map do |key|
54
- translate("#{BASE_ATTRIBUTES_KEY}.#{key}", options.reverse_merge(default: key))
54
+ translate("#{BASE_ATTRIBUTES_KEY}.#{key}", default: key, **options)
55
55
  end.join(', ')
56
56
  end
57
57
 
58
- def translate_attribute(key, options = {})
59
- translate("#{BASE_ATTRIBUTES_KEY}.#{key}", options.reverse_merge(default: key))
58
+ def translate_attribute(key, **options)
59
+ translate("#{BASE_ATTRIBUTES_KEY}.#{key}", default: key, **options)
60
60
  end
61
61
 
62
- def translate_message(key, options = {})
62
+ def translate_message(key, **options)
63
63
  case key
64
64
  when Symbol
65
- translate("#{BASE_MESSAGES_KEY}.#{key}", options.reverse_merge(default: ''))
65
+ translate("#{BASE_MESSAGES_KEY}.#{key}", default: '', **options)
66
66
  when Proc
67
67
  key.call
68
68
  else
@@ -70,9 +70,9 @@ module Grape
70
70
  end
71
71
  end
72
72
 
73
- def translate(key, options = {})
74
- message = ::I18n.translate(key, options)
75
- message.present? ? message : ::I18n.translate(key, options.merge(locale: FALLBACK_LOCALE))
73
+ def translate(key, **options)
74
+ message = ::I18n.translate(key, **options)
75
+ message.present? ? message : ::I18n.translate(key, locale: FALLBACK_LOCALE, **options)
76
76
  end
77
77
  end
78
78
  end
@@ -6,14 +6,13 @@ module Grape
6
6
  attr_accessor :params
7
7
  attr_accessor :message_key
8
8
 
9
- def initialize(args = {})
10
- raise 'Params are missing:' unless args.key? :params
11
- @params = args[:params]
12
- if args.key?(:message)
13
- @message_key = args[:message] if args[:message].is_a?(Symbol)
14
- args[:message] = translate_message(args[:message])
9
+ def initialize(params:, message: nil, **args)
10
+ @params = params
11
+ if message
12
+ @message_key = message if message.is_a?(Symbol)
13
+ args[:message] = translate_message(message)
15
14
  end
16
- super
15
+ super(args)
17
16
  end
18
17
 
19
18
  # remove all the unnecessary stuff from Grape::Exceptions::Base like status
@@ -7,14 +7,14 @@ module Grape
7
7
 
8
8
  attr_reader :errors
9
9
 
10
- def initialize(args = {})
10
+ def initialize(errors: [], headers: {}, **_options)
11
11
  @errors = {}
12
- args[:errors].each do |validation_error|
12
+ errors.each do |validation_error|
13
13
  @errors[validation_error.params] ||= []
14
14
  @errors[validation_error.params] << validation_error
15
15
  end
16
16
 
17
- super message: full_messages.join(', '), status: 400, headers: args[:headers]
17
+ super message: full_messages.join(', '), status: 400, headers: headers
18
18
  end
19
19
 
20
20
  def each
@@ -25,7 +25,7 @@ module Grape
25
25
  end
26
26
  end
27
27
 
28
- def as_json(_opts = {})
28
+ def as_json(**_opts)
29
29
  errors.map do |k, v|
30
30
  {
31
31
  params: k,
@@ -34,7 +34,7 @@ module Grape
34
34
  end
35
35
  end
36
36
 
37
- def to_json(_opts = {})
37
+ def to_json(**_opts)
38
38
  as_json.to_json
39
39
  end
40
40
 
@@ -17,8 +17,8 @@ module Grape
17
17
  builtin_formmaters.merge(default_elements).merge(options[:formatters] || {})
18
18
  end
19
19
 
20
- def formatter_for(api_format, options = {})
21
- spec = formatters(options)[api_format]
20
+ def formatter_for(api_format, **options)
21
+ spec = formatters(**options)[api_format]
22
22
  case spec
23
23
  when nil
24
24
  ->(obj, _env) { obj }
@@ -8,6 +8,7 @@ en:
8
8
  regexp: 'is invalid'
9
9
  blank: 'is empty'
10
10
  values: 'does not have a valid value'
11
+ except: 'has a value not allowed'
11
12
  missing_vendor_option:
12
13
  problem: 'missing :vendor option.'
13
14
  summary: 'when version using header, you must specify :vendor option. '
@@ -6,9 +6,9 @@ module Grape
6
6
  class Base
7
7
  attr_accessor :options, :app, :env
8
8
 
9
- def initialize(app, options = {})
9
+ def initialize(app, **options)
10
10
  @app = app
11
- @options = options || {}
11
+ @options = options
12
12
  end
13
13
 
14
14
  def context
@@ -10,9 +10,9 @@ module Grape
10
10
 
11
11
  # @param [Rack Application] app The standard argument for a Rack middleware.
12
12
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
13
- def initialize(app, options = {})
13
+ def initialize(app, **options)
14
14
  @app = app
15
- @options = default_options.merge(options)
15
+ @options = default_options.merge(**options)
16
16
  end
17
17
 
18
18
  def default_options
@@ -21,7 +21,7 @@ module Grape
21
21
  }
22
22
  end
23
23
 
24
- def initialize(app, options = {})
24
+ def initialize(app, **options)
25
25
  super
26
26
  self.class.send(:include, @options[:helpers]) if @options[:helpers]
27
27
  end
@@ -21,7 +21,7 @@ module Grape
21
21
  when Middleware
22
22
  klass == other.klass
23
23
  when Class
24
- klass == other
24
+ klass == other || (name.nil? && klass.superclass == other)
25
25
  end
26
26
  end
27
27
 
@@ -32,7 +32,7 @@ module Grape
32
32
  def before
33
33
  strict_header_checks if strict?
34
34
 
35
- if media_type || env[Grape::Env::GRAPE_METHOD_NOT_ALLOWED]
35
+ if media_type || env[Grape::Env::GRAPE_ALLOWED_METHODS]
36
36
  media_type_header_handler
37
37
  elsif headers_contain_wrong_vendor?
38
38
  fail_with_invalid_accept_header!('API vendor not found.')
@@ -10,7 +10,7 @@ module Grape
10
10
  # @option options :requirements [Hash] param-regex pairs, all of which must
11
11
  # be met by a request's params for all endpoints in this namespace, or
12
12
  # validation will fail and return a 422.
13
- def initialize(space, options = {})
13
+ def initialize(space, **options)
14
14
  @space = space.to_s
15
15
  @options = options
16
16
  end
data/lib/grape/parser.rb CHANGED
@@ -15,8 +15,8 @@ module Grape
15
15
  builtin_parsers.merge(default_elements).merge(options[:parsers] || {})
16
16
  end
17
17
 
18
- def parser_for(api_format, options = {})
19
- spec = parsers(options)[api_format]
18
+ def parser_for(api_format, **options)
19
+ spec = parsers(**options)[api_format]
20
20
  case spec
21
21
  when nil
22
22
  nil
@@ -1,7 +1,7 @@
1
1
  module Grape
2
2
  module Presenters
3
3
  class Presenter
4
- def self.represent(object, _options = {})
4
+ def self.represent(object, **_options)
5
5
  object
6
6
  end
7
7
  end
data/lib/grape/router.rb CHANGED
@@ -5,7 +5,7 @@ module Grape
5
5
  attr_reader :map, :compiled
6
6
 
7
7
  class Any < AttributeTranslator
8
- def initialize(pattern, attributes = {})
8
+ def initialize(pattern, **attributes)
9
9
  @pattern = pattern
10
10
  super(attributes)
11
11
  end
@@ -42,9 +42,9 @@ module Grape
42
42
  map[route.request_method.to_s.upcase] << route
43
43
  end
44
44
 
45
- def associate_routes(pattern, options = {})
45
+ def associate_routes(pattern, **options)
46
46
  regexp = /(?<_#{@neutral_map.length}>)#{pattern.to_regexp}/
47
- @neutral_map << Any.new(pattern, options.merge(regexp: regexp, index: @neutral_map.length))
47
+ @neutral_map << Any.new(pattern, regexp: regexp, index: @neutral_map.length, **options)
48
48
  end
49
49
 
50
50
  def call(env)
@@ -95,7 +95,7 @@ module Grape
95
95
  neighbor = greedy_match?(input)
96
96
  return unless neighbor
97
97
 
98
- (!cascade && neighbor) ? method_not_allowed(env, neighbor.allow_header, neighbor.endpoint) : nil
98
+ (!cascade && neighbor) ? call_with_allow_headers(env, neighbor.allow_header, neighbor.endpoint) : nil
99
99
  end
100
100
 
101
101
  def make_routing_args(default_args, route, input)
@@ -132,8 +132,8 @@ module Grape
132
132
  @neutral_map.detect { |route| last_match["_#{route.index}"] }
133
133
  end
134
134
 
135
- def method_not_allowed(env, methods, endpoint)
136
- env[Grape::Env::GRAPE_METHOD_NOT_ALLOWED] = methods
135
+ def call_with_allow_headers(env, methods, endpoint)
136
+ env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods
137
137
  endpoint.call(env)
138
138
  end
139
139