grape 0.8.0 → 0.9.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -2
  3. data/.rubocop_todo.yml +80 -0
  4. data/.travis.yml +2 -2
  5. data/CHANGELOG.md +21 -2
  6. data/Gemfile +1 -6
  7. data/Guardfile +1 -5
  8. data/README.md +110 -27
  9. data/Rakefile +1 -1
  10. data/UPGRADING.md +35 -0
  11. data/grape.gemspec +5 -2
  12. data/lib/grape.rb +20 -4
  13. data/lib/grape/api.rb +25 -467
  14. data/lib/grape/api/helpers.rb +7 -0
  15. data/lib/grape/dsl/callbacks.rb +27 -0
  16. data/lib/grape/dsl/configuration.rb +27 -0
  17. data/lib/grape/dsl/helpers.rb +86 -0
  18. data/lib/grape/dsl/inside_route.rb +227 -0
  19. data/lib/grape/dsl/middleware.rb +33 -0
  20. data/lib/grape/dsl/parameters.rb +79 -0
  21. data/lib/grape/dsl/request_response.rb +152 -0
  22. data/lib/grape/dsl/routing.rb +172 -0
  23. data/lib/grape/dsl/validations.rb +29 -0
  24. data/lib/grape/endpoint.rb +6 -226
  25. data/lib/grape/error_formatter/base.rb +28 -0
  26. data/lib/grape/error_formatter/json.rb +2 -0
  27. data/lib/grape/error_formatter/txt.rb +2 -0
  28. data/lib/grape/error_formatter/xml.rb +2 -0
  29. data/lib/grape/exceptions/base.rb +6 -0
  30. data/lib/grape/exceptions/validation.rb +3 -3
  31. data/lib/grape/exceptions/validation_errors.rb +19 -6
  32. data/lib/grape/locale/en.yml +5 -3
  33. data/lib/grape/middleware/auth/base.rb +28 -12
  34. data/lib/grape/middleware/auth/dsl.rb +35 -0
  35. data/lib/grape/middleware/auth/strategies.rb +24 -0
  36. data/lib/grape/middleware/auth/strategy_info.rb +15 -0
  37. data/lib/grape/validations.rb +3 -92
  38. data/lib/grape/validations/at_least_one_of.rb +25 -0
  39. data/lib/grape/validations/coerce.rb +2 -2
  40. data/lib/grape/validations/exactly_one_of.rb +2 -2
  41. data/lib/grape/validations/mutual_exclusion.rb +2 -2
  42. data/lib/grape/validations/presence.rb +1 -1
  43. data/lib/grape/validations/regexp.rb +1 -1
  44. data/lib/grape/validations/values.rb +1 -1
  45. data/lib/grape/version.rb +1 -1
  46. data/spec/grape/api/helpers_spec.rb +36 -0
  47. data/spec/grape/api_spec.rb +72 -19
  48. data/spec/grape/dsl/callbacks_spec.rb +44 -0
  49. data/spec/grape/dsl/configuration_spec.rb +37 -0
  50. data/spec/grape/dsl/helpers_spec.rb +54 -0
  51. data/spec/grape/dsl/inside_route_spec.rb +222 -0
  52. data/spec/grape/dsl/middleware_spec.rb +40 -0
  53. data/spec/grape/dsl/parameters_spec.rb +108 -0
  54. data/spec/grape/dsl/request_response_spec.rb +123 -0
  55. data/spec/grape/dsl/routing_spec.rb +132 -0
  56. data/spec/grape/dsl/validations_spec.rb +55 -0
  57. data/spec/grape/endpoint_spec.rb +60 -11
  58. data/spec/grape/entity_spec.rb +9 -4
  59. data/spec/grape/exceptions/validation_errors_spec.rb +31 -1
  60. data/spec/grape/middleware/auth/base_spec.rb +34 -0
  61. data/spec/grape/middleware/auth/dsl_spec.rb +53 -0
  62. data/spec/grape/middleware/auth/strategies_spec.rb +81 -0
  63. data/spec/grape/middleware/error_spec.rb +33 -1
  64. data/spec/grape/middleware/exception_spec.rb +13 -0
  65. data/spec/grape/validations/at_least_one_of_spec.rb +63 -0
  66. data/spec/grape/validations/exactly_one_of_spec.rb +1 -1
  67. data/spec/grape/validations/presence_spec.rb +159 -122
  68. data/spec/grape/validations/zh-CN.yml +1 -1
  69. data/spec/grape/validations_spec.rb +77 -15
  70. data/spec/spec_helper.rb +1 -0
  71. data/spec/support/endpoint_faker.rb +23 -0
  72. metadata +93 -15
  73. data/lib/grape/middleware/auth/basic.rb +0 -13
  74. data/lib/grape/middleware/auth/digest.rb +0 -13
  75. data/lib/grape/middleware/auth/oauth2.rb +0 -83
  76. data/spec/grape/middleware/auth/basic_spec.rb +0 -31
  77. data/spec/grape/middleware/auth/digest_spec.rb +0 -47
  78. data/spec/grape/middleware/auth/oauth2_spec.rb +0 -135
