grape 0.12.0 → 0.14.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +9 -4
  3. data/CHANGELOG.md +265 -215
  4. data/CONTRIBUTING.md +4 -4
  5. data/Gemfile +0 -1
  6. data/Gemfile.lock +166 -0
  7. data/README.md +426 -161
  8. data/RELEASING.md +14 -6
  9. data/Rakefile +30 -33
  10. data/UPGRADING.md +54 -23
  11. data/benchmark/simple.rb +27 -0
  12. data/gemfiles/rack_1.5.2.gemfile +13 -0
  13. data/gemfiles/rails_3.gemfile +2 -2
  14. data/gemfiles/rails_4.gemfile +1 -2
  15. data/grape.gemspec +6 -7
  16. data/lib/grape/api.rb +24 -4
  17. data/lib/grape/dsl/callbacks.rb +20 -0
  18. data/lib/grape/dsl/configuration.rb +59 -2
  19. data/lib/grape/dsl/helpers.rb +8 -3
  20. data/lib/grape/dsl/inside_route.rb +100 -45
  21. data/lib/grape/dsl/parameters.rb +96 -7
  22. data/lib/grape/dsl/request_response.rb +1 -1
  23. data/lib/grape/dsl/routing.rb +17 -4
  24. data/lib/grape/dsl/settings.rb +36 -1
  25. data/lib/grape/dsl/validations.rb +7 -5
  26. data/lib/grape/endpoint.rb +102 -57
  27. data/lib/grape/error_formatter/base.rb +6 -6
  28. data/lib/grape/exceptions/base.rb +5 -5
  29. data/lib/grape/exceptions/invalid_version_header.rb +10 -0
  30. data/lib/grape/exceptions/unknown_parameter.rb +10 -0
  31. data/lib/grape/exceptions/validation_errors.rb +4 -3
  32. data/lib/grape/formatter/serializable_hash.rb +3 -2
  33. data/lib/grape/http/headers.rb +0 -1
  34. data/lib/grape/locale/en.yml +5 -1
  35. data/lib/grape/middleware/auth/base.rb +2 -2
  36. data/lib/grape/middleware/auth/dsl.rb +1 -1
  37. data/lib/grape/middleware/auth/strategies.rb +1 -1
  38. data/lib/grape/middleware/base.rb +8 -4
  39. data/lib/grape/middleware/error.rb +3 -2
  40. data/lib/grape/middleware/filter.rb +1 -1
  41. data/lib/grape/middleware/formatter.rb +64 -45
  42. data/lib/grape/middleware/globals.rb +3 -3
  43. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
  44. data/lib/grape/middleware/versioner/header.rb +113 -50
  45. data/lib/grape/middleware/versioner/param.rb +5 -8
  46. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
  47. data/lib/grape/middleware/versioner/path.rb +3 -6
  48. data/lib/grape/namespace.rb +13 -2
  49. data/lib/grape/path.rb +4 -3
  50. data/lib/grape/request.rb +40 -0
  51. data/lib/grape/route.rb +5 -0
  52. data/lib/grape/util/content_types.rb +9 -9
  53. data/lib/grape/util/env.rb +22 -0
  54. data/lib/grape/util/file_response.rb +21 -0
  55. data/lib/grape/util/inheritable_setting.rb +23 -2
  56. data/lib/grape/util/inheritable_values.rb +1 -1
  57. data/lib/grape/util/stackable_values.rb +5 -2
  58. data/lib/grape/util/strict_hash_configuration.rb +2 -1
  59. data/lib/grape/validations/attributes_iterator.rb +8 -3
  60. data/lib/grape/validations/params_scope.rb +164 -22
  61. data/lib/grape/validations/types/build_coercer.rb +53 -0
  62. data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
  63. data/lib/grape/validations/types/file.rb +28 -0
  64. data/lib/grape/validations/types/json.rb +65 -0
  65. data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
  66. data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
  67. data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
  68. data/lib/grape/validations/types.rb +144 -0
  69. data/lib/grape/validations/validators/all_or_none.rb +1 -1
  70. data/lib/grape/validations/validators/allow_blank.rb +3 -3
  71. data/lib/grape/validations/validators/base.rb +7 -0
  72. data/lib/grape/validations/validators/coerce.rb +32 -34
  73. data/lib/grape/validations/validators/presence.rb +2 -3
  74. data/lib/grape/validations/validators/regexp.rb +2 -4
  75. data/lib/grape/validations/validators/values.rb +3 -3
  76. data/lib/grape/validations.rb +5 -0
  77. data/lib/grape/version.rb +2 -1
  78. data/lib/grape.rb +15 -12
  79. data/pkg/grape-0.13.0.gem +0 -0
  80. data/spec/grape/api/custom_validations_spec.rb +5 -4
  81. data/spec/grape/api/deeply_included_options_spec.rb +7 -7
  82. data/spec/grape/api/nested_helpers_spec.rb +4 -2
  83. data/spec/grape/api/shared_helpers_spec.rb +8 -8
  84. data/spec/grape/api_spec.rb +151 -54
  85. data/spec/grape/dsl/configuration_spec.rb +13 -0
  86. data/spec/grape/dsl/helpers_spec.rb +16 -2
  87. data/spec/grape/dsl/inside_route_spec.rb +40 -4
  88. data/spec/grape/dsl/parameters_spec.rb +0 -6
  89. data/spec/grape/dsl/routing_spec.rb +1 -1
  90. data/spec/grape/dsl/validations_spec.rb +18 -0
  91. data/spec/grape/endpoint_spec.rb +130 -6
  92. data/spec/grape/entity_spec.rb +10 -8
  93. data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
  94. data/spec/grape/exceptions/validation_errors_spec.rb +28 -0
  95. data/spec/grape/integration/rack_spec.rb +3 -2
  96. data/spec/grape/middleware/base_spec.rb +40 -16
  97. data/spec/grape/middleware/error_spec.rb +16 -15
  98. data/spec/grape/middleware/exception_spec.rb +45 -43
  99. data/spec/grape/middleware/formatter_spec.rb +34 -5
  100. data/spec/grape/middleware/versioner/header_spec.rb +79 -47
  101. data/spec/grape/path_spec.rb +10 -10
  102. data/spec/grape/presenters/presenter_spec.rb +2 -2
  103. data/spec/grape/request_spec.rb +100 -0
  104. data/spec/grape/util/inheritable_values_spec.rb +14 -0
  105. data/spec/grape/util/stackable_values_spec.rb +10 -0
  106. data/spec/grape/validations/params_scope_spec.rb +86 -0
  107. data/spec/grape/validations/types_spec.rb +95 -0
  108. data/spec/grape/validations/validators/coerce_spec.rb +364 -10
  109. data/spec/grape/validations/validators/values_spec.rb +27 -15
  110. data/spec/grape/validations_spec.rb +53 -24
  111. data/spec/shared/versioning_examples.rb +2 -2
  112. data/spec/spec_helper.rb +0 -1
  113. data/spec/support/versioned_helpers.rb +2 -2
  114. metadata +55 -14
  115. data/.gitignore +0 -46
  116. data/.rspec +0 -2
  117. data/.rubocop.yml +0 -7
  118. data/.rubocop_todo.yml +0 -84
  119. data/.travis.yml +0 -20
  120. data/.yardopts +0 -2
  121. data/lib/backports/active_support/deep_dup.rb +0 -49
  122. data/lib/backports/active_support/duplicable.rb +0 -88
  123. data/lib/grape/http/request.rb +0 -27
