grape 0.14.0 → 0.15.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 +4 -4
  2. data/CHANGELOG.md +32 -4
  3. data/Gemfile.lock +13 -13
  4. data/README.md +290 -12
  5. data/UPGRADING.md +68 -1
  6. data/gemfiles/rails_3.gemfile +1 -1
  7. data/lib/grape.rb +8 -2
  8. data/lib/grape/api.rb +40 -34
  9. data/lib/grape/dsl/configuration.rb +2 -115
  10. data/lib/grape/dsl/desc.rb +101 -0
  11. data/lib/grape/dsl/headers.rb +16 -0
  12. data/lib/grape/dsl/helpers.rb +5 -9
  13. data/lib/grape/dsl/inside_route.rb +3 -11
  14. data/lib/grape/dsl/logger.rb +20 -0
  15. data/lib/grape/dsl/parameters.rb +12 -10
  16. data/lib/grape/dsl/request_response.rb +17 -4
  17. data/lib/grape/dsl/routing.rb +24 -7
  18. data/lib/grape/dsl/settings.rb +8 -2
  19. data/lib/grape/endpoint.rb +30 -26
  20. data/lib/grape/error_formatter.rb +31 -0
  21. data/lib/grape/error_formatter/base.rb +0 -28
  22. data/lib/grape/error_formatter/json.rb +13 -2
  23. data/lib/grape/error_formatter/txt.rb +3 -1
  24. data/lib/grape/error_formatter/xml.rb +3 -1
  25. data/lib/grape/exceptions/base.rb +11 -4
  26. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  27. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  28. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  29. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  30. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  31. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  32. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
  33. data/lib/grape/exceptions/method_not_allowed.rb +10 -0
  34. data/lib/grape/exceptions/missing_group_type.rb +1 -1
  35. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  36. data/lib/grape/exceptions/missing_option.rb +1 -1
  37. data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
  38. data/lib/grape/exceptions/unknown_options.rb +1 -1
  39. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  40. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  41. data/lib/grape/exceptions/unsupported_group_type.rb +1 -1
  42. data/lib/grape/exceptions/validation.rb +2 -1
  43. data/lib/grape/formatter.rb +31 -0
  44. data/lib/grape/middleware/base.rb +28 -2
  45. data/lib/grape/middleware/error.rb +24 -1
  46. data/lib/grape/middleware/formatter.rb +4 -3
  47. data/lib/grape/middleware/versioner/param.rb +13 -2
  48. data/lib/grape/parser.rb +29 -0
  49. data/lib/grape/util/sendfile_response.rb +19 -0
  50. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  51. data/lib/grape/validations/params_scope.rb +39 -9
  52. data/lib/grape/validations/types.rb +16 -0
  53. data/lib/grape/validations/validators/all_or_none.rb +1 -1
  54. data/lib/grape/validations/validators/allow_blank.rb +2 -2
  55. data/lib/grape/validations/validators/at_least_one_of.rb +1 -1
  56. data/lib/grape/validations/validators/base.rb +26 -0
  57. data/lib/grape/validations/validators/coerce.rb +16 -14
  58. data/lib/grape/validations/validators/default.rb +1 -1
  59. data/lib/grape/validations/validators/exactly_one_of.rb +10 -1
  60. data/lib/grape/validations/validators/mutual_exclusion.rb +1 -1
  61. data/lib/grape/validations/validators/presence.rb +1 -1
  62. data/lib/grape/validations/validators/regexp.rb +2 -2
  63. data/lib/grape/validations/validators/values.rb +2 -2
  64. data/lib/grape/version.rb +1 -1
  65. data/spec/grape/api/custom_validations_spec.rb +156 -21
  66. data/spec/grape/api/namespace_parameters_in_route_spec.rb +38 -0
  67. data/spec/grape/api/optional_parameters_in_route_spec.rb +43 -0
  68. data/spec/grape/api/required_parameters_in_route_spec.rb +37 -0
  69. data/spec/grape/api_spec.rb +118 -60
  70. data/spec/grape/dsl/configuration_spec.rb +0 -75
  71. data/spec/grape/dsl/desc_spec.rb +77 -0
  72. data/spec/grape/dsl/headers_spec.rb +32 -0
  73. data/spec/grape/dsl/inside_route_spec.rb +0 -18
  74. data/spec/grape/dsl/logger_spec.rb +26 -0
  75. data/spec/grape/dsl/parameters_spec.rb +13 -7
  76. data/spec/grape/dsl/request_response_spec.rb +17 -3
  77. data/spec/grape/dsl/routing_spec.rb +8 -1
  78. data/spec/grape/dsl/settings_spec.rb +42 -0
  79. data/spec/grape/endpoint_spec.rb +60 -9
  80. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  81. data/spec/grape/exceptions/validation_spec.rb +7 -0
  82. data/spec/grape/integration/rack_sendfile_spec.rb +44 -0
  83. data/spec/grape/middleware/base_spec.rb +100 -0
  84. data/spec/grape/middleware/exception_spec.rb +1 -2
  85. data/spec/grape/middleware/formatter_spec.rb +12 -2
  86. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +1 -1
  87. data/spec/grape/middleware/versioner/header_spec.rb +11 -1
  88. data/spec/grape/middleware/versioner/param_spec.rb +105 -1
  89. data/spec/grape/validations/params_scope_spec.rb +77 -0
  90. data/spec/grape/validations/validators/allow_blank_spec.rb +277 -0
  91. data/spec/grape/validations/validators/coerce_spec.rb +91 -0
  92. data/spec/grape/validations/validators/default_spec.rb +6 -0
  93. data/spec/grape/validations/validators/presence_spec.rb +27 -0
  94. data/spec/grape/validations/validators/regexp_spec.rb +36 -0
  95. data/spec/grape/validations/validators/values_spec.rb +44 -0
  96. data/spec/grape/validations_spec.rb +149 -4
  97. data/spec/spec_helper.rb +1 -0
  98. metadata +26 -5
  99. data/lib/grape/formatter/base.rb +0 -31
  100. data/lib/grape/parser/base.rb +0 -29
  101. data/pkg/grape-0.13.0.gem +0 -0
