grape 0.3.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.

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
@@ -4,7 +4,9 @@ require 'shared/versioning_examples'
4
4
  describe Grape::API do
5
5
  subject { Class.new(Grape::API) }
6
6
 
7
- def app; subject end
7
+ def app
8
+ subject
9
+ end
8
10
 
9
11
  describe '.prefix' do
10
12
 
@@ -52,17 +54,18 @@ describe Grape::API do
52
54
  it_should_behave_like 'versioning' do
53
55
  let(:macro_options) do
54
56
  {
55
- :using => :path
57
+ using: :path
56
58
  }
57
59
  end
58
60
  end
59
61
  end
62
+
60
63
  describe '.version using param' do
61
64
  it_should_behave_like 'versioning' do
62
65
  let(:macro_options) do
63
66
  {
64
- :using => :param,
65
- :parameter => "apiver"
67
+ using: :param,
68
+ parameter: "apiver"
66
69
  }
67
70
  end
68
71
  end
@@ -72,9 +75,9 @@ describe Grape::API do
72
75
  it_should_behave_like 'versioning' do
73
76
  let(:macro_options) do
74
77
  {
75
- :using => :header,
76
- :vendor => 'mycompany',
77
- :format => 'json'
78
+ using: :header,
79
+ vendor: 'mycompany',
80
+ format: 'json'
78
81
  }
79
82
  end
80
83
  end
@@ -82,7 +85,7 @@ describe Grape::API do
82
85
  # Behavior as defined by rfc2616 when no header is defined
83
86
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
84
87
  describe 'no specified accept header' do
85
- # subject.version 'v1', :using => :header
88
+ # subject.version 'v1', using: :header
86
89
  # subject.get '/hello' do
87
90
  # 'hello'
88
91
  # end
@@ -93,22 +96,29 @@ describe Grape::API do
93
96
  # end
94
97
  end
95
98
 
96
- it 'routes if any media type is allowed' do
99
+ # pending 'routes if any media type is allowed'
100
+ end
97
101
 
102
+ describe '.version using accept_version_header' do
103
+ it_should_behave_like 'versioning' do
104
+ let(:macro_options) do
105
+ {
106
+ using: :accept_version_header
107
+ }
108
+ end
98
109
  end
99
110
  end
100
111
 
101
112
  describe '.represent' do
102
113
  it 'requires a :with option' do
103
- expect{ subject.represent Object, {} }.to raise_error(Grape::Exceptions::InvalidWithOptionForRepresent)
114
+ expect { subject.represent Object, {} }.to raise_error(Grape::Exceptions::InvalidWithOptionForRepresent)
104
115
  end
105
116
 
106
117
  it 'adds the association to the :representations setting' do
107
118
  klass = Class.new
108
- subject.represent Object, :with => klass
119
+ subject.represent Object, with: klass
109
120
  subject.settings[:representations][Object].should == klass
110
121
  end
111
-
112
122
  end
113
123
 
114
124
  describe '.namespace' do
@@ -120,10 +130,10 @@ describe Grape::API do
120
130
 
121
131
  it 'comes after the prefix and version' do
122
132
  subject.prefix :rad
123
- subject.version 'v1', :using => :path
133
+ subject.version 'v1', using: :path
124
134
 
125
135
  subject.namespace :awesome do
126
- get('/hello'){ "worked" }
136
+ get('/hello') { "worked" }
127
137
  end
128
138
 
129
139
  get "/rad/v1/awesome/hello"
@@ -163,10 +173,10 @@ describe Grape::API do
163
173
 
164
174
  it 'is callable with nil just to push onto the stack' do
165
175
  subject.namespace do
166
- version 'v2', :using => :path
167
- get('/hello'){ "inner" }
176
+ version 'v2', using: :path
177
+ get('/hello') { "inner" }
168
178
  end
169
- subject.get('/hello'){ "outer" }
179
+ subject.get('/hello') { "outer" }
170
180
 
171
181
  get '/v2/hello'
172
182
  last_response.body.should == "inner"
@@ -183,13 +193,42 @@ describe Grape::API do
183
193
  end
184
194
  end
185
195
 
196
+ describe '.route_param' do
197
+ it 'adds a parameterized route segment namespace' do
198
+ subject.namespace :users do
199
+ route_param :id do
200
+ get do
201
+ params[:id]
202
+ end
203
+ end
204
+ end
205
+
206
+ get '/users/23'
207
+ last_response.body.should == '23'
208
+ end
209
+
210
+ it 'should be able to define requirements with a single hash' do
211
+ subject.namespace :users do
212
+ route_param :id, requirements: /[0-9]+/ do
213
+ get do
214
+ params[:id]
215
+ end
216
+ end
217
+ end
218
+
219
+ get '/users/michael'
220
+ last_response.status.should == 404
221
+ get '/users/23'
222
+ last_response.status.should == 200
223
+ end
224
+ end
225
+
186
226
  describe '.route' do
187
227
  it 'allows for no path' do
188
228
  subject.namespace :votes do
189
229
  get do
190
230
  "Votes"
191
231
  end
192
-
193
232
  post do
194
233
  "Created a Vote"
195
234
  end
@@ -201,25 +240,32 @@ describe Grape::API do
201
240
  last_response.body.should eql 'Created a Vote'
202
241
  end
203
242
 
243
+ it 'handles empty calls' do
244
+ subject.get "/"
245
+ get "/"
246
+ last_response.body.should eql ""
247
+ end
248
+
204
249
  describe 'root routes should work with' do
205
250
  before do
251
+ subject.format :txt
206
252
  def subject.enable_root_route!
207
- self.get("/") {"root"}
253
+ get("/") { "root" }
208
254
  end
209
255
  end
210
256
 
211
257
  after do
212
- last_response.body.should eql 'root'
258
+ last_response.body.should eql "root"
213
259
  end
214
260
 
215
261
  describe 'path versioned APIs' do
216
262
  before do
217
- subject.version 'v1', :using => :path
263
+ subject.version 'v1', using: :path
218
264
  subject.enable_root_route!
219
265
  end
220
266
 
221
267
  it 'without a format' do
222
- versioned_get "/", "v1", :using => :path
268
+ versioned_get "/", "v1", using: :path
223
269
  end
224
270
 
225
271
  it 'with a format' do
@@ -228,17 +274,32 @@ describe Grape::API do
228
274
  end
229
275
 
230
276
  it 'header versioned APIs' do
231
- subject.version 'v1', :using => :header, :vendor => 'test'
277
+ subject.version 'v1', using: :header, vendor: 'test'
232
278
  subject.enable_root_route!
233
279
 
234
- versioned_get "/", "v1", :using => :header
280
+ versioned_get "/", "v1", using: :header, vendor: 'test'
281
+ end
282
+
283
+ it 'header versioned APIs with multiple headers' do
284
+ subject.version ['v1', 'v2'], using: :header, vendor: 'test'
285
+ subject.enable_root_route!
286
+
287
+ versioned_get "/", "v1", using: :header, vendor: 'test'
288
+ versioned_get "/", "v2", using: :header, vendor: 'test'
235
289
  end
236
290
 
237
291
  it 'param versioned APIs' do
238
- subject.version 'v1', :using => :param
292
+ subject.version 'v1', using: :param
239
293
  subject.enable_root_route!
240
294
 
241
- versioned_get "/", "v1", :using => :param
295
+ versioned_get "/", "v1", using: :param
296
+ end
297
+
298
+ it 'Accept-Version header versioned APIs' do
299
+ subject.version 'v1', using: :accept_version_header
300
+ subject.enable_root_route!
301
+
302
+ versioned_get "/", "v1", using: :accept_version_header
242
303
  end
243
304
 
244
305
  it 'unversioned APIs' do
@@ -262,7 +323,7 @@ describe Grape::API do
262
323
  context 'format' do
263
324
  before(:each) do
264
325
  subject.get("/abc") do
265
- RSpec::Mocks::Mock.new(:to_json => 'abc', :to_txt => 'def')
326
+ RSpec::Mocks::Mock.new(to_json: 'abc', to_txt: 'def')
266
327
  end
267
328
  end
268
329
 
@@ -281,7 +342,7 @@ describe Grape::API do
281
342
 
282
343
  it 'allows for format without corrupting a param' do
283
344
  subject.get('/:id') do
284
- {"id" => params[:id]}
345
+ { "id" => params[:id] }
285
346
  end
286
347
 
287
348
  get '/awesome.json'
@@ -314,6 +375,43 @@ describe Grape::API do
314
375
  last_response.body.should eql 'hiya'
315
376
  end
316
377
 
378
+ [:put, :post].each do |verb|
379
+ context verb do
380
+ ['string', :symbol, 1, -1.1, {}, [], true, false, nil].each do |object|
381
+ it "allows a(n) #{object.class} json object in params" do
382
+ subject.format :json
383
+ subject.send(verb) do
384
+ env['api.request.body']
385
+ end
386
+ send verb, '/', MultiJson.dump(object), 'CONTENT_TYPE' => 'application/json'
387
+ last_response.status.should == (verb == :post ? 201 : 200)
388
+ last_response.body.should eql MultiJson.dump(object)
389
+ last_request.params.should eql Hash.new
390
+ end
391
+ it "stores input in api.request.input" do
392
+ subject.format :json
393
+ subject.send(verb) do
394
+ env['api.request.input']
395
+ end
396
+ send verb, '/', MultiJson.dump(object), 'CONTENT_TYPE' => 'application/json'
397
+ last_response.status.should == (verb == :post ? 201 : 200)
398
+ last_response.body.should eql MultiJson.dump(object).to_json
399
+ end
400
+ context "chunked transfer encoding" do
401
+ it "stores input in api.request.input" do
402
+ subject.format :json
403
+ subject.send(verb) do
404
+ env['api.request.input']
405
+ end
406
+ send verb, '/', MultiJson.dump(object), 'CONTENT_TYPE' => 'application/json', 'HTTP_TRANSFER_ENCODING' => 'chunked', 'CONTENT_LENGTH' => nil
407
+ last_response.status.should == (verb == :post ? 201 : 200)
408
+ last_response.body.should eql MultiJson.dump(object).to_json
409
+ end
410
+ end
411
+ end
412
+ end
413
+ end
414
+
317
415
  it 'allows for multipart paths' do
