grape 0.12.0 → 0.13.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +2 -2
  3. data/CHANGELOG.md +237 -215
  4. data/CONTRIBUTING.md +4 -4
  5. data/README.md +133 -10
  6. data/RELEASING.md +14 -6
  7. data/Rakefile +1 -1
  8. data/UPGRADING.md +23 -23
  9. data/grape.gemspec +1 -3
  10. data/lib/grape/api.rb +24 -4
  11. data/lib/grape/dsl/callbacks.rb +20 -0
  12. data/lib/grape/dsl/configuration.rb +54 -0
  13. data/lib/grape/dsl/inside_route.rb +33 -1
  14. data/lib/grape/dsl/parameters.rb +80 -0
  15. data/lib/grape/dsl/routing.rb +14 -0
  16. data/lib/grape/dsl/settings.rb +36 -1
  17. data/lib/grape/dsl/validations.rb +7 -5
  18. data/lib/grape/endpoint.rb +42 -32
  19. data/lib/grape/exceptions/unknown_parameter.rb +10 -0
  20. data/lib/grape/exceptions/validation_errors.rb +4 -3
  21. data/lib/grape/http/headers.rb +0 -1
  22. data/lib/grape/http/request.rb +12 -4
  23. data/lib/grape/locale/en.yml +1 -0
  24. data/lib/grape/middleware/base.rb +1 -0
  25. data/lib/grape/middleware/formatter.rb +39 -23
  26. data/lib/grape/namespace.rb +13 -2
  27. data/lib/grape/path.rb +1 -0
  28. data/lib/grape/route.rb +5 -0
  29. data/lib/grape/util/file_response.rb +21 -0
  30. data/lib/grape/util/inheritable_setting.rb +23 -2
  31. data/lib/grape/util/inheritable_values.rb +1 -1
  32. data/lib/grape/util/parameter_types.rb +58 -0
  33. data/lib/grape/util/stackable_values.rb +5 -2
  34. data/lib/grape/validations/params_scope.rb +83 -9
  35. data/lib/grape/validations/validators/coerce.rb +11 -2
  36. data/lib/grape/validations.rb +5 -0
  37. data/lib/grape/version.rb +2 -1
  38. data/lib/grape.rb +7 -8
  39. data/spec/grape/api_spec.rb +63 -0
  40. data/spec/grape/dsl/inside_route_spec.rb +37 -2
  41. data/spec/grape/dsl/validations_spec.rb +18 -0
  42. data/spec/grape/endpoint_spec.rb +83 -0
  43. data/spec/grape/exceptions/validation_errors_spec.rb +28 -0
  44. data/spec/grape/middleware/base_spec.rb +33 -11
  45. data/spec/grape/middleware/formatter_spec.rb +0 -5
  46. data/spec/grape/util/inheritable_values_spec.rb +14 -0
  47. data/spec/grape/util/parameter_types_spec.rb +54 -0
  48. data/spec/grape/util/stackable_values_spec.rb +10 -0
  49. data/spec/grape/validations/params_scope_spec.rb +84 -0
  50. data/spec/grape/validations/validators/coerce_spec.rb +29 -8
  51. data/spec/grape/validations/validators/values_spec.rb +12 -0
  52. metadata +9 -6
  53. data/lib/backports/active_support/deep_dup.rb +0 -49
  54. data/lib/backports/active_support/duplicable.rb +0 -88
@@ -10,6 +10,9 @@ module Grape
10
10
 
11
11
  include Grape::DSL::Settings
12
12
 
13
+ # Set or retrive the configured logger. If none was configured, this
14
+ # method will create a new one, logging to stdout.
15
+ # @param logger [Object] the new logger to use
13
16
  def logger(logger = nil)
14
17
  if logger
15
18
  global_setting(:logger, logger)
@@ -19,6 +22,39 @@ module Grape
19
22
  end
20
23
 
21
24
  # Add a description to the next namespace or function.