@@ -2,8 +2,8 @@ module Grape
2
2
  module Validations
3
3
  class RegexpValidator < Base
4
4
  def validate_param!(attr_name, params)
5
- return unless params.key?(attr_name) && !params[attr_name].nil? && !(params[attr_name].to_s =~ @option)
6
- fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :regexp
5
+ return unless params.key?(attr_name) && !params[attr_name].nil? && !(params[attr_name].to_s =~ (options_key?(:value) ? @option[:value] : @option))
6
+ fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:regexp)
7
7
  end
8
8
  end
9
9
  end
@@ -2,7 +2,7 @@ module Grape
2
2
  module Validations
3
3
  class ValuesValidator < Base
4
4
  def initialize(attrs, options, required, scope)
5
- @values = options
5
+ @values = (options_key?(:value, options) ? options[:value] : options)
6
6
  super
7
7
  end
8
8
 
@@ -13,7 +13,7 @@ module Grape
13
13
  values = @values.is_a?(Proc) ? @values.call : @values
14
14
  param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name])
15
15
  return if param_array.all? { |param| values.include?(param) }
16
- fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message_key: :values
16
+ fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values)
17
17
  end
18
18
 
19
19
  private
@@ -1,4 +1,4 @@
1
1
  module Grape
2
2
  # The current version of Grape.
3
- VERSION = '0.14.0'
3
+ VERSION = '0.15.0'
4
4
  end
@@ -1,34 +1,33 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Validations do
4
- before do
5
- module CustomValidationsSpec
6
- class DefaultLength < Grape::Validations::Base
7
- def validate_param!(attr_name, params)
8
- @option = params[:max].to_i if params.key?(:max)
9
- return if params[attr_name].length <= @option
10
- fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long"
4
+ context 'using a custom length validator' do
5
+ before do
6
+ module CustomValidationsSpec
7
+ class DefaultLength < Grape::Validations::Base
8
+ def validate_param!(attr_name, params)
9
+ @option = params[:max].to_i if params.key?(:max)
10
+ return if params[attr_name].length <= @option
11
+ fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long"
12
+ end
11
13
  end
12
14
  end
13
15
  end
