grape 0.3.0 → 0.7.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.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (101) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +70 -0
  4. data/.travis.yml +7 -6
  5. data/CHANGELOG.md +134 -4
  6. data/CONTRIBUTING.md +118 -0
  7. data/Gemfile +5 -2
  8. data/README.md +551 -116
  9. data/RELEASING.md +105 -0
  10. data/Rakefile +29 -8
  11. data/UPGRADING.md +124 -0
  12. data/grape.gemspec +3 -3
  13. data/lib/grape/api.rb +207 -88
  14. data/lib/grape/cookies.rb +4 -8
  15. data/lib/grape/endpoint.rb +198 -144
  16. data/lib/grape/error_formatter/base.rb +5 -7
  17. data/lib/grape/error_formatter/json.rb +3 -5
  18. data/lib/grape/error_formatter/txt.rb +1 -3
  19. data/lib/grape/error_formatter/xml.rb +4 -6
  20. data/lib/grape/exceptions/base.rb +9 -9
  21. data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
  22. data/lib/grape/exceptions/invalid_formatter.rb +1 -4
  23. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -5
  24. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -6
  25. data/lib/grape/exceptions/missing_mime_type.rb +1 -5
  26. data/lib/grape/exceptions/missing_option.rb +1 -4
  27. data/lib/grape/exceptions/missing_vendor_option.rb +1 -4
  28. data/lib/grape/exceptions/unknown_options.rb +1 -5
  29. data/lib/grape/exceptions/unknown_validator.rb +1 -3
  30. data/lib/grape/exceptions/validation.rb +13 -3
  31. data/lib/grape/exceptions/validation_errors.rb +43 -0
  32. data/lib/grape/formatter/base.rb +5 -7
  33. data/lib/grape/formatter/json.rb +0 -3
  34. data/lib/grape/formatter/serializable_hash.rb +15 -15
  35. data/lib/grape/formatter/txt.rb +0 -2
  36. data/lib/grape/formatter/xml.rb +0 -2
  37. data/lib/grape/http/request.rb +26 -0
  38. data/lib/grape/locale/en.yml +8 -5
  39. data/lib/grape/middleware/auth/base.rb +30 -0
  40. data/lib/grape/middleware/auth/basic.rb +3 -20
  41. data/lib/grape/middleware/auth/digest.rb +2 -19
  42. data/lib/grape/middleware/auth/oauth2.rb +31 -24
  43. data/lib/grape/middleware/base.rb +7 -7
  44. data/lib/grape/middleware/error.rb +36 -22
  45. data/lib/grape/middleware/filter.rb +3 -3
  46. data/lib/grape/middleware/formatter.rb +99 -61
  47. data/lib/grape/middleware/globals.rb +13 -0
  48. data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
  49. data/lib/grape/middleware/versioner/header.rb +22 -16
  50. data/lib/grape/middleware/versioner/param.rb +9 -11
  51. data/lib/grape/middleware/versioner/path.rb +10 -13
  52. data/lib/grape/middleware/versioner.rb +3 -1
  53. data/lib/grape/namespace.rb +23 -0
  54. data/lib/grape/parser/base.rb +3 -5
  55. data/lib/grape/parser/json.rb +0 -2
  56. data/lib/grape/parser/xml.rb +0 -2
  57. data/lib/grape/path.rb +70 -0
  58. data/lib/grape/route.rb +10 -6
  59. data/lib/grape/util/content_types.rb +2 -1
  60. data/lib/grape/util/deep_merge.rb +5 -5
  61. data/lib/grape/util/hash_stack.rb +13 -2
  62. data/lib/grape/validations/coerce.rb +11 -10
  63. data/lib/grape/validations/default.rb +25 -0
  64. data/lib/grape/validations/presence.rb +7 -3
  65. data/lib/grape/validations/regexp.rb +2 -5
  66. data/lib/grape/validations/values.rb +17 -0
  67. data/lib/grape/validations.rb +161 -54
  68. data/lib/grape/version.rb +1 -1
  69. data/lib/grape.rb +19 -4
  70. data/spec/grape/api_spec.rb +897 -268
  71. data/spec/grape/endpoint_spec.rb +283 -66
  72. data/spec/grape/entity_spec.rb +132 -29
  73. data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
  74. data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
  75. data/spec/grape/middleware/auth/basic_spec.rb +8 -8
  76. data/spec/grape/middleware/auth/digest_spec.rb +5 -5
  77. data/spec/grape/middleware/auth/oauth2_spec.rb +81 -36
  78. data/spec/grape/middleware/base_spec.rb +8 -13
  79. data/spec/grape/middleware/error_spec.rb +13 -17
  80. data/spec/grape/middleware/exception_spec.rb +47 -27
  81. data/spec/grape/middleware/formatter_spec.rb +103 -41
  82. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
  83. data/spec/grape/middleware/versioner/header_spec.rb +76 -51
  84. data/spec/grape/middleware/versioner/param_spec.rb +18 -18
  85. data/spec/grape/middleware/versioner/path_spec.rb +6 -6
  86. data/spec/grape/middleware/versioner_spec.rb +5 -2
  87. data/spec/grape/path_spec.rb +229 -0
  88. data/spec/grape/util/hash_stack_spec.rb +31 -32
  89. data/spec/grape/validations/coerce_spec.rb +116 -51
  90. data/spec/grape/validations/default_spec.rb +123 -0
  91. data/spec/grape/validations/presence_spec.rb +42 -44
  92. data/spec/grape/validations/regexp_spec.rb +9 -9
  93. data/spec/grape/validations/values_spec.rb +138 -0
  94. data/spec/grape/validations/zh-CN.yml +4 -3
  95. data/spec/grape/validations_spec.rb +681 -48
  96. data/spec/shared/versioning_examples.rb +22 -6
  97. data/spec/spec_helper.rb +3 -2
  98. data/spec/support/basic_auth_encode_helpers.rb +0 -1
  99. data/spec/support/content_type_helpers.rb +11 -0
  100. data/spec/support/versioned_helpers.rb +13 -5
  101. metadata +34 -84