318
416
 
319
417
  subject.route([:get, :post], '/:id/first') do
@@ -361,7 +459,7 @@ describe Grape::API do
361
459
  last_response.body.should eql verb == 'head' ? '' : verb
362
460
  # Call it with a method other than the properly constrained one.
363
461
  send(used_verb = verbs[(verbs.index(verb) + 2) % verbs.size], '/example')
364
- last_response.status.should eql used_verb == 'options' ? 204 :405
462
+ last_response.status.should eql used_verb == 'options' ? 204 : 405
365
463
  end
366
464
  end
367
465
 
@@ -375,13 +473,15 @@ describe Grape::API do
375
473
  last_response.body.should eql 'Created'
376
474
  end
377
475
 
378
- it 'returns a 405 for an unsupported method' do
476
+ it 'returns a 405 for an unsupported method with an X-Custom-Header' do
477
+ subject.before { header 'X-Custom-Header', 'foo' }
379
478
  subject.get 'example' do
380
479
  "example"
381
480
  end
382
481
  put '/example'
383
482
  last_response.status.should eql 405
384
483
  last_response.body.should eql ''
484
+ last_response.headers['X-Custom-Header'].should eql 'foo'
385
485
  end
386
486
 
387
487
  specify '405 responses includes an Allow header specifying supported methods' do
@@ -395,7 +495,19 @@ describe Grape::API do
395
495
  last_response.headers['Allow'].should eql 'OPTIONS, GET, POST, HEAD'
396
496
  end
397
497
 
398
- it 'adds an OPTIONS route that returns a 204 and an Allow header' do
498
+ specify '405 responses includes an Content-Type header' do
499
+ subject.get 'example' do
500
+ "example"
501
+ end
502
+ subject.post 'example' do
503
+ "example"
504
+ end
505
+ put '/example'
506
+ last_response.headers['Content-Type'].should eql 'text/plain'
507
+ end
508
+
509
+ it 'adds an OPTIONS route that returns a 204, an Allow header and a X-Custom-Header' do
510
+ subject.before { header 'X-Custom-Header', 'foo' }
399
511
  subject.get 'example' do
400
512
  "example"
401
513
  end
@@ -403,6 +515,7 @@ describe Grape::API do
403
515
  last_response.status.should eql 204
404
516
  last_response.body.should eql ''
405
517
  last_response.headers['Allow'].should eql 'OPTIONS, GET, HEAD'
518
+ last_response.headers['X-Custom-Header'].should eql 'foo'
406
519
  end
407
520
 
408
521
  it 'allows HEAD on a GET request' do
@@ -470,17 +583,42 @@ describe Grape::API do
470
583
  last_response.body.should eql 'first second'
471
584
  end
472
585
 
586
+ it 'adds a before filter to current and child namespaces only' do
587
+ subject.get '/' do
588
+ "root - #{@foo}"
589
+ end
590
+ subject.namespace :blah do
591
+ before { @foo = 'foo' }
592
+ get '/' do
593
+ "blah - #{@foo}"
594
+ end
595
+
596
+ namespace :bar do
597
+ get '/' do
598
+ "blah - bar - #{@foo}"
599
+ end
600
+ end
601
+ end
602
+
603
+ get '/'
604
+ last_response.body.should eql 'root - '
605
+ get '/blah'
606
+ last_response.body.should eql 'blah - foo'
607
+ get '/blah/bar'
608
+ last_response.body.should eql 'blah - bar - foo'
609
+ end
610
+
473
611
  it 'adds a after_validation filter' do
474
- subject.after_validation { @foo = "first #{params[:id]}:#{params[:id].class}" }
612
+ subject.after_validation { @foo = "first #{params[:id] }:#{params[:id].class}" }
475
613
  subject.after_validation { @bar = 'second' }
476
614
  subject.params do
477
- requires :id, :type => Integer
615
+ requires :id, type: Integer
478
616
  end
479
617
  subject.get '/' do
480
618
  "#{@foo} #{@bar}"
481
619
  end
482
620
 
483
- get '/', :id => "32"
621
+ get '/', id: "32"
484
622
  last_response.body.should eql 'first 32:Fixnum second'
485
623
  end
486
624
 
@@ -496,6 +634,94 @@ describe Grape::API do
496
634
  get '/'
497
635
  last_response.body.should eql 'default'
498
636
  end
637
+
638
+ it 'calls all filters when validation passes' do
639
+ a = double('before mock')
640
+ b = double('before_validation mock')
641
+ c = double('after_validation mock')
642
+ d = double('after mock')
643
+
644
+ subject.params do
645
+ requires :id, type: Integer
646
+ end
647
+ subject.resource ':id' do
648
+ before { a.do_something! }
649
+ before_validation { b.do_something! }
650
+ after_validation { c.do_something! }
651
+ after { d.do_something! }
652
+ get do
653
+ 'got it'
654
+ end
655
+ end
656
+
657
+ a.should_receive(:do_something!).exactly(1).times
658
+ b.should_receive(:do_something!).exactly(1).times
659
+ c.should_receive(:do_something!).exactly(1).times
660
+ d.should_receive(:do_something!).exactly(1).times
661
+
662
+ get '/123'
663
+ last_response.status.should eql 200
664
+ last_response.body.should eql 'got it'
665
+ end
666
+
667
+ it 'calls only before filters when validation fails' do
668
+ a = double('before mock')
669
+ b = double('before_validation mock')
670
+ c = double('after_validation mock')
671
+ d = double('after mock')
672
+
673
+ subject.params do
674
+ requires :id, type: Integer
675
+ end
676
+ subject.resource ':id' do
677
+ before { a.do_something! }
678
+ before_validation { b.do_something! }
679
+ after_validation { c.do_something! }
680
+ after { d.do_something! }
681
+ get do
682
+ 'got it'
683
+ end
684
+ end
685
+
686
+ a.should_receive(:do_something!).exactly(1).times
687
+ b.should_receive(:do_something!).exactly(1).times
688
+ c.should_receive(:do_something!).exactly(0).times
689
+ d.should_receive(:do_something!).exactly(0).times
690
+
691
+ get '/abc'
692
+ last_response.status.should eql 400
693
+ last_response.body.should eql 'id is invalid'
694
+ end
695
+
696
+ it 'calls filters in the correct order' do
697
+ i = 0
698
+ a = double('before mock')
699
+ b = double('before_validation mock')
700
+ c = double('after_validation mock')
701
+ d = double('after mock')
702
+
703
+ subject.params do
704
+ requires :id, type: Integer
705
+ end
706
+ subject.resource ':id' do
707
+ before { a.here(i += 1) }
708
+ before_validation { b.here(i += 1) }
709
+ after_validation { c.here(i += 1) }
710
+ after { d.here(i += 1) }
711
+ get do
712
+ 'got it'
713
+ end
714
+ end
715
+
716
+ a.should_receive(:here).with(1).exactly(1).times
717
+ b.should_receive(:here).with(2).exactly(1).times
718
+ c.should_receive(:here).with(3).exactly(1).times
719
+ d.should_receive(:here).with(4).exactly(1).times
720
+
721
+ get '/123'
722
+ last_response.status.should eql 200
723
+ last_response.body.should eql 'got it'
724
+ end
499
725
  end
500
726
 
501
727
  context 'format' do
@@ -532,6 +758,26 @@ describe Grape::API do
532
758
  get '/error.xml'
533
759
  last_response.headers['Content-Type'].should eql 'application/xml'
534
760
  end
761
+
762
+ context 'with a custom content_type' do
763
+ before do
764
+ subject.content_type :custom, 'application/custom'
765
+ subject.formatter :custom, lambda { |object, env| "custom" }
766
+
767
+ subject.get('/custom') { 'bar' }
768
+ subject.get('/error') { error!('error in custom', 500) }
769
+ end
770
+
771
+ it 'sets content type' do
772
+ get '/custom.custom'
773
+ last_response.headers['Content-Type'].should eql 'application/custom'
774
+ end
775
+
776
+ it 'sets content type for error' do
777
+ get '/error.custom'
778
+ last_response.headers['Content-Type'].should eql 'application/custom'
779
+ end
780
+ end
535
781
  end
536
782
 
537
783
  context 'custom middleware' do
@@ -555,18 +801,19 @@ describe Grape::API do
555
801
  describe '.middleware' do
556
802
  it 'includes middleware arguments from settings' do
557
803
  settings = Grape::Util::HashStack.new
558
- settings.stub!(:stack).and_return([{:middleware => [[ApiSpec::PhonyMiddleware, 'abc', 123]]}])
559
- subject.stub!(:settings).and_return(settings)
804
+ settings.stub(:stack).and_return([{ middleware: [[ApiSpec::PhonyMiddleware, 'abc', 123]] }])
805
+ subject.stub(:settings).and_return(settings)
560
806
  subject.middleware.should eql [[ApiSpec::PhonyMiddleware, 'abc', 123]]
561
807
  end
562
808
 
563
809
  it 'includes all middleware from stacked settings' do
564
810
  settings = Grape::Util::HashStack.new