@@ -2,24 +2,37 @@ require 'active_support/concern'
2
2
 
3
3
  module Grape
4
4
  module DSL
5
+ # Keeps track of settings (impemented as key-value pairs, grouped by
6
+ # types), in two contexts: top-level settings which apply globally no
7
+ # matter where they're defined, and inheritable settings which apply only
8
+ # in the current scope and scopes nested under it.
5
9
  module Settings
6
10
  extend ActiveSupport::Concern
7
11
 
8
12
  attr_accessor :inheritable_setting, :top_level_setting
9
13
 
14
+ # Fetch our top-level settings, which apply to all endpoints in the API.
10
15
  def top_level_setting
11
16
  @top_level_setting ||= Grape::Util::InheritableSetting.new
12
17
  end
13
18
 
19
+ # Fetch our current inheritable settings, which are inherited by
20
+ # nested scopes but not shared across siblings.
14
21
  def inheritable_setting
15
22
  @inheritable_setting ||= Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from top_level_setting }
16
23
  end
17
24
 
25
+ # @param type [Symbol]
26
+ # @param key [Symbol]
18
27
  def unset(type, key)
19
28
  setting = inheritable_setting.send(type)
20
29
  setting.delete key
21
30
  end
22
31
 
32
+ # @param type [Symbol]
33
+ # @param key [Symbol]
34
+ # @param value [Object] will be stored if the value is currently empty
35
+ # @return either the old value, if it wasn't nil, or the given value
23
36
  def get_or_set(type, key, value)
