grape 1.3.0 → 1.5.2

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +119 -1
  3. data/LICENSE +1 -1
  4. data/README.md +123 -29
  5. data/UPGRADING.md +265 -39
  6. data/lib/grape/api/instance.rb +32 -31
  7. data/lib/grape/api.rb +5 -5
  8. data/lib/grape/content_types.rb +34 -0
  9. data/lib/grape/dsl/callbacks.rb +1 -1
  10. data/lib/grape/dsl/helpers.rb +2 -1
  11. data/lib/grape/dsl/inside_route.rb +77 -43
  12. data/lib/grape/dsl/parameters.rb +12 -8
  13. data/lib/grape/dsl/routing.rb +12 -11
  14. data/lib/grape/dsl/validations.rb +18 -1
  15. data/lib/grape/eager_load.rb +1 -1
  16. data/lib/grape/endpoint.rb +8 -6
  17. data/lib/grape/exceptions/base.rb +0 -4
  18. data/lib/grape/exceptions/validation.rb +1 -1
  19. data/lib/grape/exceptions/validation_errors.rb +12 -13
  20. data/lib/grape/http/headers.rb +26 -0
  21. data/lib/grape/middleware/auth/base.rb +3 -3
  22. data/lib/grape/middleware/base.rb +4 -5
  23. data/lib/grape/middleware/error.rb +11 -13
  24. data/lib/grape/middleware/formatter.rb +3 -3
  25. data/lib/grape/middleware/stack.rb +10 -2
  26. data/lib/grape/middleware/versioner/header.rb +4 -4
  27. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  28. data/lib/grape/middleware/versioner/path.rb +1 -1
  29. data/lib/grape/namespace.rb +12 -2
  30. data/lib/grape/path.rb +13 -3
  31. data/lib/grape/request.rb +13 -8
  32. data/lib/grape/router/attribute_translator.rb +26 -5
  33. data/lib/grape/router/pattern.rb +17 -16
  34. data/lib/grape/router/route.rb +5 -24
  35. data/lib/grape/router.rb +26 -30
  36. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  37. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  38. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  39. data/lib/grape/util/base_inheritable.rb +15 -8
  40. data/lib/grape/util/cache.rb +20 -0
  41. data/lib/grape/util/lazy_object.rb +43 -0
  42. data/lib/grape/util/lazy_value.rb +1 -0
  43. data/lib/grape/util/reverse_stackable_values.rb +2 -0
  44. data/lib/grape/util/stackable_values.rb +7 -20
  45. data/lib/grape/validations/attributes_iterator.rb +8 -0
  46. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  47. data/lib/grape/validations/params_scope.rb +10 -8
  48. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  49. data/lib/grape/validations/types/array_coercer.rb +14 -5
  50. data/lib/grape/validations/types/build_coercer.rb +5 -8
  51. data/lib/grape/validations/types/custom_type_coercer.rb +16 -2
  52. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  53. data/lib/grape/validations/types/file.rb +15 -12
  54. data/lib/grape/validations/types/invalid_value.rb +24 -0
  55. data/lib/grape/validations/types/json.rb +40 -36
  56. data/lib/grape/validations/types/primitive_coercer.rb +15 -6
  57. data/lib/grape/validations/types/set_coercer.rb +6 -4
  58. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  59. data/lib/grape/validations/types.rb +7 -9
  60. data/lib/grape/validations/validator_factory.rb +1 -1
  61. data/lib/grape/validations/validators/as.rb +1 -1
  62. data/lib/grape/validations/validators/base.rb +8 -8
  63. data/lib/grape/validations/validators/coerce.rb +11 -15
  64. data/lib/grape/validations/validators/default.rb +3 -5
  65. data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
  66. data/lib/grape/validations/validators/except_values.rb +1 -1
  67. data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
  68. data/lib/grape/validations/validators/regexp.rb +1 -1
  69. data/lib/grape/validations/validators/values.rb +1 -1
  70. data/lib/grape/version.rb +1 -1
  71. data/lib/grape.rb +5 -5
  72. data/spec/grape/api/instance_spec.rb +50 -0
  73. data/spec/grape/api_remount_spec.rb +9 -4
  74. data/spec/grape/api_spec.rb +82 -6
  75. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  76. data/spec/grape/endpoint/declared_spec.rb +601 -0
  77. data/spec/grape/endpoint_spec.rb +0 -521
  78. data/spec/grape/entity_spec.rb +7 -1
  79. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  80. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  81. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  82. data/spec/grape/middleware/error_spec.rb +1 -1
  83. data/spec/grape/middleware/formatter_spec.rb +3 -3
  84. data/spec/grape/middleware/stack_spec.rb +10 -0
  85. data/spec/grape/path_spec.rb +4 -4
  86. data/spec/grape/request_spec.rb +1 -1
  87. data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
  88. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  89. data/spec/grape/validations/params_scope_spec.rb +26 -0
  90. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  91. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  92. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  93. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  94. data/spec/grape/validations/types_spec.rb +1 -1
  95. data/spec/grape/validations/validators/coerce_spec.rb +366 -86
  96. data/spec/grape/validations/validators/default_spec.rb +170 -0
  97. data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
  98. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  99. data/spec/grape/validations/validators/values_spec.rb +1 -1
  100. data/spec/grape/validations_spec.rb +298 -30
  101. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  102. data/spec/shared/versioning_examples.rb +20 -20
  103. data/spec/spec_helper.rb +3 -10
  104. data/spec/support/chunks.rb +14 -0
  105. data/spec/support/eager_load.rb +19 -0
  106. data/spec/support/versioned_helpers.rb +4 -6
  107. metadata +27 -10
  108. data/lib/grape/util/content_types.rb +0 -28