565
- settings.stub!(:stack).and_return [
566
- {:middleware => [[ApiSpec::PhonyMiddleware, 123],[ApiSpec::PhonyMiddleware, 'abc']]},
567
- {:middleware => [[ApiSpec::PhonyMiddleware, 'foo']]}
811
+ settings.stub(:stack).and_return [
812
+ { middleware: [[ApiSpec::PhonyMiddleware, 123], [ApiSpec::PhonyMiddleware, 'abc']] },
813
+ { middleware: [[ApiSpec::PhonyMiddleware, 'foo']] }
568
814
  ]
569
- subject.stub!(:settings).and_return(settings)
815
+
816
+ subject.stub(:settings).and_return(settings)
570
817
 
571
818
  subject.middleware.should eql [
572
819
  [ApiSpec::PhonyMiddleware, 123],
@@ -586,7 +833,7 @@ describe Grape::API do
586
833
  subject.use ApiSpec::PhonyMiddleware, 123
587
834
  subject.namespace :awesome do
588
835
  use ApiSpec::PhonyMiddleware, 'abc'
589
- middleware.should == [[ApiSpec::PhonyMiddleware, 123],[ApiSpec::PhonyMiddleware, 'abc']]
836
+ middleware.should == [[ApiSpec::PhonyMiddleware, 123], [ApiSpec::PhonyMiddleware, 'abc']]
590
837
  end
591
838
 
592
839
  subject.middleware.should eql [[ApiSpec::PhonyMiddleware, 123]]
@@ -603,13 +850,13 @@ describe Grape::API do
603
850
  end
604
851
 
605
852
  it 'adds a block if one is given' do
606
- block = lambda{ }
853
+ block = lambda {}
607
854
  subject.use ApiSpec::PhonyMiddleware, &block
608
855
  subject.middleware.should eql [[ApiSpec::PhonyMiddleware, block]]
609
856
  end
610
857
 
611
858
  it 'uses a block if one is given' do
612
- block = lambda{ }
859
+ block = lambda {}
613
860
  subject.use ApiSpec::PhonyMiddleware, &block
614
861
  subject.get '/' do
615
862
  env['phony.block'].inspect
@@ -620,7 +867,7 @@ describe Grape::API do
620
867
  end
621
868
 
622
869
  it 'does not destroy the middleware settings on multiple runs' do
623
- block = lambda{ }
870
+ block = lambda {}
624
871
  subject.use ApiSpec::PhonyMiddleware, &block
625
872
  subject.get '/' do
626
873
  env['phony.block'].inspect
@@ -631,28 +878,42 @@ describe Grape::API do
631
878
  last_response.body.should == 'true'
632
879
  end
633
880
  end
881
+
882
+ it 'mounts behind error middleware' do
883
+ m = Class.new(Grape::Middleware::Base) do
884
+ def before
885
+ throw :error, message: "Caught in the Net", status: 400
886
+ end
887
+ end
888
+ subject.use m
889
+ subject.get "/" do
890
+ end
891
+ get "/"
892
+ last_response.status.should == 400
893
+ last_response.body.should == "Caught in the Net"
894
+ end
634
895
  end
635
896
  end
636
- describe '.basic' do
897
+ describe '.http_basic' do
637
898
  it 'protects any resources on the same scope' do
638
- subject.http_basic do |u,p|
899
+ subject.http_basic do |u, p|
639
900
  u == 'allow'
640
901
  end
641
- subject.get(:hello){ "Hello, world."}
902
+ subject.get(:hello) { "Hello, world." }
642
903
  get '/hello'
643
904
  last_response.status.should eql 401
644
- get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow','whatever')
905
+ get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
645
906
  last_response.status.should eql 200
646
907
  end
647
908
 
648
909
  it 'is scopable' do
649
- subject.get(:hello){ "Hello, world."}
910
+ subject.get(:hello) { "Hello, world." }
650
911
  subject.namespace :admin do
651
- http_basic do |u,p|
912
+ http_basic do |u, p|
652
913
  u == 'allow'
653
914
  end
654
915
 
655
- get(:hello){ "Hello, world." }
916
+ get(:hello) { "Hello, world." }
656
917
  end
657
918
 
658
919
  get '/hello'
@@ -662,15 +923,60 @@ describe Grape::API do
662
923
  end
663
924
 
664
925
  it 'is callable via .auth as well' do
665
- subject.auth :http_basic do |u,p|
926
+ subject.auth :http_basic do |u, p|
666
927
  u == 'allow'
667
928
  end
668
929
 
669
- subject.get(:hello){ "Hello, world."}
930
+ subject.get(:hello) { "Hello, world." }
670
931
  get '/hello'
671
932
  last_response.status.should eql 401
672
- get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow','whatever')
933
+ get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
934
+ last_response.status.should eql 200
935
+ end
936
+
937
+ it 'has access to the current endpoint' do
938
+ basic_auth_context = nil
939
+
940
+ subject.http_basic do |u, p|
941
+ basic_auth_context = self
942
+
943
+ u == 'allow'
944
+ end
945
+
946
+ subject.get(:hello) { "Hello, world." }
947
+ get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
948
+ basic_auth_context.should be_an_instance_of(Grape::Endpoint)
949
+ end
950
+
951
+ it 'has access to helper methods' do
952
+ subject.helpers do
953
+ def authorize(u, p)
954
+ u == 'allow' && p == 'whatever'
955
+ end
956
+ end
957
+
958
+ subject.http_basic do |u, p|
959
+ authorize(u, p)
960
+ end
961
+
962
+ subject.get(:hello) { "Hello, world." }
963
+ get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
964
+ last_response.status.should eql 200
965
+ get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('disallow', 'whatever')
966
+ last_response.status.should eql 401
967
+ end
968
+
969
+ it 'can set instance variables accessible to routes' do
970
+ subject.http_basic do |u, p|
971
+ @hello = "Hello, world."
972
+
973
+ u == 'allow'
974
+ end
975
+
976
+ subject.get(:hello) { @hello }
977
+ get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
673
978
  last_response.status.should eql 200
979
+ last_response.body.should eql "Hello, world."
674
980
  end
675
981
  end
676
982
 
@@ -685,6 +991,13 @@ describe Grape::API do
685
991
  mylogger.should_receive(:info).exactly(1).times
686
992
  subject.logger.info "this will be logged"
687
993
  end
994
+
995
+ it "defaults to a standard logger log format" do
996
+ t = Time.at(100)
997
+ Time.stub(:now).and_return(t)
998
+ STDOUT.should_receive(:write).with("I, [#{Logger::Formatter.new.send(:format_datetime, t)}\##{Process.pid}] INFO -- : this will be logged\n")
999
+ subject.logger.info "this will be logged"
1000
+ end
688
1001
  end
689
1002
 
690
1003
  describe '.helpers' do
@@ -749,7 +1062,7 @@ describe Grape::API do
749
1062
  [one, two]
750
1063
  end
751
1064
 
752
- lambda{get '/howdy'}.should_not raise_error
1065
+ lambda { get '/howdy' }.should_not raise_error
753
1066
  end
754
1067
 
755
1068
  it 'allows for modules' do
@@ -787,7 +1100,7 @@ describe Grape::API do
787
1100
  subject.get 'howdy' do
788
1101
  [one, two, three]
789
1102
  end
790
- lambda{get '/howdy'}.should_not raise_error
1103
+ lambda { get '/howdy' }.should_not raise_error
791
1104
  end
792
1105
  end
793
1106
 
@@ -833,39 +1146,58 @@ describe Grape::API do
833
1146
  raise "rain!"
834
1147
  end
835
1148
  get '/exception'
836
- last_response.status.should eql 403
1149
+ last_response.status.should eql 500
837
1150
  end
838
1151
 
839
1152
  it 'rescues only certain errors if rescue_from is called with specific errors' do
840
1153
  subject.rescue_from ArgumentError
841
- subject.get('/rescued'){ raise ArgumentError }
842
- subject.get('/unrescued'){ raise "beefcake" }
1154
+ subject.get('/rescued') { raise ArgumentError }
1155
+ subject.get('/unrescued') { raise "beefcake" }
843
1156
 
844
1157
  get '/rescued'
845
- last_response.status.should eql 403
1158
+ last_response.status.should eql 500
846
1159
 
847
- lambda{ get '/unrescued' }.should raise_error
1160
+ lambda { get '/unrescued' }.should raise_error
848
1161
  end
849
1162
 
850
- it 'does not re-raise exceptions of type Grape::Exception::Base' do
851
- class CustomError < Grape::Exceptions::Base; end
852
- subject.get('/custom_exception'){ raise CustomError }
1163
+ context 'CustomError subclass of Grape::Exceptions::Base' do
1164
+ before do
1165
+ class CustomError < Grape::Exceptions::Base; end
1166
+ end
853
1167
 
854
- lambda{ get '/custom_exception' }.should_not raise_error
855
- end
1168
+ it 'does not re-raise exceptions of type Grape::Exceptions::Base' do
1169
+ subject.get('/custom_exception') { raise CustomError }
856
1170
 
857
- it 'rescues custom grape exceptions' do
858
- class CustomError < Grape::Exceptions::Base; end
859
- subject.rescue_from CustomError do |e|
860
- rack_response('New Error', e.status)
1171
+ lambda { get '/custom_exception' }.should_not raise_error
861
1172
  end
862
- subject.get '/custom_error' do
863
- raise CustomError, :status => 400, :message => 'Custom Error'
1173
+
1174
+ it 'rescues custom grape exceptions' do
1175
+ subject.rescue_from CustomError do |e|
1176
+ rack_response('New Error', e.status)
1177
+ end
1178
+ subject.get '/custom_error' do
1179
+ raise CustomError, status: 400, message: 'Custom Error'
1180
+ end
1181
+
1182
+ get '/custom_error'
1183
+ last_response.status.should == 400
1184
+ last_response.body.should == 'New Error'
864
1185
  end
1186
+ end
865
1187
 
866
- get '/custom_error'
867
- last_response.status.should == 400
868
- last_response.body.should == 'New Error'
1188
+ it 'can rescue exceptions raised in the formatter' do
1189
+ formatter = double(:formatter)
1190
+ formatter.stub(:call) { raise StandardError }
1191
+ Grape::Formatter::Base.stub(:formatter_for) { formatter }
1192
+
1193
+ subject.rescue_from :all do |e|
1194
+ rack_response('Formatter Error', 500)
1195
+ end
1196
+ subject.get('/formatter_exception') { 'Hello world' }
1197
+
1198
+ get '/formatter_exception'
1199
+ last_response.status.should eql 500
1200
+ last_response.body.should == 'Formatter Error'
869
1201
  end
870
1202
  end
871
1203
 
@@ -881,61 +1213,144 @@ describe Grape::API do
881
1213
  last_response.status.should eql 202
882
1214
  last_response.body.should == 'rescued from rain!'
883
1215
  end
884
- it 'rescues an error via rescue_from :all' do
885
- class ConnectionError < RuntimeError; end
886
- subject.rescue_from :all do |e|
887
- rack_response("rescued from #{e.class.name}", 500)
1216
+
1217
+ context 'custom errors' do
1218
+ before do
1219
+ class ConnectionError < RuntimeError; end
1220
+ class DatabaseError < RuntimeError; end
1221
+ class CommunicationError < StandardError; end
888
1222
  end
889
- subject.get '/exception' do
890
- raise ConnectionError
1223
+
1224
+ it 'rescues an error via rescue_from :all' do
1225
+ subject.rescue_from :all do |e|
1226
+ rack_response("rescued from #{e.class.name}", 500)
1227
+ end
1228
+ subject.get '/exception' do
1229
+ raise ConnectionError
1230
+ end
1231
+ get '/exception'
1232
+ last_response.status.should eql 500
1233
+ last_response.body.should == 'rescued from ConnectionError'
1234
+ end
1235
+ it 'rescues a specific error' do
1236
+ subject.rescue_from ConnectionError do |e|
1237
+ rack_response("rescued from #{e.class.name}", 500)
1238
+ end
1239
+ subject.get '/exception' do
1240
+ raise ConnectionError
1241
+ end
1242
+ get '/exception'
1243
+ last_response.status.should eql 500
1244
+ last_response.body.should == 'rescued from ConnectionError'
1245
+ end
1246
+ it 'rescues multiple specific errors' do
1247
+ subject.rescue_from ConnectionError do |e|
1248
+ rack_response("rescued from #{e.class.name}", 500)
1249
+ end
1250
+ subject.rescue_from DatabaseError do |e|
1251
+ rack_response("rescued from #{e.class.name}", 500)
1252
+ end
1253
+ subject.get '/connection' do
1254
+ raise ConnectionError
1255
+ end
1256
+ subject.get '/database' do
1257
+ raise DatabaseError
1258
+ end
1259
+ get '/connection'
1260
+ last_response.status.should eql 500
1261
+ last_response.body.should == 'rescued from ConnectionError'
1262
+ get '/database'
1263
+ last_response.status.should eql 500
1264
+ last_response.body.should == 'rescued from DatabaseError'
1265
+ end
1266
+ it 'does not rescue a different error' do
1267
+ subject.rescue_from RuntimeError do |e|
1268
+ rack_response("rescued from #{e.class.name}", 500)
1269
+ end
1270
+ subject.get '/uncaught' do
1271
+ raise CommunicationError
1272
+ end
1273
+ lambda { get '/uncaught' }.should raise_error(CommunicationError)
891
1274
  end
892
- get '/exception'
893
- last_response.status.should eql 500
894
- last_response.body.should == 'rescued from ConnectionError'
895
1275
  end
896
- it 'rescues a specific error' do
897
- class ConnectionError < RuntimeError; end
898
- subject.rescue_from ConnectionError do |e|
899
- rack_response("rescued from #{e.class.name}", 500)
1276
+ end
1277
+
1278
+ describe '.rescue_from klass, lambda' do
1279
+ it 'rescues an error with the lambda' do
1280
+ subject.rescue_from ArgumentError, lambda {
1281
+ rack_response("rescued with a lambda", 400)
1282
+ }
1283
+ subject.get('/rescue_lambda') { raise ArgumentError }
1284
+
1285
+ get '/rescue_lambda'
1286
+ last_response.status.should == 400
1287
+ last_response.body.should == "rescued with a lambda"
1288
+ end
1289
+
1290
+ it 'can execute the lambda with an argument' do
1291
+ subject.rescue_from ArgumentError, lambda { |e|
1292
+ rack_response(e.message, 400)
1293
+ }
1294
+ subject.get('/rescue_lambda') { raise ArgumentError, 'lambda takes an argument' }
1295
+
1296
+ get '/rescue_lambda'
1297
+ last_response.status.should == 400
1298
+ last_response.body.should == 'lambda takes an argument'
1299
+ end
1300
+ end
1301
+
1302
+ describe '.rescue_from klass, with: method' do
1303
+ it 'rescues an error with the specified message' do
1304
+ def rescue_arg_error
1305
+ Rack::Response.new('rescued with a method', 400)
900
1306
  end
901
- subject.get '/exception' do
902
- raise ConnectionError
1307
+
1308
+ subject.rescue_from ArgumentError, with: rescue_arg_error
1309
+ subject.get('/rescue_method') { raise ArgumentError }
1310
+
1311
+ get '/rescue_method'
1312
+ last_response.status.should == 400
1313
+ last_response.body.should == 'rescued with a method'
1314
+ end
1315
+ end
1316
+
1317
+ describe '.rescue_from klass, rescue_subclasses: boolean' do
1318
+ before do
1319
+ module APIErrors
1320
+ class ParentError < StandardError; end
1321
+ class ChildError < ParentError; end
903
1322
  end
904
- get '/exception'
905
- last_response.status.should eql 500
906
- last_response.body.should == 'rescued from ConnectionError'
907
1323
  end
908
- it 'rescues multiple specific errors' do
909
- class ConnectionError < RuntimeError; end
910
- class DatabaseError < RuntimeError; end
911
- subject.rescue_from ConnectionError do |e|
1324
+
1325
+ it 'rescues error as well as subclass errors with rescue_subclasses option set' do
1326
+ subject.rescue_from APIErrors::ParentError, rescue_subclasses: true do |e|
912
1327
  rack_response("rescued from #{e.class.name}", 500)
913
1328
  end
914
- subject.rescue_from DatabaseError do |e|
915
- rack_response("rescued from #{e.class.name}", 500)
1329
+ subject.get '/caught_child' do
1330
+ raise APIErrors::ChildError
916
1331
  end
917
- subject.get '/connection' do
918
- raise ConnectionError
1332
+ subject.get '/caught_parent' do
1333
+ raise APIErrors::ParentError
919
1334
  end
920
- subject.get '/database' do
921
- raise DatabaseError
1335
+ subject.get '/uncaught_parent' do
1336
+ raise StandardError
922
1337
  end
923
- get '/connection'
1338
+
1339
+ get '/caught_child'
924
1340
  last_response.status.should eql 500
925
- last_response.body.should == 'rescued from ConnectionError'
926
- get '/database'
1341
+ get '/caught_parent'
927
1342
  last_response.status.should eql 500
928
- last_response.body.should == 'rescued from DatabaseError'
1343
+ lambda { get '/uncaught_parent' }.should raise_error(StandardError)
929
1344
  end
930
- it 'does not rescue a different error' do
931
- class CommunicationError < RuntimeError; end
932
- subject.rescue_from RuntimeError do |e|
1345
+
1346
+ it 'does not rescue child errors if rescue_subclasses is false' do
1347
+ subject.rescue_from APIErrors::ParentError, rescue_subclasses: false do |e|
933
1348
  rack_response("rescued from #{e.class.name}", 500)
934
1349
  end
935
1350
  subject.get '/uncaught' do
936
- raise CommunicationError
1351
+ raise APIErrors::ChildError
937
1352
  end
938
- lambda { get '/uncaught' }.should raise_error(CommunicationError)
1353
+ lambda { get '/uncaught' }.should raise_error(APIErrors::ChildError)
939
1354
  end
940
1355
  end
941
1356
 
@@ -951,13 +1366,13 @@ describe Grape::API do
951
1366
  end
952
1367
 
953
1368
  it 'rescues all errors and return :txt with backtrace' do
954
- subject.rescue_from :all, :backtrace => true
1369
+ subject.rescue_from :all, backtrace: true
955
1370
  subject.format :txt
956
1371
  subject.get '/exception' do
957
1372
  raise "rain!"
958
1373
  end
959
1374
  get '/exception'
960
- last_response.body.start_with?("rain!\r\n").should be_true
1375
+ last_response.body.start_with?("rain!\r\n").should be true
961
1376
  end
962
1377
 
963
1378
  it 'rescues all errors with a default formatter' do
@@ -994,7 +1409,7 @@ describe Grape::API do
994
1409
  end
995
1410
  end
996
1411
  it 'returns a custom error format' do
997
- subject.rescue_from :all, :backtrace => true
1412
+ subject.rescue_from :all, backtrace: true
998
1413
  subject.error_formatter :txt, CustomErrorFormatter
999
1414
  subject.get '/exception' do
1000
1415
  raise "rain!"
@@ -1004,6 +1419,27 @@ describe Grape::API do
1004
1419
  end
1005
1420
  end
1006
1421
 
1422
+ describe 'with' do
1423
+ context 'class' do
1424
+ before :each do
1425
+ class CustomErrorFormatter
1426
+ def self.call(message, backtrace, option, env)
1427
+ "message: #{message} @backtrace"
1428
+ end
1429
+ end
1430
+ end
1431
+
1432
+ it 'returns a custom error format' do
1433
+ subject.rescue_from :all, backtrace: true
1434
+ subject.error_formatter :txt, with: CustomErrorFormatter
1435
+ subject.get('/exception') { raise "rain!" }
1436
+
1437
+ get '/exception'
1438
+ last_response.body.should == 'message: rain! @backtrace'
1439
+ end
1440
+ end
1441
+ end
1442
+
1007
1443
  it 'rescues all errors and return :json' do
1008
1444
  subject.rescue_from :all
1009
1445
  subject.format :json
@@ -1014,7 +1450,7 @@ describe Grape::API do
1014
1450
  last_response.body.should eql '{"error":"rain!"}'
1015
1451
  end
1016
1452
  it 'rescues all errors and return :json with backtrace' do
1017
- subject.rescue_from :all, :backtrace => true
1453
+ subject.rescue_from :all, backtrace: true
1018
1454
  subject.format :json
1019
1455
  subject.get '/exception' do
1020
1456
  raise "rain!"
@@ -1073,10 +1509,10 @@ describe Grape::API do
1073
1509
  describe '.formatter' do
1074
1510
  context 'multiple formatters' do
1075
1511
  before :each do
1076
- subject.formatter :json, lambda { |object, env| "{\"custom_formatter\":\"#{object[:some]}\"}" }
1077
- subject.formatter :txt, lambda { |object, env| "custom_formatter: #{object[:some]}" }
1512
+ subject.formatter :json, lambda { |object, env| "{\"custom_formatter\":\"#{object[:some] }\"}" }
1513
+ subject.formatter :txt, lambda { |object, env| "custom_formatter: #{object[:some] }" }
1078
1514
  subject.get :simple do
1079
- {:some => 'hash'}
1515
+ { some: 'hash' }
1080
1516
  end
1081
1517
  end
1082
1518
  it 'sets one formatter' do
@@ -1092,9 +1528,9 @@ describe Grape::API do
1092
1528
  before :each do
1093
1529
  subject.content_type :json, 'application/json'
1094
1530
  subject.content_type :custom, 'application/custom'
1095
- subject.formatter :custom, lambda { |object, env| "{\"custom_formatter\":\"#{object[:some]}\"}" }
1531
+ subject.formatter :custom, lambda { |object, env| "{\"custom_formatter\":\"#{object[:some] }\"}" }
1096
1532
  subject.get :simple do
1097
- {:some => 'hash'}
1533
+ { some: 'hash' }
1098
1534
  end
1099
1535
  end
1100
1536
  it 'uses json' do
@@ -1102,14 +1538,14 @@ describe Grape::API do
1102
1538
  last_response.body.should eql '{"some":"hash"}'
1103
1539
  end
1104
1540
  it 'uses custom formatter' do
1105
- get '/simple.custom', { 'HTTP_ACCEPT' => 'application/custom' }
1541
+ get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
1106
1542
  last_response.body.should eql '{"custom_formatter":"hash"}'
1107
1543
  end
1108
1544
  end
1109
1545
  context 'custom formatter class' do
1110
1546
  module CustomFormatter
1111
1547
  def self.call(object, env)
1112
- "{\"custom_formatter\":\"#{object[:some]}\"}"
1548
+ "{\"custom_formatter\":\"#{object[:some] }\"}"
1113
1549
  end
1114
1550
  end
1115
1551
  before :each do
@@ -1117,7 +1553,7 @@ describe Grape::API do
1117
1553
  subject.content_type :custom, 'application/custom'
1118
1554
  subject.formatter :custom, CustomFormatter
1119
1555
  subject.get :simple do
1120
- {:some => 'hash'}
1556
+ { some: 'hash' }
1121
1557
  end
1122
1558
  end
1123
1559
  it 'uses json' do
@@ -1125,13 +1561,22 @@ describe Grape::API do
1125
1561
  last_response.body.should eql '{"some":"hash"}'
1126
1562
  end
1127
1563
  it 'uses custom formatter' do
1128
- get '/simple.custom', { 'HTTP_ACCEPT' => 'application/custom' }
1564
+ get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
1129
1565
  last_response.body.should eql '{"custom_formatter":"hash"}'
1130
1566
  end
1131
1567
  end
1132
1568
  end
1133
1569
 
1134
1570
  describe '.parser' do
1571
+ it 'parses data in format requested by content-type' do
1572
+ subject.format :json
1573
+ subject.post '/data' do
1574
+ { x: params[:x] }
1575
+ end
1576
+ post "/data", '{"x":42}', 'CONTENT_TYPE' => 'application/json'
1577
+ last_response.status.should == 201
1578
+ last_response.body.should == '{"x":42}'
1579
+ end
1135
1580
  context 'lambda parser' do
1136
1581
  before :each do
1137
1582
  subject.content_type :txt, "text/plain"
@@ -1141,7 +1586,7 @@ describe Grape::API do
1141
1586
  params[:simple]
1142
1587
  end
1143
1588
  end
1144
- [ "text/custom", "text/custom; charset=UTF-8" ].each do |content_type|
1589
+ ["text/custom", "text/custom; charset=UTF-8"].each do |content_type|
1145
1590
  it "uses parser for #{content_type}" do
1146
1591
  put '/simple', "simple", "CONTENT_TYPE" => content_type
1147
1592
  last_response.status.should == 200
@@ -1179,6 +1624,42 @@ describe Grape::API do
1179
1624
  last_response.body.should eql 'Disallowed type attribute: "symbol"'
1180
1625
  end
1181
1626
  end
1627
+ context "none parser class" do
1628
+ before :each do
1629
+ subject.parser :json, nil
1630
+ subject.put "data" do
1631
+ "body: #{env['api.request.body'] }"
1632
+ end
1633
+ end
1634
+ it "does not parse data" do
1635
+ put '/data', 'not valid json', "CONTENT_TYPE" => "application/json"
1636
+ last_response.status.should == 200
1637
+ last_response.body.should == "body: not valid json"
1638
+ end
1639
+ end
1640
+ end
1641
+
1642
+ describe '.default_format' do
1643
+ before :each do
1644
+ subject.format :json
1645
+ subject.default_format :json
1646
+ end
1647
+ it 'returns data in default format' do
1648
+ subject.get '/data' do
1649
+ { x: 42 }
1650
+ end
1651
+ get "/data"
1652
+ last_response.status.should == 200
1653
+ last_response.body.should == '{"x":42}'
1654
+ end
1655
+ it 'parses data in default format' do
1656
+ subject.post '/data' do
1657
+ { x: params[:x] }
1658
+ end
1659
+ post "/data", '{"x":42}', "CONTENT_TYPE" => ""
1660
+ last_response.status.should == 201
1661
+ last_response.body.should == '{"x":42}'
1662
+ end
1182
1663
  end
1183
1664
 
1184
1665
  describe '.default_error_status' do
@@ -1197,7 +1678,16 @@ describe Grape::API do
1197
1678
  raise "rain!"
1198
1679
  end
1199
1680
  get '/exception'
1200
- last_response.status.should eql 403
1681
+ last_response.status.should eql 500
1682
+ end
1683
+ it 'uses the default error status in error!' do
1684
+ subject.rescue_from :all
1685
+ subject.default_error_status 400
1686
+ subject.get '/exception' do
1687
+ error! "rain!"
1688
+ end
1689
+ get '/exception'
1690
+ last_response.status.should eql 400
1201
1691
  end
1202
1692
  end
1203
1693
 
@@ -1210,76 +1700,76 @@ describe Grape::API do
1210
1700
  describe 'single method api structure' do
1211
1701
  before(:each) do
1212
1702
  subject.get :ping do
1213
- 'pong'
1703
+ 'pong'
1214
1704
  end
1215
1705
  end
1216
1706
  it 'returns one route' do
1217
- subject.routes.size.should == 1
1218
- route = subject.routes[0]
1219
- route.route_version.should be_nil
1220
- route.route_path.should == "/ping(.:format)"
1221
- route.route_method.should == "GET"
1707
+ subject.routes.size.should == 1
1708
+ route = subject.routes[0]
1709
+ route.route_version.should be_nil
1710
+ route.route_path.should == "/ping(.:format)"
1711
+ route.route_method.should == "GET"
1222
1712
  end
1223
1713
  end
1224
1714
  describe 'api structure with two versions and a namespace' do
1225
1715
  before :each do
1226
- subject.version 'v1', :using => :path
1716
+ subject.version 'v1', using: :path
1227
1717
  subject.get 'version' do
1228
- api.version
1718
+ api.version
1229
1719
  end
1230
1720
  # version v2
1231
- subject.version 'v2', :using => :path
1721
+ subject.version 'v2', using: :path
1232
1722
  subject.prefix 'p'
1233
1723
  subject.namespace 'n1' do
1234
1724
  namespace 'n2' do
1235
1725
  get 'version' do
1236
- api.version
1726
+ api.version
1237
1727
  end
1238
1728
  end
1239
1729
  end
1240
1730
  end
1241
1731
  it 'returns the latest version set' do
1242
- subject.version.should == 'v2'
1732
+ subject.version.should == 'v2'
1243
1733
  end
1244
1734
  it 'returns versions' do
1245
- subject.versions.should == [ 'v1', 'v2' ]
1735
+ subject.versions.should == ['v1', 'v2']
1246
1736
  end
1247
1737
  it 'sets route paths' do
1248
- subject.routes.size.should >= 2
1249
- subject.routes[0].route_path.should == "/:version/version(.:format)"
1250
- subject.routes[1].route_path.should == "/p/:version/n1/n2/version(.:format)"
1738
+ subject.routes.size.should >= 2
1739
+ subject.routes[0].route_path.should == "/:version/version(.:format)"
1740
+ subject.routes[1].route_path.should == "/p/:version/n1/n2/version(.:format)"
1251
1741
  end
1252
1742
  it 'sets route versions' do
1253
- subject.routes[0].route_version.should == 'v1'
1254
- subject.routes[1].route_version.should == 'v2'
1743
+ subject.routes[0].route_version.should == 'v1'
1744
+ subject.routes[1].route_version.should == 'v2'
1255
1745
  end
1256
1746
  it 'sets a nested namespace' do
1257
- subject.routes[1].route_namespace.should == "/n1/n2"
1747
+ subject.routes[1].route_namespace.should == "/n1/n2"
1258
1748
  end
1259
1749
  it 'sets prefix' do
1260
- subject.routes[1].route_prefix.should == 'p'
1750
+ subject.routes[1].route_prefix.should == 'p'
1261
1751
  end
1262
1752
  end
1263
1753
  describe 'api structure with additional parameters' do
1264
1754
  before(:each) do
1265
- subject.get 'split/:string', { :params => { "token" => "a token" }, :optional_params => { "limit" => "the limit" } } do
1755
+ subject.get 'split/:string', params: { "token" => "a token" }, optional_params: { "limit" => "the limit" } do
1266
1756
  params[:string].split(params[:token], (params[:limit] || 0).to_i)
1267
1757
  end
1268
1758
  end
1269
1759
  it 'splits a string' do
1270
- get "/split/a,b,c.json", :token => ','
1760
+ get "/split/a,b,c.json", token: ','
1271
1761
  last_response.body.should == '["a","b","c"]'
1272
1762
  end
1273
1763
  it 'splits a string with limit' do
1274
- get "/split/a,b,c.json", :token => ',', :limit => '2'
1764
+ get "/split/a,b,c.json", token: ',', limit: '2'
1275
1765
  last_response.body.should == '["a","b,c"]'
1276
1766
  end
1277
1767
  it 'sets route_params' do
1278
1768
  subject.routes.map { |route|
1279
- { :params => route.route_params, :optional_params => route.route_optional_params }
1769
+ { params: route.route_params, optional_params: route.route_optional_params }
1280
1770
  }.should eq [
1281
- { :params => { "string" => "", "token" => "a token" }, :optional_params => { "limit" => "the limit" } }
1282
- ]
1771
+ { params: { "string" => "", "token" => "a token" }, optional_params: { "limit" => "the limit" } }
1772
+ ]
1283
1773
  end
1284
1774
  end
1285
1775
  end
@@ -1299,7 +1789,7 @@ describe Grape::API do
1299
1789
  route = subject.routes.first
1300
1790
  route.route_description.should == "first method"
1301
1791
  route.route_foo.should be_nil
1302
- route.route_params.should == { }
1792
+ route.route_params.should == {}
1303
1793
  end
1304
1794
  it 'describes methods separately' do
1305
1795
  subject.desc "first method"
@@ -1308,122 +1798,120 @@ describe Grape::API do
1308
1798
  subject.get :second do ; end
1309
1799
  subject.routes.count.should == 2
1310
1800
  subject.routes.map { |route|
1311
- { :description => route.route_description, :params => route.route_params }
1801
+ { description: route.route_description, params: route.route_params }
1312
1802
  }.should eq [
1313
- { :description => "first method", :params => {} },
1314
- { :description => "second method", :params => {} }
1315
- ]
1803
+ { description: "first method", params: {} },
1804
+ { description: "second method", params: {} }
1805
+ ]
1316
1806
  end
