grape 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -0
  3. data/LICENSE +1 -1
  4. data/README.md +104 -21
  5. data/UPGRADING.md +243 -39
  6. data/lib/grape.rb +4 -5
  7. data/lib/grape/api.rb +4 -4
  8. data/lib/grape/api/instance.rb +32 -31
  9. data/lib/grape/content_types.rb +34 -0
  10. data/lib/grape/dsl/helpers.rb +2 -1
  11. data/lib/grape/dsl/inside_route.rb +76 -42
  12. data/lib/grape/dsl/parameters.rb +4 -4
  13. data/lib/grape/dsl/routing.rb +8 -8
  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_errors.rb +11 -12
  19. data/lib/grape/http/headers.rb +26 -0
  20. data/lib/grape/middleware/base.rb +3 -4
  21. data/lib/grape/middleware/error.rb +10 -12
  22. data/lib/grape/middleware/formatter.rb +3 -3
  23. data/lib/grape/middleware/stack.rb +19 -5
  24. data/lib/grape/middleware/versioner/header.rb +4 -4
  25. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  26. data/lib/grape/middleware/versioner/path.rb +1 -1
  27. data/lib/grape/namespace.rb +12 -2
  28. data/lib/grape/path.rb +13 -3
  29. data/lib/grape/request.rb +13 -8
  30. data/lib/grape/router.rb +26 -30
  31. data/lib/grape/router/attribute_translator.rb +25 -4
  32. data/lib/grape/router/pattern.rb +17 -16
  33. data/lib/grape/router/route.rb +5 -24
  34. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  35. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  36. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  37. data/lib/grape/util/base_inheritable.rb +15 -8
  38. data/lib/grape/util/cache.rb +20 -0
  39. data/lib/grape/util/lazy_object.rb +43 -0
  40. data/lib/grape/util/lazy_value.rb +1 -0
  41. data/lib/grape/util/reverse_stackable_values.rb +2 -0
  42. data/lib/grape/util/stackable_values.rb +7 -20
  43. data/lib/grape/validations/params_scope.rb +6 -5
  44. data/lib/grape/validations/types.rb +6 -5
  45. data/lib/grape/validations/types/array_coercer.rb +14 -5
  46. data/lib/grape/validations/types/build_coercer.rb +5 -8
  47. data/lib/grape/validations/types/custom_type_coercer.rb +14 -2
  48. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  49. data/lib/grape/validations/types/file.rb +15 -12
  50. data/lib/grape/validations/types/json.rb +40 -36
  51. data/lib/grape/validations/types/primitive_coercer.rb +15 -6
  52. data/lib/grape/validations/types/set_coercer.rb +6 -4
  53. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  54. data/lib/grape/validations/validators/as.rb +1 -1
  55. data/lib/grape/validations/validators/base.rb +2 -4
  56. data/lib/grape/validations/validators/coerce.rb +4 -11
  57. data/lib/grape/validations/validators/default.rb +3 -5
  58. data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
  59. data/lib/grape/validations/validators/except_values.rb +1 -1
  60. data/lib/grape/validations/validators/regexp.rb +1 -1
  61. data/lib/grape/validations/validators/values.rb +1 -1
  62. data/lib/grape/version.rb +1 -1
  63. data/spec/grape/api/instance_spec.rb +50 -0
  64. data/spec/grape/api_spec.rb +82 -6
  65. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  66. data/spec/grape/endpoint/declared_spec.rb +590 -0
  67. data/spec/grape/endpoint_spec.rb +0 -521
  68. data/spec/grape/entity_spec.rb +6 -0
  69. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  70. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  71. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  72. data/spec/grape/middleware/error_spec.rb +1 -1
  73. data/spec/grape/middleware/formatter_spec.rb +3 -3
  74. data/spec/grape/middleware/stack_spec.rb +12 -1
  75. data/spec/grape/path_spec.rb +4 -4
  76. data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
  77. data/spec/grape/validations/params_scope_spec.rb +26 -0
  78. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  79. data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
  80. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  81. data/spec/grape/validations/types_spec.rb +1 -1
  82. data/spec/grape/validations/validators/coerce_spec.rb +329 -77
  83. data/spec/grape/validations/validators/default_spec.rb +170 -0
  84. data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
  85. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  86. data/spec/grape/validations/validators/values_spec.rb +1 -1
  87. data/spec/grape/validations_spec.rb +30 -30
  88. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  89. data/spec/spec_helper.rb +3 -10
  90. data/spec/support/chunks.rb +14 -0
  91. data/spec/support/eager_load.rb +19 -0
  92. data/spec/support/versioned_helpers.rb +3 -5
  93. metadata +121 -105
  94. data/lib/grape/util/content_types.rb +0 -28