@@ -27,7 +27,7 @@ describe Grape::Endpoint do
27
27
  expect { get '/' }.to raise_error(NameError)
28
28
 
29
29
  Grape::Endpoint.before_each do |endpoint|
30
- endpoint.stub(:current_user).and_return("Bob")
30
+ allow(endpoint).to receive(:current_user).and_return("Bob")
31
31
  end
32
32
 
33
33
  get '/'
@@ -45,7 +45,7 @@ describe Grape::Endpoint do
45
45
  Grape::Endpoint.new(Grape::Util::HashStack.new, {
46
46
  path: '/',
47
47
  method: :get
48
- }, &p)
48
+ }, &p)
49
49
  }.not_to raise_error
50
50
  end
51
51
  end
@@ -67,6 +67,33 @@ describe Grape::Endpoint do
67
67
  expect(last_response.status).to eq(206)
68
68
  expect(last_response.body).to eq("Hello")
69
69
  end
70
+
71
+ it 'is set as default to 200 for get' do
72
+ memoized_status = nil
73
+ subject.get('/home') do
74
+ memoized_status = status
75
+ "Hello"
76
+ end
77
+
78
+ get '/home'
79
+ expect(last_response.status).to eq(200)
80
+ expect(memoized_status).to eq(200)
81
+ expect(last_response.body).to eq("Hello")
82
+ end
83
+
84
+ it 'is set as default to 201 for post' do
85
+ memoized_status = nil
86
+ subject.post('/home') do
87
+ memoized_status = status
88
+ "Hello"
89
+ end
90
+
91
+ post '/home'
92
+ expect(last_response.status).to eq(201)
93
+ expect(memoized_status).to eq(201)
94
+ expect(last_response.body).to eq("Hello")
95
+ end
96
+
70
97
  end
71
98
 
72
99
  describe '#header' do
@@ -214,33 +241,37 @@ describe Grape::Endpoint do
214
241
  end
215
242
 
216
243
  it 'has as many keys as there are declared params' do
244
+ inner_params = nil
217
245
  subject.get '/declared' do
218
- declared(params).keys.size.should == 4
246
+ inner_params = declared(params).keys
219
247
  ""
220
248
  end
221
-
222
249
  get '/declared?first=present'
223
250
  expect(last_response.status).to eq(200)
251
+ expect(inner_params.size).to eq(4)
224
252
  end
225
253
 
226
254
  it 'has a optional param with default value all the time' do
255
+ inner_params = nil
227
256
  subject.get '/declared' do
228
- params[:third].should == 'third-default'
257
+ inner_params = declared(params)
229
258
  ""
230
259
  end
231
-
232
260
  get '/declared?first=one'
233
261
  expect(last_response.status).to eq(200)
262
+ expect(inner_params[:third]).to eql('third-default')
234
263
  end
235
264
 
236
265
  it 'builds nested params' do
266
+ inner_params = nil
237
267
  subject.get '/declared' do
238
- declared(params)[:nested].keys.size.should == 1
268
+ inner_params = declared(params)
239
269
  ""
240
270
  end
241
271
 
242
272
  get '/declared?first=present&nested[fourth]=1'
243
273
  expect(last_response.status).to eq(200)
274
+ expect(inner_params[:nested].keys.size).to eq 1
244
275
  end
245
276
 
246
277
  it 'builds nested params when given array' do
@@ -254,33 +285,38 @@ describe Grape::Endpoint do
254
285
  optional :fourth
255
286
  end