1317
1807
  it 'resets desc' do
1318
1808
  subject.desc "first method"
1319
1809
  subject.get :first do ; end
1320
1810
  subject.get :second do ; end
1321
1811
  subject.routes.map { |route|
1322
- { :description => route.route_description, :params => route.route_params }
1812
+ { description: route.route_description, params: route.route_params }
1323
1813
  }.should eq [
1324
- { :description => "first method", :params => {} },
1325
- { :description => nil, :params => {} }
1326
- ]
1814
+ { description: "first method", params: {} },
1815
+ { description: nil, params: {} }
1816
+ ]
1327
1817
  end
1328
1818
  it 'namespaces and describe arbitrary parameters' do
1329
1819
  subject.namespace 'ns' do
1330
- desc "ns second", :foo => "bar"
1820
+ desc "ns second", foo: "bar"
1331
1821
  get 'second' do ; end
1332
1822
  end
1333
1823
  subject.routes.map { |route|
1334
- { :description => route.route_description, :foo => route.route_foo, :params => route.route_params }
1824
+ { description: route.route_description, foo: route.route_foo, params: route.route_params }
1335
1825
  }.should eq [
1336
- { :description => "ns second", :foo => "bar", :params => {} },
1337
- ]
1826
+ { description: "ns second", foo: "bar", params: {} },
1827
+ ]
1338
1828
  end