@@ -28,36 +28,38 @@ module Grape
28
28
  # Methods which should not be available in filters until the before filter
29
29
  # has completed
30
30
  module PostBeforeFilter
31
- def declared(passed_params, options = {}, declared_params = nil)
31
+ def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
32
32
  options = options.reverse_merge(include_missing: true, include_parent_namespaces: true)
33
33
  declared_params ||= optioned_declared_params(**options)
34
34
 
35
35
  if passed_params.is_a?(Array)
36
- declared_array(passed_params, options, declared_params)
36
+ declared_array(passed_params, options, declared_params, params_nested_path)
37
37
  else
38
- declared_hash(passed_params, options, declared_params)
38
+ declared_hash(passed_params, options, declared_params, params_nested_path)
39
39
  end
40
40
  end
41
41
 
42
42
  private
43
43
 
44
- def declared_array(passed_params, options, declared_params)
44
+ def declared_array(passed_params, options, declared_params, params_nested_path)
45
45
  passed_params.map do |passed_param|
46
- declared(passed_param || {}, options, declared_params)
46
+ declared(passed_param || {}, options, declared_params, params_nested_path)
47
47
  end
48
48
  end
49
49
 
50
- def declared_hash(passed_params, options, declared_params)
50
+ def declared_hash(passed_params, options, declared_params, params_nested_path)
51
51
  declared_params.each_with_object(passed_params.class.new) do |declared_param, memo|
52
52
  if declared_param.is_a?(Hash)
53
53
  declared_param.each_pair do |declared_parent_param, declared_children_params|
54
+ params_nested_path_dup = params_nested_path.dup
55
+ params_nested_path_dup << declared_parent_param.to_s
54
56
  next unless options[:include_missing] || passed_params.key?(declared_parent_param)
55
57
 
56
58
  passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
57
59
  memo_key = optioned_param_key(declared_parent_param, options)
58
60
 
59
- memo[memo_key] = handle_passed_param(declared_parent_param, passed_children_params) do
60
- declared(passed_children_params, options, declared_children_params)
61
+ memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do
62
+ declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
61
63
  end
62
64
  end
63
65
  else
@@ -68,30 +70,37 @@ module Grape
68
70
 
69
71
  next unless options[:include_missing] || passed_params.key?(declared_param) || (param_renaming && passed_params.key?(param_renaming))
70
72
 
71
- if param_renaming
72
- memo[optioned_param_key(param_renaming, options)] = passed_params[param_renaming]
73
- else
74
- memo[optioned_param_key(declared_param, options)] = passed_params[declared_param]
73
+ memo_key = optioned_param_key(param_renaming || declared_param, options)
74
+ passed_param = passed_params[param_renaming || declared_param]
75
+
76
+ params_nested_path_dup = params_nested_path.dup
77
+ params_nested_path_dup << declared_param.to_s
78
+ memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do
79
+ passed_param
75
80
  end