256
287
  end
288
+ inner_params = nil
257
289
  subject.get '/declared' do
258
- declared(params)[:nested].size.should == 2
290
+ inner_params = declared(params)
259
291
  ""
260
292
  end
261
293
 
262
294
  get '/declared?first=present&nested[][fourth]=1&nested[][fourth]=2'
263
295
  expect(last_response.status).to eq(200)
296
+ expect(inner_params[:nested].size).to eq 2
264
297
  end
265
298
 
266
299
  it 'filters out any additional params that are given' do
300
+ inner_params = nil
267
301
  subject.get '/declared' do
268
- declared(params).key?(:other).should == false
302
+ inner_params = declared(params)
269
303
  ""
270
304
  end
271
-
272
305
  get '/declared?first=one&other=two'
273
306
  expect(last_response.status).to eq(200)
307
+ expect(inner_params.key?(:other)).to eq false
274
308
  end
275
309
 
276
310
  it 'stringifies if that option is passed' do
311
+ inner_params = nil
277
312
  subject.get '/declared' do
278
- declared(params, stringify: true)["first"].should == "one"
313
+ inner_params = declared(params, stringify: true)
279
314
  ""
280
315
  end
281
316
 
282
317
  get '/declared?first=one&other=two'
283
318
  expect(last_response.status).to eq(200)
319
+ expect(inner_params["first"]).to eq "one"
284
320
  end
285
321
 
286
322
  it 'does not include missing attributes if that option is passed' do
@@ -586,6 +622,19 @@ describe Grape::Endpoint do
586
622
  expect(last_response.status).to eq(403)
587
623
  expect(last_response.headers['X-Custom']).to eq('value')
588
624
  end
625
+
626
+ it 'sets the status code for the endpoint' do
627
+ memoized_endpoint = nil
628
+
629
+ subject.get '/hey' do
630
+ memoized_endpoint = self
631
+ error!({ 'dude' => 'rad' }, 403, 'X-Custom' => 'value')
632
+ end
633
+
634
+ get '/hey.json'
635
+
636
+ expect(memoized_endpoint.status).to eq(403)
637
+ end
589
638
  end
590
639
 
591
640
  describe '#redirect' do
@@ -10,17 +10,20 @@ describe Grape::Entity do
10
10
 
11
11
  describe '#present' do
12
12
  it 'sets the object as the body if no options are provided' do
13
+ inner_body = nil
13
14
  subject.get '/example' do
14
15
  present(abc: 'def')
15
- body.should == { abc: 'def' }
16
+ inner_body = body
16
17
  end
17
18
  get '/example'
19
+ expect(inner_body).to eql(abc: 'def')
18
20
  end
19
21
 
20
22
  it 'calls through to the provided entity class if one is given' do
23
+ entity_mock = Object.new
24
+ allow(entity_mock).to receive(:represent)
25
+
21
26
  subject.get '/example' do
22
- entity_mock = Object.new
23
- entity_mock.should_receive(:represent)
24
27
  present Object.new, with: entity_mock
25
28
  end
26
29
  get '/example'
@@ -155,11 +158,13 @@ describe Grape::Entity do
155
158
  end
156
159
 
157
160
  it 'adds a root key to the output if one is given' do
161
+ inner_body = nil
158
162
  subject.get '/example' do
159
163
  present({ abc: 'def' }, root: :root)
160
- body.should == { root: { abc: 'def' } }
164
+ inner_body = body
161
165
  end
162
166
  get '/example'
167
+ expect(inner_body).to eql(root: { abc: 'def' })
163
168
  end
164
169
 
165
170
  [:json, :serializable_hash].each do |format|
@@ -3,7 +3,7 @@ require 'ostruct'
3
3
 
4
4
  describe Grape::Exceptions::ValidationErrors do
5
5
  let(:validation_message) { "FooBar is invalid" }
6
- let(:validation_error) { OpenStruct.new(param: validation_message) }
6
+ let(:validation_error) { OpenStruct.new(params: [validation_message]) }
7
7
 
8
8
  context "message" do
9
9
  context "is not repeated" do
@@ -16,4 +16,34 @@ describe Grape::Exceptions::ValidationErrors do
16
16
  it { expect(message.size).to eq 1 }
17
17
  end
18
18
  end
