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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -4
- data/Gemfile.lock +13 -13
- data/README.md +290 -12
- data/UPGRADING.md +68 -1
- data/gemfiles/rails_3.gemfile +1 -1
- data/lib/grape.rb +8 -2
- data/lib/grape/api.rb +40 -34
- data/lib/grape/dsl/configuration.rb +2 -115
- data/lib/grape/dsl/desc.rb +101 -0
- data/lib/grape/dsl/headers.rb +16 -0
- data/lib/grape/dsl/helpers.rb +5 -9
- data/lib/grape/dsl/inside_route.rb +3 -11
- data/lib/grape/dsl/logger.rb +20 -0
- data/lib/grape/dsl/parameters.rb +12 -10
- data/lib/grape/dsl/request_response.rb +17 -4
- data/lib/grape/dsl/routing.rb +24 -7
- data/lib/grape/dsl/settings.rb +8 -2
- data/lib/grape/endpoint.rb +30 -26
- data/lib/grape/error_formatter.rb +31 -0
- data/lib/grape/error_formatter/base.rb +0 -28
- data/lib/grape/error_formatter/json.rb +13 -2
- data/lib/grape/error_formatter/txt.rb +3 -1
- data/lib/grape/error_formatter/xml.rb +3 -1
- data/lib/grape/exceptions/base.rb +11 -4
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_message_body.rb +1 -1
- data/lib/grape/exceptions/invalid_version_header.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
- data/lib/grape/exceptions/method_not_allowed.rb +10 -0
- data/lib/grape/exceptions/missing_group_type.rb +1 -1
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/missing_option.rb +1 -1
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
- data/lib/grape/exceptions/unknown_options.rb +1 -1
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +1 -1
- data/lib/grape/exceptions/validation.rb +2 -1
- data/lib/grape/formatter.rb +31 -0
- data/lib/grape/middleware/base.rb +28 -2
- data/lib/grape/middleware/error.rb +24 -1
- data/lib/grape/middleware/formatter.rb +4 -3
- data/lib/grape/middleware/versioner/param.rb +13 -2
- data/lib/grape/parser.rb +29 -0
- data/lib/grape/util/sendfile_response.rb +19 -0
- data/lib/grape/util/strict_hash_configuration.rb +1 -1
- data/lib/grape/validations/params_scope.rb +39 -9
- data/lib/grape/validations/types.rb +16 -0
- data/lib/grape/validations/validators/all_or_none.rb +1 -1
- data/lib/grape/validations/validators/allow_blank.rb +2 -2
- data/lib/grape/validations/validators/at_least_one_of.rb +1 -1
- data/lib/grape/validations/validators/base.rb +26 -0
- data/lib/grape/validations/validators/coerce.rb +16 -14
- data/lib/grape/validations/validators/default.rb +1 -1
- data/lib/grape/validations/validators/exactly_one_of.rb +10 -1
- data/lib/grape/validations/validators/mutual_exclusion.rb +1 -1
- data/lib/grape/validations/validators/presence.rb +1 -1
- data/lib/grape/validations/validators/regexp.rb +2 -2
- data/lib/grape/validations/validators/values.rb +2 -2
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/custom_validations_spec.rb +156 -21
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +38 -0
- data/spec/grape/api/optional_parameters_in_route_spec.rb +43 -0
- data/spec/grape/api/required_parameters_in_route_spec.rb +37 -0
- data/spec/grape/api_spec.rb +118 -60
- data/spec/grape/dsl/configuration_spec.rb +0 -75
- data/spec/grape/dsl/desc_spec.rb +77 -0
- data/spec/grape/dsl/headers_spec.rb +32 -0
- data/spec/grape/dsl/inside_route_spec.rb +0 -18
- data/spec/grape/dsl/logger_spec.rb +26 -0
- data/spec/grape/dsl/parameters_spec.rb +13 -7
- data/spec/grape/dsl/request_response_spec.rb +17 -3
- data/spec/grape/dsl/routing_spec.rb +8 -1
- data/spec/grape/dsl/settings_spec.rb +42 -0
- data/spec/grape/endpoint_spec.rb +60 -9
- data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
- data/spec/grape/exceptions/validation_spec.rb +7 -0
- data/spec/grape/integration/rack_sendfile_spec.rb +44 -0
- data/spec/grape/middleware/base_spec.rb +100 -0
- data/spec/grape/middleware/exception_spec.rb +1 -2
- data/spec/grape/middleware/formatter_spec.rb +12 -2
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +1 -1
- data/spec/grape/middleware/versioner/header_spec.rb +11 -1
- data/spec/grape/middleware/versioner/param_spec.rb +105 -1
- data/spec/grape/validations/params_scope_spec.rb +77 -0
- data/spec/grape/validations/validators/allow_blank_spec.rb +277 -0
- data/spec/grape/validations/validators/coerce_spec.rb +91 -0
- data/spec/grape/validations/validators/default_spec.rb +6 -0
- data/spec/grape/validations/validators/presence_spec.rb +27 -0
- data/spec/grape/validations/validators/regexp_spec.rb +36 -0
- data/spec/grape/validations/validators/values_spec.rb +44 -0
- data/spec/grape/validations_spec.rb +149 -4
- data/spec/spec_helper.rb +1 -0
- metadata +26 -5
- data/lib/grape/formatter/base.rb +0 -31
- data/lib/grape/parser/base.rb +0 -29
- 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)],
|
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)],
|
16
|
+
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values)
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
data/lib/grape/version.rb
CHANGED
@@ -1,34 +1,33 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Grape::Validations do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
data/spec/grape/api_spec.rb
CHANGED
@@ -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(
|
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
|
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 '
|
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
|
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
|
-
|
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
|
-
|
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
|
550
|
-
expect(last_response.status).to eql
|
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
|
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
|
-
|
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''#{
|
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
|
-
|
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
|
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:
|
1485
|
-
it 'rescues an error with the specified
|
1486
|
-
|
1487
|
-
|
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(
|
1495
|
-
expect(last_response.body).to eq('
|
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
|
-
|
1672
|
-
subject.format :json
|
1673
|
-
|
1674
|
-
|
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
|
|