76
81
  end
77
82
  end
78
83
  end
79
84
 
80
- def handle_passed_param(declared_param, passed_children_params, &_block)
81
- should_be_empty_array?(declared_param, passed_children_params) ? [] : yield
82
- end
85
+ def handle_passed_param(params_nested_path, has_passed_children = false, &_block)
86
+ return yield if has_passed_children
83
87
 
84
- def should_be_empty_array?(declared_param, passed_children_params)
85
- declared_param_is_array?(declared_param) && passed_children_params.empty?
86
- end
88
+ key = params_nested_path[0]
89
+ key += '[' + params_nested_path[1..-1].join('][') + ']' if params_nested_path.size > 1
87
90
 
88
- def declared_param_is_array?(declared_param)
89
- key = declared_param.to_s
90
- route_options_params[key] && route_options_params[key][:type] == 'Array'
91
- end
91
+ route_options_params = options[:route_options][:params] || {}
92
+ type = route_options_params.dig(key, :type)
93
+ has_children = route_options_params.keys.any? { |k| k != key && k.start_with?(key) }
92
94
 
93
- def route_options_params
94
- options[:route_options][:params] || {}
95
+ if type == 'Hash' && !has_children
96
+ {}
97
+ elsif type == 'Array' || type&.start_with?('[') && !type&.include?(',')
98
+ []
99
+ elsif type == 'Set' || type&.start_with?('#<Set')
100
+ Set.new
101
+ else
102
+ yield
103
+ end
95
104
  end
96
105
 
97
106
  def optioned_param_key(declared_param, options)
@@ -101,10 +110,10 @@ module Grape
101
110
  def optioned_declared_params(**options)
102
111
  declared_params = if options[:include_parent_namespaces]
103
112
  # Declared params including parent namespaces
104
- route_setting(:saved_declared_params).flatten | Array(route_setting(:declared_params))
113
+ route_setting(:declared_params)
105
114
  else
106
115
  # Declared params at current namespace
107
- route_setting(:saved_declared_params).last & Array(route_setting(:declared_params))
116
+ namespace_stackable(:declared_params).last || []
108
117
  end
109
118
 
110
119
  raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
@@ -177,17 +186,17 @@ module Grape
177
186
  def status(status = nil)
178
187
  case status
179
188
  when Symbol
180
- raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.keys.include?(status)
189
+ raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
181
190
  @status = Rack::Utils.status_code(status)
182
191
  when Integer
183
192
  @status = status
184
193
  when nil
185
- return @status if @status
194
+ return @status if instance_variable_defined?(:@status) && @status
186
195
  case request.request_method.to_s.upcase
187
196
  when Grape::Http::Headers::POST
188
197
  201
189
198
  when Grape::Http::Headers::DELETE
190
- if @body.present?
199
+ if instance_variable_defined?(:@body) && @body.present?
191
200
  200
192
201
  else
193
202
  204
@@ -238,7 +247,7 @@ module Grape
238
247
  @body = ''
239
248
  status 204
240
249
  else
241
- @body
250
+ instance_variable_defined?(:@body) ? @body : nil
242
251
  end
243
252
  end
244
253
 
@@ -256,23 +265,36 @@ module Grape
256
265
  body false
257
266
  end
258
267
 
259
- # Allows you to define the response as a file-like object.
268
+ # Deprecated method to send files to the client. Use `sendfile` or `stream`
269
+ def file(value = nil)
270
+ if value.is_a?(String)
271
+ warn '[DEPRECATION] Use sendfile or stream to send files.'
272
+ sendfile(value)
273
+ elsif !value.is_a?(NilClass)
274
+ warn '[DEPRECATION] Use stream to use a Stream object.'
275
+ stream(value)
276
+ else
277
+ warn '[DEPRECATION] Use sendfile or stream to send files.'
278
+ sendfile
279
+ end
280
+ end
281
+
282
+ # Allows you to send a file to the client via sendfile.
260
283
  #
261
284
  # @example
262
285
  # get '/file' do
263
- # file FileStreamer.new(...)
286
+ # sendfile FileStreamer.new(...)
264
287
  # end
265
288
  #