24
37
  setting = inheritable_setting.send(type)
25
38
  if value.nil?
@@ -29,72 +42,94 @@ module Grape
29
42
  end
30
43
  end
31
44
 
45
+ # @param key [Symbol]
46
+ # @param value [Object]
47
+ # @return (see #get_or_set)
32
48
  def global_setting(key, value = nil)
33
49
  get_or_set :global, key, value
34
50
  end
35
51
 
52
+ # @param key [Symbol]
36
53
  def unset_global_setting(key)
37
54
  unset :global, key
38
55
  end
39
56
 
57
+ # (see #global_setting)
40
58
  def route_setting(key, value = nil)
41
59
  get_or_set :route, key, value
42
60
  end
43
61
 
62
+ # (see #unset_global_setting)
44
63
  def unset_route_setting(key)
45
64
  unset :route, key
46
65
  end
47
66
 
67
+ # (see #global_setting)
48
68
  def namespace_setting(key, value = nil)
49
69
  get_or_set :namespace, key, value
50
70
  end
51
71
 
72
+ # (see #unset_global_setting)
52
73
  def unset_namespace_setting(key)
53
74
  unset :namespace_setting, key
54
75
  end
55
76
 
77
+ # (see #global_setting)
56
78
  def namespace_inheritable(key, value = nil)
57
79
  get_or_set :namespace_inheritable, key, value
58
80
  end
59
81
 
82
+ # (see #unset_global_setting)
60
83
  def unset_namespace_inheritable(key)
61
84
  unset :namespace_inheritable, key
62
85
  end
63
86
 
87
+ # @param key [Symbol]
64
88
  def namespace_inheritable_to_nil(key)
65
89
  inheritable_setting.namespace_inheritable[key] = nil
66
90
  end
67
91
 
92
+ # (see #global_setting)
68
93
  def namespace_stackable(key, value = nil)
69
94
  get_or_set :namespace_stackable, key, value
70
95
  end
71
96
 
97
+ # (see #unset_global_setting)
72
98
  def unset_namespace_stackable(key)
73
99
  unset :namespace_stackable, key
74
100
  end
75
101
 
102
+ # (see #global_setting)
76
103
  def api_class_setting(key, value = nil)
77
104
  get_or_set :api_class, key, value
78
105
  end
79
106
 
107
+ # (see #unset_global_setting)
80
108
  def unset_api_class_setting(key)
81
109
  unset :api_class_setting, key
82
110
  end
83
111
 
112
+ # Fork our inheritable settings to a new instance, copied from our
113
+ # parent's, but separate so we won't modify it. Every call to this
114
+ # method should have an answering call to #namespace_end.
84
115
  def namespace_start
85
116
  @inheritable_setting = Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from inheritable_setting }
86
117
  end
87
118
 
119
+ # Set the inheritable settings pointer back up by one level.
88
120
  def namespace_end
89
121
  route_end
90
122
  @inheritable_setting = inheritable_setting.parent
91
123
  end
92
124
 
125
+ # Stop defining settings for the current route and clear them for the
126
+ # next, within a namespace.
93
127
  def route_end