1339
1829
  it 'includes details' do
1340
- subject.desc "method", :details => "method details"
1830
+ subject.desc "method", details: "method details"
1341
1831
  subject.get 'method' do ; end
1342
1832
  subject.routes.map { |route|
1343
- { :description => route.route_description, :details => route.route_details, :params => route.route_params }
1833
+ { description: route.route_description, details: route.route_details, params: route.route_params }
1344
1834
  }.should eq [
1345
- { :description => "method", :details => "method details", :params => {} },
1346
- ]
1835
+ { description: "method", details: "method details", params: {} },
1836
+ ]
1347
1837
  end
1348
1838
  it 'describes a method with parameters' do
1349
- subject.desc "Reverses a string.", { :params =>
1350
- { "s" => { :desc => "string to reverse", :type => "string" }}
1351
- }
1839
+ subject.desc "Reverses a string.", params: { "s" => { desc: "string to reverse", type: "string" } }
1352
1840
  subject.get 'reverse' do
1353
1841
  params[:s].reverse
1354
1842
  end
1355
1843
  subject.routes.map { |route|
1356
- { :description => route.route_description, :params => route.route_params }
1844
+ { description: route.route_description, params: route.route_params }
1357
1845
  }.should eq [
1358
- { :description => "Reverses a string.", :params => { "s" => { :desc => "string to reverse", :type => "string" } } }
1359
- ]
1846
+ { description: "Reverses a string.", params: { "s" => { desc: "string to reverse", type: "string" } } }
1847
+ ]
1360
1848
  end