266
289
  # GET /file # => "contents of file"
267
- def file(value = nil)
290
+ def sendfile(value = nil)
268
291
  if value.is_a?(String)
269
- file_body = Grape::ServeFile::FileBody.new(value)
270
- @file = Grape::ServeFile::FileResponse.new(file_body)
292
+ file_body = Grape::ServeStream::FileBody.new(value)
293
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
271
294
  elsif !value.is_a?(NilClass)
272
- warn '[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.'
273
- @file = Grape::ServeFile::FileResponse.new(value)
295
+ raise ArgumentError, 'Argument must be a file path'
274
296
  else
275
- @file
297
+ stream
276
298
  end
277
299
  end
278
300
 
@@ -292,10 +314,21 @@ module Grape
292
314
  # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/chunked.rb
293
315
  # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/etag.rb
294
316
  def stream(value = nil)
317
+ return if value.nil? && @stream.nil?
318
+
295
319
  header 'Content-Length', nil
296
320
  header 'Transfer-Encoding', nil
297
321
  header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
298
- file(value)
322
+ if value.is_a?(String)
323
+ file_body = Grape::ServeStream::FileBody.new(value)
324
+ @stream = Grape::ServeStream::StreamResponse.new(file_body)
325
+ elsif value.respond_to?(:each)
326
+ @stream = Grape::ServeStream::StreamResponse.new(value)
327
+ elsif !value.is_a?(NilClass)
328
+ raise ArgumentError, 'Stream object must respond to :each.'
329
+ else
330
+ @stream
331
+ end
299
332
  end
300
333
 
301
334
  # Allows you to make use of Grape Entities by setting
@@ -331,11 +364,12 @@ module Grape
331
364
  end
332
365
 
333
366
  representation = { root => representation } if root
367
+
334
368
  if key
335
- representation = (@body || {}).merge(key => representation)
336
- elsif entity_class.present? && @body
369
+ representation = (body || {}).merge(key => representation)
370
+ elsif entity_class.present? && body
337
371
  raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
338
- representation = @body.merge(representation)
372
+ representation = body.merge(representation)
339
373
  end
340
374
 
341
375
  body representation
@@ -365,7 +399,7 @@ module Grape
365
399
  entity_class = options.delete(:with)
366
400
 
367
401
  if entity_class.nil?
368
- # entity class not explicitely defined, auto-detect from relation#klass or first object in the collection
402
+ # entity class not explicitly defined, auto-detect from relation#klass or first object in the collection
369
403
  object_class = if object.respond_to?(:klass)
370
404
  object.klass
371
405
  else
@@ -72,7 +72,7 @@ module Grape
72
72
 
73
73
  # Require one or more parameters for the current endpoint.
74
74
  #
75
- # @param attrs list of parameter names, or, if :using is
75
+ # @param attrs list of parameters names, or, if :using is
76
76
  # passed as an option, which keys to include (:all or :none) from
77
77
  # the :using hash. The last key can be a hash, which specifies
78
78
  # options for the parameters
@@ -127,7 +127,7 @@ module Grape
127
127
 
128
128
  opts = attrs.extract_options!.clone
129
129
  opts[:presence] = { value: true, message: opts[:message] }
130
- opts = @group.merge(opts) if @group
130
+ opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group
131
131
 
132
132
  if opts[:using]
133
133
  require_required_and_optional_fields(attrs.first, opts)
@@ -146,7 +146,7 @@ module Grape
146
146
 
147
147
  opts = attrs.extract_options!.clone
148
148
  type = opts[:type]
149
- opts = @group.merge(opts) if @group
149
+ opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group
150
150
 
151
151
  # check type for optional parameter group
152
152
  if attrs && block_given?
@@ -227,13 +227,17 @@ module Grape
227
227
 
228
228
  alias group requires
229
229
 
230
- def map_params(params, element)
230
+ class EmptyOptionalValue; end
231
+
232
+ def map_params(params, element, is_array = false)
231
233
  if params.is_a?(Array)
232
234
  params.map do |el|
233
- map_params(el, element)
235
+ map_params(el, element, true)
234
236
  end
235
237
  elsif params.is_a?(Hash)
236
- params[element] || {}
238
+ params[element] || (@optional && is_array ? EmptyOptionalValue : {})
239
+ elsif params == EmptyOptionalValue
240
+ EmptyOptionalValue
237
241
  else