19
+
20
+ context 'api' do
21
+ subject { Class.new(Grape::API) }
22
+
23
+ def app
24
+ subject
25
+ end
26
+
27
+ it 'can return structured json with separate fields' do
28
+ subject.format :json
29
+ subject.rescue_from Grape::Exceptions::ValidationErrors do |e|
30
+ rack_response e.to_json, 400
31
+ end
32
+ subject.params do
33
+ optional :beer
34
+ optional :wine
35
+ optional :juice
36
+ exactly_one_of :beer, :wine, :juice
37
+ end
38
+ subject.get '/exactly_one_of' do
39
+ 'exactly_one_of works!'
40
+ end
41
+ get '/exactly_one_of', beer: 'string', wine: 'anotherstring'
42
+ expect(last_response.status).to eq(400)
43
+ expect(JSON.parse(last_response.body)).to eq([
44
+ "params" => ["beer", "wine"],
45
+ "messages" => ["are mutually exclusive"]
46
+ ])
47
+ end
48
+ end
19
49
  end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'base64'
3
+
4
+ describe Grape::Middleware::Auth::Base do
5
+ subject do
6
+ Class.new(Grape::API) do
7
+
8
+ http_basic realm: 'my_realm' do |user, password|
9
+ user && password && user == password
10
+ end
11
+ get '/authorized' do
12
+ 'DONE'
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ def app
20
+ subject
21
+ end
22
+
23
+ it 'authenticates if given valid creds' do
24
+ get '/authorized', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'admin')
25
+ expect(last_response.status).to eq(200)
26
+ expect(last_response.body).to eq('DONE')
27
+ end
28
+
29
+ it 'throws a 401 is wrong auth is given' do
30
+ get '/authorized', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'wrong')
31
+ expect(last_response.status).to eq(401)
32
+ end
33
+
34
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Middleware::Auth::DSL do
4
+ subject { Class.new(Grape::API) }
5
+
6
+ let(:block) { ->() {} }
7
+ let(:settings) do
8
+ {
9
+ opaque: "secret",
10
+ proc: block,
11
+ realm: "API Authorization",
12
+ type: :http_digest
13
+ }
14
+ end
15
+
16
+ describe '.auth' do
17
+ it 'stets auth parameters' do
18
+ expect(subject).to receive(:use).with(Grape::Middleware::Auth::Base, settings)
19
+
20
+ subject.auth :http_digest, realm: settings[:realm], opaque: settings[:opaque], &settings[:proc]
21
+ expect(subject.auth).to eq(settings)
22
+ end
23
+
24
+ it 'can be called multiple times' do
25
+ expect(subject).to receive(:use).with(Grape::Middleware::Auth::Base, settings)
26
+ expect(subject).to receive(:use).with(Grape::Middleware::Auth::Base, settings.merge(realm: 'super_secret'))
27
+
28
+ subject.auth :http_digest, realm: settings[:realm], opaque: settings[:opaque], &settings[:proc]
29
+ first_settings = subject.auth
30
+
31
+ subject.auth :http_digest, realm: 'super_secret', opaque: settings[:opaque], &settings[:proc]
32
+
33
+ expect(subject.auth).to eq(settings.merge(realm: 'super_secret'))
34
+ expect(subject.auth.object_id).not_to eq(first_settings.object_id)
35
+ end
36
+
37
+ end
38
+
39
+ describe '.http_basic' do
40
+ it 'stets auth parameters' do
41
+ subject.http_basic realm: 'my_realm', &settings[:proc]
42
+ expect(subject.auth).to eq(realm: 'my_realm', type: :http_basic, proc: block)
43
+ end
44
+ end
45
+
46
+ describe '.http_digest' do
47
+ it 'stets auth parameters' do
48
+ subject.http_digest realm: 'my_realm', opaque: 'my_opaque', &settings[:proc]
49
+ expect(subject.auth).to eq(realm: 'my_realm', type: :http_digest, proc: block, opaque: 'my_opaque')
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ require 'base64'
4
+
5
+ describe Grape::Middleware::Auth::Strategies do
6
+ context 'Basic Auth' do
7
+ def app
8
+ proc = ->(u, p) { u && p && u == p }
9
+ Rack::Builder.new do |b|
10
+ b.use Grape::Middleware::Error
11
+ b.use(Grape::Middleware::Auth::Base, type: :http_basic, proc: proc)
12
+ b.run lambda { |env| [200, {}, ["Hello there."]] }
13
+ end
14
+ end
15
+
16
+ it 'throws a 401 if no auth is given' do
17
+ @proc = lambda { false }
18
+ get '/whatever'
19
+ expect(last_response.status).to eq(401)
20
+ end
21
+
22
+ it 'authenticates if given valid creds' do
23
+ get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'admin')
24
+ expect(last_response.status).to eq(200)
25
+ end
26
+
27
+ it 'throws a 401 is wrong auth is given' do
28
+ get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'wrong')
29
+ expect(last_response.status).to eq(401)
30
+ end
31
+ end
32
+
33
+ context 'Digest MD5 Auth' do
34
+ RSpec::Matchers.define :be_challenge do
35
+ match do |actual_response|
36
+ actual_response.status == 401 &&
37
+ actual_response['WWW-Authenticate'] =~ /^Digest / &&
38
+ actual_response.body.empty?
39
+ end
40
+ end
41
+
42
+ module StrategiesSpec
43
+ class Test < Grape::API
44
+ http_digest(realm: 'Test Api', opaque: 'secret') do |username|
45
+ { 'foo' => 'bar' }[username]
46
+ end
47
+
48
+ get '/test' do
49
+ [{ hey: 'you' }, { there: 'bar' }, { foo: 'baz' }]
50
+ end
51
+ end
52
+ end
53
+
54
+ def app
55
+ StrategiesSpec::Test
56
+ end
57
+
58
+ it 'is a digest authentication challenge' do
59
+ get '/test'
60
+ expect(last_response).to be_challenge
61
+ end
62
+
63
+ it 'throws a 401 if no auth is given' do
64
+ get '/test'
65
+ expect(last_response.status).to eq(401)
66
+ end
67
+
68
+ it 'authenticates if given valid creds' do
69
+ digest_authorize "foo", "bar"
70
+ get '/test'
71
+ expect(last_response.status).to eq(200)
72
+ end
73
+
74
+ it 'throws a 401 if given invalid creds' do
75
+ digest_authorize "bar", "foo"
76
+ get '/test'
77
+ expect(last_response.status).to eq(401)
78
+ end
79
+ end
80
+
81
+ end
@@ -1,6 +1,17 @@
1
1
  require 'spec_helper'
