grape 1.3.3 → 1.4.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 +19 -1
- data/README.md +30 -7
- data/UPGRADING.md +106 -45
- data/lib/grape.rb +2 -2
- data/lib/grape/api/instance.rb +22 -26
- data/lib/grape/dsl/inside_route.rb +33 -11
- 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/middleware/base.rb +2 -2
- data/lib/grape/middleware/formatter.rb +3 -3
- data/lib/grape/router.rb +24 -29
- 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/validations/params_scope.rb +3 -3
- data/lib/grape/validations/types/primitive_coercer.rb +1 -2
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +65 -0
- data/spec/grape/dsl/inside_route_spec.rb +175 -33
- data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
- data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
- data/spec/grape/middleware/formatter_spec.rb +1 -1
- data/spec/grape/validations/types/primitive_coercer_spec.rb +60 -4
- data/spec/grape/validations/validators/coerce_spec.rb +86 -0
- data/spec/grape/validations_spec.rb +17 -13
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- metadata +108 -106
@@ -124,10 +124,10 @@ module Grape
|
|
124
124
|
def optioned_declared_params(**options)
|
125
125
|
declared_params = if options[:include_parent_namespaces]
|
126
126
|
# Declared params including parent namespaces
|
127
|
-
route_setting(:
|
127
|
+
route_setting(:declared_params)
|
128
128
|
else
|
129
129
|
# Declared params at current namespace
|
130
|
-
|
130
|
+
namespace_stackable(:declared_params).last || []
|
131
131
|
end
|
132
132
|
|
133
133
|
raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
|
@@ -279,23 +279,36 @@ module Grape
|
|
279
279
|
body false
|
280
280
|
end
|
281
281
|
|
282
|
-
#
|
282
|
+
# Deprecated method to send files to the client. Use `sendfile` or `stream`
|
283
|
+
def file(value = nil)
|
284
|
+
if value.is_a?(String)
|
285
|
+
warn '[DEPRECATION] Use sendfile or stream to send files.'
|
286
|
+
sendfile(value)
|
287
|
+
elsif !value.is_a?(NilClass)
|
288
|
+
warn '[DEPRECATION] Use stream to use a Stream object.'
|
289
|
+
stream(value)
|
290
|
+
else
|
291
|
+
warn '[DEPRECATION] Use sendfile or stream to send files.'
|
292
|
+
sendfile
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Allows you to send a file to the client via sendfile.
|
283
297
|
#
|
284
298
|
# @example
|
285
299
|
# get '/file' do
|
286
|
-
#
|
300
|
+
# sendfile FileStreamer.new(...)
|
287
301
|
# end
|
288
302
|
#
|
289
303
|
# GET /file # => "contents of file"
|
290
|
-
def
|
304
|
+
def sendfile(value = nil)
|
291
305
|
if value.is_a?(String)
|
292
|
-
file_body = Grape::
|
293
|
-
@
|
306
|
+
file_body = Grape::ServeStream::FileBody.new(value)
|
307
|
+
@stream = Grape::ServeStream::StreamResponse.new(file_body)
|
294
308
|
elsif !value.is_a?(NilClass)
|
295
|
-
|
296
|
-
@file = Grape::ServeFile::FileResponse.new(value)
|
309
|
+
raise ArgumentError, 'Argument must be a file path'
|
297
310
|
else
|
298
|
-
|
311
|
+
stream
|
299
312
|
end
|
300
313
|
end
|
301
314
|
|
@@ -318,7 +331,16 @@ module Grape
|
|
318
331
|
header 'Content-Length', nil
|
319
332
|
header 'Transfer-Encoding', nil
|
320
333
|
header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front)
|
321
|
-
|
334
|
+
if value.is_a?(String)
|
335
|
+
file_body = Grape::ServeStream::FileBody.new(value)
|
336
|
+
@stream = Grape::ServeStream::StreamResponse.new(file_body)
|
337
|
+
elsif value.respond_to?(:each)
|
338
|
+
@stream = Grape::ServeStream::StreamResponse.new(value)
|
339
|
+
elsif !value.is_a?(NilClass)
|
340
|
+
raise ArgumentError, 'Stream object must respond to :each.'
|
341
|
+
else
|
342
|
+
instance_variable_defined?(:@stream) ? @stream : nil
|
343
|
+
end
|
322
344
|
end
|
323
345
|
|
324
346
|
# Allows you to make use of Grape Entities by setting
|
@@ -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
|
data/lib/grape/eager_load.rb
CHANGED
data/lib/grape/endpoint.rb
CHANGED
@@ -80,7 +80,10 @@ module Grape
|
|
80
80
|
|
81
81
|
self.inheritable_setting = new_settings.point_in_time_copy
|
82
82
|
|
83
|
-
|
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
|
-
@
|
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(:
|
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
|
275
|
-
response_object =
|
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
|
@@ -14,9 +14,9 @@ module Grape
|
|
14
14
|
|
15
15
|
# @param [Rack Application] app The standard argument for a Rack middleware.
|
16
16
|
# @param [Hash] options A hash of options, simply stored for use by subclasses.
|
17
|
-
def initialize(app,
|
17
|
+
def initialize(app, options = {})
|
18
18
|
@app = app
|
19
|
-
@options = default_options.merge(
|
19
|
+
@options = default_options.merge(options)
|
20
20
|
@app_response = nil
|
21
21
|
end
|
22
22
|
|
@@ -36,9 +36,9 @@ module Grape
|
|
36
36
|
def build_formatted_response(status, headers, bodies)
|
37
37
|
headers = ensure_content_type(headers)
|
38
38
|
|
39
|
-
if bodies.is_a?(Grape::
|
40
|
-
Grape::
|
41
|
-
resp.body = bodies.
|
39
|
+
if bodies.is_a?(Grape::ServeStream::StreamResponse)
|
40
|
+
Grape::ServeStream::SendfileResponse.new([], status, headers) do |resp|
|
41
|
+
resp.body = bodies.stream
|
42
42
|
end
|
43
43
|
else
|
44
44
|
# Allow content-type to be explicitly overwritten
|
data/lib/grape/router.rb
CHANGED
@@ -7,20 +7,12 @@ module Grape
|
|
7
7
|
class Router
|
8
8
|
attr_reader :map, :compiled
|
9
9
|
|
10
|
-
class NormalizePathCache < Grape::Util::Cache
|
11
|
-
def initialize
|
12
|
-
@cache = Hash.new do |h, path|
|
13
|
-
normalized_path = +"/#{path}"
|
14
|
-
normalized_path.squeeze!('/')
|
15
|
-
normalized_path.sub!(%r{/+\Z}, '')
|
16
|
-
normalized_path = '/' if normalized_path.empty?
|
17
|
-
h[path] = -normalized_path
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
10
|
def self.normalize_path(path)
|
23
|
-
|
11
|
+
path = +"/#{path}"
|
12
|
+
path.squeeze!('/')
|
13
|
+
path.sub!(%r{/+\Z}, '')
|
14
|
+
path = '/' if path == ''
|
15
|
+
path
|
24
16
|
end
|
25
17
|
|
26
18
|
def self.supported_methods
|
@@ -99,37 +91,34 @@ module Grape
|
|
99
91
|
response = yield(input, method)
|
100
92
|
|
101
93
|
return response if response && !(cascade = cascade?(response))
|
102
|
-
|
94
|
+
last_neighbor_route = greedy_match?(input)
|
103
95
|
|
104
|
-
# If
|
96
|
+
# If last_neighbor_route exists and request method is OPTIONS,
|
105
97
|
# return response by using #call_with_allow_headers.
|
106
|
-
return call_with_allow_headers(
|
107
|
-
env,
|
108
|
-
neighbor.allow_header,
|
109
|
-
neighbor.endpoint
|
110
|
-
) if neighbor && method == Grape::Http::Headers::OPTIONS && !cascade
|
98
|
+
return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Grape::Http::Headers::OPTIONS && !cascade
|
111
99
|
|
112
100
|
route = match?(input, '*')
|
113
|
-
|
101
|
+
|
102
|
+
return last_neighbor_route.endpoint.call(env) if last_neighbor_route && cascade && route
|
114
103
|
|
115
104
|
if route
|
116
105
|
response = process_route(route, env)
|
117
106
|
return response if response && !(cascade = cascade?(response))
|
118
107
|
end
|
119
108
|
|
120
|
-
|
109
|
+
return call_with_allow_headers(env, last_neighbor_route) if !cascade && last_neighbor_route
|
110
|
+
|
111
|
+
nil
|
121
112
|
end
|
122
113
|
|
123
114
|
def process_route(route, env)
|
124
|
-
|
125
|
-
routing_args = env[Grape::Env::GRAPE_ROUTING_ARGS]
|
126
|
-
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(routing_args, route, input)
|
115
|
+
prepare_env_from_route(env, route)
|
127
116
|
route.exec(env)
|
128
117
|
end
|
129
118
|
|
130
119
|
def make_routing_args(default_args, route, input)
|
131
120
|
args = default_args || { route_info: route }
|
132
|
-
args.merge(route.params(input))
|
121
|
+
args.merge(route.params(input) || {})
|
133
122
|
end
|
134
123
|
|
135
124
|
def extract_input_and_method(env)
|
@@ -160,9 +149,15 @@ module Grape
|
|
160
149
|
@neutral_map.detect { |route| last_match["_#{route.index}"] }
|
161
150
|
end
|
162
151
|
|
163
|
-
def call_with_allow_headers(env,
|
164
|
-
env
|
165
|
-
|
152
|
+
def call_with_allow_headers(env, route)
|
153
|
+
prepare_env_from_route(env, route)
|
154
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze
|
155
|
+
route.endpoint.call(env)
|
156
|
+
end
|
157
|
+
|
158
|
+
def prepare_env_from_route(env, route)
|
159
|
+
input, = *extract_input_and_method(env)
|
160
|
+
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
|
166
161
|
end
|
167
162
|
|
168
163
|
def cascade?(response)
|
@@ -1,22 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Grape
|
4
|
-
module
|
5
|
-
# A simple class used to identify responses which represent files and do not
|
4
|
+
module ServeStream
|
5
|
+
# A simple class used to identify responses which represent streams (or files) and do not
|
6
6
|
# need to be formatted or pre-read by Rack::Response
|
7
|
-
class
|
8
|
-
attr_reader :
|
7
|
+
class StreamResponse
|
8
|
+
attr_reader :stream
|
9
9
|
|
10
|
-
# @param
|
11
|
-
def initialize(
|
12
|
-
@
|
10
|
+
# @param stream [Object]
|
11
|
+
def initialize(stream)
|
12
|
+
@stream = stream
|
13
13
|
end
|
14
14
|
|
15
15
|
# Equality provided mostly for tests.
|
16
16
|
#
|
17
17
|
# @return [Boolean]
|
18
18
|
def ==(other)
|
19
|
-
|
19
|
+
stream == other.stream
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -237,10 +237,10 @@ module Grape
|
|
237
237
|
@parent.push_declared_params [element => @declared_params]
|
238
238
|
else
|
239
239
|
@api.namespace_stackable(:declared_params, @declared_params)
|
240
|
-
|
241
|
-
@api.route_setting(:declared_params, []) unless @api.route_setting(:declared_params)
|
242
|
-
@api.route_setting(:declared_params, @api.namespace_stackable(:declared_params).flatten)
|
243
240
|
end
|
241
|
+
|
242
|
+
# params were stored in settings, it can be cleaned from the params scope
|
243
|
+
@declared_params = nil
|
244
244
|
end
|
245
245
|
|
246
246
|
def validates(attrs, validations)
|
@@ -37,7 +37,6 @@ module Grape
|
|
37
37
|
def call(val)
|
38
38
|
return InvalidValue.new if reject?(val)
|
39
39
|
return nil if val.nil? || treat_as_nil?(val)
|
40
|
-
return '' if val == ''
|
41
40
|
|
42
41
|
super
|
43
42
|
end
|
@@ -60,7 +59,7 @@ module Grape
|
|
60
59
|
# absence of a value and coerces it into nil. See a discussion there
|
61
60
|
# https://github.com/ruby-grape/grape/pull/2045
|
62
61
|
def treat_as_nil?(val)
|
63
|
-
val == '' && type
|
62
|
+
val == '' && type != String
|
64
63
|
end
|
65
64
|
end
|
66
65
|
end
|
data/lib/grape/version.rb
CHANGED
data/spec/grape/api_spec.rb
CHANGED
@@ -816,6 +816,71 @@ XML
|
|
816
816
|
end
|
817
817
|
end
|
818
818
|
|
819
|
+
describe 'when hook behaviour is controlled by attributes on the route ' do
|
820
|
+
before do
|
821
|
+
subject.before do
|
822
|
+
error!('Access Denied', 401) unless route.options[:secret] == params[:secret]
|
823
|
+
end
|
824
|
+
|
825
|
+
subject.namespace 'example' do
|
826
|
+
before do
|
827
|
+
error!('Access Denied', 401) unless route.options[:namespace_secret] == params[:namespace_secret]
|
828
|
+
end
|
829
|
+
|
830
|
+
desc 'it gets with secret', secret: 'password'
|
831
|
+
get { status(params[:id] == '504' ? 200 : 404) }
|
832
|
+
|
833
|
+
desc 'it post with secret', secret: 'password', namespace_secret: 'namespace_password'
|
834
|
+
post {}
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
context 'when HTTP method is not defined' do
|
839
|
+
let(:response) { delete('/example') }
|
840
|
+
|
841
|
+
it 'responds with a 405 status' do
|
842
|
+
expect(response.status).to eql 405
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
846
|
+
context 'when HTTP method is defined with attribute' do
|
847
|
+
let(:response) { post('/example?secret=incorrect_password') }
|
848
|
+
it 'responds with the defined error in the before hook' do
|
849
|
+
expect(response.status).to eql 401
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
context 'when HTTP method is defined and the underlying before hook expectation is not met' do
|
854
|
+
let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') }
|
855
|
+
it 'ends up in the endpoint' do
|
856
|
+
expect(response.status).to eql 401
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
context 'when HTTP method is defined and everything is like the before hooks expect' do
|
861
|
+
let(:response) { post('/example?secret=password&namespace_secret=namespace_password') }
|
862
|
+
it 'ends up in the endpoint' do
|
863
|
+
expect(response.status).to eql 201
|
864
|
+
end
|
865
|
+
end
|
866
|
+
|
867
|
+
context 'when HEAD is called for the defined GET' do
|
868
|
+
let(:response) { head('/example?id=504') }
|
869
|
+
|
870
|
+
it 'responds with 401 because before expectations in before hooks are not met' do
|
871
|
+
expect(response.status).to eql 401
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
context 'when HEAD is called for the defined GET' do
|
876
|
+
let(:response) { head('/example?id=504&secret=password') }
|
877
|
+
|
878
|
+
it 'responds with 200 because before hooks are not called' do
|
879
|
+
expect(response.status).to eql 200
|
880
|
+
end
|
881
|
+
end
|
882
|
+
end
|
883
|
+
|
819
884
|
context 'allows HEAD on a GET request that' do
|
820
885
|
before do
|
821
886
|
subject.get 'example' do
|
@@ -203,80 +203,222 @@ describe Grape::Endpoint do
|
|
203
203
|
end
|
204
204
|
|
205
205
|
describe '#file' do
|
206
|
+
before do
|
207
|
+
allow(subject).to receive(:warn)
|
208
|
+
end
|
209
|
+
|
206
210
|
describe 'set' do
|
207
211
|
context 'as file path' do
|
208
212
|
let(:file_path) { '/some/file/path' }
|
209
213
|
|
210
|
-
|
211
|
-
|
212
|
-
Grape::ServeFile::FileResponse.new(file_body)
|
213
|
-
end
|
214
|
+
it 'emits a warning that this method is deprecated' do
|
215
|
+
expect(subject).to receive(:warn).with(/Use sendfile or stream/)
|
214
216
|
|
215
|
-
before do
|
216
217
|
subject.file file_path
|
217
218
|
end
|
218
219
|
|
219
|
-
it '
|
220
|
-
expect(subject
|
220
|
+
it 'forwards the call to sendfile' do
|
221
|
+
expect(subject).to receive(:sendfile).with(file_path)
|
222
|
+
|
223
|
+
subject.file file_path
|
221
224
|
end
|
222
225
|
end
|
223
226
|
|
224
227
|
context 'as object (backward compatibility)' do
|
225
|
-
let(:file_object) {
|
228
|
+
let(:file_object) { double('StreamerObject', each: nil) }
|
229
|
+
|
230
|
+
it 'emits a warning that this method is deprecated' do
|
231
|
+
expect(subject).to receive(:warn).with(/Use stream to use a Stream object/)
|
232
|
+
|
233
|
+
subject.file file_object
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'forwards the call to stream' do
|
237
|
+
expect(subject).to receive(:stream).with(file_object)
|
238
|
+
|
239
|
+
subject.file file_object
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe 'get' do
|
245
|
+
it 'emits a warning that this method is deprecated' do
|
246
|
+
expect(subject).to receive(:warn).with(/Use sendfile or stream/)
|
247
|
+
|
248
|
+
subject.file
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'fowards call to sendfile' do
|
252
|
+
expect(subject).to receive(:sendfile)
|
253
|
+
|
254
|
+
subject.file
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe '#sendfile' do
|
260
|
+
describe 'set' do
|
261
|
+
context 'as file path' do
|
262
|
+
let(:file_path) { '/some/file/path' }
|
226
263
|
|
227
264
|
let(:file_response) do
|
228
|
-
Grape::
|
265
|
+
file_body = Grape::ServeStream::FileBody.new(file_path)
|
266
|
+
Grape::ServeStream::StreamResponse.new(file_body)
|
229
267
|
end
|
230
268
|
|
231
269
|
before do
|
232
|
-
subject.
|
270
|
+
subject.header 'Cache-Control', 'cache'
|
271
|
+
subject.header 'Content-Length', 123
|
272
|
+
subject.header 'Transfer-Encoding', 'base64'
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'sends no deprecation warnings' do
|
276
|
+
expect(subject).to_not receive(:warn)
|
277
|
+
|
278
|
+
subject.sendfile file_path
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'returns value wrapped in StreamResponse' do
|
282
|
+
subject.sendfile file_path
|
283
|
+
|
284
|
+
expect(subject.sendfile).to eq file_response
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'does not change the Cache-Control header' do
|
288
|
+
subject.sendfile file_path
|
289
|
+
|
290
|
+
expect(subject.header['Cache-Control']).to eq 'cache'
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'does not change the Content-Length header' do
|
294
|
+
subject.sendfile file_path
|
295
|
+
|
296
|
+
expect(subject.header['Content-Length']).to eq 123
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'does not change the Transfer-Encoding header' do
|
300
|
+
subject.sendfile file_path
|
301
|
+
|
302
|
+
expect(subject.header['Transfer-Encoding']).to eq 'base64'
|
233
303
|
end
|
304
|
+
end
|
305
|
+
|
306
|
+
context 'as object' do
|
307
|
+
let(:file_object) { double('StreamerObject', each: nil) }
|
234
308
|
|
235
|
-
it '
|
236
|
-
expect
|
309
|
+
it 'raises an error that only a file path is supported' do
|
310
|
+
expect { subject.sendfile file_object }.to raise_error(ArgumentError, /Argument must be a file path/)
|
237
311
|
end
|
238
312
|
end
|
239
313
|
end
|
240
314
|
|
241
315
|
it 'returns default' do
|
242
|
-
expect(subject.
|
316
|
+
expect(subject.sendfile).to be nil
|
243
317
|
end
|
244
318
|
end
|
245
319
|
|
246
320
|
describe '#stream' do
|
247
321
|
describe 'set' do
|
248
|
-
|
322
|
+
context 'as a file path' do
|
323
|
+
let(:file_path) { '/some/file/path' }
|
249
324
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
subject.stream file_object
|
255
|
-
end
|
325
|
+
let(:file_response) do
|
326
|
+
file_body = Grape::ServeStream::FileBody.new(file_path)
|
327
|
+
Grape::ServeStream::StreamResponse.new(file_body)
|
328
|
+
end
|
256
329
|
|
257
|
-
|
258
|
-
|
259
|
-
|
330
|
+
before do
|
331
|
+
subject.header 'Cache-Control', 'cache'
|
332
|
+
subject.header 'Content-Length', 123
|
333
|
+
subject.header 'Transfer-Encoding', 'base64'
|
334
|
+
end
|
260
335
|
|
261
|
-
|
262
|
-
|
263
|
-
|
336
|
+
it 'emits no deprecation warnings' do
|
337
|
+
expect(subject).to_not receive(:warn)
|
338
|
+
|
339
|
+
subject.stream file_path
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'returns file body wrapped in StreamResponse' do
|
343
|
+
subject.stream file_path
|
344
|
+
|
345
|
+
expect(subject.stream).to eq file_response
|
346
|
+
end
|
347
|
+
|
348
|
+
it 'sets Cache-Control header to no-cache' do
|
349
|
+
subject.stream file_path
|
350
|
+
|
351
|
+
expect(subject.header['Cache-Control']).to eq 'no-cache'
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'sets Content-Length header to nil' do
|
355
|
+
subject.stream file_path
|
356
|
+
|
357
|
+
expect(subject.header['Content-Length']).to eq nil
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'sets Transfer-Encoding header to nil' do
|
361
|
+
subject.stream file_path
|
264
362
|
|
265
|
-
|
266
|
-
|
363
|
+
expect(subject.header['Transfer-Encoding']).to eq nil
|
364
|
+
end
|
267
365
|
end
|
268
366
|
|
269
|
-
|
270
|
-
|
367
|
+
context 'as a stream object' do
|
368
|
+
let(:stream_object) { double('StreamerObject', each: nil) }
|
369
|
+
|
370
|
+
let(:stream_response) do
|
371
|
+
Grape::ServeStream::StreamResponse.new(stream_object)
|
372
|
+
end
|
373
|
+
|
374
|
+
before do
|
375
|
+
subject.header 'Cache-Control', 'cache'
|
376
|
+
subject.header 'Content-Length', 123
|
377
|
+
subject.header 'Transfer-Encoding', 'base64'
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'emits no deprecation warnings' do
|
381
|
+
expect(subject).to_not receive(:warn)
|
382
|
+
|
383
|
+
subject.stream stream_object
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'returns value wrapped in StreamResponse' do
|
387
|
+
subject.stream stream_object
|
388
|
+
|
389
|
+
expect(subject.stream).to eq stream_response
|
390
|
+
end
|
391
|
+
|
392
|
+
it 'sets Cache-Control header to no-cache' do
|
393
|
+
subject.stream stream_object
|
394
|
+
|
395
|
+
expect(subject.header['Cache-Control']).to eq 'no-cache'
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'sets Content-Length header to nil' do
|
399
|
+
subject.stream stream_object
|
400
|
+
|
401
|
+
expect(subject.header['Content-Length']).to eq nil
|
402
|
+
end
|
403
|
+
|
404
|
+
it 'sets Transfer-Encoding header to nil' do
|
405
|
+
subject.stream stream_object
|
406
|
+
|
407
|
+
expect(subject.header['Transfer-Encoding']).to eq nil
|
408
|
+
end
|
271
409
|
end
|
272
410
|
|
273
|
-
|
274
|
-
|
411
|
+
context 'as a non-stream object' do
|
412
|
+
let(:non_stream_object) { double('NonStreamerObject') }
|
413
|
+
|
414
|
+
it 'raises an error that the object must implement :each' do
|
415
|
+
expect { subject.stream non_stream_object }.to raise_error(ArgumentError, /:each/)
|
416
|
+
end
|
275
417
|
end
|
276
418
|
end
|
277
419
|
|
278
420
|
it 'returns default' do
|
279
|
-
expect(subject.
|
421
|
+
expect(subject.stream).to be nil
|
280
422
|
end
|
281
423
|
end
|
282
424
|
|