238
242
  {}
239
243
  end
@@ -243,8 +247,8 @@ module Grape
243
247
  # @return hash of parameters relevant for the current scope
244
248
  # @api private
245
249
  def params(params)
246
- params = @parent.params(params) if @parent
247
- params = map_params(params, @element) if @element
250
+ params = @parent.params(params) if instance_variable_defined?(:@parent) && @parent
251
+ params = map_params(params, @element) if instance_variable_defined?(:@element) && @element
248
252
  params
249
253
  end
250
254
 
@@ -51,7 +51,7 @@ module Grape
51
51
  end
52
52
  end
53
53
 
54
- @versions.last unless @versions.nil?
54
+ @versions.last if instance_variable_defined?(:@versions) && @versions
55
55
  end
56
56
 
57
57
  # Define a root URL prefix for your entire API.
@@ -79,11 +79,12 @@ module Grape
79
79
  namespace_inheritable(:do_not_route_options, true)
80
80
  end
81
81
 
82
- def mount(mounts, opts = {})
82
+ def mount(mounts, *opts)
83
83
  mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
84
84
  mounts.each_pair do |app, path|
85
85
  if app.respond_to?(:mount_instance)
86
- mount(app.mount_instance(configuration: opts[:with] || {}) => path)
86
+ opts_with = opts.any? ? opts.shift[:with] : {}
87
+ mount({ app.mount_instance(configuration: opts_with) => path })
87
88
  next
88
89
  end
89
90
  in_setting = inheritable_setting
@@ -142,16 +143,16 @@ module Grape
142
143
  reset_validations!
143
144
  end
144
145
 
145
- %w[get post put head delete options patch].each do |meth|
146
- define_method meth do |*args, &block|
146
+ Grape::Http::Headers::SUPPORTED_METHODS.each do |supported_method|
147
+ define_method supported_method.downcase do |*args, &block|
147
148
  options = args.extract_options!
148
149
  paths = args.first || ['/']
149
- route(meth.upcase, paths, options, &block)
150
+ route(supported_method, paths, options, &block)
150
151
  end
151
152
  end
152
153
 
153
154
  # Declare a "namespace", which prefixes all subordinate routes with its
154
- # name. Any endpoints within a namespace, or group, resource, segment,
155
+ # name. Any endpoints within a namespace, group, resource or segment,
155
156
  # etc., will share their parent context as well as any configuration
156
157
  # done in the namespace context.
157
158
  #
@@ -163,14 +164,14 @@ module Grape
163
164
  # end
164
165
  # end
165
166
  def namespace(space = nil, options = {}, &block)
167
+ @namespace_description = nil unless instance_variable_defined?(:@namespace_description) && @namespace_description
168
+
166
169
  if space || block_given?
167
170
  within_namespace do
168
171
  previous_namespace_description = @namespace_description
169
172
  @namespace_description = (@namespace_description || {}).deep_merge(namespace_setting(:description) || {})
170
173
  nest(block) do
171
- if space
172
- namespace_stackable(:namespace, Namespace.new(space, **options))
173
- end
174
+ namespace_stackable(:namespace, Namespace.new(space, **options)) if space
174
175
  end
175
176
  @namespace_description = previous_namespace_description
176
177
  end
@@ -199,7 +200,7 @@ module Grape
199
200
  @endpoints = []
200
201
  end
201
202
 
202
- # Thie method allows you to quickly define a parameter route segment
203
+ # This method allows you to quickly define a parameter route segment
203
204
  # in your API.
204
205
  #
205
206
  # @param param [Symbol] The name of the parameter you wish to declare.
@@ -10,7 +10,24 @@ module Grape
10
10
  include Grape::DSL::Configuration
11
11
 
12
12
  module ClassMethods
13
- # Clears all defined parameters and validations.
13
+ # Clears all defined parameters and validations. The main purpose of it is to clean up
14
+ # settings, so next endpoint won't interfere with previous one.
15
+ #
16
+ # params do
17
+ # # params for the endpoint below this block
18
+ # end
19
+ # post '/current' do
20
+ # # whatever
21
+ # end
22
+ #
23
+ # # somewhere between them the reset_validations! method gets called
24
+ #
25
+ # params do
26
+ # # params for the endpoint below this block
27
+ # end
28
+ # post '/next' do
29
+ # # whatever
30
+ # end
14
31
  def reset_validations!