@@ -2,22 +2,25 @@ require 'spec_helper'
2
2
 
3
3
  describe Grape::Endpoint do
4
4
  subject { Class.new(Grape::API) }
5
- def app; subject end
5
+
6
+ def app
7
+ subject
8
+ end
6
9
 
7
10
  describe '#initialize' do
8
11
  it 'takes a settings stack, options, and a block' do
9
- p = Proc.new {}
12
+ p = proc {}
10
13
  expect {
11
14
  Grape::Endpoint.new(Grape::Util::HashStack.new, {
12
- :path => '/',
13
- :method => :get
15
+ path: '/',
16
+ method: :get
14
17
  }, &p)
15
18
  }.not_to raise_error
16
19
  end
17
20
  end
18
21
 
19
22
  it 'sets itself in the env upon call' do
20
- subject.get('/'){ "Hello world." }
23
+ subject.get('/') { "Hello world." }
21
24
  get '/'
22
25
  last_request.env['api.endpoint'].should be_kind_of(Grape::Endpoint)
23
26
  end
@@ -61,9 +64,15 @@ describe Grape::Endpoint do
61
64
  }
62
65
  end
63
66
  it 'includes additional request headers' do
64
- get '/headers', nil, { "HTTP_X_GRAPE_CLIENT" => "1" }
67
+ get '/headers', nil, "HTTP_X_GRAPE_CLIENT" => "1"
65
68
  JSON.parse(last_response.body)["X-Grape-Client"].should == "1"
66
69
  end
70
+ it 'includes headers passed as symbols' do
71
+ env = Rack::MockRequest.env_for("/headers")
72
+ env["HTTP_SYMBOL_HEADER".to_sym] = "Goliath passes symbols"
73
+ body = subject.call(env)[2].body.first
74
+ JSON.parse(body)["Symbol-Header"].should == "Goliath passes symbols"
75
+ end
67
76
  end
68
77
 
69
78
  describe '#cookies' do
@@ -71,10 +80,10 @@ describe Grape::Endpoint do
71
80
  subject.get('/get/cookies') do
72
81
  cookies['my-awesome-cookie1'] = 'is cool'
73
82
  cookies['my-awesome-cookie2'] = {
74
- :value => 'is cool too',
75
- :domain => 'my.example.com',
76
- :path => '/',
77
- :secure => true,
83
+ value: 'is cool too',
84
+ domain: 'my.example.com',
85
+ path: '/',
86
+ secure: true,
78
87
  }
79
88
  cookies[:cookie3] = 'symbol'
80
89
  cookies['cookie4'] = 'secret code here'