1361
1849
  it 'merges the parameters of the namespace with the parameters of the method' do
1362
1850
  subject.desc "namespace"
1363
1851
  subject.params do
1364
- requires :ns_param, :desc => "namespace parameter"
1852
+ requires :ns_param, desc: "namespace parameter"
1365
1853
  end
1366
1854
  subject.namespace 'ns' do
1367
1855
  desc "method"
1368
1856
  params do
1369
- optional :method_param, :desc => "method parameter"
1857
+ optional :method_param, desc: "method parameter"
1370
1858
  end
1371
1859
  get 'method' do ; end
1372
1860
  end
1373
1861
  subject.routes.map { |route|
1374
- { :description => route.route_description, :params => route.route_params }
1862
+ { description: route.route_description, params: route.route_params }
1375
1863
  }.should eq [
1376
- { :description => "method",
1377
- :params => {
1378
- "ns_param" => { :required => true, :desc => "namespace parameter" },
1379
- "method_param" => { :required => false, :desc => "method parameter" }
1864
+ { description: "method",
1865
+ params: {
1866
+ "ns_param" => { required: true, desc: "namespace parameter" },
1867
+ "method_param" => { required: false, desc: "method parameter" }
1380
1868
  }
1381
1869
  }
1382
- ]
1870
+ ]
1383
1871
  end
1384
1872
  it 'merges the parameters of nested namespaces' do
1385
1873
  subject.desc "ns1"
1386
1874
  subject.params do
1387
- optional :ns_param, :desc => "ns param 1"
1388
- requires :ns1_param, :desc => "ns1 param"
1875
+ optional :ns_param, desc: "ns param 1"
1876
+ requires :ns1_param, desc: "ns1 param"
1389
1877
  end
1390
1878
  subject.namespace 'ns1' do
1391
1879
  desc "ns2"
1392
1880
  params do
1393
- requires :ns_param, :desc => "ns param 2"
1394
- requires :ns2_param, :desc => "ns2 param"
1881
+ requires :ns_param, desc: "ns param 2"
1882
+ requires :ns2_param, desc: "ns2 param"
1395
1883
  end
1396
1884
  namespace 'ns2' do
1397
1885
  desc "method"
1398
1886
  params do
1399
- optional :method_param, :desc => "method param"
1887
+ optional :method_param, desc: "method param"
1400
1888
  end
1401
1889
  get 'method' do ; end
1402
1890
  end
1403
1891
  end
1404
1892
  subject.routes.map { |route|
1405
- { :description => route.route_description, :params => route.route_params }
1893
+ { description: route.route_description, params: route.route_params }
1406
1894
  }.should eq [
1407
- { :description => "method",
1408
- :params => {
1409
- "ns_param" => { :required => true, :desc => "ns param 2" },
1410
- "ns1_param" => { :required => true, :desc => "ns1 param" },
1411
- "ns2_param" => { :required => true, :desc => "ns2 param" },
1412
- "method_param" => { :required => false, :desc => "method param" }
1895
+ { description: "method",
1896
+ params: {
1897
+ "ns_param" => { required: true, desc: "ns param 2" },
1898
+ "ns1_param" => { required: true, desc: "ns1 param" },
1899
+ "ns2_param" => { required: true, desc: "ns2 param" },
1900
+ "method_param" => { required: false, desc: "method param" }
1413
1901
  }
1414
1902
  }
1415
- ]
1903
+ ]
1416
1904
  end
1417
1905
  it "groups nested params and prevents overwriting of params with same name in different groups" do
1418
1906
  subject.desc "method"
1419
1907
  subject.params do
1420
1908
  group :group1 do
1421
- optional :param1, :desc => "group1 param1 desc"
1422
- requires :param2, :desc => "group1 param2 desc"
1909
+ optional :param1, desc: "group1 param1 desc"
1910
+ requires :param2, desc: "group1 param2 desc"
1423
1911
  end
1424
1912
  group :group2 do
1425
- optional :param1, :desc => "group2 param1 desc"
1426
- requires :param2, :desc => "group2 param2 desc"
1913
+ optional :param1, desc: "group2 param1 desc"
1914
+ requires :param2, desc: "group2 param2 desc"
1427
1915
  end
1428
1916
  end
1429
1917
  subject.get "method" do ; end
@@ -1431,60 +1919,68 @@ describe Grape::API do
1431
1919
  subject.routes.map { |route|
1432
1920
  route.route_params
1433
1921
  }.should eq [{
1434
- "group1[param1]" => { :required => false, :desc => "group1 param1 desc" },
1435
- "group1[param2]" => { :required => true, :desc => "group1 param2 desc" },
1436
- "group2[param1]" => { :required => false, :desc => "group2 param1 desc" },
1437
- "group2[param2]" => { :required => true, :desc => "group2 param2 desc" }
1922
+ "group1" => { required: true, type: "Array" },
1923
+ "group1[param1]" => { required: false, desc: "group1 param1 desc" },
1924
+ "group1[param2]" => { required: true, desc: "group1 param2 desc" },
1925
+ "group2" => { required: true, type: "Array" },
1926
+ "group2[param1]" => { required: false, desc: "group2 param1 desc" },
1927
+ "group2[param2]" => { required: true, desc: "group2 param2 desc" }
1438
1928
  }]
1439
1929
  end
1440
1930
  it 'uses full name of parameters in nested groups' do
1441
1931
  subject.desc "nesting"
1442
1932
  subject.params do
1443
- requires :root_param, :desc => "root param"
1933
+ requires :root_param, desc: "root param"
1444
1934
  group :nested do
1445
- requires :nested_param, :desc => "nested param"
1935
+ requires :nested_param, desc: "nested param"
1446
1936
  end
1447
1937
  end
1448
1938
  subject.get 'method' do ; end
1449
1939
  subject.routes.map { |route|
1450
- { :description => route.route_description, :params => route.route_params }
1940
+ { description: route.route_description, params: route.route_params }
1451
1941
  }.should eq [
1452
- { :description => "nesting",
1453
- :params => {
1454
- "root_param" => { :required => true, :desc => "root param" },
1455
- "nested[nested_param]" => { :required => true, :desc => "nested param" }
1942
+ { description: "nesting",
1943
+ params: {
1944
+ "root_param" => { required: true, desc: "root param" },
1945
+ "nested" => { required: true, type: "Array" },
1946
+ "nested[nested_param]" => { required: true, desc: "nested param" }
1456
1947
  }
1457
1948
  }
1458
- ]
1949
+ ]
1950
+ end
1951
+ it 'allows to set the type attribute on :group element' do
1952
+ subject.params do
1953
+ group :foo, type: Array do
1954
+ optional :bar
1955
+ end
1956
+ end
1459
1957
  end
1460
1958
  it 'parses parameters when no description is given' do
1461
1959
  subject.params do
1462
- requires :one_param, :desc => "one param"
1960
+ requires :one_param, desc: "one param"
1463
1961
  end
1464
1962
  subject.get 'method' do ; end
1465
1963
  subject.routes.map { |route|
1466
- { :description => route.route_description, :params => route.route_params }
1964
+ { description: route.route_description, params: route.route_params }
1467
1965
  }.should eq [
1468
- { :description => nil, :params => { "one_param" => { :required => true, :desc => "one param" } } }
1469
- ]
1966
+ { description: nil, params: { "one_param" => { required: true, desc: "one param" } } }
1967
+ ]
1470
1968
  end
1471
1969
  it 'does not symbolize params' do
1472
- subject.desc "Reverses a string.", { :params =>
1473
- { "s" => { :desc => "string to reverse", :type => "string" }}
1474
- }
1970
+ subject.desc "Reverses a string.", params: { "s" => { desc: "string to reverse", type: "string" } }
1475
1971
  subject.get 'reverse/:s' do
1476
1972
  params[:s].reverse
1477
1973
  end
1478
1974
  subject.routes.map { |route|
1479
- { :description => route.route_description, :params => route.route_params }
1975
+ { description: route.route_description, params: route.route_params }
1480
1976
  }.should eq [
1481
- { :description => "Reverses a string.", :params => { "s" => { :desc => "string to reverse", :type => "string" } } }
1482
- ]
1977
+ { description: "Reverses a string.", params: { "s" => { desc: "string to reverse", type: "string" } } }
1978
+ ]
1483
1979
  end
1484
1980
  end
1485
1981
 
1486
1982
  describe '.mount' do
1487
- let(:mounted_app){ lambda{|env| [200, {}, ["MOUNTED"]]} }
1983
+ let(:mounted_app) { lambda { |env| [200, {}, ["MOUNTED"]] } }
1488
1984
 
1489
1985
  context 'with a bare rack app' do
1490
1986
  before do
@@ -1502,7 +1998,7 @@ describe Grape::API do
1502
1998
  end
1503
1999
 
1504
2000
  it 'is able to cascade' do
1505
- subject.mount lambda{ |env|
2001
+ subject.mount lambda { |env|
1506
2002
  headers = {}
1507
2003
  headers['X-Cascade'] == 'pass' unless env['PATH_INFO'].include?('boo')
1508
2004
  [200, headers, ["Farfegnugen"]]
@@ -1525,7 +2021,7 @@ describe Grape::API do
1525
2021
 
1526
2022
  context 'mounting an API' do
1527
2023
  it 'applies the settings of the mounting api' do
1528
- subject.version 'v1', :using => :path
2024
+ subject.version 'v1', using: :path
1529
2025
 
1530
2026
  subject.namespace :cool do
1531
2027
  app = Class.new(Grape::API)
@@ -1535,6 +2031,25 @@ describe Grape::API do
1535
2031
 
1536
2032
  mount app
1537
2033
  end
2034
+
2035
+ get '/v1/cool/awesome'
2036
+ last_response.body.should == 'yo'
2037
+ end
2038
+
2039
+ it 'applies the settings to nested mounted apis' do
2040
+ subject.version 'v1', using: :path
2041
+
2042
+ subject.namespace :cool do
2043
+ inner_app = Class.new(Grape::API)
2044
+ inner_app.get('/awesome') do
2045
+ "yo"
2046
+ end
2047
+
2048
+ app = Class.new(Grape::API)
2049
+ app.mount inner_app
2050
+ mount app
2051
+ end
2052
+
1538
2053
  get '/v1/cool/awesome'
1539
2054
  last_response.body.should == 'yo'
1540
2055
  end
@@ -1562,8 +2077,8 @@ describe Grape::API do
1562
2077
  mount app
1563
2078
  end
1564
2079
  subject.routes.size.should == 2
1565
- subject.routes.first.route_path.should =~ /\/cool\/awesome/
1566
- subject.routes.last.route_path.should =~ /\/cool\/sauce/
2080
+ subject.routes.first.route_path.should =~ %r{\/cool\/awesome}
2081
+ subject.routes.last.route_path.should =~ %r{\/cool\/sauce}
1567
2082
  end
1568
2083
 
1569
2084
  it 'mounts on a path' do
@@ -1591,6 +2106,50 @@ describe Grape::API do
1591
2106
  get "/app1/app2/nice"
1592
2107
  last_response.status.should == 200
1593
2108
  last_response.body.should == "play"
2109
+ options "/app1/app2/nice"
2110
+ last_response.status.should == 204
2111
+ end
2112
+
2113
+ it 'responds to options' do
2114
+ app = Class.new(Grape::API)
2115
+ app.get '/colour' do
2116
+ 'red'
2117
+ end
2118
+ app.namespace :pears do
2119
+ get '/colour' do
2120
+ 'green'
2121
+ end
2122
+ end
2123
+ subject.namespace :apples do
2124
+ mount app
2125
+ end
2126
+ get '/apples/colour'
2127
+ last_response.status.should eql 200
2128
+ last_response.body.should == 'red'
2129
+ options '/apples/colour'
2130
+ last_response.status.should eql 204
2131
+ get '/apples/pears/colour'
2132
+ last_response.status.should eql 200
2133
+ last_response.body.should == 'green'
2134
+ options '/apples/pears/colour'
2135
+ last_response.status.should eql 204
2136
+ end
2137
+
2138
+ it 'responds to options with path versioning' do
2139
+ subject.version 'v1', using: :path
2140
+ subject.namespace :apples do
2141
+ app = Class.new(Grape::API)
2142
+ app.get('/colour') do
2143
+ "red"
2144
+ end
2145
+ mount app
2146
+ end
2147
+
2148
+ get '/v1/apples/colour'
2149
+ last_response.status.should eql 200
2150
+ last_response.body.should == 'red'
2151
+ options '/v1/apples/colour'
2152
+ last_response.status.should eql 204
1594
2153
  end
1595
2154
 
1596
2155
  end
@@ -1625,8 +2184,8 @@ describe Grape::API do
1625
2184
  subject.format :json
1626
2185
  subject.get '/endpoint/options' do
1627
2186
  {
1628
- :path => options[:path],
1629
- :source_location => source.source_location
2187
+ path: options[:path],
2188
+ source_location: source.source_location
1630
2189
  }
1631
2190
  end
1632
2191
  end
@@ -1662,7 +2221,7 @@ describe Grape::API do
1662
2221
  subject.get '/description' do
1663
2222
  route.route_description
1664
2223
  end
1665
- subject.desc 'returns parameters', { :params => { "x" => "y" }}
2224
+ subject.desc 'returns parameters', params: { "x" => "y" }
1666
2225
  subject.get '/params/:id' do
1667
2226
  route.route_params[params[:id]]
1668
2227
  end
@@ -1677,46 +2236,46 @@ describe Grape::API do
1677
2236
  end
1678
2237
  end
1679
2238
  end
1680
- context 'format' do
2239
+ describe '.format' do
1681
2240
  context ':txt' do
1682
2241
  before(:each) do
1683
2242
  subject.format :txt
1684
2243
  subject.content_type :json, "application/json"
1685
2244
  subject.get '/meaning_of_life' do
1686
- { :meaning_of_life => 42 }
2245
+ { meaning_of_life: 42 }
1687
2246
  end
1688
2247
  end
1689
2248
  it 'forces txt without an extension' do
1690
2249
  get '/meaning_of_life'
1691
- last_response.body.should == { :meaning_of_life => 42 }.to_s
2250
+ last_response.body.should == { meaning_of_life: 42 }.to_s
1692
2251
  end
1693
2252
  it 'does not force txt with an extension' do
1694
2253
  get '/meaning_of_life.json'
1695
- last_response.body.should == { :meaning_of_life => 42 }.to_json
2254
+ last_response.body.should == { meaning_of_life: 42 }.to_json
1696
2255
  end
1697
2256
  it 'forces txt from a non-accepting header' do
1698
- get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'application/json' }
1699
- last_response.body.should == { :meaning_of_life => 42 }.to_s
2257
+ get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
2258
+ last_response.body.should == { meaning_of_life: 42 }.to_s
1700
2259
  end
1701
2260
  end
1702
2261
  context ':txt only' do
1703
2262
  before(:each) do
1704
2263
  subject.format :txt
1705
2264
  subject.get '/meaning_of_life' do
1706
- { :meaning_of_life => 42 }
2265
+ { meaning_of_life: 42 }
1707
2266
  end
1708
2267
  end
1709
2268
  it 'forces txt without an extension' do
1710
2269
  get '/meaning_of_life'
1711
- last_response.body.should == { :meaning_of_life => 42 }.to_s
2270
+ last_response.body.should == { meaning_of_life: 42 }.to_s
1712
2271
  end
1713
2272
  it 'forces txt with the wrong extension' do
1714
2273
  get '/meaning_of_life.json'
1715
- last_response.body.should == { :meaning_of_life => 42 }.to_s
2274
+ last_response.body.should == { meaning_of_life: 42 }.to_s
1716
2275
  end
1717
2276
  it 'forces txt from a non-accepting header' do
1718
- get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'application/json' }
1719
- last_response.body.should == { :meaning_of_life => 42 }.to_s
2277
+ get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
2278
+ last_response.body.should == { meaning_of_life: 42 }.to_s
1720
2279
  end
1721
2280
  end
1722
2281
  context ':json' do
@@ -1724,48 +2283,71 @@ describe Grape::API do
1724
2283
  subject.format :json
1725
2284
  subject.content_type :txt, "text/plain"
1726
2285
  subject.get '/meaning_of_life' do
1727
- { :meaning_of_life => 42 }
2286
+ { meaning_of_life: 42 }
1728
2287
  end
1729
2288
  end
1730
2289
  it 'forces json without an extension' do
1731
2290
  get '/meaning_of_life'
1732
- last_response.body.should == { :meaning_of_life => 42 }.to_json
2291
+ last_response.body.should == { meaning_of_life: 42 }.to_json
1733
2292
  end
1734
2293
  it 'does not force json with an extension' do
1735
2294
  get '/meaning_of_life.txt'
1736
- last_response.body.should == { :meaning_of_life => 42 }.to_s
2295
+ last_response.body.should == { meaning_of_life: 42 }.to_s
1737
2296
  end
1738
2297
  it 'forces json from a non-accepting header' do
1739
- get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'text/html' }
1740
- last_response.body.should == { :meaning_of_life => 42 }.to_json
2298
+ get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'text/html'
2299
+ last_response.body.should == { meaning_of_life: 42 }.to_json
2300
+ end
2301
+ it 'can be overwritten with an explicit content type' do
2302
+ subject.get '/meaning_of_life_with_content_type' do
2303
+ content_type "text/plain"
2304
+ { meaning_of_life: 42 }.to_s
2305
+ end
2306
+ get '/meaning_of_life_with_content_type'
2307
+ last_response.body.should == { meaning_of_life: 42 }.to_s
2308
+ end
2309
+ it 'raised :error from middleware' do
2310
+ middleware = Class.new(Grape::Middleware::Base) do
2311
+ def before
2312
+ throw :error, message: "Unauthorized", status: 42
2313
+ end
2314
+ end
2315
+ subject.use middleware
2316
+ subject.get do
2317
+
2318
+ end
2319
+ get "/"
2320
+ last_response.status.should == 42
2321
+ last_response.body.should == { error: "Unauthorized" }.to_json
1741
2322
  end