15
32
  unset_namespace_stackable :declared_params
16
33
  unset_namespace_stackable :validations
@@ -16,5 +16,5 @@ Grape::Parser.eager_load!
16
16
  Grape::DSL.eager_load!
17
17
  Grape::API.eager_load!
18
18
  Grape::Presenters.eager_load!
19
- Grape::ServeFile.eager_load!
19
+ Grape::ServeStream.eager_load!
20
20
  Rack::Head # AutoLoads the Rack::Head
@@ -80,7 +80,10 @@ module Grape
80
80
 
81
81
  self.inheritable_setting = new_settings.point_in_time_copy
82
82
 
83
- route_setting(:saved_declared_params, namespace_stackable(:declared_params))
83
+ # now +namespace_stackable(:declared_params)+ contains all params defined for
84
+ # this endpoint and its parents, but later it will be cleaned up,
85
+ # see +reset_validations!+ in lib/grape/dsl/validations.rb
86
+ route_setting(:declared_params, namespace_stackable(:declared_params).flatten)
84
87
  route_setting(:saved_validations, namespace_stackable(:validations))
85
88
 
86
89
  namespace_stackable(:representations, []) unless namespace_stackable(:representations)
@@ -99,7 +102,7 @@ module Grape
99
102
  @block = nil
100
103
 
101
104
  @status = nil
102
- @file = nil
105
+ @stream = nil
103
106
  @body = nil
104
107
  @proc = nil
105
108
 
@@ -116,7 +119,6 @@ module Grape
116
119
  parent_declared_params = namespace_stackable[:declared_params]
117
120
 
118
121
  if parent_declared_params
119
- inheritable_setting.route[:declared_params] ||= []
120
122
  inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten)
121
123
  end
122
124
 
@@ -190,7 +192,7 @@ module Grape
190
192
  requirements: prepare_routes_requirements,
191
193
  prefix: namespace_inheritable(:root_prefix),
192
194
  anchor: options[:route_options].fetch(:anchor, true),
193
- settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations),
195
+ settings: inheritable_setting.route.except(:declared_params, :saved_validations),
194
196
  forward_match: options[:forward_match]
195
197
  }
196
198
  end
@@ -271,8 +273,8 @@ module Grape
271
273
  # status verifies body presence when DELETE
272
274
  @body ||= response_object
273
275
 
274
- # The body commonly is an Array of Strings, the application instance itself, or a File-like object
275
- response_object = file || [body]
276
+ # The body commonly is an Array of Strings, the application instance itself, or a Stream-like object
277
+ response_object = stream || [body]
276
278
 
277
279
  [status, header, response_object]
278
280
  ensure
@@ -57,10 +57,6 @@ module Grape
57
57
  end.join(', ')
58
58
  end
59
59
 
60
- def translate_attribute(key, **options)
61
- translate("#{BASE_ATTRIBUTES_KEY}.#{key}", default: key, **options)
62
- end
63
-
64
60
  def translate_message(key, **options)
65
61
  case key
66
62
  when Symbol
@@ -17,7 +17,7 @@ module Grape
17
17
  super(**args)
18
18
  end
19
19
 
20
- # remove all the unnecessary stuff from Grape::Exceptions::Base like status
20
+ # Remove all the unnecessary stuff from Grape::Exceptions::Base like status
21
21
  # and headers when converting a validation error to json or string
22
22
  def as_json(*_args)
23
23
  to_s
@@ -5,6 +5,9 @@ require 'grape/exceptions/base'
5
5
  module Grape
6
6
  module Exceptions
7
7
  class ValidationErrors < Grape::Exceptions::Base
8
+ ERRORS_FORMAT_KEY = 'grape.errors.format'
9
+ DEFAULT_ERRORS_FORMAT = '%{attributes} %{message}'
10
+
8
11
  include Enumerable
9
12
 
10
13
  attr_reader :errors
@@ -36,26 +39,22 @@ module Grape
36
39
  end
37
40
  end
38
41
 
39
- def to_json(**_opts)
42
+ def to_json(*_opts)
40
43
  as_json.to_json