@@ -87,7 +96,7 @@ describe Grape::Endpoint do
87
96
  "cookie4=secret+code+here",
88
97
  "my-awesome-cookie1=is+cool",
89
98
  "my-awesome-cookie2=is+cool+too; domain=my.example.com; path=/; secure"
90
- ]
99
+ ]
91
100
  end
92
101
 
93
102
  it 'sets browser cookies and does not set response cookies' do
@@ -121,18 +130,18 @@ describe Grape::Endpoint do
121
130
  end
122
131
  sum
123
132
  end
124
- get('/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2')
133
+ get '/test', {}, 'HTTP_COOKIE' => 'delete_this_cookie=1; and_this=2'
125
134
  last_response.body.should == '3'
126
135
  cookies = Hash[last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
127
136
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
128
- [ cookie.name, cookie ]
137
+ [cookie.name, cookie]
129
138
  end]
130
139
  cookies.size.should == 2
131
- [ "and_this", "delete_this_cookie" ].each do |cookie_name|
140
+ ["and_this", "delete_this_cookie"].each do |cookie_name|
132
141
  cookie = cookies[cookie_name]
133
142
  cookie.should_not be_nil
134
143
  cookie.value.should == "deleted"
135
- cookie.expired?.should be_true
144
+ cookie.expired?.should be true
136
145
  end
137
146
  end
138
147
 
@@ -141,7 +150,7 @@ describe Grape::Endpoint do
141
150
  sum = 0
142
151
  cookies.each do |name, val|
143
152
  sum += val.to_i
144
- cookies.delete name, { :path => '/test' }
153
+ cookies.delete name, path: '/test'
145
154
  end
146
155
  sum
147
156
  end
@@ -149,15 +158,15 @@ describe Grape::Endpoint do
149
158
  last_response.body.should == '3'
150
159
  cookies = Hash[last_response.headers['Set-Cookie'].split("\n").map do |set_cookie|
151
160
  cookie = CookieJar::Cookie.from_set_cookie 'http://localhost/test', set_cookie
152
- [ cookie.name, cookie ]
161
+ [cookie.name, cookie]
153
162
  end]
154
163
  cookies.size.should == 2
155
- [ "and_this", "delete_this_cookie" ].each do |cookie_name|
164
+ ["and_this", "delete_this_cookie"].each do |cookie_name|
156
165
  cookie = cookies[cookie_name]
157
166
  cookie.should_not be_nil
158
167
  cookie.value.should == "deleted"
159
168
  cookie.path.should == "/test"
160
- cookie.expired?.should be_true
169
+ cookie.expired?.should be true
161
170
  end
162
171
  end
163
172
  end
@@ -167,14 +176,16 @@ describe Grape::Endpoint do
167
176
  subject.params do
168
177
  requires :first
169
178
  optional :second
179
+ optional :third, default: 'third-default'
180
+ optional :nested, type: Hash do
181
+ optional :fourth
182
+ end
170
183
  end
171
-
172
-
173
184
  end
174
185
 
175
186
  it 'has as many keys as there are declared params' do
176
187
  subject.get '/declared' do
177
- declared(params).keys.size.should == 2
188
+ declared(params).keys.size.should == 4
178
189
  ""
179
190
  end
180
191
 
@@ -182,6 +193,46 @@ describe Grape::Endpoint do
182
193
  last_response.status.should == 200
183
194
  end
184
195
 
196
+ it 'has a optional param with default value all the time' do
197
+ subject.get '/declared' do
198
+ params[:third].should == 'third-default'
199
+ ""
200
+ end
201
+
202
+ get '/declared?first=one'
203
+ last_response.status.should == 200
204
+ end
205
+
206
+ it 'builds nested params' do
207
+ subject.get '/declared' do
208
+ declared(params)[:nested].keys.size.should == 1
209
+ ""
210
+ end
211
+
212
+ get '/declared?first=present&nested[fourth]=1'
213
+ last_response.status.should == 200
214
+ end
215
+
216
+ it 'builds nested params when given array' do
217
+ subject.get '/dummy' do
218
+ end
219
+ subject.params do
220
+ requires :first
221
+ optional :second
222
+ optional :third, default: 'third-default'
223
+ optional :nested, type: Array do
224
+ optional :fourth
225
+ end
226
+ end
227
+ subject.get '/declared' do
228
+ declared(params)[:nested].size.should == 2
229
+ ""
230
+ end
231
+
232
+ get '/declared?first=present&nested[][fourth]=1&nested[][fourth]=2'
233
+ last_response.status.should == 200
234
+ end
235
+
185
236
  it 'filters out any additional params that are given' do