25
+ # @param description [String] descriptive string for this endpoint
26
+ # or namespace
27
+ # @param options [Hash] other properties you can set to describe the
28
+ # endpoint or namespace. Optional.
29
+ # @option options :detail [String] additional detail about this endpoint
30
+ # @option options :params [Hash] param types and info. normally, you set
31
+ # these via the `params` dsl method.
32
+ # @option options :entity [Grape::Entity] the entity returned upon a
33
+ # successful call to this action
34
+ # @option options :http_codes [Array[Array]] possible HTTP codes this
35
+ # endpoint may return, with their meanings, in a 2d array
36
+ # @option options :named [String] a specific name to help find this route
37
+ # @option options :headers [Hash] HTTP headers this method can accept
38
+ # @yield a block yielding an instance context with methods mapping to
39
+ # each of the above, except that :entity is also aliased as #success
40
+ # and :http_codes is aliased as #failure.
41
+ #
42
+ # @example
43
+ #
44
+ # desc 'create a user'
45
+ # post '/users' do
46
+ # # ...
47
+ # end
48
+ #
49
+ # desc 'find a user' do
50
+ # detail 'locates the user from the given user ID'
51
+ # failure [ [404, 'Couldn\'t find the given user' ] ]
52
+ # success User::Entity
53
+ # end
54
+ # get '/user/:id' do
55
+ # # ...
56
+ # end
57
+ #
22
58
  def desc(description, options = {}, &config_block)
23
59
  if block_given?
24
60
  config_class = Grape::DSL::Configuration.desc_container
@@ -36,15 +72,33 @@ module Grape
36
72
  namespace_setting :description, options
37
73
  route_setting :description, options
38
74
  end
75
+
76
+ def description_field(field, value = nil)
77
+ if value
78
+ description = route_setting(:description)
79
+ description ||= route_setting(:description, {})
80
+ description[field] = value
81
+ else
82
+ description = route_setting(:description)
83
+ description[field] if description
84
+ end
85
+ end
86
+
87
+ def unset_description_field(field)
88
+ description = route_setting(:description)
89
+ description.delete(field) if description
90
+ end
39
91
  end
40
92
 
41
93
  module_function
42
94
 
95
+ # Merge multiple layers of settings into one hash.
43
96
  def stacked_hash_to_hash(settings)
44
97
  return nil if settings.nil? || settings.blank?
45
98
  settings.each_with_object(ActiveSupport::OrderedHash.new) { |value, result| result.deep_merge!(value) }
46
99
  end
47
100
 
101
+ # Returns an object which configures itself via an instance-context DSL.
48
102
  def desc_container
49
103
  Module.new do