41
44
  end
42
45
 
43
46
  def full_messages
44
- messages = map { |attributes, error| full_message(attributes, error) }
47
+ messages = map do |attributes, error|
48
+ I18n.t(
49
+ ERRORS_FORMAT_KEY,
50
+ default: DEFAULT_ERRORS_FORMAT,
51
+ attributes: translate_attributes(attributes),
52
+ message: error.message
53
+ )
54
+ end
45
55
  messages.uniq!
46
56
  messages
47
57
  end
48
-
49
- private
50
-
51
- def full_message(attributes, error)
52
- I18n.t(
53
- 'grape.errors.format',
54
- default: '%{attributes} %{message}',
55
- attributes: attributes.count == 1 ? translate_attribute(attributes.first) : translate_attributes(attributes),
56
- message: error.message
57
- )
58
- end
59
58
  end
60
59
  end
61
60
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'grape/util/lazy_object'
4
+
3
5
  module Grape
4
6
  module Http
5
7
  module Headers
@@ -19,6 +21,7 @@ module Grape
19
21
  OPTIONS = 'OPTIONS'
20
22
 
21
23
  SUPPORTED_METHODS = [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS].freeze
24
+ SUPPORTED_METHODS_WITHOUT_OPTIONS = Grape::Util::LazyObject.new { [GET, POST, PUT, PATCH, DELETE, HEAD].freeze }
22
25
 
23
26
  HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
24
27
  X_CASCADE = 'X-Cascade'
@@ -27,6 +30,29 @@ module Grape
27
30
 
28
31
  FORMAT = 'format'
29
32
 
33
+ HTTP_HEADERS = Grape::Util::LazyObject.new do
34
+ common_http_headers = %w[
35
+ Version
36
+ Host
37
+ Connection
38
+ Cache-Control
39
+ Dnt
40
+ Upgrade-Insecure-Requests
41
+ User-Agent
42
+ Sec-Fetch-Dest
43
+ Accept
44
+ Sec-Fetch-Site
45
+ Sec-Fetch-Mode
46
+ Sec-Fetch-User
47
+ Accept-Encoding
48
+ Accept-Language
49
+ Cookie
50
+ ].freeze
51
+ common_http_headers.each_with_object({}) do |header, response|
52
+ response["HTTP_#{header.upcase.tr('-', '_')}"] = header
53
+ end.freeze
54
+ end
55
+
30
56
  def self.find_supported_method(route_method)
31
57
  Grape::Http::Headers::SUPPORTED_METHODS.detect { |supported_method| supported_method.casecmp(route_method).zero? }
32
58
  end
@@ -10,9 +10,9 @@ module Grape
10
10
 
11
11
  attr_accessor :options, :app, :env
12
12
 
13
- def initialize(app, **options)
13
+ def initialize(app, *options)
14
14
  @app = app
15
- @options = options
15
+ @options = options.shift
16
16
  end
17
17
 
18
18
  def call(env)
@@ -23,7 +23,7 @@ module Grape
23
23
  self.env = env
24
24
 
25
25
  if options.key?(:type)
26
- auth_proc = options[:proc]
26
+ auth_proc = options[:proc]
27
27
  auth_proc_context = context
28
28
 
29
29
  strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
@@ -8,15 +8,16 @@ module Grape
8
8
  include Helpers
9
9
 
10
10
  attr_reader :app, :env, :options
11
+
11
12
  TEXT_HTML = 'text/html'
12
13
 
13
14
  include Grape::DSL::Headers
14
15
 
15
16
  # @param [Rack Application] app The standard argument for a Rack middleware.
16
17
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
17
- def initialize(app, **options)
18
+ def initialize(app, *options)
18
19
  @app = app
19
- @options = default_options.merge(**options)
20
+ @options = options.any? ? default_options.merge(options.shift) : default_options
20
21
  @app_response = nil
21
22
  end
22
23
 
@@ -74,11 +75,9 @@ module Grape
74
75
  end
75
76
 
76
77
  def mime_types
77
- types_without_params = {}
78
- content_types.each_pair do |k, v|
78
+ @mime_type ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params|
79
79
  types_without_params[v.split(';').first] = k
80
80
  end
81
- types_without_params
82
81
  end
83
82
 
84
83
  private