14
- end
15
-
16
- subject do
17
- Class.new(Grape::API) do
18
- params do
19
- requires :text, default_length: 140
20
- end
21
- get do
22
- 'bacon'
16
+ subject do
17
+ Class.new(Grape::API) do
18
+ params do
19
+ requires :text, default_length: 140
20
+ end
21
+ get do
22
+ 'bacon'
23
+ end
23
24
  end
24
25
  end
25
- end
26
26
 
27
- def app
28
- subject
29
- end
27
+ def app
28
+ subject
29
+ end
30
30
 
31
- context 'using a custom length validator' do
32
31
  it 'under 140 characters' do
33
32
  get '/', text: 'abc'
34
33
  expect(last_response.status).to eq 200
@@ -45,4 +44,140 @@ describe Grape::Validations do
45
44
  expect(last_response.body).to eq 'bacon'
46
45
  end
47
46
  end
47
+
48
+ context 'using a custom body-only validator' do
49
+ before do
50
+ module CustomValidationsSpec
51
+ class InBody < Grape::Validations::PresenceValidator
52
+ def validate(request)
53
+ validate!(request.env['api.request.body'])
54
+ end
55
+ end
56
+ end
57
+ end
58
+ subject do
59
+ Class.new(Grape::API) do
60
+ params do
61
+ requires :text, in_body: true
62
+ end
63
+ get do
64
+ 'bacon'
65
+ end
66
+ end
67
+ end
68
+
69
+ def app
70
+ subject
71
+ end
72
+
73
+ it 'allows field in body' do
74
+ get '/', text: 'abc'
75
+ expect(last_response.status).to eq 200
76
+ expect(last_response.body).to eq 'bacon'
77
+ end
78
+ it 'ignores field in query' do
79
+ get '/', nil, text: 'abc'
80
+ expect(last_response.status).to eq 400
81
+ expect(last_response.body).to eq 'text is missing'
82
+ end
83
+ end
84
+
85
+ context 'using a custom validator with message_key' do
86
+ before do
87
+ module CustomValidationsSpec
88
+ class WithMessageKey < Grape::Validations::PresenceValidator
89
+ def validate_param!(attr_name, _params)
90
+ fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: :presence
91
+ end
92
+ end
93
+ end
94
+ end
95
+ subject do
96
+ Class.new(Grape::API) do
97
+ params do
98
+ requires :text, with_message_key: true
99
+ end
100
+ get do
101
+ 'bacon'
102
+ end
103
+ end
104
+ end
105
+
106
+ def app
107
+ subject
108
+ end
109
+
110
+ it 'fails with message' do
111
+ get '/', text: 'foobar'
112
+ expect(last_response.status).to eq 400
113
+ expect(last_response.body).to eq 'text is missing'
114
+ end
115
+ end
116
+
117
+ context 'using a custom request/param validator' do
118
+ before do
119
+ module CustomValidationsSpec
120
+ class Admin < Grape::Validations::Base
121
+ def validate(request)
122
+ # return if the param we are checking was not in request
123
+ # @attrs is a list containing the attribute we are currently validating
124
+ return unless request.params.key? @attrs.first
125
+ # check if admin flag is set to true
126
+ return unless @option
127
+ # check if user is admin or not
128
+ # as an example get a token from request and check if it's admin or not
129
+ fail Grape::Exceptions::Validation, params: @attrs, message: 'Can not set Admin only field.' unless request.headers['X-Access-Token'] == 'admin'
130
+ end
131
+ end
132
+ end
133
+ end
134
+ subject do
135
+ Class.new(Grape::API) do
136
+ params do
137
+ optional :admin_field, type: String, admin: true
138
+ optional :non_admin_field, type: String
139
+ optional :admin_false_field, type: String, admin: false
140
+ end
141
+ get do
142
+ 'bacon'
143
+ end
144
+ end
145
+ end
146
+
147
+ def app
148
+ subject
149
+ end
150
+
151
+ it 'fail when non-admin user sets an admin field' do
152
+ get '/', admin_field: 'tester', non_admin_field: 'toaster'
153
+ expect(last_response.status).to eq 400
154
+ expect(last_response.body).to include 'Can not set Admin only field.'
155
+ end
156
+
157
+ it 'does not fail when we send non-admin fields only' do
158
+ get '/', non_admin_field: 'toaster'
159
+ expect(last_response.status).to eq 200
160
+ expect(last_response.body).to eq 'bacon'
161
+ end
162
+
163
+ it 'does not fail when we send non-admin and admin=false fields only' do
164
+ get '/', non_admin_field: 'toaster', admin_false_field: 'test'
165
+ expect(last_response.status).to eq 200
166
+ expect(last_response.body).to eq 'bacon'
167
+ end
168
+
169
+ it 'does not fail when we send admin fields and we are admin' do
170
+ header 'X-Access-Token', 'admin'
171
+ get '/', admin_field: 'tester', non_admin_field: 'toaster', admin_false_field: 'test'
172
+ expect(last_response.status).to eq 200
173
+ expect(last_response.body).to eq 'bacon'
174
+ end
175
+
176
+ it 'fails when we send admin fields and we are not admin' do
177
+ header 'X-Access-Token', 'user'
178
+ get '/', admin_field: 'tester', non_admin_field: 'toaster', admin_false_field: 'test'
179
+ expect(last_response.status).to eq 400
180
+ expect(last_response.body).to include 'Can not set Admin only field.'
181
+ end
182
+ end
48
183
  end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Endpoint do