186
237
  subject.get '/declared' do
187
238
  declared(params).key?(:other).should == false
@@ -194,7 +245,7 @@ describe Grape::Endpoint do
194
245
 
195
246
  it 'stringifies if that option is passed' do
196
247
  subject.get '/declared' do
197
- declared(params, :stringify => true)["first"].should == "one"
248
+ declared(params, stringify: true)["first"].should == "one"
198
249
  ""
199
250
  end
200
251
 
@@ -204,7 +255,7 @@ describe Grape::Endpoint do
204
255
 
205
256
  it 'does not include missing attributes if that option is passed' do
206
257
  subject.get '/declared' do
207
- declared(params, :include_missing => false)[:second].should == nil
258
+ error! 400, "expected nil" if declared(params, include_missing: false)[:second]
208
259
  ""
209
260
  end
210
261
 
@@ -213,6 +264,55 @@ describe Grape::Endpoint do
213
264
  end
214
265
  end
215
266
 
267
+ describe '#declared; call from child namespace' do
268
+ before do
269
+ subject.format :json
270
+ subject.namespace :something do
271
+ params do
272
+ requires :id, type: Integer
273
+ end
274
+ resource ':id' do
275
+ params do
276
+ requires :foo
277
+ optional :bar
278
+ end
279
+ get do
280
+ {
281
+ params: params,
282
+ declared_params: declared(params)
283
+ }
284
+ end
285
+ params do
286
+ requires :happy
287
+ optional :days
288
+ end
289
+ get '/test' do
290
+ {
291
+ params: params,
292
+ declared_params: declared(params, include_parent_namespaces: false)
293
+ }
294
+ end
295
+ end
296
+ end
297
+ end
298
+
299
+ it 'should include params defined in the parent namespace' do
300
+ get '/something/123', foo: 'test', extra: 'hello'
301
+ expect(last_response.status).to eq 200
302
+ json = JSON.parse(last_response.body, symbolize_names: true)
303
+ expect(json[:params][:id]).to eq 123
304
+ expect(json[:declared_params].keys).to match_array [:foo, :bar, :id]
305
+ end
306
+
307
+ it 'does not include params defined in the parent namespace with include_parent_namespaces: false' do
308
+ get '/something/123/test', happy: 'test', extra: 'hello'
309
+ expect(last_response.status).to eq 200
310
+ json = JSON.parse(last_response.body, symbolize_names: true)
311
+ expect(json[:params][:id]).to eq 123
312
+ expect(json[:declared_params].keys).to match_array [:happy, :days]
313
+ end
314
+ end
315
+
216
316
  describe '#params' do
217
317
  it 'is available to the caller' do
218
318
  subject.get('/hey') do
@@ -242,37 +342,64 @@ describe Grape::Endpoint do
242
342
 
243
343
  context 'with special requirements' do
244
344
  it 'parses email param with provided requirements for params' do
245
- subject.get('/:person_email', :requirements => { :person_email => /.*/ }) do
246
- params[:person_email]
345
+ subject.get('/:person_email', requirements: { person_email: /.*/ }) do
346
+ params[:person_email]
247
347
  end
248
348
 
249
- get '/rodzyn@grape.com'
250
- last_response.body.should == 'rodzyn@grape.com'
349
+ get '/someone@example.com'
350
+ last_response.body.should == 'someone@example.com'
251
351
 
252
- get 'rodzyn@grape.com.pl'
253
- last_response.body.should == 'rodzyn@grape.com.pl'
352
+ get 'someone@example.com.pl'
353
+ last_response.body.should == 'someone@example.com.pl'
254
354
  end
255
355
 
256
356
  it 'parses many params with provided regexps' do
257
- subject.get('/:person_email/test/:number',
258
- :requirements => {
259
- :person_email => /rodzyn@(.*).com/,
260
- :number => /[0-9]/ }) do
261
- params[:person_email] << params[:number]
357
+ subject.get('/:person_email/test/:number', requirements: { person_email: /someone@(.*).com/, number: /[0-9]/ }) do
358
+ params[:person_email] << params[:number]
262
359
  end
263
360
 