2323
+
1742
2324
  end
1743
2325
  context ':serializable_hash' do
1744
2326
  before(:each) do
1745
- class SimpleExample
2327
+ class SerializableHashExample
1746
2328
  def serializable_hash
1747
- {:abc => 'def'}
2329
+ { abc: 'def' }
1748
2330
  end
1749
2331
  end
1750
2332
  subject.format :serializable_hash
1751
2333
  end
1752
2334
  it 'instance' do
1753
2335
  subject.get '/example' do
1754
- SimpleExample.new
2336
+ SerializableHashExample.new
1755
2337
  end
1756
2338
  get '/example'
1757
2339
  last_response.body.should == '{"abc":"def"}'
1758
2340
  end
1759
2341
  it 'root' do
1760
2342
  subject.get '/example' do
1761
- { "root" => SimpleExample.new }
2343
+ { "root" => SerializableHashExample.new }
1762
2344
  end
1763
2345
  get '/example'
1764
2346
  last_response.body.should == '{"root":{"abc":"def"}}'
1765
2347
  end
1766
2348
  it 'array' do
1767
2349
  subject.get '/examples' do
1768
- [ SimpleExample.new, SimpleExample.new ]
2350
+ [SerializableHashExample.new, SerializableHashExample.new]
1769
2351
  end