50
104
  include Grape::Util::StrictHashConfiguration.module(
@@ -177,12 +177,34 @@ module Grape
177
177
  # GET /file # => "contents of file"
178
178
  def file(value = nil)
179
179
  if value
180
- @file = value
180
+ @file = Grape::Util::FileResponse.new(value)
181
181
  else
182
182
  @file
183
183
  end
184
184
  end
185
185
 
186
+ # Allows you to define the response as a streamable object.
187
+ #
188
+ # If Content-Length and Transfer-Encoding are blank (among other conditions),
189
+ # Rack assumes this response can be streamed in chunks.
190
+ #
191
+ # @example
192
+ # get '/stream' do
193
+ # stream FileStreamer.new(...)
194
+ # end
195
+ #
196
+ # GET /stream # => "chunked contents of file"
197
+ #
198
+ # See:
199
+ # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/chunked.rb
200
+ # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/etag.rb
201
+ def stream(value = nil)
202
+ header 'Content-Length', nil
203
+ header 'Transfer-Encoding', nil
204
+ header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
205
+ file(value)
206
+ end
207
+
186
208
  # Allows you to make use of Grape Entities by setting
187
209
  # the response body to the serializable hash of the
188
210
  # entity provided in the `:with` option. This has the
@@ -238,6 +260,14 @@ module Grape
238
260
  env['rack.routing_args'][:route_info]
239
261
  end
240
262
 
263
+ # Attempt to locate the Entity class for a given object, if not given
264
+ # explicitly. This is done by looking for the presence of Klass::Entity,
265
+ # where Klass is the class of the `object` parameter, or one of its
266
+ # ancestors.
267
+ # @param object [Object] the object to locate the Entity class for
268
+ # @param options [Hash]
269
+ # @option options :with [Class] the explicit entity class to use
270
+ # @return [Class] the located Entity class, or nil if none is found
241
271
  def entity_class_for_obj(object, options)
242
272
  entity_class = options.delete(:with)
243
273
 
@@ -259,6 +289,8 @@ module Grape
259
289
  entity_class
260
290
  end
261
291
 
292
+ # @return the representation of the given object as done through
293
+ # the given entity_class.
262
294
  def entity_representation_for(entity_class, object, options)
263
295
  embeds = { env: env }
264
296
  embeds[:version] = env['api.version'] if env['api.version']
@@ -2,6 +2,9 @@ require 'active_support/concern'
2
2
 
3
3
  module Grape
4
4
  module DSL
5
+ # Defines DSL methods, meant to be applied to a ParamsScope, which define
6
+ # and describe the parameters accepted by an endpoint, or all endpoints
7
+ # within a namespace.
5
8
  module Parameters
6
9
  extend ActiveSupport::Concern
7
10
 
@@ -39,6 +42,47 @@ module Grape
39
42
  alias_method :use_scope, :use
40
43
  alias_method :includes, :use
41
44
 
45
+ # Require one or more parameters for the current endpoint.
46
+ #
47
+ # @param attrs list of parameter names, or, if :using is
48
+ # passed as an option, which keys to include (:all or :none) from
49
+ # the :using hash. The last key can be a hash, which specifies
50
+ # options for the parameters
51
+ # @option attrs :type [Class] the type to coerce this parameter to before
52
+ # passing it to the endpoint. See Grape::ParameterTypes for supported
53
+ # types, or use a class that defines `::parse` as a custom type
54
+ # @option attrs :desc [String] description to document this parameter
55
+ # @option attrs :default [Object] default value, if parameter is optional
56
+ # @option attrs :values [Array] permissable values for this field. If any
57
+ # other value is given, it will be handled as a validation error
58
+ # @option attrs :using [Hash[Symbol => Hash]] a hash defining keys and
59
+ # options, like that returned by Grape::Entity#documentation. The value
60
+ # of each key is an options hash accepting the same parameters
61
+ # @option attrs :except [Array[Symbol]] a list of keys to exclude from
62
+ # the :using Hash. The meaning of this depends on if :all or :none was
63
+ # passed; :all + :except will make the :except fields optional, whereas
64
+ # :none + :except will make the :except fields required
65
+ #
66
+ # @example
67
+ #
68
+ # params do
69
+ # # Basic usage: require a parameter of a certain type
70
+ # requires :user_id, type: Integer
71
+ #
72
+ # # You don't need to specify type; String is default
73
+ # requires :foo
74
+ #
75
+ # # Multiple params can be specified at once if they share
76
+ # # the same options.
77
+ # requires :x, :y, :z, type: Date
78
+ #
79
+ # # Nested parameters can be handled as hashes. You must
80
+ # # pass in a block, within which you can use any of the
81
+ # # parameters DSL methods.
82
+ # requires :user, type: Hash do
83
+ # requires :name, type: String
84
+ # end
85
+ # end
42
86
  def requires(*attrs, &block)
43
87
  orig_attrs = attrs.clone
44
88
 
@@ -55,6 +99,10 @@ module Grape
55
99
  end
56
100
  end
57
101
 
102
+ # Allow, but don't require, one or more parameters for the current
103
+ # endpoint.
104
+ # @param (see #requires)
105
+ # @option (see #requires)
58
106
  def optional(*attrs, &block)
59
107
  orig_attrs = attrs.clone
60
108
 
@@ -77,24 +125,56 @@ module Grape
77
125
  end
78
126
  end
79
127
 
128
+ # Disallow the given parameters to be present in the same request.
129
+ # @param attrs [*Symbol] parameters to validate
80
130
  def mutually_exclusive(*attrs)
81
131
  validates(attrs, mutual_exclusion: true)
82
132
  end
83
133
 
134
+ # Require exactly one of the given parameters to be present.
135
+ # @param (see #mutually_exclusive)
84
136
  def exactly_one_of(*attrs)
85
137
  validates(attrs, exactly_one_of: true)
86
138
  end
87
139
 
140
+ # Require at least one of the given parameters to be present.
141
+ # @param (see #mutually_exclusive)
88
142
  def at_least_one_of(*attrs)
89
143
  validates(attrs, at_least_one_of: true)
90
144
  end
91
145
 
146
+ # Require that either all given params are present, or none are.
147
+ # @param (see #mutually_exclusive)
92
148
  def all_or_none_of(*attrs)
93
149
  validates(attrs, all_or_none_of: true)
94
150
  end
95
151
 
152
+ # Define a block of validations which should be applied if and only if
153
+ # the given parameter is present. The parameters are not nested.
154
+ # @param attr [Symbol] the parameter which, if present, triggers the
155
+ # validations
156
+ # @throws Grape::Exceptions::UnknownParameter if `attr` has not been
157
+ # defined in this scope yet
158
+ # @yield a parameter definition DSL
159
+ def given(attr, &block)
160
+ fail Grape::Exceptions::UnknownParameter.new(attr) unless declared_param?(attr)
161
+ new_lateral_scope(dependent_on: attr, &block)
162
+ end
163
+
164
+ # Test for whether a certain parameter has been defined in this params
165
+ # block yet.
166
+ # @returns [Boolean] whether the parameter has been defined
167
+ def declared_param?(param)
168
+ # @declared_params also includes hashes of options and such, but those
169
+ # won't be flattened out.
170
+ @declared_params.flatten.include?(param)
171
+ end
172
+
96
173
  alias_method :group, :requires
97
174
 
175
+ # @param params [Hash] initial hash of parameters
176
+ # @return hash of parameters relevant for the current scope
177
+ # @api private
98
178
  def params(params)
99
179
  params = @parent.params(params) if @parent
100
180
  if @element
@@ -135,6 +135,18 @@ module Grape
135
135
  end
136
136
  end
137
137
 
138
+ # Declare a "namespace", which prefixes all subordinate routes with its
139
+ # name. Any endpoints within a namespace, or group, resource, segment,
140
+ # etc., will share their parent context as well as any configuration
141
+ # done in the namespace context.
142
+ #
143
+ # @example
144
+ #
145
+ # namespace :foo do
146
+ # get 'bar' do
147
+ # # defines the endpoint: GET /foo/bar
148
+ # end
149
+ # end
138
150
  def namespace(space = nil, options = {}, &block)
139
151
  if space || block_given?
140
152
  within_namespace do
@@ -162,6 +174,7 @@ module Grape
162
174
  @routes ||= prepare_routes
163
175
  end
164
176
 
177
+ # Remove all defined routes.
165
178
  def reset_routes!
166
179
  @routes = nil
167
180
  end
@@ -177,6 +190,7 @@ module Grape
177
190
  namespace(":#{param}", options, &block)
178
191
  end
179
192
 
193
+ # @return array of defined versions
180
194
  def versions
181
195
  @versions ||= []
182
196
  end
@@ -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)
@@ -41,10 +41,16 @@ module Grape
41
41
  if instance_methods.include?(method_name.to_sym) || instance_methods.include?(method_name.to_s)