264
- get '/rodzyn@grape.com/test/1'
265
- last_response.body.should == 'rodzyn@grape.com1'
361
+ get '/someone@example.com/test/1'
362
+ last_response.body.should == 'someone@example.com1'
266
363
 
267
- get '/rodzyn@testing.wrong/test/1'
364
+ get '/someone@testing.wrong/test/1'
268
365
  last_response.status.should == 404
269
366
 
270
- get 'rodzyn@test.com/test/wrong_number'
367
+ get 'someone@test.com/test/wrong_number'
271
368
  last_response.status.should == 404
272
369
 
273
- get 'rodzyn@test.com/wrong_middle/1'
370
+ get 'someone@test.com/wrong_middle/1'
274
371
  last_response.status.should == 404
275
372
  end
373
+
374
+ context 'namespace requirements' do
375
+ before :each do
376
+ subject.namespace :outer, requirements: { person_email: /abc@(.*).com/ } do
377
+ get('/:person_email') do
378
+ params[:person_email]
379
+ end
380
+
381
+ namespace :inner, requirements: { number: /[0-9]/, person_email: /someone@(.*).com/ }do
382
+ get '/:person_email/test/:number' do
383
+ params[:person_email] << params[:number]
384
+ end
385
+ end
386
+ end
387
+ end
388
+ it "parse email param with provided requirements for params" do
389
+ get '/outer/abc@example.com'
390
+ last_response.body.should == 'abc@example.com'
391
+ end
392
+
393
+ it "should override outer namespace's requirements" do
394
+ get '/outer/inner/someone@testing.wrong/test/1'
395
+ last_response.status.should == 404
396
+
397
+ get '/outer/inner/someone@testing.com/test/1'
398
+ last_response.status.should == 200
399
+ last_response.body.should == 'someone@testing.com1'
400
+ end
401
+
402
+ end
276
403
  end
277
404
 
278
405
  context 'from body parameters' do
@@ -286,31 +413,33 @@ describe Grape::Endpoint do
286
413
  end
287
414
 
288
415
  it 'converts JSON bodies to params' do
289
- post '/request_body', MultiJson.dump(:user => 'Bobby T.'), {'CONTENT_TYPE' => 'application/json'}
416
+ post '/request_body', MultiJson.dump(user: 'Bobby T.'), 'CONTENT_TYPE' => 'application/json'
290
417
  last_response.body.should == 'Bobby T.'
291
418
  end
292
419
 
293
420
  it 'does not convert empty JSON bodies to params' do
294
- put '/request_body', '', {'CONTENT_TYPE' => 'application/json'}
421
+ put '/request_body', '', 'CONTENT_TYPE' => 'application/json'
295
422
  last_response.body.should == ''
296
423
  end
297
424
 
298
425
  it 'converts XML bodies to params' do
299
- post '/request_body', '<user>Bobby T.</user>', {'CONTENT_TYPE' => 'application/xml'}
426
+ post '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'
300
427
  last_response.body.should == 'Bobby T.'
301
428
  end
302
429
 
303
430
  it 'converts XML bodies to params' do
304
- put '/request_body', '<user>Bobby T.</user>', {'CONTENT_TYPE' => 'application/xml'}
431
+ put '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'
305
432
  last_response.body.should == 'Bobby T.'
306
433
  end
307
434
 
308
435
  it 'does not include parameters not defined by the body' do
309
436
  subject.post '/omitted_params' do
310
- params[:version].should == nil
311
- params[:user].should == 'Bob'
437
+ error! 400, "expected nil" if params[:version]
438
+ params[:user]
312
439
  end
313
- post '/omitted_params', MultiJson.dump(:user => 'Bob'), {'CONTENT_TYPE' => 'application/json'}
440
+ post '/omitted_params', MultiJson.dump(user: 'Bob'), 'CONTENT_TYPE' => 'application/json'
441
+ last_response.status.should == 201
442
+ last_response.body.should == "Bob"
314
443
  end
315
444
  end
316
445
 
@@ -320,11 +449,70 @@ describe Grape::Endpoint do
320
449
  subject.put '/request_body' do
321
450
  params[:user]
322
451
  end
323
- put '/request_body', '<user>Bobby T.</user>', {'CONTENT_TYPE' => 'application/xml'}
452
+ put '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'
324
453
  last_response.status.should == 406
325
454
  last_response.body.should == '{"error":"The requested content-type \'application/xml\' is not supported."}'