1770
2352
  get '/examples'
1771
2353
  last_response.body.should == '[{"abc":"def"},{"abc":"def"}]'
@@ -1793,7 +2375,7 @@ XML
1793
2375
  ActiveSupport::OrderedHash[
1794
2376
  :example1, "example1",
1795
2377
  :example2, "example2"
1796
- ]
2378
+ ]
1797
2379
  end
1798
2380
  get '/example'
1799
2381
  last_response.status.should == 200
@@ -1807,7 +2389,7 @@ XML
1807
2389
  end
1808
2390
  it 'array' do
1809
2391
  subject.get "/example" do
1810
- [ "example1", "example2" ]
2392
+ ["example1", "example2"]
1811
2393
  end
1812
2394
  get '/example'
1813
2395
  last_response.status.should == 200
@@ -1817,6 +2399,25 @@ XML
1817
2399
  <string>example1</string>
1818
2400
  <string>example2</string>
1819
2401
  </strings>
2402
+ XML
2403
+ end
2404
+ it 'raised :error from middleware' do
2405
+ middleware = Class.new(Grape::Middleware::Base) do
2406
+ def before
2407
+ throw :error, message: "Unauthorized", status: 42
2408
+ end
2409
+ end
2410
+ subject.use middleware
2411
+ subject.get do
2412
+
2413
+ end
2414
+ get "/"
2415
+ last_response.status.should == 42
2416
+ last_response.body.should == <<-XML
2417
+ <?xml version="1.0" encoding="UTF-8"?>
2418
+ <error>
2419
+ <message>Unauthorized</message>
2420
+ </error>
1820
2421
  XML
1821
2422
  end
1822
2423
  end
@@ -1825,22 +2426,22 @@ XML
1825
2426
  context "catch-all" do
1826
2427
  before do
1827
2428
  api1 = Class.new(Grape::API)
1828
- api1.version 'v1', :using => :path
2429
+ api1.version 'v1', using: :path
1829
2430
  api1.get "hello" do
1830
2431
  "v1"
1831
2432
  end
1832
2433
  api2 = Class.new(Grape::API)
1833
- api2.version 'v2', :using => :path
2434
+ api2.version 'v2', using: :path
1834
2435
  api2.get "hello" do
1835
2436
  "v2"
1836
2437
  end
1837
2438
  subject.mount api1
1838
2439
  subject.mount api2
1839
2440
  end
1840
- [ true, false ].each do |anchor|
2441
+ [true, false].each do |anchor|
1841
2442
  it "anchor=#{anchor}" do
1842
- subject.route :any, '*path', :anchor => anchor do
1843
- error!("Unrecognized request path: #{params[:path]} - #{env['PATH_INFO']}#{env['SCRIPT_NAME']}", 404)
2443
+ subject.route :any, '*path', anchor: anchor do
2444
+ error!("Unrecognized request path: #{params[:path] } - #{env['PATH_INFO'] }#{env['SCRIPT_NAME'] }", 404)
1844
2445
  end
1845
2446
  get "/v1/hello"
1846
2447
  last_response.status.should == 200
@@ -1856,18 +2457,46 @@ XML
1856
2457
  end
1857
2458
 
1858
2459
  context "cascading" do
1859
- it "cascades" do
1860
- subject.version 'v1', :using => :path, :cascade => true
1861
- get "/v1/hello"
1862
- last_response.status.should == 404
1863
- last_response.headers["X-Cascade"].should == "pass"
2460
+ context "via version" do
2461
+ it "cascades" do
2462
+ subject.version 'v1', using: :path, cascade: true
2463
+ get "/v1/hello"
2464
+ last_response.status.should == 404
2465
+ last_response.headers["X-Cascade"].should == "pass"
2466
+ end
2467
+ it "does not cascade" do
2468
+ subject.version 'v2', using: :path, cascade: false
2469
+ get "/v2/hello"
2470
+ last_response.status.should == 404
2471
+ last_response.headers.keys.should_not include "X-Cascade"
2472
+ end
1864
2473
  end
2474
+ context "via endpoint" do
2475
+ it "cascades" do
2476
+ subject.cascade true
2477
+ get "/hello"
2478
+ last_response.status.should == 404
2479
+ last_response.headers["X-Cascade"].should == "pass"
2480
+ end
2481
+ it "does not cascade" do
2482
+ subject.cascade false
2483
+ get "/hello"
2484
+ last_response.status.should == 404
2485
+ last_response.headers.keys.should_not include "X-Cascade"
2486
+ end
2487
+ end
2488
+ end
1865
2489
 
1866
- it "does not cascade" do
1867
- subject.version 'v2', :using => :path, :cascade => false
1868
- get "/v2/hello"
1869
- last_response.status.should == 404
1870
- last_response.headers.keys.should_not include "X-Cascade"
2490
+ context 'with json default_error_formatter' do
2491
+ it 'returns json error' do
2492
+ subject.content_type :json, "application/json"
2493
+ subject.default_error_formatter :json
2494
+ subject.get '/something' do
2495
+ 'foo'
2496
+ end
2497
+ get '/something'
2498
+ last_response.status.should == 406
2499
+ last_response.body.should == "{\"error\":\"The requested format 'txt' is not supported.\"}"
1871
2500
  end
1872
2501
  end
1873
2502
  end