@@ -20,7 +20,6 @@ require 'active_support/core_ext/hash/conversions'
20
20
  require 'active_support/dependencies/autoload'
21
21
  require 'active_support/notifications'
22
22
  require 'i18n'
23
- require 'thread'
24
23
 
25
24
  I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
26
25
 
@@ -84,7 +83,6 @@ module Grape
84
83
  eager_autoload do
85
84
  autoload :DeepMergeableHash
86
85
  autoload :DeepSymbolizeHash
87
- autoload :DeepHashWithIndifferentAccess
88
86
  autoload :Hash
89
87
  end
90
88
  module ActiveSupport
@@ -208,18 +206,19 @@ module Grape
208
206
  end
209
207
  end
210
208
 
211
- module ServeFile
209
+ module ServeStream
212
210
  extend ::ActiveSupport::Autoload
213
211
  eager_autoload do
214
- autoload :FileResponse
215
212
  autoload :FileBody
216
213
  autoload :SendfileResponse
214
+ autoload :StreamResponse
217
215
  end
218
216
  end
219
217
  end
220
218
 
221
219
  require 'grape/config'
222
- require 'grape/util/content_types'
220
+ require 'grape/content_types'
221
+
223
222
  require 'grape/util/lazy_value'
224
223
  require 'grape/util/lazy_block'
225
224
  require 'grape/util/endpoint_configuration'
@@ -8,7 +8,7 @@ module Grape
8
8
  # should subclass this class in order to build an API.
9
9
  class API
10
10
  # Class methods that we want to call on the API rather than on the API object
11
- NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile!]).freeze
11
+ NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile! inherited]).freeze
12
12
 
13
13
  class << self
14
14
  attr_accessor :base_instance, :instances
@@ -30,7 +30,7 @@ module Grape
30
30
  # an instance that will be used to create the set up but will not be mounted
31
31
  def initial_setup(base_instance_parent)
32
32
  @instances = []
33
- @setup = []
33
+ @setup = Set.new
34
34
  @base_parent = base_instance_parent
35
35
  @base_instance = mount_instance
36
36
  end
@@ -90,7 +90,7 @@ module Grape
90
90
  # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary
91
91
  # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
92
92
  # too much, you may actually want to provide a new API rather than remount it.
93
- def mount_instance(opts = {})
93
+ def mount_instance(**opts)
94
94
  instance = Class.new(@base_parent)
95
95
  instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
96
96
  instance.base = self
@@ -175,7 +175,7 @@ module Grape
175
175
  if argument.respond_to?(:lazy?) && argument.lazy?
176
176
  argument.evaluate_from(configuration)
177
177
  elsif argument.is_a?(Hash)
178
- argument.map { |key, value| [key, evaluate_arguments(configuration, value).first] }.to_h
178
+ argument.transform_values { |value| evaluate_arguments(configuration, value).first }
179
179
  elsif argument.is_a?(Array)
180
180
  evaluate_arguments(configuration, *argument)
181
181
  else
@@ -74,7 +74,7 @@ module Grape
74
74
  # (see #cascade?)
75
75
  def cascade(value = nil)
76
76
  if value.nil?
77
- inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
77
+ inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
78
78
  else
79
79
  namespace_inheritable(:cascade, value)
80
80
  end
@@ -178,7 +178,7 @@ module Grape
178
178
  # errors from reaching upstream. This is effectivelly done by unsetting
179
179
  # X-Cascade. Default :cascade is true.
180
180
  def cascade?
181
- return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
181
+ return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
182
182
  return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
183
183
  true
184
184
  end
@@ -192,42 +192,21 @@ module Grape
192
192
  # will return an HTTP 405 response for any HTTP method that the resource
193
193
  # cannot handle.
194
194
  def add_head_not_allowed_methods_and_options_methods