326
455
  end
327
456
 
457
+ context 'content type with params' do
458
+ before do
459
+ subject.format :json
460
+ subject.content_type :json, 'application/json; charset=utf-8'
461
+
462
+ subject.post do
463
+ params[:data]
464
+ end
465
+ post '/', MultiJson.dump(data: { some: 'payload' }), 'CONTENT_TYPE' => 'application/json'
466
+ end
467
+
468
+ it "should not response with 406 for same type without params" do
469
+ last_response.status.should_not be 406
470
+ end
471
+
472
+ it "should response with given content type in headers" do
473
+ last_response.headers['Content-Type'].should eq 'application/json; charset=utf-8'
474
+ end
475
+
476
+ end
477
+
478
+ context 'precedence' do
479
+
480
+ before do
481
+ subject.format :json
482
+ subject.namespace '/:id' do
483
+ get do
484
+ {
485
+ params: params[:id]
486
+ }
487
+ end
488
+ post do
489
+ {
490
+ params: params[:id]
491
+ }
492
+ end
493
+ put do
494
+ {
495
+ params: params[:id]
496
+ }
497
+ end
498
+ end
499
+ end
500
+
501
+ it 'route string params have higher precedence than body params' do
502
+ post '/123', { id: 456 }.to_json
503
+ expect(JSON.parse(last_response.body)['params']).to eq '123'
504
+ put '/123', { id: 456 }.to_json
505
+ expect(JSON.parse(last_response.body)['params']).to eq '123'
506
+ end
507
+
508
+ it 'route string params have higher precedence than URL params' do
509
+ get '/123?id=456'
510
+ expect(JSON.parse(last_response.body)['params']).to eq '123'
511
+ post '/123?id=456'
512
+ expect(JSON.parse(last_response.body)['params']).to eq '123'
513
+ end
514
+ end
515
+
328
516
  end
329
517
 
330
518
  describe '#error!' do
@@ -335,7 +523,7 @@ describe Grape::Endpoint do
335
523
  end
336
524
 
337
525
  get '/hey'
338
- last_response.status.should == 403
526
+ last_response.status.should == 500
339
527
  last_response.body.should == "This is not valid."
340
528
  end
341
529
 
@@ -351,13 +539,23 @@ describe Grape::Endpoint do
351
539
 
352
540
  it 'accepts an object and render it in format' do
353
541
  subject.get '/hey' do
354
- error!({'dude' => 'rad'}, 403)
542
+ error!({ 'dude' => 'rad' }, 403)
355
543
  end
356
544
 
357
545
  get '/hey.json'
358
546
  last_response.status.should == 403
359
547
  last_response.body.should == '{"dude":"rad"}'
360
548
  end
549
+
550
+ it 'can specifiy headers' do
551
+ subject.get '/hey' do
552
+ error!({ 'dude' => 'rad' }, 403, 'X-Custom' => 'value')
553
+ end
554
+
555
+ get '/hey.json'
556
+ last_response.status.should == 403
557
+ last_response.headers['X-Custom'].should == 'value'
558
+ end
361
559
  end
362
560
 
363
561
  describe '#redirect' do
@@ -382,7 +580,7 @@ describe Grape::Endpoint do
382
580
 
383
581
  it 'support permanent redirect' do
384
582
  subject.get('/hey') do
385
- redirect "/ha", :permanent => true
583
+ redirect "/ha", permanent: true
386
584
  end
387
585
  get '/hey'
388
586
  last_response.status.should eq 301
@@ -396,10 +594,10 @@ describe Grape::Endpoint do
396
594
  params[:text]
397
595
  end
398
596
 
399
- post '/new', :text => 'abc'
597
+ post '/new', text: 'abc'
400
598
  last_response.body.should == 'abc'
401
599
 
402
- post '/new', :text => 'def'
600
+ post '/new', text: 'def'
403
601
  last_response.body.should == 'def'
404
602
  end
405
603
 
@@ -433,7 +631,7 @@ describe Grape::Endpoint do
433
631
  describe '.generate_api_method' do
434
632
  it 'raises NameError if the method name is already in use' do
435
633
  expect {
436
- Grape::Endpoint.generate_api_method("version", &proc{})
634
+ Grape::Endpoint.generate_api_method("version", &proc {})
437
635
  }.to raise_error(NameError)
438
636
  end