4
+ subject { Class.new(Grape::API) }
5
+
6
+ def app
7
+ subject
8
+ end
9
+
10
+ before do
11
+ subject.namespace :me do
12
+ namespace :pending do
13
+ get '/' do
14
+ 'banana'
15
+ end
16
+ end
17
+ put ':id' do
18
+ params[:id]
19
+ end
20
+ end
21
+ end
22
+
23
+ context 'get' do
24
+ it 'responds without ext' do
25
+ get '/me/pending'
26
+ expect(last_response.status).to eq 200
27
+ expect(last_response.body).to eq 'banana'
28
+ end
29
+ end
30
+
31
+ context 'put' do
32
+ it 'responds' do
33
+ put '/me/foo'
34
+ expect(last_response.status).to eq 200
35
+ expect(last_response.body).to eq 'foo'
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Endpoint do
4
+ subject { Class.new(Grape::API) }
5
+
6
+ def app
7
+ subject
8
+ end
9
+
10
+ before do
11
+ subject.namespace :api do
12
+ get ':id(/:ext)' do
13
+ [params[:id], params[:ext]].compact.join('/')
14
+ end
15
+
16
+ put ':id' do
17
+ params[:id]
18
+ end
19
+ end
20
+ end
21
+
22
+ context 'get' do
23
+ it 'responds without ext' do
24
+ get '/api/foo'
25
+ expect(last_response.status).to eq 200
26
+ expect(last_response.body).to eq 'foo'
27
+ end
28
+
29
+ it 'responds with ext' do
30
+ get '/api/foo/bar'
31
+ expect(last_response.status).to eq 200
32
+ expect(last_response.body).to eq 'foo/bar'
33
+ end
34
+ end
35
+
36
+ context 'put' do
37
+ it 'responds' do
38
+ put '/api/foo'
39
+ expect(last_response.status).to eq 200
40
+ expect(last_response.body).to eq 'foo'
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Endpoint do
4
+ subject { Class.new(Grape::API) }
5
+
6
+ def app
7
+ subject
8
+ end
9
+
10
+ before do
11
+ subject.namespace :api do
12
+ get ':id' do
13
+ [params[:id], params[:ext]].compact.join('/')
14
+ end
15
+
16
+ put ':something_id' do
17
+ params[:something_id]
18
+ end
19
+ end
20
+ end
21
+
22
+ context 'get' do
23
+ it 'responds' do
24
+ get '/api/foo'
25
+ expect(last_response.status).to eq 200
26
+ expect(last_response.body).to eq 'foo'
27
+ end
28
+ end
29
+
30
+ context 'put' do
31
+ it 'responds' do
32
+ put '/api/foo'
33
+ expect(last_response.status).to eq 200
34
+ expect(last_response.body).to eq 'foo'
35
+ end
36
+ end
37
+ end
@@ -136,7 +136,7 @@ describe Grape::API do
136
136
  it 'adds the association to the :representations setting' do
137
137
  klass = Class.new
138
138
  subject.represent Object, with: klass