94
128
  inheritable_setting.route_end
95
- reset_validations!
96
129
  end
97
130
 
131
+ # Execute the block within a context where our inheritable settings are forked
132
+ # to a new copy (see #namespace_start).
98
133
  def within_namespace(&_block)
99
134
  namespace_start
100
135
 
@@ -8,22 +8,24 @@ module Grape
8
8
  include Grape::DSL::Configuration
9
9
 
10
10
  module ClassMethods
11
+ # Clears all defined parameters and validations.
11
12
  def reset_validations!
12
13
  unset_namespace_stackable :declared_params
13
14
  unset_namespace_stackable :validations
14
15
  unset_namespace_stackable :params
16
+ unset_description_field :params
15
17
  end
16
18
 
19
+ # Opens a root-level ParamsScope, defining parameter coercions and
20
+ # validations for the endpoint.
21
+ # @yield instance context of the new scope
17
22
  def params(&block)
18
23
  Grape::Validations::ParamsScope.new(api: self, type: Hash, &block)
19
24
  end
20
25
 
21
26
  def document_attribute(names, opts)
22
- route_setting(:description, {}) unless route_setting(:description)
23
-
24
- route_setting(:description)[:params] ||= {}
25
-
26
- setting = route_setting(:description)[:params]
27
+ setting = description_field(:params)
28
+ setting ||= description_field(:params, {})
27
29
  Array(names).each do |name|
28
30
  setting[name[:full_name].to_s] ||= {}
29
31
  setting[name[:full_name].to_s].merge!(opts)
@@ -12,15 +12,31 @@ module Grape
12
12
  include Grape::DSL::InsideRoute
13
13
 
14
14
  class << self
15
+ def new(*args, &block)
16
+ if self == Endpoint
17
+ Class.new(Endpoint).new(*args, &block)
18
+ else
19
+ super
20
+ end
21
+ end
22
+
15
23
  def before_each(new_setup = false, &block)
24
+ @before_each ||= []
16
25
  if new_setup == false
17
26
  if block_given?
18
- @before_each = block
27
+ @before_each << block
19
28
  else
20
29
  return @before_each
21
30
  end
22
31
  else
23
- @before_each = new_setup
32
+ @before_each = [new_setup]
33
+ end
34
+ end
35
+
36
+ def run_before_each(endpoint)
37
+ superclass.run_before_each(endpoint) unless self == Endpoint
38
+ before_each.each do |blk|
39
+ blk.call(endpoint) if blk.respond_to? :call
24
40
  end
25
41
  end
26
42
 
@@ -41,10 +57,16 @@ module Grape
41
57
  if instance_methods.include?(method_name.to_sym) || instance_methods.include?(method_name.to_s)
42
58
  fail NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
43
59
  end
60
+
44
61
  define_method(method_name, &block)
45
62
  method = instance_method(method_name)
46
63
  remove_method(method_name)
47
- proc { |endpoint_instance| method.bind(endpoint_instance).call }
64
+
65
+ proc do |endpoint_instance|
66
+ ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
67
+ method.bind(endpoint_instance).call
68
+ end
69
+ end
48
70
  end
49
71
  end
50
72
 
@@ -68,10 +90,12 @@ module Grape
68
90
  @options[:method] = Array(options[:method])
69
91
  @options[:route_options] ||= {}
70
92
 
71
- if block_given?
72
- @source = block
73
- @block = self.class.generate_api_method(method_name, &block)
74
- end
93
+ @lazy_initialize_lock = Mutex.new
94
+
95
+ return unless block_given?
96
+
97
+ @source = block
98
+ @block = self.class.generate_api_method(method_name, &block)
75
99
  end
76
100
 
77
101
  def require_option(options, key)
@@ -113,7 +137,7 @@ module Grape
113
137
  route_set.add_route(self, {
114
138
  path_info: route.route_compiled,
115
139
  request_method: method
116
- }, route_info: route)
140
+ }, route_info: route)
117
141
  end
118
142
  end
119
143
  end
@@ -181,20 +205,14 @@ module Grape
181
205
  end