195
- routes_map = {}
196
-
197
- self.class.endpoints.each do |endpoint|
198
- routes = endpoint.routes
199
- routes.each do |route|
200
- # using the :any shorthand produces [nil] for route methods, substitute all manually
201
- route_key = route.pattern.to_regexp
202
- routes_map[route_key] ||= {}
203
- route_settings = routes_map[route_key]
204
- route_settings[:pattern] = route.pattern
205
- route_settings[:requirements] = route.requirements
206
- route_settings[:path] = route.origin
207
- route_settings[:methods] ||= []
208
- route_settings[:methods] << route.request_method
209
- route_settings[:endpoint] = route.app
210
-
211
- # using the :any shorthand produces [nil] for route methods, substitute all manually
212
- route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
213
- end
214
- end
215
-
195
+ versioned_route_configs = collect_route_config_per_pattern
216
196
  # The paths we collected are prepared (cf. Path#prepare), so they
217
197
  # contain already versioning information when using path versioning.
218
198
  # Disable versioning so adding a route won't prepend versioning
219
199
  # informations again.
220
200
  without_root_prefix do
221
201
  without_versioning do
222
- routes_map.each_value do |config|
223
- methods = config[:methods]
224
- allowed_methods = methods.dup
202
+ versioned_route_configs.each do |config|
203
+ allowed_methods = config[:methods].dup
225
204
 
226
205
  unless self.class.namespace_inheritable(:do_not_route_head)
227
206
  allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
228
207
  end
229
208
 
230
- allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
209
+ allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods)
231
210
 
232
211
  unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
233
212
  config[:endpoint].options[:options_route_enabled] = true
@@ -240,14 +219,36 @@ module Grape
240
219
  end
241
220
  end
242
221
 
222
+ def collect_route_config_per_pattern
223
+ all_routes = self.class.endpoints.map(&:routes).flatten
224
+ routes_by_regexp = all_routes.group_by { |route| route.pattern.to_regexp }
225
+
226
+ # Build the configuration based on the first endpoint and the collection of methods supported.
227
+ routes_by_regexp.values.map do |routes|
228
+ last_route = routes.last # Most of the configuration is taken from the last endpoint
229
+ matching_wildchar = routes.any? { |route| route.request_method == '*' }
230
+ {
231
+ options: {},
232
+ pattern: last_route.pattern,
233
+ requirements: last_route.requirements,
234
+ path: last_route.origin,
235
+ endpoint: last_route.app,
236
+ methods: matching_wildchar ? Grape::Http::Headers::SUPPORTED_METHODS : routes.map(&:request_method)
237
+ }
238
+ end
239
+ end
240
+
243
241
  # Generate a route that returns an HTTP 405 response for a user defined
244
242
  # path on methods not specified
245
243
  def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
246
- not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods
247
- not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
248
-
244
+ supported_methods =
245
+ if self.class.namespace_inheritable(:do_not_route_options)
246
+ Grape::Http::Headers::SUPPORTED_METHODS
247
+ else
248
+ Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS
249
+ end
250
+ not_allowed_methods = supported_methods - allowed_methods
249
251
  return if not_allowed_methods.empty?
250
-
251
252
  @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
252
253
  end
253
254
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grape/util/registrable'
4
+
5
+ module Grape
6
+ module ContentTypes
7
+ extend Util::Registrable
8
+
9
+ # Content types are listed in order of preference.
10
+ CONTENT_TYPES = {
11
+ xml: 'application/xml',
12
+ serializable_hash: 'application/json',
13
+ json: 'application/json',
14
+ binary: 'application/octet-stream',
15
+ txt: 'text/plain'
16
+ }.freeze
17
+
18
+ class << self
19
+ def content_types_for_settings(settings)
20
+ return if settings.blank?
21
+
22
+ settings.each_with_object({}) { |value, result| result.merge!(value) }
23
+ end
24
+
25
+ def content_types_for(from_settings)
26
+ if from_settings.present?
27
+ from_settings
28
+ else
29
+ Grape::ContentTypes::CONTENT_TYPES.merge(default_elements)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -81,6 +81,7 @@ module Grape
81
81
  # to provide some API-specific functionality.
82
82
  module BaseHelper
83
83
  attr_accessor :api
84
+
84
85
  def params(name, &block)
85
86
  @named_params ||= {}
86
87
  @named_params[name] = block
@@ -94,7 +95,7 @@ module Grape
94
95
  protected
95
96
 
96
97
  def process_named_params
97
- return unless @named_params && @named_params.any?
98
+ return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any?
98
99
  api.namespace_stackable(:named_params, @named_params)
99
100
  end
100
101
  end
@@ -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?('[')
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