2
+ require 'grape-entity'
2
3
 
3
4
  describe Grape::Middleware::Error do
5
+ module ErrorSpec
6
+ class ErrorEntity < Grape::Entity
7
+ expose :code
8
+ expose :static
9
+
10
+ def static
11
+ 'static text'
12
+ end
13
+ end
14
+ end
4
15
  class ErrApp
5
16
  class << self
6
17
  attr_accessor :error
@@ -13,12 +24,16 @@ describe Grape::Middleware::Error do
13
24
  end
14
25
 
15
26
  def app
27
+ opts = options
16
28
  Rack::Builder.app do
17
- use Grape::Middleware::Error, default_message: 'Aww, hamburgers.'
29
+ use Spec::Support::EndpointFaker
30
+ use Grape::Middleware::Error, opts
18
31
  run ErrApp
19
32
  end
20
33
  end
21
34
 
35
+ let(:options) { { default_message: 'Aww, hamburgers.' } }
36
+
22
37
  it 'sets the status code appropriately' do
23
38
  ErrApp.error = { status: 410 }
24
39
  get '/'
@@ -42,4 +57,21 @@ describe Grape::Middleware::Error do
42
57
  get '/'
43
58
  expect(last_response.body).to eq('Aww, hamburgers.')
44
59
  end
60
+
61
+ context 'with http code' do
62
+ let(:options) { { default_message: 'Aww, hamburgers.' } }
63
+ it 'adds the status code if wanted' do
64
+ ErrApp.error = { message: { code: 200 } }
65
+ get '/'
66
+
67
+ expect(last_response.body).to eq({ code: 200 }.to_json)
68
+ end
69
+
70
+ it 'presents an error message' do
71
+ ErrApp.error = { message: { code: 200, with: ErrorSpec::ErrorEntity } }
72
+ get '/'
73
+
74
+ expect(last_response.body).to eq({ code: 200, static: 'static text' }.to_json)
75
+ end
76
+ end
45
77
  end