182
206
 
183
207
  def call(env)
208
+ lazy_initialize!
184
209
  dup.call!(env)
185
210
  end
186
211
 
187
212
  def call!(env)
188
- extend helpers
189
-
190
- env['api.endpoint'] = self
191
- if options[:app]
192
- options[:app].call(env)
193
- else
194
- builder = build_middleware
195
- builder.run ->(arg) { run(arg) }
196
- builder.call(env)
197
- end
213
+ env[Grape::Env::API_ENDPOINT] = self
214
+ @env = env
215
+ @app.call(env)
198
216
  end
199
217
 
200
218
  # Return the collection of endpoints within this endpoint.
@@ -209,51 +227,50 @@ module Grape
209
227
 
210
228
  protected
211
229
 
212
- def run(env)
213
- @env = env
214
- @header = {}
215
-
216
- @request = Grape::Request.new(env)
217
- @params = @request.params
218
- @headers = @request.headers
230
+ def run
231
+ ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
232
+ @header = {}
219
233
 
220
- cookies.read(@request)
234
+ @request = Grape::Request.new(env)
235
+ @params = @request.params
236
+ @headers = @request.headers
221
237
 
222
- self.class.before_each.call(self) if self.class.before_each
238
+ cookies.read(@request)
223
239
 
224
- run_filters befores
240
+ self.class.run_before_each(self)
225
241
 
226
- run_filters before_validations
242
+ run_filters befores, :before
227
243
 
228
- # Retrieve validations from this namespace and all parent namespaces.
229
- validation_errors = []
244
+ run_filters before_validations, :before_validation
230
245
 
231
- # require 'pry-byebug'; binding.pry
246
+ # Retrieve validations from this namespace and all parent namespaces.
247
+ validation_errors = []
232
248
 
233
- route_setting(:saved_validations).each do |validator|
234
- begin
235
- validator.validate!(params)
236
- rescue Grape::Exceptions::Validation => e
237
- validation_errors << e
249
+ route_setting(:saved_validations).each do |validator|
250
+ begin
251
+ validator.validate!(params)
252
+ rescue Grape::Exceptions::Validation => e
253
+ validation_errors << e
254
+ end
238
255
  end
239
- end
240
256
 
241
- if validation_errors.any?
242
- fail Grape::Exceptions::ValidationErrors, errors: validation_errors
243
- end
257
+ if validation_errors.any?
258
+ fail Grape::Exceptions::ValidationErrors, errors: validation_errors, headers: header
259
+ end
244
260
 
245
- run_filters after_validations
261
+ run_filters after_validations, :after_validation
246
262
 
247
- response_object = @block ? @block.call(self) : nil
248
- run_filters afters
249
- cookies.write(header)
263
+ response_object = @block ? @block.call(self) : nil
264
+ run_filters afters, :after
265
+ cookies.write(header)
250
266
 
251
- # The Body commonly is an Array of Strings, the application instance itself, or a File-like object.
252
- response_object = file || [body || response_object]
253
- [status, header, response_object]
267
+ # The Body commonly is an Array of Strings, the application instance itself, or a File-like object.
268
+ response_object = file || [body || response_object]
269
+ [status, header, response_object]
270
+ end
254
271
  end
255
272
 
256
- def build_middleware
273
+ def build_stack
257
274
  b = Rack::Builder.new
258
275
 
259
276
  b.use Rack::Head
@@ -294,21 +311,49 @@ module Grape
294
311
  formatters: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:formatters)),
295
312
  parsers: Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:parsers))
296
313
 
297
- b
314
+ b.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
315
+
316
+ b.to_app
298
317
  end
299
318
 
319
+ def build_helpers
320
+ helpers = namespace_stackable(:helpers) || []
321
+ Module.new do
322
+ helpers.each do |mod_to_include|
323
+ include mod_to_include
324
+ end
325
+ end
326
+ end
327
+
328
+ private :build_stack, :build_helpers
329
+
300
330
  def helpers