439
637
  it 'raises ArgumentError if a block is not given' do
@@ -442,15 +640,15 @@ describe Grape::Endpoint do
442
640
  }.to raise_error(ArgumentError)
443
641
  end
444
642
  it 'returns a Proc' do
445
- Grape::Endpoint.generate_api_method("GET test for a proc", &proc{}).should be_a Proc
643
+ Grape::Endpoint.generate_api_method("GET test for a proc", &proc {}).should be_a Proc
446
644
  end
447
645
  end
448
646
 
449
647
  context 'filters' do
450
648
  describe 'before filters' do
451
649
  it 'runs the before filter if set' do
452
- subject.before{ env['before_test'] = "OK" }
453
- subject.get('/before_test'){ env['before_test'] }
650
+ subject.before { env['before_test'] = "OK" }
651
+ subject.get('/before_test') { env['before_test'] }
454
652
 
455
653
  get '/before_test'
456
654
  last_response.body.should == "OK"
@@ -459,15 +657,15 @@ describe Grape::Endpoint do
459
657
 
460
658
  describe 'after filters' do
461
659
  it 'overrides the response body if it sets it' do
462
- subject.after{ body "after" }
463
- subject.get('/after_test'){ "during" }
660
+ subject.after { body "after" }
661
+ subject.get('/after_test') { "during" }
464
662
  get '/after_test'
465
663
  last_response.body.should == 'after'
466
664
  end
467
665
 
468
666
  it 'does not override the response body with its return' do
469
- subject.after{ "after" }
470
- subject.get('/after_test'){ "body" }
667
+ subject.after { "after" }
668
+ subject.get('/after_test') { "body" }
471
669
  get '/after_test'
472
670
  last_response.body.should == "body"
473
671
  end
@@ -479,7 +677,7 @@ describe Grape::Endpoint do
479
677
 
480
678
  verbs.each do |verb|
481
679
  it 'allows for the anchoring option with a #{verb.upcase} method' do
482
- subject.send(verb, '/example', :anchor => true) do
680
+ subject.send(verb, '/example', anchor: true) do
483
681
  verb
484
682
  end
485
683
  send(verb, '/example/and/some/more')
@@ -495,7 +693,7 @@ describe Grape::Endpoint do
495
693
  end
496
694
 
497
695
  it 'responds to /example/and/some/more for the non-anchored #{verb.upcase} method' do
498
- subject.send(verb, '/example', :anchor => false) do
696
+ subject.send(verb, '/example', anchor: false) do
499
697
  verb
500
698
  end
501
699
  send(verb, '/example/and/some/more')
@@ -513,9 +711,9 @@ describe Grape::Endpoint do
513
711
  get '/url'
514
712
  last_response.body.should == "http://example.org/url"
515
713
  end
516
- [ 'v1', :v1 ].each do |version|
714
+ ['v1', :v1].each do |version|
517
715
  it 'should include version #{version}' do
518
- subject.version version, :using => :path
716
+ subject.version version, using: :path
519
717
  subject.get('/url') do
520
718
  request.url
521
719
  end
@@ -524,7 +722,7 @@ describe Grape::Endpoint do
524
722
  end
525
723
  end
526
724
  it 'should include prefix' do
527
- subject.version 'v1', :using => :path
725
+ subject.version 'v1', using: :path
528
726
  subject.prefix 'api'
529
727
  subject.get('/url') do
530
728
  request.url
@@ -534,4 +732,23 @@ describe Grape::Endpoint do
534
732
  end
535
733
  end
536
734
 
735
+ context 'version headers' do
736
+ before do
737
+ # NOTE: a 404 is returned instead of the 406 if cascade: false is not set.
738
+ subject.version 'v1', using: :header, vendor: 'ohanapi', cascade: false
739
+ subject.get '/test' do
740
+ "Hello!"
741
+ end
742
+ end
743
+
744
+ it 'result in a 406 response if they are invalid' do
745
+ get '/test', {}, 'HTTP_ACCEPT' => 'application/vnd.ohanapi.v1+json'
746
+ last_response.status.should == 406
747
+ end
748
+
749
+ it 'result in a 406 response if they cannot be parsed by rack-accept' do
750
+ get '/test', {}, 'HTTP_ACCEPT' => 'application/vnd.ohanapi.v1+json; version=1'
751
+ last_response.status.should == 406
752
+ end
753
+ end
537
754
  end