139
- expect(Grape::DSL::Configuration.stacked_hash_to_hash(subject.namespace_stackable(:representations))[Object]).to eq(klass)
139
+ expect(subject.namespace_stackable_with_hash(:representations)[Object]).to eq(klass)
140
140
  end
141
141
  end
142
142
 
@@ -211,7 +211,7 @@ describe Grape::API do
211
211
  end
212
212
 
213
213
  %w(group resource resources segment).each do |als|
214
- it '`.#{als}` is an alias' do
214
+ it "`.#{als}` is an alias" do
215
215
  inner_namespace = nil
216
216
  subject.send(als, :awesome) do
217
217
  inner_namespace = namespace
@@ -235,7 +235,7 @@ describe Grape::API do
235
235
  expect(last_response.body).to eq('23')
236
236
  end
237
237
 
238
- it 'should be able to define requirements with a single hash' do
238
+ it 'defines requirements with a single hash' do
239
239
  subject.namespace :users do
240
240
  route_param :id, requirements: /[0-9]+/ do
241
241
  get do
@@ -249,6 +249,18 @@ describe Grape::API do
249
249
  get '/users/23'
250
250
  expect(last_response.status).to eq(200)
251
251
  end
252
+
253
+ context 'with param type definitions' do
254
+ it 'is used by passing to options' do
255
+ subject.namespace :route_param do
256
+ route_param :foo, type: Integer do
257
+ get { params.to_json }
258
+ end
259
+ end
260
+ get '/route_param/1234'
261
+ expect(last_response.body).to eq("{\"foo\":1234}")
262
+ end
263
+ end
252
264
  end
253
265
 
254
266
  describe '.route' do
@@ -482,7 +494,7 @@ describe Grape::API do
482
494
 
483
495
  verbs = %w(post get head delete put options patch)
484
496
  verbs.each do |verb|
485
- it 'allows and properly constrain a #{verb.upcase} method' do
497
+ it "allows and properly constrain a #{verb.upcase} method" do
486
498
  subject.send(verb, '/example') do
487
499
  verb
488
500
  end
@@ -511,10 +523,28 @@ describe Grape::API do
511
523
  end
512
524
  put '/example'
513
525
  expect(last_response.status).to eql 405
514
- expect(last_response.body).to eql ''
526
+ expect(last_response.body).to eql '405 Not Allowed'
515
527
  expect(last_response.headers['X-Custom-Header']).to eql 'foo'
516
528
  end
517
529
 
530
+ context 'when format is xml' do
531
+ it 'returns a 405 for an unsupported method' do
532
+ subject.format :xml
533
+ subject.get 'example' do
534
+ 'example'
535
+ end
536
+
537
+ put '/example'
538
+ expect(last_response.status).to eql 405
539
+ expect(last_response.body).to eq <<-XML
540
+ <?xml version="1.0" encoding="UTF-8"?>
541
+ <error>
542
+ <message>405 Not Allowed</message>
543
+ </error>
544
+ XML
545
+ end
546
+ end
547
+
518
548
  specify '405 responses includes an Allow header specifying supported methods' do
519
549
  subject.get 'example' do
520
550
  'example'
@@ -537,49 +567,26 @@ describe Grape::API do
537
567
  expect(last_response.headers['Content-Type']).to eql 'text/plain'
538
568
  end
539
569
 
540
- describe 'adds an OPTIONS route that' do
570
+ context 'allows HEAD on a GET request that' do
541
571
  before do
542
- subject.before { header 'X-Custom-Header', 'foo' }
543
572
  subject.get 'example' do
544
573
  'example'
545
574
  end
546
- options '/example'
575
+ subject.route :any, '*path' do
576
+ error! :not_found, 404
577
+ end
578
+ head '/example'
547
579
  end
548
580
 
549
- it 'returns a 204' do
550
- expect(last_response.status).to eql 204
581
+ it 'returns a 200' do
582
+ expect(last_response.status).to eql 200
551
583
  end
552
584
 
553
585
  it 'has an empty body' do