301
- mod = Module.new
302
- (namespace_stackable(:helpers) || []).each do |mod_to_include|
303
- mod.send :include, mod_to_include
331
+ lazy_initialize! && @helpers
332
+ end
333
+
334
+ def lazy_initialize!
335
+ return true if @lazy_initialized
336
+
337
+ @lazy_initialize_lock.synchronize do
338
+ return true if @lazy_initialized
339
+
340
+ @app = options[:app] || build_stack
341
+ @helpers = build_helpers.tap do |mod|
342
+ self.class.send(:include, mod)
343
+ end
344
+
345
+ @lazy_initialized = true
304
346
  end
305
- mod
306
347
  end
307
348
 
308
- def run_filters(filters)
309
- (filters || []).each do |filter|
310
- instance_eval(&filter)
349
+ def run_filters(filters, type = :other)
350
+ ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
351
+ (filters || []).each do |filter|
352
+ instance_eval(&filter)
353
+ end
311
354
  end
355
+ post_extension = DSL::InsideRoute.post_filter_methods(type)
356
+ extend post_extension if post_extension
312
357
  end
313
358
 
314
359
  def befores
@@ -33,22 +33,22 @@ module Grape
33
33
  present_options = {}
34
34
  present_options[:with] = message.delete(:with) if message.is_a?(Hash)
35
35
 
36
- presenter = env['api.endpoint'].entity_class_for_obj(message, present_options)
36
+ presenter = env[Grape::Env::API_ENDPOINT].entity_class_for_obj(message, present_options)
37
37
 
38
- unless presenter || env['rack.routing_args'].nil?
38
+ unless presenter || env[Grape::Env::RACK_ROUTING_ARGS].nil?
39
39
  # env['api.endpoint'].route does not work when the error occurs within a middleware
40
40
  # the Endpoint does not have a valid env at this moment
41
- http_codes = env['rack.routing_args'][:route_info].route_http_codes || []
41
+ http_codes = env[Grape::Env::RACK_ROUTING_ARGS][:route_info].route_http_codes || []
42
42
  found_code = http_codes.find do |http_code|
