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
@@ -203,80 +203,229 @@ 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 'does not change Cache-Control header' do
|
355
|
+
subject.stream
|
356
|
+
|
357
|
+
expect(subject.header['Cache-Control']).to eq 'cache'
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'sets Content-Length header to nil' do
|
361
|
+
subject.stream file_path
|
362
|
+
|
363
|
+
expect(subject.header['Content-Length']).to eq nil
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'sets Transfer-Encoding header to nil' do
|
367
|
+
subject.stream file_path
|
264
368
|
|
265
|
-
|
266
|
-
|
369
|
+
expect(subject.header['Transfer-Encoding']).to eq nil
|
370
|
+
end
|
267
371
|
end
|
268
372
|
|
269
|
-
|
270
|
-
|
373
|
+
context 'as a stream object' do
|
374
|
+
let(:stream_object) { double('StreamerObject', each: nil) }
|
375
|
+
|
376
|
+
let(:stream_response) do
|
377
|
+
Grape::ServeStream::StreamResponse.new(stream_object)
|
378
|
+
end
|
379
|
+
|
380
|
+
before do
|
381
|
+
subject.header 'Cache-Control', 'cache'
|
382
|
+
subject.header 'Content-Length', 123
|
383
|
+
subject.header 'Transfer-Encoding', 'base64'
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'emits no deprecation warnings' do
|
387
|
+
expect(subject).to_not receive(:warn)
|
388
|
+
|
389
|
+
subject.stream stream_object
|
390
|
+
end
|
391
|
+
|
392
|
+
it 'returns value wrapped in StreamResponse' do
|
393
|
+
subject.stream stream_object
|
394
|
+
|
395
|
+
expect(subject.stream).to eq stream_response
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'sets Cache-Control header to no-cache' do
|
399
|
+
subject.stream stream_object
|
400
|
+
|
401
|
+
expect(subject.header['Cache-Control']).to eq 'no-cache'
|
402
|
+
end
|
403
|
+
|
404
|
+
it 'sets Content-Length header to nil' do
|
405
|
+
subject.stream stream_object
|
406
|
+
|
407
|
+
expect(subject.header['Content-Length']).to eq nil
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'sets Transfer-Encoding header to nil' do
|
411
|
+
subject.stream stream_object
|
412
|
+
|
413
|
+
expect(subject.header['Transfer-Encoding']).to eq nil
|
414
|
+
end
|
271
415
|
end
|
272
416
|
|
273
|
-
|
274
|
-
|
417
|
+
context 'as a non-stream object' do
|
418
|
+
let(:non_stream_object) { double('NonStreamerObject') }
|
419
|
+
|
420
|
+
it 'raises an error that the object must implement :each' do
|
421
|
+
expect { subject.stream non_stream_object }.to raise_error(ArgumentError, /:each/)
|
422
|
+
end
|
275
423
|
end
|
276
424
|
end
|
277
425
|
|
278
426
|
it 'returns default' do
|
279
|
-
expect(subject.
|
427
|
+
expect(subject.stream).to be nil
|
428
|
+
expect(subject.header['Cache-Control']).to eq nil
|
280
429
|
end
|
281
430
|
end
|
282
431
|
|
@@ -0,0 +1,590 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Grape::Endpoint do
|
6
|
+
subject { Class.new(Grape::API) }
|
7
|
+
|
8
|
+
def app
|
9
|
+
subject
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#declared' do
|
13
|
+
before do
|
14
|
+
subject.format :json
|
15
|
+
subject.params do
|
16
|
+
requires :first
|
17
|
+
optional :second
|
18
|
+
optional :third, default: 'third-default'
|
19
|
+
optional :nested, type: Hash do
|
20
|
+
optional :fourth
|
21
|
+
optional :fifth
|
22
|
+
optional :nested_two, type: Hash do
|
23
|
+
optional :sixth
|
24
|
+
optional :nested_three, type: Hash do
|
25
|
+
optional :seventh
|
26
|
+
end
|
27
|
+
end
|
28
|
+
optional :nested_arr, type: Array do
|
29
|
+
optional :eighth
|
30
|
+
end
|
31
|
+
optional :empty_arr, type: Array
|
32
|
+
optional :empty_typed_arr, type: Array[String]
|
33
|
+
optional :empty_hash, type: Hash
|
34
|
+
optional :empty_set, type: Set
|
35
|
+
optional :empty_typed_set, type: Set[String]
|
36
|
+
end
|
37
|
+
optional :arr, type: Array do
|
38
|
+
optional :nineth
|
39
|
+
end
|
40
|
+
optional :empty_arr, type: Array
|
41
|
+
optional :empty_typed_arr, type: Array[String]
|
42
|
+
optional :empty_hash, type: Hash
|
43
|
+
optional :empty_set, type: Set
|
44
|
+
optional :empty_typed_set, type: Set[String]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when params are not built with default class' do
|
49
|
+
it 'returns an object that corresponds with the params class - hash with indifferent access' do
|
50
|
+
subject.params do
|
51
|
+
build_with Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
|
52
|
+
end
|
53
|
+
subject.get '/declared' do
|
54
|
+
d = declared(params, include_missing: true)
|
55
|
+
{ declared_class: d.class.to_s }
|
56
|
+
end
|
57
|
+
|
58
|
+
get '/declared?first=present'
|
59
|
+
expect(JSON.parse(last_response.body)['declared_class']).to eq('ActiveSupport::HashWithIndifferentAccess')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns an object that corresponds with the params class - hashie mash' do
|
63
|
+
subject.params do
|
64
|
+
build_with Grape::Extensions::Hashie::Mash::ParamBuilder
|
65
|
+
end
|
66
|
+
subject.get '/declared' do
|
67
|
+
d = declared(params, include_missing: true)
|
68
|
+
{ declared_class: d.class.to_s }
|
69
|
+
end
|
70
|
+
|
71
|
+
get '/declared?first=present'
|
72
|
+
expect(JSON.parse(last_response.body)['declared_class']).to eq('Hashie::Mash')
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'returns an object that corresponds with the params class - hash' do
|
76
|
+
subject.params do
|
77
|
+
build_with Grape::Extensions::Hash::ParamBuilder
|
78
|
+
end
|
79
|
+
subject.get '/declared' do
|
80
|
+
d = declared(params, include_missing: true)
|
81
|
+
{ declared_class: d.class.to_s }
|
82
|
+
end
|
83
|
+
|
84
|
+
get '/declared?first=present'
|
85
|
+
expect(JSON.parse(last_response.body)['declared_class']).to eq('Hash')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should show nil for nested params if include_missing is true' do
|
90
|
+
subject.get '/declared' do
|
91
|
+
declared(params, include_missing: true)
|
92
|
+
end
|
93
|
+
|
94
|
+
get '/declared?first=present'
|
95
|
+
expect(last_response.status).to eq(200)
|
96
|
+
expect(JSON.parse(last_response.body)['nested']['fourth']).to be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'does not work in a before filter' do
|
100
|
+
subject.before do
|
101
|
+
declared(params)
|
102
|
+
end
|
103
|
+
subject.get('/declared') { declared(params) }
|
104
|
+
|
105
|
+
expect { get('/declared') }.to raise_error(
|
106
|
+
Grape::DSL::InsideRoute::MethodNotYetAvailable
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'has as many keys as there are declared params' do
|
111
|
+
subject.get '/declared' do
|
112
|
+
declared(params)
|
113
|
+
end
|
114
|
+
get '/declared?first=present'
|
115
|
+
expect(last_response.status).to eq(200)
|
116
|
+
expect(JSON.parse(last_response.body).keys.size).to eq(10)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'has a optional param with default value all the time' do
|
120
|
+
subject.get '/declared' do
|
121
|
+
declared(params)
|
122
|
+
end
|
123
|
+
get '/declared?first=one'
|
124
|
+
expect(last_response.status).to eq(200)
|
125
|
+
expect(JSON.parse(last_response.body)['third']).to eql('third-default')
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'builds nested params' do
|
129
|
+
subject.get '/declared' do
|
130
|
+
declared(params)
|
131
|
+
end
|
132
|
+
|
133
|
+
get '/declared?first=present&nested[fourth]=1'
|
134
|
+
expect(last_response.status).to eq(200)
|
135
|
+
expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 9
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'builds arrays correctly' do
|
139
|
+
subject.params do
|
140
|
+
requires :first
|
141
|
+
optional :second, type: Array
|
142
|
+
end
|
143
|
+
subject.post('/declared') { declared(params) }
|
144
|
+
|
145
|
+
post '/declared', first: 'present', second: ['present']
|
146
|
+
expect(last_response.status).to eq(201)
|
147
|
+
|
148
|
+
body = JSON.parse(last_response.body)
|
149
|
+
expect(body['second']).to eq(['present'])
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'builds nested params when given array' do
|
153
|
+
subject.get '/dummy' do
|
154
|
+
end
|
155
|
+
subject.params do
|
156
|
+
requires :first
|
157
|
+
optional :second
|
158
|
+
optional :third, default: 'third-default'
|
159
|
+
optional :nested, type: Array do
|
160
|
+
optional :fourth
|
161
|
+
end
|
162
|
+
end
|
163
|
+
subject.get '/declared' do
|
164
|
+
declared(params)
|
165
|
+
end
|
166
|
+
|
167
|
+
get '/declared?first=present&nested[][fourth]=1&nested[][fourth]=2'
|
168
|
+
expect(last_response.status).to eq(200)
|
169
|
+
expect(JSON.parse(last_response.body)['nested'].size).to eq 2
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'when the param is missing and include_missing=false' do
|
173
|
+
before do
|
174
|
+
subject.get('/declared') { declared(params, include_missing: false) }
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'sets nested objects to be nil' do
|
178
|
+
get '/declared?first=present'
|
179
|
+
expect(last_response.status).to eq(200)
|
180
|
+
expect(JSON.parse(last_response.body)['nested']).to be_nil
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context 'when the param is missing and include_missing=true' do
|
185
|
+
before do
|
186
|
+
subject.get('/declared') { declared(params, include_missing: true) }
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'sets objects with type=Hash to be a hash' do
|
190
|
+
get '/declared?first=present'
|
191
|
+
expect(last_response.status).to eq(200)
|
192
|
+
|
193
|
+
body = JSON.parse(last_response.body)
|
194
|
+
expect(body['empty_hash']).to eq({})
|
195
|
+
expect(body['nested']).to be_a(Hash)
|
196
|
+
expect(body['nested']['empty_hash']).to eq({})
|
197
|
+
expect(body['nested']['nested_two']).to be_a(Hash)
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'sets objects with type=Set to be a set' do
|
201
|
+
get '/declared?first=present'
|
202
|
+
expect(last_response.status).to eq(200)
|
203
|
+
|
204
|
+
body = JSON.parse(last_response.body)
|
205
|
+
expect(['#<Set: {}>', []]).to include(body['empty_set'])
|
206
|
+
expect(['#<Set: {}>', []]).to include(body['empty_typed_set'])
|
207
|
+
expect(['#<Set: {}>', []]).to include(body['nested']['empty_set'])
|
208
|
+
expect(['#<Set: {}>', []]).to include(body['nested']['empty_typed_set'])
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'sets objects with type=Array to be an array' do
|
212
|
+
get '/declared?first=present'
|
213
|
+
expect(last_response.status).to eq(200)
|
214
|
+
|
215
|
+
body = JSON.parse(last_response.body)
|
216
|
+
expect(body['empty_arr']).to eq([])
|
217
|
+
expect(body['empty_typed_arr']).to eq([])
|
218
|
+
expect(body['arr']).to eq([])
|
219
|
+
expect(body['nested']['empty_arr']).to eq([])
|
220
|
+
expect(body['nested']['empty_typed_arr']).to eq([])
|
221
|
+
expect(body['nested']['nested_arr']).to eq([])
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'includes all declared children when type=Hash' do
|
225
|
+
get '/declared?first=present'
|
226
|
+
expect(last_response.status).to eq(200)
|
227
|
+
|
228
|
+
body = JSON.parse(last_response.body)
|
229
|
+
expect(body['nested'].keys).to eq(%w[fourth fifth nested_two nested_arr empty_arr empty_typed_arr empty_hash empty_set empty_typed_set])
|
230
|
+
expect(body['nested']['nested_two'].keys).to eq(%w[sixth nested_three])
|
231
|
+
expect(body['nested']['nested_two']['nested_three'].keys).to eq(%w[seventh])
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'filters out any additional params that are given' do
|
236
|
+
subject.get '/declared' do
|
237
|
+
declared(params)
|
238
|
+
end
|
239
|
+
get '/declared?first=one&other=two'
|
240
|
+
expect(last_response.status).to eq(200)
|
241
|
+
expect(JSON.parse(last_response.body).key?(:other)).to eq false
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'stringifies if that option is passed' do
|
245
|
+
subject.get '/declared' do
|
246
|
+
declared(params, stringify: true)
|
247
|
+
end
|
248
|
+
|
249
|
+
get '/declared?first=one&other=two'
|
250
|
+
expect(last_response.status).to eq(200)
|
251
|
+
expect(JSON.parse(last_response.body)['first']).to eq 'one'
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'does not include missing attributes if that option is passed' do
|
255
|
+
subject.get '/declared' do
|
256
|
+
error! 'expected nil', 400 if declared(params, include_missing: false).key?(:second)
|
257
|
+
''
|
258
|
+
end
|
259
|
+
|
260
|
+
get '/declared?first=one&other=two'
|
261
|
+
expect(last_response.status).to eq(200)
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'does not include renamed missing attributes if that option is passed' do
|
265
|
+
subject.params do
|
266
|
+
optional :renamed_original, as: :renamed
|
267
|
+
end
|
268
|
+
subject.get '/declared' do
|
269
|
+
error! 'expected nil', 400 if declared(params, include_missing: false).key?(:renamed)
|
270
|
+
''
|
271
|
+
end
|
272
|
+
|
273
|
+
get '/declared?first=one&other=two'
|
274
|
+
expect(last_response.status).to eq(200)
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'includes attributes with value that evaluates to false' do
|
278
|
+
subject.params do
|
279
|
+
requires :first
|
280
|
+
optional :boolean
|
281
|
+
end
|
282
|
+
|
283
|
+
subject.post '/declared' do
|
284
|
+
error!('expected false', 400) if declared(params, include_missing: false)[:boolean] != false
|
285
|
+
''
|
286
|
+
end
|
287
|
+
|
288
|
+
post '/declared', ::Grape::Json.dump(first: 'one', boolean: false), 'CONTENT_TYPE' => 'application/json'
|
289
|
+
expect(last_response.status).to eq(201)
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'includes attributes with value that evaluates to nil' do
|
293
|
+
subject.params do
|
294
|
+
requires :first
|
295
|
+
optional :second
|
296
|
+
end
|
297
|
+
|
298
|
+
subject.post '/declared' do
|
299
|
+
error!('expected nil', 400) unless declared(params, include_missing: false)[:second].nil?
|
300
|
+
''
|
301
|
+
end
|
302
|
+
|
303
|
+
post '/declared', ::Grape::Json.dump(first: 'one', second: nil), 'CONTENT_TYPE' => 'application/json'
|
304
|
+
expect(last_response.status).to eq(201)
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'includes missing attributes with defaults when there are nested hashes' do
|
308
|
+
subject.get '/dummy' do
|
309
|
+
end
|
310
|
+
|
311
|
+
subject.params do
|
312
|
+
requires :first
|
313
|
+
optional :second
|
314
|
+
optional :third, default: nil
|
315
|
+
optional :nested, type: Hash do
|
316
|
+
optional :fourth, default: nil
|
317
|
+
optional :fifth, default: nil
|
318
|
+
requires :nested_nested, type: Hash do
|
319
|
+
optional :sixth, default: 'sixth-default'
|
320
|
+
optional :seven, default: nil
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
subject.get '/declared' do
|
326
|
+
declared(params, include_missing: false)
|
327
|
+
end
|
328
|
+
|
329
|
+
get '/declared?first=present&nested[fourth]=&nested[nested_nested][sixth]=sixth'
|
330
|
+
json = JSON.parse(last_response.body)
|
331
|
+
expect(last_response.status).to eq(200)
|
332
|
+
expect(json['first']).to eq 'present'
|
333
|
+
expect(json['nested'].keys).to eq %w[fourth fifth nested_nested]
|
334
|
+
expect(json['nested']['fourth']).to eq ''
|
335
|
+
expect(json['nested']['nested_nested'].keys).to eq %w[sixth seven]
|
336
|
+
expect(json['nested']['nested_nested']['sixth']).to eq 'sixth'
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'does not include missing attributes when there are nested hashes' do
|
340
|
+
subject.get '/dummy' do
|
341
|
+
end
|
342
|
+
|
343
|
+
subject.params do
|
344
|
+
requires :first
|
345
|
+
optional :second
|
346
|
+
optional :third
|
347
|
+
optional :nested, type: Hash do
|
348
|
+
optional :fourth
|
349
|
+
optional :fifth
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
subject.get '/declared' do
|
354
|
+
declared(params, include_missing: false)
|
355
|
+
end
|
356
|
+
|
357
|
+
get '/declared?first=present&nested[fourth]=4'
|
358
|
+
json = JSON.parse(last_response.body)
|
359
|
+
expect(last_response.status).to eq(200)
|
360
|
+
expect(json['first']).to eq 'present'
|
361
|
+
expect(json['nested'].keys).to eq %w[fourth]
|
362
|
+
expect(json['nested']['fourth']).to eq '4'
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
describe '#declared; call from child namespace' do
|
367
|
+
before do
|
368
|
+
subject.format :json
|
369
|
+
subject.namespace :parent do
|
370
|
+
params do
|
371
|
+
requires :parent_name, type: String
|
372
|
+
end
|
373
|
+
|
374
|
+
namespace ':parent_name' do
|
375
|
+
params do
|
376
|
+
requires :child_name, type: String
|
377
|
+
requires :child_age, type: Integer
|
378
|
+
end
|
379
|
+
|
380
|
+
namespace ':child_name' do
|
381
|
+
params do
|
382
|
+
requires :grandchild_name, type: String
|
383
|
+
end
|
384
|
+
|
385
|
+
get ':grandchild_name' do
|
386
|
+
{
|
387
|
+
'params' => params,
|
388
|
+
'without_parent_namespaces' => declared(params, include_parent_namespaces: false),
|
389
|
+
'with_parent_namespaces' => declared(params, include_parent_namespaces: true)
|
390
|
+
}
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
get '/parent/foo/bar/baz', child_age: 5, extra: 'hello'
|
397
|
+
end
|
398
|
+
|
399
|
+
let(:parsed_response) { JSON.parse(last_response.body, symbolize_names: true) }
|
400
|
+
|
401
|
+
it { expect(last_response.status).to eq 200 }
|
402
|
+
|
403
|
+
context 'with include_parent_namespaces: false' do
|
404
|
+
it 'returns declared parameters only from current namespace' do
|
405
|
+
expect(parsed_response[:without_parent_namespaces]).to eq(
|
406
|
+
grandchild_name: 'baz'
|
407
|
+
)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
context 'with include_parent_namespaces: true' do
|
412
|
+
it 'returns declared parameters from every parent namespace' do
|
413
|
+
expect(parsed_response[:with_parent_namespaces]).to eq(
|
414
|
+
parent_name: 'foo',
|
415
|
+
child_name: 'bar',
|
416
|
+
grandchild_name: 'baz',
|
417
|
+
child_age: 5
|
418
|
+
)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
context 'without declaration' do
|
423
|
+
it 'returns all requested parameters' do
|
424
|
+
expect(parsed_response[:params]).to eq(
|
425
|
+
parent_name: 'foo',
|
426
|
+
child_name: 'bar',
|
427
|
+
grandchild_name: 'baz',
|
428
|
+
child_age: 5,
|
429
|
+
extra: 'hello'
|
430
|
+
)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
describe '#declared; from a nested mounted endpoint' do
|
436
|
+
before do
|
437
|
+
doubly_mounted = Class.new(Grape::API)
|
438
|
+
doubly_mounted.namespace :more do
|
439
|
+
params do
|
440
|
+
requires :y, type: Integer
|
441
|
+
end
|
442
|
+
route_param :y do
|
443
|
+
get do
|
444
|
+
{
|
445
|
+
params: params,
|
446
|
+
declared_params: declared(params)
|
447
|
+
}
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
mounted = Class.new(Grape::API)
|
453
|
+
mounted.namespace :another do
|
454
|
+
params do
|
455
|
+
requires :mount_space, type: Integer
|
456
|
+
end
|
457
|
+
route_param :mount_space do
|
458
|
+
mount doubly_mounted
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
subject.format :json
|
463
|
+
subject.namespace :something do
|
464
|
+
params do
|
465
|
+
requires :id, type: Integer
|
466
|
+
end
|
467
|
+
resource ':id' do
|
468
|
+
mount mounted
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'can access parent attributes' do
|
474
|
+
get '/something/123/another/456/more/789'
|
475
|
+
expect(last_response.status).to eq 200
|
476
|
+
json = JSON.parse(last_response.body, symbolize_names: true)
|
477
|
+
|
478
|
+
# test all three levels of params
|
479
|
+
expect(json[:declared_params][:y]).to eq 789
|
480
|
+
expect(json[:declared_params][:mount_space]).to eq 456
|
481
|
+
expect(json[:declared_params][:id]).to eq 123
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
describe '#declared; mixed nesting' do
|
486
|
+
before do
|
487
|
+
subject.format :json
|
488
|
+
subject.resource :users do
|
489
|
+
route_param :id, type: Integer, desc: 'ID desc' do
|
490
|
+
# Adding this causes route_setting(:declared_params) to be nil for the
|
491
|
+
# get block in namespace 'foo' below
|
492
|
+
get do
|
493
|
+
end
|
494
|
+
|
495
|
+
namespace 'foo' do
|
496
|
+
get do
|
497
|
+
{
|
498
|
+
params: params,
|
499
|
+
declared_params: declared(params),
|
500
|
+
declared_params_no_parent: declared(params, include_parent_namespaces: false)
|
501
|
+
}
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
it 'can access parent route_param' do
|
509
|
+
get '/users/123/foo', bar: 'bar'
|
510
|
+
expect(last_response.status).to eq 200
|
511
|
+
json = JSON.parse(last_response.body, symbolize_names: true)
|
512
|
+
|
513
|
+
expect(json[:declared_params][:id]).to eq 123
|
514
|
+
expect(json[:declared_params_no_parent][:id]).to eq nil
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
describe '#declared; with multiple route_param' do
|
519
|
+
before do
|
520
|
+
mounted = Class.new(Grape::API)
|
521
|
+
mounted.namespace :albums do
|
522
|
+
get do
|
523
|
+
declared(params)
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
subject.format :json
|
528
|
+
subject.namespace :artists do
|
529
|
+
route_param :id, type: Integer do
|
530
|
+
get do
|
531
|
+
declared(params)
|
532
|
+
end
|
533
|
+
|
534
|
+
params do
|
535
|
+
requires :filter, type: String
|
536
|
+
end
|
537
|
+
get :some_route do
|
538
|
+
declared(params)
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
route_param :artist_id, type: Integer do
|
543
|
+
namespace :compositions do
|
544
|
+
get do
|
545
|
+
declared(params)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
route_param :compositor_id, type: Integer do
|
551
|
+
mount mounted
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
it 'return only :id without :artist_id' do
|
557
|
+
get '/artists/1'
|
558
|
+
json = JSON.parse(last_response.body, symbolize_names: true)
|
559
|
+
|
560
|
+
expect(json.key?(:id)).to be_truthy
|
561
|
+
expect(json.key?(:artist_id)).not_to be_truthy
|
562
|
+
end
|
563
|
+
|
564
|
+
it 'return only :artist_id without :id' do
|
565
|
+
get '/artists/1/compositions'
|
566
|
+
json = JSON.parse(last_response.body, symbolize_names: true)
|
567
|
+
|
568
|
+
expect(json.key?(:artist_id)).to be_truthy
|
569
|
+
expect(json.key?(:id)).not_to be_truthy
|
570
|
+
end
|
571
|
+
|
572
|
+
it 'return :filter and :id parameters in declared for second enpoint inside route_param' do
|
573
|
+
get '/artists/1/some_route', filter: 'some_filter'
|
574
|
+
json = JSON.parse(last_response.body, symbolize_names: true)
|
575
|
+
|
576
|
+
expect(json.key?(:filter)).to be_truthy
|
577
|
+
expect(json.key?(:id)).to be_truthy
|
578
|
+
expect(json.key?(:artist_id)).not_to be_truthy
|
579
|
+
end
|
580
|
+
|
581
|
+
it 'return :compositor_id for mounter in route_param' do
|
582
|
+
get '/artists/1/albums'
|
583
|
+
json = JSON.parse(last_response.body, symbolize_names: true)
|
584
|
+
|
585
|
+
expect(json.key?(:compositor_id)).to be_truthy
|
586
|
+
expect(json.key?(:id)).not_to be_truthy
|
587
|
+
expect(json.key?(:artist_id)).not_to be_truthy
|
588
|
+
end
|
589
|
+
end
|
590
|
+
end
|