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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +90 -0
- data/LICENSE +1 -1
- data/README.md +104 -21
- data/UPGRADING.md +243 -39
- data/lib/grape.rb +4 -5
- data/lib/grape/api.rb +4 -4
- data/lib/grape/api/instance.rb +32 -31
- data/lib/grape/content_types.rb +34 -0
- data/lib/grape/dsl/helpers.rb +2 -1
- data/lib/grape/dsl/inside_route.rb +76 -42
- data/lib/grape/dsl/parameters.rb +4 -4
- data/lib/grape/dsl/routing.rb +8 -8
- data/lib/grape/dsl/validations.rb +18 -1
- data/lib/grape/eager_load.rb +1 -1
- data/lib/grape/endpoint.rb +8 -6
- data/lib/grape/exceptions/base.rb +0 -4
- data/lib/grape/exceptions/validation_errors.rb +11 -12
- data/lib/grape/http/headers.rb +26 -0
- data/lib/grape/middleware/base.rb +3 -4
- data/lib/grape/middleware/error.rb +10 -12
- data/lib/grape/middleware/formatter.rb +3 -3
- data/lib/grape/middleware/stack.rb +19 -5
- data/lib/grape/middleware/versioner/header.rb +4 -4
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +12 -2
- data/lib/grape/path.rb +13 -3
- data/lib/grape/request.rb +13 -8
- data/lib/grape/router.rb +26 -30
- data/lib/grape/router/attribute_translator.rb +25 -4
- data/lib/grape/router/pattern.rb +17 -16
- data/lib/grape/router/route.rb +5 -24
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
- data/lib/grape/util/base_inheritable.rb +15 -8
- data/lib/grape/util/cache.rb +20 -0
- data/lib/grape/util/lazy_object.rb +43 -0
- data/lib/grape/util/lazy_value.rb +1 -0
- data/lib/grape/util/reverse_stackable_values.rb +2 -0
- data/lib/grape/util/stackable_values.rb +7 -20
- data/lib/grape/validations/params_scope.rb +6 -5
- data/lib/grape/validations/types.rb +6 -5
- data/lib/grape/validations/types/array_coercer.rb +14 -5
- data/lib/grape/validations/types/build_coercer.rb +5 -8
- data/lib/grape/validations/types/custom_type_coercer.rb +14 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
- data/lib/grape/validations/types/file.rb +15 -12
- data/lib/grape/validations/types/json.rb +40 -36
- data/lib/grape/validations/types/primitive_coercer.rb +15 -6
- data/lib/grape/validations/types/set_coercer.rb +6 -4
- data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
- data/lib/grape/validations/validators/as.rb +1 -1
- data/lib/grape/validations/validators/base.rb +2 -4
- data/lib/grape/validations/validators/coerce.rb +4 -11
- data/lib/grape/validations/validators/default.rb +3 -5
- data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
- data/lib/grape/validations/validators/except_values.rb +1 -1
- data/lib/grape/validations/validators/regexp.rb +1 -1
- data/lib/grape/validations/validators/values.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/instance_spec.rb +50 -0
- data/spec/grape/api_spec.rb +82 -6
- data/spec/grape/dsl/inside_route_spec.rb +182 -33
- data/spec/grape/endpoint/declared_spec.rb +590 -0
- data/spec/grape/endpoint_spec.rb +0 -521
- data/spec/grape/entity_spec.rb +6 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
- data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
- data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -1
- data/spec/grape/middleware/formatter_spec.rb +3 -3
- data/spec/grape/middleware/stack_spec.rb +12 -1
- data/spec/grape/path_spec.rb +4 -4
- data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
- data/spec/grape/validations/params_scope_spec.rb +26 -0
- data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
- data/spec/grape/validations/types/primitive_coercer_spec.rb +135 -0
- data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
- data/spec/grape/validations/types_spec.rb +1 -1
- data/spec/grape/validations/validators/coerce_spec.rb +329 -77
- data/spec/grape/validations/validators/default_spec.rb +170 -0
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
- data/spec/grape/validations/validators/except_values_spec.rb +1 -0
- data/spec/grape/validations/validators/values_spec.rb +1 -1
- data/spec/grape/validations_spec.rb +30 -30
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/spec_helper.rb +3 -10
- data/spec/support/chunks.rb +14 -0
- data/spec/support/eager_load.rb +19 -0
- data/spec/support/versioned_helpers.rb +3 -5
- metadata +121 -105
- data/lib/grape/util/content_types.rb +0 -28
data/lib/grape.rb
CHANGED
@@ -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
|
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/
|
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'
|
data/lib/grape/api.rb
CHANGED
@@ -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.
|
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
|
data/lib/grape/api/instance.rb
CHANGED
@@ -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.
|
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.
|
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
|
-
|
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
|
-
|
223
|
-
|
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)
|
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
|
-
|
247
|
-
|
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
|
data/lib/grape/dsl/helpers.rb
CHANGED
@@ -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(
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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(
|
81
|
-
|
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
|
-
|
85
|
-
|
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
|
-
|
89
|
-
|
90
|
-
route_options_params
|
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
|
-
|
94
|
-
|
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(:
|
113
|
+
route_setting(:declared_params)
|
105
114
|
else
|
106
115
|
# Declared params at current namespace
|
107
|
-
|
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.
|
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
|
-
#
|
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
|
-
#
|
286
|
+
# sendfile FileStreamer.new(...)
|
264
287
|
# end
|
265
288
|
#
|
266
289
|
# GET /file # => "contents of file"
|
267
|
-
def
|
290
|
+
def sendfile(value = nil)
|
268
291
|
if value.is_a?(String)
|
269
|
-
file_body = Grape::
|
270
|
-
@
|
292
|
+
file_body = Grape::ServeStream::FileBody.new(value)
|
293
|
+
@stream = Grape::ServeStream::StreamResponse.new(file_body)
|
271
294
|
elsif !value.is_a?(NilClass)
|
272
|
-
|
273
|
-
@file = Grape::ServeFile::FileResponse.new(value)
|
295
|
+
raise ArgumentError, 'Argument must be a file path'
|
274
296
|
else
|
275
|
-
|
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
|
-
|
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 = (
|
336
|
-
elsif entity_class.present? &&
|
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 =
|
372
|
+
representation = body.merge(representation)
|
339
373
|
end
|
340
374
|
|
341
375
|
body representation
|