43
- (http_code[0].to_i == env['api.endpoint'].status) && http_code[2].respond_to?(:represent)
44
- end if env['api.endpoint'].request
43
+ (http_code[0].to_i == env[Grape::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent)
44
+ end if env[Grape::Env::API_ENDPOINT].request
45
45
 
46
46
  presenter = found_code[2] if found_code
47
47
  end
48
48
 
49
49
  if presenter
50
50
  embeds = { env: env }
51
- embeds[:version] = env['api.version'] if env['api.version']
51
+ embeds[:version] = env[Grape::Env::API_VERSION] if env[Grape::Env::API_VERSION]
52
52
  message = presenter.represent(message, embeds).serializable_hash
53
53
  end
54
54
 
@@ -1,8 +1,8 @@
1
1
  module Grape
2
2
  module Exceptions
3
3
  class Base < StandardError
4
- BASE_MESSAGES_KEY = 'grape.errors.messages'
5
- BASE_ATTRIBUTES_KEY = 'grape.errors.attributes'
4
+ BASE_MESSAGES_KEY = 'grape.errors.messages'.freeze
5
+ BASE_ATTRIBUTES_KEY = 'grape.errors.attributes'.freeze
6
6
  FALLBACK_LOCALE = :en
7
7
 
8
8
  attr_reader :status, :message, :headers
@@ -51,16 +51,16 @@ module Grape
51
51
 
52
52
  def translate_attributes(keys, options = {})
53
53
  keys.map do |key|
54
- translate("#{BASE_ATTRIBUTES_KEY}.#{key}", { default: key }.merge(options))
54
+ translate("#{BASE_ATTRIBUTES_KEY}.#{key}", options.reverse_merge(default: key))
55
55
  end.join(', ')
56
56
  end
57
57
 
58
58
  def translate_attribute(key, options = {})
59
- translate("#{BASE_ATTRIBUTES_KEY}.#{key}", { default: key }.merge(options))
59
+ translate("#{BASE_ATTRIBUTES_KEY}.#{key}", options.reverse_merge(default: key))
60
60
  end
61
61
 
62
62
  def translate_message(key, options = {})
63
- translate("#{BASE_MESSAGES_KEY}.#{key}", { default: '' }.merge(options))
63
+ translate("#{BASE_MESSAGES_KEY}.#{key}", options.reverse_merge(default: ''))
64
64
  end
65
65
 
66
66
  def translate(key, options = {})
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class InvalidVersionHeader < Base
5
+ def initialize(message, headers)
6
+ super(message: compose_message('invalid_version_header', message: message), status: 406, headers: headers)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class UnknownParameter < Base
5
+ def initialize(param)
6
+ super(message: compose_message('unknown_parameter', param: param))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -13,7 +13,8 @@ module Grape
13
13
  @errors[validation_error.params] ||= []
14
14
  @errors[validation_error.params] << validation_error
15
15
  end
16
- super message: full_messages.join(', '), status: 400
16
+
17
+ super message: full_messages.join(', '), status: 400, headers: args[:headers]
17
18
  end
18
19
 
19
20
  def each
@@ -37,12 +38,12 @@ module Grape
37
38
  as_json.to_json
38
39
  end
39
40
 
40
- private
41
-
42
41
  def full_messages
43
42
  map { |attributes, error| full_message(attributes, error) }.uniq
44
43
  end
45
44
 
45
+ private
46
+
46
47
  def full_message(attributes, error)
47
48
  I18n.t(
48
49
  'grape.errors.format'.to_sym,
@@ -21,10 +21,11 @@ module Grape
21
21
  elsif object.is_a?(Array) && !object.map { |o| o.respond_to? :serializable_hash }.include?(false)
22
22
  object.map(&:serializable_hash)
23
23
  elsif object.is_a?(Hash)
24
- object.inject({}) do |h, (k, v)|
24
+ h = {}
25
+ object.each_pair do |k, v|
25
26
  h[k] = serialize(v)
26
- h
27
27
  end
28
+ h
28
29
  else
29
30
  object
30
31
  end
@@ -20,7 +20,6 @@ module Grape
20
20
  HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'.freeze
21
21
  HTTP_ACCEPT = 'HTTP_ACCEPT'.freeze
22
22
 
23
- ACCEPT = 'accept'.freeze
24
23
  FORMAT = 'format'.freeze
25
24
  end
26
25
  end
@@ -28,13 +28,14 @@ en:
28
28
  resolution: 'available strategy for :using is :path, :header, :param'
29
29
  unknown_validator: 'unknown validator: %{validator_type}'
30
30
  unknown_options: 'unknown options: %{options}'
31
+ unknown_parameter: 'unknown parameter: %{param}'
31
32
  incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
32
33
  mutual_exclusion: 'are mutually exclusive'
33
34
  at_least_one: 'are missing, at least one parameter must be provided'
34
35
  exactly_one: 'are missing, exactly one parameter must be provided'
35
36
  all_or_none: 'provide all or none of parameters'
36
37
  missing_group_type: 'group type is required'
37
- unsupported_group_type: 'group type must be Array or Hash'
38
+ unsupported_group_type: 'group type must be Array, Hash, JSON or Array[JSON]'
38
39
  invalid_message_body:
39
40
  problem: "message body does not match declared format"
40
41
  resolution:
@@ -44,4 +45,7 @@ en:
44
45
  invalid_accept_header:
45
46
  problem: 'Invalid accept header'
46
47
  resolution: '%{message}'
48
+ invalid_version_header:
49
+ problem: 'Invalid version header'
50
+ resolution: '%{message}'
47
51
 
@@ -12,7 +12,7 @@ module Grape
12
12
  end
13
13
 
14
14
  def context
15
- env['api.endpoint']
15
+ env[Grape::Env::API_ENDPOINT]
16
16
  end
17
17
 
18
18
  def call(env)
@@ -26,7 +26,7 @@ module Grape
26
26
  auth_proc = options[:proc]
27
27
  auth_proc_context = context
28
28
 
29
- strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
29
+ strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
30
30
 
31
31
  throw(:error, status: 401, message: 'API Authorization Failed.') unless strategy_info.present?
32
32
 
@@ -12,7 +12,7 @@ module Grape
12
12
  # only `:http_basic`, `:http_digest` are supported.
13
13
  def auth(type = nil, options = {}, &block)
14
14
  if type
15
- namespace_inheritable(:auth, { type: type.to_sym, proc: block }.merge(options))
15
+ namespace_inheritable(:auth, options.reverse_merge(type: type.to_sym, proc: block))
16
16
  use Grape::Middleware::Auth::Base, namespace_inheritable(:auth)
17
17
  else
18
18
  namespace_inheritable(:auth)
@@ -5,7 +5,7 @@ module Grape
5
5
  module_function
6
6
 
7
7
  def add(label, strategy, option_fetcher = ->(_) { [] })
8
- auth_strategies[label] = StrategyInfo.new(strategy, option_fetcher)
8
+ auth_strategies[label] = StrategyInfo.new(strategy, option_fetcher)
9
9
  end
10
10
 
11
11
  def auth_strategies
@@ -2,6 +2,7 @@ module Grape
2
2
  module Middleware
3
3
  class Base
4
4
  attr_reader :app, :env, :options
5
+ TEXT_HTML = 'text/html'.freeze
5
6
 
6
7
  # @param [Rack Application] app The standard argument for a Rack middleware.
7
8
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
@@ -37,6 +38,7 @@ module Grape
37
38
  end
38
39
 
39
40
  def response
41
+ return @app_response if @app_response.is_a?(Rack::Response)
40
42
  Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
41
43
  end
42
44
 
@@ -49,13 +51,15 @@ module Grape
49
51
  end
50
52
 
51
53
  def content_type
52
- content_type_for(env['api.format'] || options[:format]) || 'text/html'
54
+ content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || TEXT_HTML
53
55
  end
54
56
 
55
57
  def mime_types
56
- content_types.each_with_object({}) do |(k, v), types_without_params|
57
- types_without_params[k] = v.split(';').first
58
- end.invert
58
+ types_without_params = {}
59
+ content_types.each_pair do |k, v|
60
+ types_without_params[v.split(';').first] = k
61
+ end
62
+ types_without_params
59
63
  end
60
64
  end
61
65
  end
@@ -47,6 +47,7 @@ module Grape
47
47
  end
48
48
 
49
49
  def rescuable?(klass)
50
+ return false if klass == Grape::Exceptions::InvalidVersionHeader
50
51
  options[:rescue_all] || (options[:rescue_handlers] || []).any? { |error, _handler| klass <= error } || (options[:base_only_rescue_handlers] || []).include?(klass)
51
52
  end
52
53
 
@@ -59,7 +60,7 @@ module Grape
59
60
  end
60
61
 
61
62
  def error!(message, status = options[:default_status], headers = {}, backtrace = [])
62
- headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }.merge(headers)
63
+ headers = headers.reverse_merge(Grape::Http::Headers::CONTENT_TYPE => content_type)
63
64
  rack_response(format_message(message, backtrace), status, headers)
64
65
  end
65
66
 
@@ -82,7 +83,7 @@ module Grape
82
83
  end
83
84
 
84
85
  def format_message(message, backtrace)
85
- format = env['api.format'] || options[:format]
86
+ format = env[Grape::Env::API_FORMAT] || options[:format]
86
87
  formatter = Grape::ErrorFormatter::Base.formatter_for(format, options)
87
88
  throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
88
89
  formatter.call(message, backtrace, options, env)
@@ -3,7 +3,7 @@ module Grape
3
3
  # This is a simple middleware for adding before and after filters
4
4
  # to Grape APIs. It is used like so:
5
5
  #
6
- # use Grape::Middleware::Filter, before: lambda { do_something }, after: lambda { do_something }
6
+ # use Grape::Middleware::Filter, before: -> { do_something }, after: -> { do_something }
7
7
  class Filter < Base
8
8
  def before
9
9
  app.instance_eval(&options[:before]) if options[:before]