554
- expect(last_response.body).to be_blank
555
- end
556
-
557
- it 'has an Allow header' do
558
- expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, HEAD'
559
- end
560
-
561
- it 'has a X-Custom-Header' do
562
- expect(last_response.headers['X-Custom-Header']).to eql 'foo'
563
- end
564
-
565
- it 'has no Content-Type' do
566
- expect(last_response.content_type).to be_nil
567
- end
568
-
569
- it 'has no Content-Length' do
570
- expect(last_response.content_length).to be_nil
586
+ expect(last_response.body).to eql ''
571
587
  end
572
588
  end
573
589
 
574
- it 'allows HEAD on a GET request' do
575
- subject.get 'example' do
576
- 'example'
577
- end
578
- head '/example'
579
- expect(last_response.status).to eql 200
580
- expect(last_response.body).to eql ''
581
- end
582
-
583
590
  it 'overwrites the default HEAD request' do
584
591
  subject.head 'example' do
585
592
  error! 'nothing to see here', 400
@@ -618,9 +625,16 @@ describe Grape::API do
618
625
  'example'
619
626
  end
620
627
  end
621
- it 'options does not exist' do
628
+
629
+ it 'does not create an OPTIONS route' do
630
+ options '/example'
631
+ expect(last_response.status).to eql 405
632
+ end
633
+
634
+ it 'does not include OPTIONS in Allow header' do
622
635
  options '/example'
623
636
  expect(last_response.status).to eql 405
637
+ expect(last_response.headers['Allow']).to eql 'GET, HEAD'
624
638
  end
625
639
  end
626
640
 
@@ -870,6 +884,22 @@ describe Grape::API do
870
884
  expect(last_response.headers['Content-Type']).to eql 'application/xml'
871
885
  end
872
886
 
887
+ it 'includes extension in format' do
888
+ subject.get(':id') { params[:format] }
889
+
890
+ get '/baz.bar'
891
+ expect(last_response.status).to eq 200
892
+ expect(last_response.body).to eq 'bar'
893
+ end
894
+
895
+ it 'does not include extension in id' do
896
+ subject.format :json
897
+ subject.get(':id') { params }
898
+
899
+ get '/baz.bar'
900
+ expect(last_response.status).to eq 404
901
+ end
902
+
873
903
  context 'with a custom content_type' do
874
904
  before do
875
905
  subject.content_type :custom, 'application/custom'
@@ -896,7 +926,7 @@ describe Grape::API do
896
926
  filename = params[:file][:filename]
897
927
  content_type MIME::Types.type_for(filename)[0].to_s
898
928
  env['api.format'] = :binary # there's no formatter for :binary, data will be returned "as is"
899
- header 'Content-Disposition', "attachment; filename*=UTF-8''#{URI.escape(filename)}"
929
+ header 'Content-Disposition', "attachment; filename*=UTF-8''#{CGI.escape(filename)}"
900
930
  params[:file][:tempfile].read
901
931
  end
902
932
  end
@@ -1147,11 +1177,7 @@ describe Grape::API do
1147
1177
  it 'defaults to a standard logger log format' do
1148
1178
  t = Time.at(100)
1149
1179
  allow(Time).to receive(:now).and_return(t)
1150
- if ActiveSupport::VERSION::MAJOR >= 4
1151
- expect(subject.io).to receive(:write).with("I, [#{Logger::Formatter.new.send(:format_datetime, t)}\##{Process.pid}] INFO -- : this will be logged\n")
1152
- else
1153
- expect(subject.io).to receive(:write).with("this will be logged\n")
1154
- end
1180
+ expect(subject.io).to receive(:write).with("I, [#{Logger::Formatter.new.send(:format_datetime, t)}\##{Process.pid}] INFO -- : this will be logged\n")
1155
1181
  subject.logger.info 'this will be logged'
1156
1182
  end
1157
1183
  end
@@ -1293,7 +1319,21 @@ describe Grape::API do
1293
1319
  subject.get '/exception' do
1294
1320
  fail 'rain!'
1295
1321
  end
1296
- expect { get '/exception' }.to raise_error
1322
+ expect { get '/exception' }.to raise_error(RuntimeError, 'rain!')
1323
+ end
1324
+
1325
+ it 'uses custom helpers defined by using #helpers method' do
1326
+ subject.helpers do
1327
+ def custom_error!(name)
1328
+ error! "hello #{name}"
1329
+ end
1330
+ end
1331
+ subject.rescue_from(ArgumentError) { custom_error! :bob }
1332
+ subject.get '/custom_error' do
1333
+ fail ArgumentError
1334
+ end
1335
+ get '/custom_error'
1336
+ expect(last_response.body).to eq 'hello bob'
1297
1337
  end