42
42
  fail NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
43
43
  end
44
+
44
45
  define_method(method_name, &block)
45
46
  method = instance_method(method_name)
46
47
  remove_method(method_name)
47
- proc { |endpoint_instance| method.bind(endpoint_instance).call }
48
+
49
+ proc do |endpoint_instance|
50
+ ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
51
+ method.bind(endpoint_instance).call
52
+ end
53
+ end
48
54
  end
49
55
  end
50
56
 
@@ -210,47 +216,49 @@ module Grape
210
216
  protected
211
217
 
212
218
  def run(env)
213
- @env = env
214
- @header = {}
219
+ ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
220
+ @env = env
221
+ @header = {}
215
222
 
216
- @request = Grape::Request.new(env)
217
- @params = @request.params
218
- @headers = @request.headers
223
+ @request = Grape::Request.new(env)
224
+ @params = @request.params
225
+ @headers = @request.headers
219
226
 
220
- cookies.read(@request)
227
+ cookies.read(@request)
221
228
 
222
- self.class.before_each.call(self) if self.class.before_each
229
+ self.class.before_each.call(self) if self.class.before_each
223
230
 
224
- run_filters befores
231
+ run_filters befores, :before
225
232
 
226
- run_filters before_validations
233
+ run_filters before_validations, :before_validation
227
234
 
