grape 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Appraisals +9 -4
- data/CHANGELOG.md +265 -215
- data/CONTRIBUTING.md +4 -4
- data/Gemfile +0 -1
- data/Gemfile.lock +166 -0
- data/README.md +426 -161
- data/RELEASING.md +14 -6
- data/Rakefile +30 -33
- data/UPGRADING.md +54 -23
- data/benchmark/simple.rb +27 -0
- data/gemfiles/rack_1.5.2.gemfile +13 -0
- data/gemfiles/rails_3.gemfile +2 -2
- data/gemfiles/rails_4.gemfile +1 -2
- data/grape.gemspec +6 -7
- data/lib/grape/api.rb +24 -4
- data/lib/grape/dsl/callbacks.rb +20 -0
- data/lib/grape/dsl/configuration.rb +59 -2
- data/lib/grape/dsl/helpers.rb +8 -3
- data/lib/grape/dsl/inside_route.rb +100 -45
- data/lib/grape/dsl/parameters.rb +96 -7
- data/lib/grape/dsl/request_response.rb +1 -1
- data/lib/grape/dsl/routing.rb +17 -4
- data/lib/grape/dsl/settings.rb +36 -1
- data/lib/grape/dsl/validations.rb +7 -5
- data/lib/grape/endpoint.rb +102 -57
- data/lib/grape/error_formatter/base.rb +6 -6
- data/lib/grape/exceptions/base.rb +5 -5
- data/lib/grape/exceptions/invalid_version_header.rb +10 -0
- data/lib/grape/exceptions/unknown_parameter.rb +10 -0
- data/lib/grape/exceptions/validation_errors.rb +4 -3
- data/lib/grape/formatter/serializable_hash.rb +3 -2
- data/lib/grape/http/headers.rb +0 -1
- data/lib/grape/locale/en.yml +5 -1
- data/lib/grape/middleware/auth/base.rb +2 -2
- data/lib/grape/middleware/auth/dsl.rb +1 -1
- data/lib/grape/middleware/auth/strategies.rb +1 -1
- data/lib/grape/middleware/base.rb +8 -4
- data/lib/grape/middleware/error.rb +3 -2
- data/lib/grape/middleware/filter.rb +1 -1
- data/lib/grape/middleware/formatter.rb +64 -45
- data/lib/grape/middleware/globals.rb +3 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
- data/lib/grape/middleware/versioner/header.rb +113 -50
- data/lib/grape/middleware/versioner/param.rb +5 -8
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
- data/lib/grape/middleware/versioner/path.rb +3 -6
- data/lib/grape/namespace.rb +13 -2
- data/lib/grape/path.rb +4 -3
- data/lib/grape/request.rb +40 -0
- data/lib/grape/route.rb +5 -0
- data/lib/grape/util/content_types.rb +9 -9
- data/lib/grape/util/env.rb +22 -0
- data/lib/grape/util/file_response.rb +21 -0
- data/lib/grape/util/inheritable_setting.rb +23 -2
- data/lib/grape/util/inheritable_values.rb +1 -1
- data/lib/grape/util/stackable_values.rb +5 -2
- data/lib/grape/util/strict_hash_configuration.rb +2 -1
- data/lib/grape/validations/attributes_iterator.rb +8 -3
- data/lib/grape/validations/params_scope.rb +164 -22
- data/lib/grape/validations/types/build_coercer.rb +53 -0
- data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
- data/lib/grape/validations/types/file.rb +28 -0
- data/lib/grape/validations/types/json.rb +65 -0
- data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
- data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
- data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
- data/lib/grape/validations/types.rb +144 -0
- data/lib/grape/validations/validators/all_or_none.rb +1 -1
- data/lib/grape/validations/validators/allow_blank.rb +3 -3
- data/lib/grape/validations/validators/base.rb +7 -0
- data/lib/grape/validations/validators/coerce.rb +32 -34
- data/lib/grape/validations/validators/presence.rb +2 -3
- data/lib/grape/validations/validators/regexp.rb +2 -4
- data/lib/grape/validations/validators/values.rb +3 -3
- data/lib/grape/validations.rb +5 -0
- data/lib/grape/version.rb +2 -1
- data/lib/grape.rb +15 -12
- data/pkg/grape-0.13.0.gem +0 -0
- data/spec/grape/api/custom_validations_spec.rb +5 -4
- data/spec/grape/api/deeply_included_options_spec.rb +7 -7
- data/spec/grape/api/nested_helpers_spec.rb +4 -2
- data/spec/grape/api/shared_helpers_spec.rb +8 -8
- data/spec/grape/api_spec.rb +151 -54
- data/spec/grape/dsl/configuration_spec.rb +13 -0
- data/spec/grape/dsl/helpers_spec.rb +16 -2
- data/spec/grape/dsl/inside_route_spec.rb +40 -4
- data/spec/grape/dsl/parameters_spec.rb +0 -6
- data/spec/grape/dsl/routing_spec.rb +1 -1
- data/spec/grape/dsl/validations_spec.rb +18 -0
- data/spec/grape/endpoint_spec.rb +130 -6
- data/spec/grape/entity_spec.rb +10 -8
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
- data/spec/grape/exceptions/validation_errors_spec.rb +28 -0
- data/spec/grape/integration/rack_spec.rb +3 -2
- data/spec/grape/middleware/base_spec.rb +40 -16
- data/spec/grape/middleware/error_spec.rb +16 -15
- data/spec/grape/middleware/exception_spec.rb +45 -43
- data/spec/grape/middleware/formatter_spec.rb +34 -5
- data/spec/grape/middleware/versioner/header_spec.rb +79 -47
- data/spec/grape/path_spec.rb +10 -10
- data/spec/grape/presenters/presenter_spec.rb +2 -2
- data/spec/grape/request_spec.rb +100 -0
- data/spec/grape/util/inheritable_values_spec.rb +14 -0
- data/spec/grape/util/stackable_values_spec.rb +10 -0
- data/spec/grape/validations/params_scope_spec.rb +86 -0
- data/spec/grape/validations/types_spec.rb +95 -0
- data/spec/grape/validations/validators/coerce_spec.rb +364 -10
- data/spec/grape/validations/validators/values_spec.rb +27 -15
- data/spec/grape/validations_spec.rb +53 -24
- data/spec/shared/versioning_examples.rb +2 -2
- data/spec/spec_helper.rb +0 -1
- data/spec/support/versioned_helpers.rb +2 -2
- metadata +55 -14
- data/.gitignore +0 -46
- data/.rspec +0 -2
- data/.rubocop.yml +0 -7
- data/.rubocop_todo.yml +0 -84
- data/.travis.yml +0 -20
- data/.yardopts +0 -2
- data/lib/backports/active_support/deep_dup.rb +0 -49
- data/lib/backports/active_support/duplicable.rb +0 -88
- data/lib/grape/http/request.rb +0 -27
@@ -110,11 +110,6 @@ describe Grape::Middleware::Formatter do
|
|
110
110
|
expect(subject.env['api.format']).to eq(:xml)
|
111
111
|
end
|
112
112
|
|
113
|
-
it 'looks for case-indifferent headers' do
|
114
|
-
subject.call('PATH_INFO' => '/info', 'http_accept' => 'application/xml')
|
115
|
-
expect(subject.env['api.format']).to eq(:xml)
|
116
|
-
end
|
117
|
-
|
118
113
|
it 'uses quality rankings to determine formats' do
|
119
114
|
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/json; q=0.3,application/xml; q=1.0')
|
120
115
|
expect(subject.env['api.format']).to eq(:xml)
|
@@ -137,6 +132,18 @@ describe Grape::Middleware::Formatter do
|
|
137
132
|
expect(subject.env['api.format']).to eq(:xml)
|
138
133
|
end
|
139
134
|
|
135
|
+
context 'with custom vendored content types' do
|
136
|
+
before do
|
137
|
+
subject.options[:content_types] = {}
|
138
|
+
subject.options[:content_types][:custom] = 'application/vnd.test+json'
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'it uses the custom type' do
|
142
|
+
subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
|
143
|
+
expect(subject.env['api.format']).to eq(:custom)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
140
147
|
it 'parses headers with symbols as hash keys' do
|
141
148
|
subject.call('PATH_INFO' => '/info', 'http_accept' => 'application/xml', system_time: '091293')
|
142
149
|
expect(subject.env[:system_time]).to eq('091293')
|
@@ -162,6 +169,16 @@ describe Grape::Middleware::Formatter do
|
|
162
169
|
_, headers, = subject.call('PATH_INFO' => '/info.custom')
|
163
170
|
expect(headers['Content-type']).to eq('application/x-custom')
|
164
171
|
end
|
172
|
+
it 'is set for vendored with registered type' do
|
173
|
+
subject.options[:content_types] = {}
|
174
|
+
subject.options[:content_types][:custom] = 'application/vnd.test+json'
|
175
|
+
_, headers, = subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
|
176
|
+
expect(headers['Content-type']).to eq('application/vnd.test+json')
|
177
|
+
end
|
178
|
+
it 'is set to closest generic for custom vendored/versioned without registered type' do
|
179
|
+
_, headers, = subject.call('PATH_INFO' => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')
|
180
|
+
expect(headers['Content-type']).to eq('application/json')
|
181
|
+
end
|
165
182
|
end
|
166
183
|
|
167
184
|
context 'format' do
|
@@ -184,6 +201,18 @@ describe Grape::Middleware::Formatter do
|
|
184
201
|
end
|
185
202
|
end
|
186
203
|
|
204
|
+
context 'no content responses' do
|
205
|
+
let(:no_content_response) { ->(status) { [status, {}, ['']] } }
|
206
|
+
|
207
|
+
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.each do |status|
|
208
|
+
it "does not modify a #{status} response" do
|
209
|
+
expected_response = no_content_response[status]
|
210
|
+
allow(app).to receive(:call).and_return(expected_response)
|
211
|
+
expect(subject.call({})).to eq(expected_response)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
187
216
|
context 'input' do
|
188
217
|
%w(POST PATCH PUT DELETE).each do |method|
|
189
218
|
['application/json', 'application/json; charset=utf-8'].each do |content_type|
|
@@ -56,13 +56,13 @@ describe Grape::Middleware::Versioner::Header do
|
|
56
56
|
end
|
57
57
|
|
58
58
|
it 'is set' do
|
59
|
-
status, _, env =
|
59
|
+
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
|
60
60
|
expect(env['api.format']).to eql 'json'
|
61
61
|
expect(status).to eq(200)
|
62
62
|
end
|
63
63
|
|
64
64
|
it 'is nil if not provided' do
|
65
|
-
status, _, env =
|
65
|
+
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
|
66
66
|
expect(env['api.format']).to eql nil
|
67
67
|
expect(status).to eq(200)
|
68
68
|
end
|
@@ -72,13 +72,13 @@ describe Grape::Middleware::Versioner::Header do
|
|
72
72
|
|
73
73
|
context 'api.vendor' do
|
74
74
|
it 'is set' do
|
75
|
-
status, _, env =
|
75
|
+
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor')
|
76
76
|
expect(env['api.vendor']).to eql 'vendor'
|
77
77
|
expect(status).to eq(200)
|
78
78
|
end
|
79
79
|
|
80
80
|
it 'is set if format provided' do
|
81
|
-
status, _, env =
|
81
|
+
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor+json')
|
82
82
|
expect(env['api.vendor']).to eql 'vendor'
|
83
83
|
expect(status).to eq(200)
|
84
84
|
end
|
@@ -89,7 +89,7 @@ describe Grape::Middleware::Versioner::Header do
|
|
89
89
|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
|
90
90
|
expect(exception.headers).to eql('X-Cascade' => 'pass')
|
91
91
|
expect(exception.status).to eql 406
|
92
|
-
expect(exception.message).to include 'API vendor
|
92
|
+
expect(exception.message).to include 'API vendor not found'
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
@@ -99,13 +99,13 @@ describe Grape::Middleware::Versioner::Header do
|
|
99
99
|
end
|
100
100
|
|
101
101
|
it 'is set' do
|
102
|
-
status, _, env =
|
102
|
+
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
|
103
103
|
expect(env['api.vendor']).to eql 'vendor'
|
104
104
|
expect(status).to eq(200)
|
105
105
|
end
|
106
106
|
|
107
107
|
it 'is set if format provided' do
|
108
|
-
status, _, env =
|
108
|
+
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
|
109
109
|
expect(env['api.vendor']).to eql 'vendor'
|
110
110
|
expect(status).to eq(200)
|
111
111
|
end
|
@@ -116,7 +116,7 @@ describe Grape::Middleware::Versioner::Header do
|
|
116
116
|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
|
117
117
|
expect(exception.headers).to eql('X-Cascade' => 'pass')
|
118
118
|
expect(exception.status).to eql 406
|
119
|
-
expect(exception.message).to include('API vendor
|
119
|
+
expect(exception.message).to include('API vendor not found')
|
120
120
|
end
|
121
121
|
end
|
122
122
|
end
|
@@ -128,23 +128,23 @@ describe Grape::Middleware::Versioner::Header do
|
|
128
128
|
end
|
129
129
|
|
130
130
|
it 'is set' do
|
131
|
-
status, _, env =
|
131
|
+
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')
|
132
132
|
expect(env['api.version']).to eql 'v1'
|
133
133
|
expect(status).to eq(200)
|
134
134
|
end
|
135
135
|
|
136
136
|
it 'is set if format provided' do
|
137
|
-
status, _, env =
|
137
|
+
status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')
|
138
138
|
expect(env['api.version']).to eql 'v1'
|
139
139
|
expect(status).to eq(200)
|
140
140
|
end
|
141
141
|
|
142
142
|
it 'fails with 406 Not Acceptable if version is invalid' do
|
143
143
|
expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v2+json').last }.to raise_exception do |exception|
|
144
|
-
expect(exception).to be_a(Grape::Exceptions::
|
144
|
+
expect(exception).to be_a(Grape::Exceptions::InvalidVersionHeader)
|
145
145
|
expect(exception.headers).to eql('X-Cascade' => 'pass')
|
146
146
|
expect(exception.status).to eql 406
|
147
|
-
expect(exception.message).to include('API
|
147
|
+
expect(exception.message).to include('API version not found')
|
148
148
|
end
|
149
149
|
end
|
150
150
|
end
|
@@ -184,24 +184,6 @@ describe Grape::Middleware::Versioner::Header do
|
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
187
|
-
it 'fails with 406 Not Acceptable if type is a range' do
|
188
|
-
expect { subject.call('HTTP_ACCEPT' => '*/*').last }.to raise_exception do |exception|
|
189
|
-
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
|
190
|
-
expect(exception.headers).to eql('X-Cascade' => 'pass')
|
191
|
-
expect(exception.status).to eql 406
|
192
|
-
expect(exception.message).to include('Accept header must not contain ranges ("*").')
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
it 'fails with 406 Not Acceptable if subtype is a range' do
|
197
|
-
expect { subject.call('HTTP_ACCEPT' => 'application/*').last }.to raise_exception do |exception|
|
198
|
-
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
|
199
|
-
expect(exception.headers).to eql('X-Cascade' => 'pass')
|
200
|
-
expect(exception.status).to eql 406
|
201
|
-
expect(exception.message).to include('Accept header must not contain ranges ("*").')
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
187
|
it 'succeeds if proper header is set' do
|
206
188
|
expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first).to eq(200)
|
207
189
|
end
|
@@ -223,30 +205,22 @@ describe Grape::Middleware::Versioner::Header do
|
|
223
205
|
end
|
224
206
|
end
|
225
207
|
|
226
|
-
it 'fails with 406 Not Acceptable if header is
|
227
|
-
expect { subject.call('HTTP_ACCEPT' => '').last }
|
228
|
-
|
229
|
-
expect(exception.headers).to eql({})
|
230
|
-
expect(exception.status).to eql 406
|
231
|
-
expect(exception.message).to include('Accept header must be set.')
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
it 'fails with 406 Not Acceptable if type is a range' do
|
236
|
-
expect { subject.call('HTTP_ACCEPT' => '*/*').last }.to raise_exception do |exception|
|
208
|
+
it 'fails with 406 Not Acceptable if header is application/xml' do
|
209
|
+
expect { subject.call('HTTP_ACCEPT' => 'application/xml').last }
|
210
|
+
.to raise_exception do |exception|
|
237
211
|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
|
238
212
|
expect(exception.headers).to eql({})
|
239
213
|
expect(exception.status).to eql 406
|
240
|
-
expect(exception.message).to include('
|
214
|
+
expect(exception.message).to include('API vendor or version not found.')
|
241
215
|
end
|
242
216
|
end
|
243
217
|
|
244
|
-
it 'fails with 406 Not Acceptable if
|
245
|
-
expect { subject.call('HTTP_ACCEPT' => '
|
218
|
+
it 'fails with 406 Not Acceptable if header is empty' do
|
219
|
+
expect { subject.call('HTTP_ACCEPT' => '').last }.to raise_exception do |exception|
|
246
220
|
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
|
247
221
|
expect(exception.headers).to eql({})
|
248
222
|
expect(exception.status).to eql 406
|
249
|
-
expect(exception.message).to include('Accept header must
|
223
|
+
expect(exception.message).to include('Accept header must be set.')
|
250
224
|
end
|
251
225
|
end
|
252
226
|
|
@@ -270,10 +244,68 @@ describe Grape::Middleware::Versioner::Header do
|
|
270
244
|
|
271
245
|
it 'fails with another version' do
|
272
246
|
expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v3+json') }.to raise_exception do |exception|
|
273
|
-
expect(exception).to be_a(Grape::Exceptions::
|
247
|
+
expect(exception).to be_a(Grape::Exceptions::InvalidVersionHeader)
|
274
248
|
expect(exception.headers).to eql('X-Cascade' => 'pass')
|
275
249
|
expect(exception.status).to eql 406
|
276
|
-
expect(exception.message).to include('API
|
250
|
+
expect(exception.message).to include('API version not found')
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
context 'when there are multiple versions with complex vendor specified with rescue_from :all' do
|
256
|
+
subject {
|
257
|
+
Class.new(Grape::API) do
|
258
|
+
rescue_from :all
|
259
|
+
end
|
260
|
+
}
|
261
|
+
|
262
|
+
let(:v1_app) {
|
263
|
+
Class.new(Grape::API) do
|
264
|
+
version 'v1', using: :header, vendor: 'test.a-cool_resource', cascade: false, strict: true
|
265
|
+
content_type :v1_test, 'application/vnd.test.a-cool_resource-v1+json'
|
266
|
+
formatter :v1_test, ->(object, _) { object }
|
267
|
+
format :v1_test
|
268
|
+
|
269
|
+
resources :users do
|
270
|
+
get :hello do
|
271
|
+
'one'
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
}
|
276
|
+
|
277
|
+
let(:v2_app) {
|
278
|
+
Class.new(Grape::API) do
|
279
|
+
version 'v2', using: :header, vendor: 'test.a-cool_resource', strict: true
|
280
|
+
content_type :v2_test, 'application/vnd.test.a-cool_resource-v2+json'
|
281
|
+
formatter :v2_test, ->(object, _) { object }
|
282
|
+
format :v2_test
|
283
|
+
|
284
|
+
resources :users do
|
285
|
+
get :hello do
|
286
|
+
'two'
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
}
|
291
|
+
|
292
|
+
def app
|
293
|
+
subject.mount v2_app
|
294
|
+
subject.mount v1_app
|
295
|
+
subject
|
296
|
+
end
|
297
|
+
|
298
|
+
context 'with header versioned endpoints and a rescue_all block defined' do
|
299
|
+
it 'responds correctly to a v1 request' do
|
300
|
+
versioned_get '/users/hello', 'v1', using: :header, vendor: 'test.a-cool_resource'
|
301
|
+
expect(last_response.body).to eq('one')
|
302
|
+
expect(last_response.body).not_to include('API vendor or version not found')
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'responds correctly to a v2 request' do
|
306
|
+
versioned_get '/users/hello', 'v2', using: :header, vendor: 'test.a-cool_resource'
|
307
|
+
expect(last_response.body).to eq('two')
|
308
|
+
expect(last_response.body).not_to include('API vendor or version not found')
|
277
309
|
end
|
278
310
|
end
|
279
311
|
end
|
data/spec/grape/path_spec.rb
CHANGED
@@ -82,47 +82,47 @@ module Grape
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
describe '#
|
85
|
+
describe '#namespace?' do
|
86
86
|
it 'is false when the namespace is nil' do
|
87
87
|
path = Path.new(anything, nil, anything)
|
88
|
-
expect(path).
|
88
|
+
expect(path.namespace?).to be nil
|
89
89
|
end
|
90
90
|
|
91
91
|
it 'is false when the namespace starts with whitespace' do
|
92
92
|
path = Path.new(anything, ' /foo', anything)
|
93
|
-
expect(path).
|
93
|
+
expect(path.namespace?).to be nil
|
94
94
|
end
|
95
95
|
|
96
96
|
it 'is false when the namespace is the root path' do
|
97
97
|
path = Path.new(anything, '/', anything)
|
98
|
-
expect(path).
|
98
|
+
expect(path.namespace?).to be false
|
99
99
|
end
|
100
100
|
|
101
101
|
it 'is true otherwise' do
|
102
102
|
path = Path.new(anything, '/world', anything)
|
103
|
-
expect(path).to
|
103
|
+
expect(path.namespace?).to be true
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
-
describe '#
|
107
|
+
describe '#path?' do
|
108
108
|
it 'is false when the path is nil' do
|
109
109
|
path = Path.new(nil, anything, anything)
|
110
|
-
expect(path).
|
110
|
+
expect(path.path?).to be nil
|
111
111
|
end
|
112
112
|
|
113
113
|
it 'is false when the path starts with whitespace' do
|
114
114
|
path = Path.new(' /foo', anything, anything)
|
115
|
-
expect(path).
|
115
|
+
expect(path.path?).to be nil
|
116
116
|
end
|
117
117
|
|
118
118
|
it 'is false when the path is the root path' do
|
119
119
|
path = Path.new('/', anything, anything)
|
120
|
-
expect(path).
|
120
|
+
expect(path.path?).to be false
|
121
121
|
end
|
122
122
|
|
123
123
|
it 'is true otherwise' do
|
124
124
|
path = Path.new('/hello', anything, anything)
|
125
|
-
expect(path).to
|
125
|
+
expect(path.path?).to be true
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Presenters
|
5
|
-
module
|
5
|
+
module PresenterSpec
|
6
6
|
class Dummy
|
7
7
|
include Grape::DSL::InsideRoute
|
8
8
|
|
@@ -27,7 +27,7 @@ module Grape
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
subject {
|
30
|
+
subject { PresenterSpec::Dummy.new }
|
31
31
|
|
32
32
|
describe 'present' do
|
33
33
|
let(:hash_mock) do
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
describe Request do
|
5
|
+
let(:default_method) { 'GET' }
|
6
|
+
let(:default_params) { {} }
|
7
|
+
let(:default_options) {
|
8
|
+
{
|
9
|
+
method: method,
|
10
|
+
params: params
|
11
|
+
}
|
12
|
+
}
|
13
|
+
let(:default_env) {
|
14
|
+
Rack::MockRequest.env_for('/', options)
|
15
|
+
}
|
16
|
+
let(:method) { default_method }
|
17
|
+
let(:params) { default_params }
|
18
|
+
let(:options) { default_options }
|
19
|
+
let(:env) { default_env }
|
20
|
+
|
21
|
+
let(:request) {
|
22
|
+
Grape::Request.new(env)
|
23
|
+
}
|
24
|
+
|
25
|
+
describe '#params' do
|
26
|
+
let(:params) {
|
27
|
+
{
|
28
|
+
a: '123',
|
29
|
+
b: 'xyz'
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
it 'returns params' do
|
34
|
+
expect(request.params).to eq('a' => '123', 'b' => 'xyz')
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'with rack.routing_args' do
|
38
|
+
let(:options) {
|
39
|
+
default_options.merge('rack.routing_args' => routing_args)
|
40
|
+
}
|
41
|
+
let(:routing_args) {
|
42
|
+
{
|
43
|
+
version: '123',
|
44
|
+
route_info: '456',
|
45
|
+
c: 'ccc'
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
it 'cuts version and route_info' do
|
50
|
+
expect(request.params).to eq('a' => '123', 'b' => 'xyz', 'c' => 'ccc')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#headers' do
|
56
|
+
let(:options) {
|
57
|
+
default_options.merge(request_headers)
|
58
|
+
}
|
59
|
+
|
60
|
+
describe 'with http headers in env' do
|
61
|
+
let(:request_headers) {
|
62
|
+
{
|
63
|
+
'HTTP_X_GRAPE_IS_COOL' => 'yeah'
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
it 'cuts HTTP_ prefix and capitalizes header name words' do
|
68
|
+
expect(request.headers).to eq('X-Grape-Is-Cool' => 'yeah')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'with non-HTTP_* stuff in env' do
|
73
|
+
let(:request_headers) {
|
74
|
+
{
|
75
|
+
'HTP_X_GRAPE_ENTITY_TOO' => 'but now we are testing Grape'
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
it 'does not include them' do
|
80
|
+
expect(request.headers).to eq({})
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'with symbolic header names' do
|
85
|
+
let(:request_headers) {
|
86
|
+
{
|
87
|
+
HTTP_GRAPE_LIKES_SYMBOLIC: 'it is true'
|
88
|
+
}
|
89
|
+
}
|
90
|
+
let(:env) {
|
91
|
+
default_env.merge(request_headers)
|
92
|
+
}
|
93
|
+
|
94
|
+
it 'converts them to string' do
|
95
|
+
expect(request.headers).to eq('Grape-Likes-Symbolic' => 'it is true')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -58,6 +58,20 @@ module Grape
|
|
58
58
|
expect(subject.to_hash).to eq(some_thing: :foo, some_thing_more: :foo_bar)
|
59
59
|
end
|
60
60
|
end
|
61
|
+
|
62
|
+
describe '#clone' do
|
63
|
+
let(:obj_cloned) { subject.clone }
|
64
|
+
|
65
|
+
context 'complex (i.e. not primitive) data types (ex. entity classes, please see bug #891)' do
|
66
|
+
let(:description) { { entity: double } }
|
67
|
+
|
68
|
+
before { subject[:description] = description }
|
69
|
+
|
70
|
+
it 'copies values; does not duplicate them' do
|
71
|
+
expect(obj_cloned[:description]).to eq description
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
61
75
|
end
|
62
76
|
end
|
63
77
|
end
|
@@ -109,6 +109,16 @@ module Grape
|
|
109
109
|
|
110
110
|
expect(grandchild.clone.to_hash).to eq(some_thing: [:foo, [:bar, :more], :grand_foo_bar], some_thing_more: [:foo_bar])
|
111
111
|
end
|
112
|
+
|
113
|
+
context 'complex (i.e. not primitive) data types (ex. middleware, please see bug #930)' do
|
114
|
+
let(:middleware) { double }
|
115
|
+
|
116
|
+
before { subject[:middleware] = middleware }
|
117
|
+
|
118
|
+
it 'copies values; does not duplicate them' do
|
119
|
+
expect(obj_cloned[:middleware]).to eq [middleware]
|
120
|
+
end
|
121
|
+
end
|
112
122
|
end
|
113
123
|
end
|
114
124
|
end
|
@@ -89,6 +89,37 @@ describe Grape::Validations::ParamsScope do
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
+
context 'when using custom types' do
|
93
|
+
module ParamsScopeSpec
|
94
|
+
class CustomType
|
95
|
+
attr_reader :value
|
96
|
+
def self.parse(value)
|
97
|
+
fail if value == 'invalid'
|
98
|
+
new(value)
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize(value)
|
102
|
+
@value = value
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'coerces the parameter via the type\'s parse method' do
|
108
|
+
subject.params do
|
109
|
+
requires :foo, type: ParamsScopeSpec::CustomType
|
110
|
+
end
|
111
|
+
subject.get('/types') { params[:foo].value }
|
112
|
+
|
113
|
+
get '/types', foo: 'valid'
|
114
|
+
expect(last_response.status).to eq(200)
|
115
|
+
expect(last_response.body).to eq('valid')
|
116
|
+
|
117
|
+
get '/types', foo: 'invalid'
|
118
|
+
expect(last_response.status).to eq(400)
|
119
|
+
expect(last_response.body).to match(/foo is invalid/)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
92
123
|
context 'array without coerce type explicitly given' do
|
93
124
|
it 'sets the type based on first element' do
|
94
125
|
subject.params do
|
@@ -242,4 +273,59 @@ describe Grape::Validations::ParamsScope do
|
|
242
273
|
end.to raise_error Grape::Exceptions::UnsupportedGroupTypeError
|
243
274
|
end
|
244
275
|
end
|
276
|
+
|
277
|
+
context 'when validations are dependent on a parameter' do
|
278
|
+
before do
|
279
|
+
subject.params do
|
280
|
+
optional :a
|
281
|
+
given :a do
|
282
|
+
requires :b
|
283
|
+
end
|
284
|
+
end
|
285
|
+
subject.get('/test') { declared(params).to_json }
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'applies the validations only if the parameter is present' do
|
289
|
+
get '/test'
|
290
|
+
expect(last_response.status).to eq(200)
|
291
|
+
|
292
|
+
get '/test', a: true
|
293
|
+
expect(last_response.status).to eq(400)
|
294
|
+
expect(last_response.body).to eq('b is missing')
|
295
|
+
|
296
|
+
get '/test', a: true, b: true
|
297
|
+
expect(last_response.status).to eq(200)
|
298
|
+
end
|
299
|
+
|
300
|
+
it 'raises an error if the dependent parameter was never specified' do
|
301
|
+
expect do
|
302
|
+
subject.params do
|
303
|
+
given :c do
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end.to raise_error(Grape::Exceptions::UnknownParameter)
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'includes the parameter within #declared(params)' do
|
310
|
+
get '/test', a: true, b: true
|
311
|
+
|
312
|
+
expect(JSON.parse(last_response.body)).to eq('a' => 'true', 'b' => 'true')
|
313
|
+
end
|
314
|
+
|
315
|
+
it 'returns a sensible error message within a nested context' do
|
316
|
+
subject.params do
|
317
|
+
requires :bar, type: Hash do
|
318
|
+
optional :a
|
319
|
+
given :a do
|
320
|
+
requires :b
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
subject.get('/nested') { 'worked' }
|
325
|
+
|
326
|
+
get '/nested', bar: { a: true }
|
327
|
+
expect(last_response.status).to eq(400)
|
328
|
+
expect(last_response.body).to eq('bar[b] is missing')
|
329
|
+
end
|
330
|
+
end
|
245
331
|
end
|