1298
1338
 
1299
1339
  it 'rescues all errors if rescue_from :all is called' do
@@ -1326,7 +1366,7 @@ describe Grape::API do
1326
1366
  get '/rescued'
1327
1367
  expect(last_response.status).to eql 500
1328
1368
 
1329
- expect { get '/unrescued' }.to raise_error
1369
+ expect { get '/unrescued' }.to raise_error(RuntimeError, 'beefcake')
1330
1370
  end
1331
1371
 
1332
1372
  context 'CustomError subclass of Grape::Exceptions::Base' do
@@ -1359,7 +1399,7 @@ describe Grape::API do
1359
1399
  it 'can rescue exceptions raised in the formatter' do
1360
1400
  formatter = double(:formatter)
1361
1401
  allow(formatter).to receive(:call) { fail StandardError }
1362
- allow(Grape::Formatter::Base).to receive(:formatter_for) { formatter }
1402
+ allow(Grape::Formatter).to receive(:formatter_for) { formatter }
1363
1403
 
1364
1404
  subject.rescue_from :all do |_e|
1365
1405
  rack_response('Formatter Error', 500)
@@ -1481,18 +1521,26 @@ describe Grape::API do
1481
1521
  end
1482
1522
  end
1483
1523
 
1484
- describe '.rescue_from klass, with: method' do
1485
- it 'rescues an error with the specified message' do
1486
- def rescue_arg_error
1487
- Rack::Response.new('rescued with a method', 400)
1524
+ describe '.rescue_from klass, with: :method_name' do
1525
+ it 'rescues an error with the specified method name' do
1526
+ subject.helpers do
1527
+ def rescue_arg_error
1528
+ error!('500 ArgumentError', 500)
1529
+ end
1488
1530
  end
1489
-
1490
- subject.rescue_from ArgumentError, with: rescue_arg_error
1531
+ subject.rescue_from ArgumentError, with: :rescue_arg_error
1491
1532
  subject.get('/rescue_method') { fail ArgumentError }
1492
1533
 
1493
1534
  get '/rescue_method'
1494
- expect(last_response.status).to eq(400)
1495
- expect(last_response.body).to eq('rescued with a method')
1535
+ expect(last_response.status).to eq(500)
1536
+ expect(last_response.body).to eq('500 ArgumentError')
1537
+ end
1538
+
1539
+ it 'aborts if the specified method name does not exist' do
1540
+ subject.rescue_from :all, with: :not_exist_method
1541
+ subject.get('/rescue_method') { fail StandardError }
1542
+
1543
+ expect { get '/rescue_method' }.to raise_error(NoMethodError, 'undefined method `not_exist_method\'')
1496
1544
  end
1497
1545
  end
1498
1546
 
@@ -1668,13 +1716,23 @@ describe Grape::API do
1668
1716
  get '/error'
1669
1717
  expect(last_response.body).to eql 'Access Denied'
1670
1718
  end
1671
- it 'rescues error! and return json' do
1672
- subject.format :json
1673
- subject.get '/error' do
1674
- error!('Access Denied', 401)
1719
+ context 'with json format' do
1720
+ before { subject.format :json }
1721
+
1722
+ it 'rescues error! called with a string and returns json' do
1723
+ subject.get('/error') { error!(:failure, 401) }
1724
+ end
1725
+ it 'rescues error! called with a symbol and returns json' do
1726
+ subject.get('/error') { error!(:failure, 401) }
1727
+ end
1728
+ it 'rescues error! called with a hash and returns json' do
1729
+ subject.get('/error') { error!({ error: :failure }, 401) }
1730
+ end
1731
+
1732
+ after do
1733
+ get '/error'
1734
+ expect(last_response.body).to eql('{"error":"failure"}')
1675
1735
  end
1676
- get '/error'
1677
- expect(last_response.body).to eql '{"error":"Access Denied"}'
1678
1736
  end
1679
1737
  end
1680
1738