228
- # Retrieve validations from this namespace and all parent namespaces.
229
- validation_errors = []
235
+ # Retrieve validations from this namespace and all parent namespaces.
236
+ validation_errors = []
230
237
 
231
- # require 'pry-byebug'; binding.pry
238
+ # require 'pry-byebug'; binding.pry
232
239
 
233
- route_setting(:saved_validations).each do |validator|
234
- begin
235
- validator.validate!(params)
236
- rescue Grape::Exceptions::Validation => e
237
- validation_errors << e
240
+ route_setting(:saved_validations).each do |validator|
241
+ begin
242
+ validator.validate!(params)
243
+ rescue Grape::Exceptions::Validation => e
244
+ validation_errors << e
245
+ end
238
246
  end
239
- end
240
247
 
241
- if validation_errors.any?
242
- fail Grape::Exceptions::ValidationErrors, errors: validation_errors
243
- end
248
+ if validation_errors.any?
249
+ fail Grape::Exceptions::ValidationErrors, errors: validation_errors, headers: header
250
+ end
244
251
 
245
- run_filters after_validations
252
+ run_filters after_validations, :after_validation
246
253
 
247
- response_object = @block ? @block.call(self) : nil
248
- run_filters afters
249
- cookies.write(header)
254
+ response_object = @block ? @block.call(self) : nil
255
+ run_filters afters, :after
256
+ cookies.write(header)
250
257
 
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]
258
+ # The Body commonly is an Array of Strings, the application instance itself, or a File-like object.
259
+ response_object = file || [body || response_object]
260
+ [status, header, response_object]
261
+ end
254
262
  end
255
263
 
256
264
  def build_middleware
@@ -305,9 +313,11 @@ module Grape
305
313
  mod
306
314
  end
307
315
 
308
- def run_filters(filters)
309
- (filters || []).each do |filter|
310
- instance_eval(&filter)
316
+ def run_filters(filters, type = :other)
317
+ ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
318
+ (filters || []).each do |filter|
319
+ instance_eval(&filter)
320
+ end
311
321
  end
312
322
  end
313
323
 
@@ -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,
@@ -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
@@ -1,10 +1,15 @@
1
1
  module Grape
2
2
  class Request < Rack::Request
3
+ ROUTING_ARGS = 'rack.routing_args'
4
+ HTTP_PREFIX = 'HTTP_'
5
+ UNDERSCORE = '_'
6
+ MINUS = '-'
7
+
3
8
  def params
4
9
  @params ||= begin
5
10
  params = Hashie::Mash.new(super)
6
- if env['rack.routing_args']
7
- args = env['rack.routing_args'].dup
11
+ if env[ROUTING_ARGS]
12
+ args = env[ROUTING_ARGS].dup
8
13
  # preserve version from query string parameters
9
14
  args.delete(:version)
10
15
  args.delete(:route_info)
@@ -16,8 +21,11 @@ module Grape
16
21
 
17
22
  def headers
18
23
  @headers ||= env.dup.inject({}) do |h, (k, v)|
19
- if k.to_s.start_with? 'HTTP_'
20
- k = k[5..-1].tr('_', '-').downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }
24
+ if k.to_s.start_with? HTTP_PREFIX
25
+ k = k[5..-1]
26
+ k.tr!(UNDERSCORE, MINUS)
27
+ k.downcase!
28
+ k.gsub!(/^.|[-_\s]./, &:upcase!)
21
29
  h[k] = v
22
30
  end
23
31
  h
@@ -28,6 +28,7 @@ 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'
@@ -37,6 +37,7 @@ module Grape
37
37
  end
38
38
 
39
39
  def response
40
+ return @app_response if @app_response.is_a?(Rack::Response)
40
41
  Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
41
42
  end
42
43