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
@@ -54,6 +54,7 @@ describe Grape::Middleware::Error do
54
54
 
55
55
  it 'does not trap errors by default' do
56
56
  @app ||= Rack::Builder.app do
57
+ use Spec::Support::EndpointFaker
57
58
  use Grape::Middleware::Error
58
59
  run ExceptionApp
59
60
  end
@@ -63,6 +64,7 @@ describe Grape::Middleware::Error do
63
64
  context 'with rescue_all set to true' do
64
65
  it 'sets the message appropriately' do
65
66
  @app ||= Rack::Builder.app do
67
+ use Spec::Support::EndpointFaker
66
68
  use Grape::Middleware::Error, rescue_all: true
67
69
  run ExceptionApp
68
70
  end
@@ -72,6 +74,7 @@ describe Grape::Middleware::Error do
72
74
 
73
75
  it 'defaults to a 500 status' do
74
76
  @app ||= Rack::Builder.app do
77
+ use Spec::Support::EndpointFaker
75
78
  use Grape::Middleware::Error, rescue_all: true
76
79
  run ExceptionApp
77
80
  end
@@ -81,6 +84,7 @@ describe Grape::Middleware::Error do
81
84
 
82
85
  it 'is possible to specify a different default status code' do
83
86
  @app ||= Rack::Builder.app do
87
+ use Spec::Support::EndpointFaker
84
88
  use Grape::Middleware::Error, rescue_all: true, default_status: 500
85
89
  run ExceptionApp
86
90
  end
@@ -90,6 +94,7 @@ describe Grape::Middleware::Error do
90
94
 
91
95
  it 'is possible to return errors in json format' do
92
96
  @app ||= Rack::Builder.app do
97
+ use Spec::Support::EndpointFaker
93
98
  use Grape::Middleware::Error, rescue_all: true, format: :json
94
99
  run ExceptionApp
95
100
  end
@@ -99,6 +104,7 @@ describe Grape::Middleware::Error do
99
104
 
100
105
  it 'is possible to return hash errors in json format' do
101
106
  @app ||= Rack::Builder.app do
107
+ use Spec::Support::EndpointFaker
102
108
  use Grape::Middleware::Error, rescue_all: true, format: :json
103
109
  run ErrorHashApp
104
110
  end
@@ -109,6 +115,7 @@ describe Grape::Middleware::Error do
109
115
 
110
116
  it 'is possible to return errors in jsonapi format' do
111
117
  @app ||= Rack::Builder.app do
118
+ use Spec::Support::EndpointFaker
112
119
  use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
113
120
  run ExceptionApp
114
121
  end
@@ -118,6 +125,7 @@ describe Grape::Middleware::Error do
118
125
 
119
126
  it 'is possible to return hash errors in jsonapi format' do
120
127
  @app ||= Rack::Builder.app do
128
+ use Spec::Support::EndpointFaker
121
129
  use Grape::Middleware::Error, rescue_all: true, format: :jsonapi
122
130
  run ErrorHashApp
123
131
  end
@@ -128,6 +136,7 @@ describe Grape::Middleware::Error do
128
136
 
129
137
  it 'is possible to return errors in xml format' do
130
138
  @app ||= Rack::Builder.app do
139
+ use Spec::Support::EndpointFaker
131
140
  use Grape::Middleware::Error, rescue_all: true, format: :xml
132
141
  run ExceptionApp
133
142
  end
@@ -137,6 +146,7 @@ describe Grape::Middleware::Error do
137
146
 
138
147
  it 'is possible to return hash errors in xml format' do
139
148
  @app ||= Rack::Builder.app do
149
+ use Spec::Support::EndpointFaker
140
150
  use Grape::Middleware::Error, rescue_all: true, format: :xml
141
151
  run ErrorHashApp
142
152
  end
@@ -147,6 +157,7 @@ describe Grape::Middleware::Error do
147
157
 
148
158
  it 'is possible to specify a custom formatter' do
149
159
  @app ||= Rack::Builder.app do
160
+ use Spec::Support::EndpointFaker
150
161
  use Grape::Middleware::Error, rescue_all: true,
151
162
  format: :custom,
152
163
  error_formatters: {
@@ -162,6 +173,7 @@ describe Grape::Middleware::Error do
162
173
 
163
174
  it 'does not trap regular error! codes' do
164
175
  @app ||= Rack::Builder.app do
176
+ use Spec::Support::EndpointFaker
165
177
  use Grape::Middleware::Error
166
178
  run AccessDeniedApp
167
179
  end
@@ -171,6 +183,7 @@ describe Grape::Middleware::Error do
171
183
 
172
184
  it 'responds to custom Grape exceptions appropriately' do
173
185
  @app ||= Rack::Builder.app do
186
+ use Spec::Support::EndpointFaker
174
187
  use Grape::Middleware::Error, rescue_all: false
175
188
  run CustomErrorApp
176
189
  end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Validations::AtLeastOneOfValidator do
4
+ describe '#validate!' do
5
+ let(:scope) do
6
+ Struct.new(:opts) do
7
+ def params(arg); end
8
+ end
9
+ end
10
+ let(:at_least_one_of_params) { [:beer, :wine, :grapefruit] }
11
+ let(:validator) { described_class.new(at_least_one_of_params, {}, false, scope.new) }
12
+
13
+ context 'when all restricted params are present' do
14
+ let(:params) { { beer: true, wine: true, grapefruit: true } }
15
+
16
+ it 'does not raise a validation exception' do
17
+ expect(validator.validate!(params)).to eql params
18
+ end
19
+
20
+ context 'mixed with other params' do
21
+ let(:mixed_params) { params.merge!(other: true, andanother: true) }
22
+
23
+ it 'does not raise a validation exception' do
24
+ expect(validator.validate!(mixed_params)).to eql mixed_params
25
+ end
26
+ end
27
+ end
28
+
29
+ context 'when a subset of restricted params are present' do
30
+ let(:params) { { beer: true, grapefruit: true } }
31
+
32
+ it 'does not raise a validation exception' do
33
+ expect(validator.validate!(params)).to eql params
34
+ end
35
+ end
36
+
37
+ context 'when params keys come as strings' do
38
+ let(:params) { { 'beer' => true, 'grapefruit' => true } }
39
+
40
+ it 'does not raise a validation exception' do
41
+ expect(validator.validate!(params)).to eql params
42
+ end
43
+ end
44
+
45
+ context 'when none of the restricted params is selected' do
46
+ let(:params) { { somethingelse: true } }
47
+
48
+ it 'raises a validation exception' do
49
+ expect {
50
+ validator.validate! params
51
+ }.to raise_error(Grape::Exceptions::Validation)
52
+ end
53
+ end
54
+
55
+ context 'when exactly one of the restricted params is selected' do
56
+ let(:params) { { beer: true, somethingelse: true } }
57
+
58
+ it 'does not raise a validation exception' do
59
+ expect(validator.validate!(params)).to eql params
60
+ end
61
+ end
62
+ end
63
+ end
@@ -63,7 +63,7 @@ describe Grape::Validations::ExactlyOneOfValidator do
63
63
  context 'when exactly one of the restricted params is selected' do
64
64
  let(:params) { { beer: true, somethingelse: true } }
65
65
 
66
- it 'params' do
66
+ it 'does not raise a validation exception' do
67
67
  expect(validator.validate!(params)).to eql params
68
68
  end
69
69
  end
@@ -1,142 +1,179 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Validations::PresenceValidator do
4
+ subject do
5
+ Class.new(Grape::API) do
6
+ format :json
7
+ end
8
+ end
9
+ def app
10
+ subject
11
+ end
4
12
 
5
- module ValidationsSpec
6
- module PresenceValidatorSpec
7
- class API < Grape::API
8
- default_format :json
9
-
10
- resource :bacons do
11
- get do
12
- "All the bacon"
13
- end
14
- end
15
-
16
- params do
17
- requires :id, regexp: /^[0-9]+$/
18
- end
19
- post do
20
- { ret: params[:id] }
21
- end
22
-
23
- params do
24
- requires :name, :company
25
- end
13
+ context "without validation" do
14
+ before do
15
+ subject.resource :bacons do
26
16
  get do
27
- "Hello"
28
- end
29
-
30
- params do
31
- requires :user, type: Hash do
32
- requires :first_name
33
- requires :last_name
34
- end
35
- end
36
- get '/nested' do
37
- "Nested"
38
- end
39
-
40
- params do
41
- requires :admin, type: Hash do
42
- requires :admin_name
43
- requires :super, type: Hash do
44
- requires :user, type: Hash do
45
- requires :first_name
46
- requires :last_name
47
- end
48
- end
49
- end
50
- end
51
- get '/nested_triple' do
52
- "Nested triple"
17
+ "All the bacon"
53
18
  end
54
19
  end
55
20
  end
21
+ it 'does not validate for any params' do
22
+ get "/bacons"
23
+ expect(last_response.status).to eq(200)
24
+ expect(last_response.body).to eq("All the bacon".to_json)
25
+ end
56
26
  end
57
27
 
58
- def app
59
- ValidationsSpec::PresenceValidatorSpec::API
60
- end
61
-
62
- it 'does not validate for any params' do
63
- get "/bacons"
64
- expect(last_response.status).to eq(200)
65
- expect(last_response.body).to eq("All the bacon".to_json)
28
+ context "with a required regexp parameter supplied in the POST body" do
29
+ before do
30
+ subject.format :json
31
+ subject.params do
32
+ requires :id, regexp: /^[0-9]+$/
33
+ end
34
+ subject.post do
35
+ { ret: params[:id] }
36
+ end
37
+ end
38
+ it 'validates id' do
39
+ post '/'
40
+ expect(last_response.status).to eq(400)
41
+ expect(last_response.body).to eq('{"error":"id is missing"}')
42
+
43
+ io = StringIO.new('{"id" : "a56b"}')
44
+ post '/', {}, 'rack.input' => io, 'CONTENT_TYPE' => 'application/json', 'CONTENT_LENGTH' => io.length
45
+ expect(last_response.body).to eq('{"error":"id is invalid"}')
46
+ expect(last_response.status).to eq(400)
47
+
48
+ io = StringIO.new('{"id" : 56}')
49
+ post '/', {}, 'rack.input' => io, 'CONTENT_TYPE' => 'application/json', 'CONTENT_LENGTH' => io.length
50
+ expect(last_response.body).to eq('{"ret":56}')
51
+ expect(last_response.status).to eq(201)
52
+ end
66
53
  end
67
54
 
68
- it 'validates id' do
69
- post '/'
70
- expect(last_response.status).to eq(400)
71
- expect(last_response.body).to eq('{"error":"id is missing"}')
72
-
73
- io = StringIO.new('{"id" : "a56b"}')
74
- post '/', {}, 'rack.input' => io, 'CONTENT_TYPE' => 'application/json', 'CONTENT_LENGTH' => io.length
75
- expect(last_response.body).to eq('{"error":"id is invalid"}')
76
- expect(last_response.status).to eq(400)
77
-
78
- io = StringIO.new('{"id" : 56}')
79
- post '/', {}, 'rack.input' => io, 'CONTENT_TYPE' => 'application/json', 'CONTENT_LENGTH' => io.length
80
- expect(last_response.body).to eq('{"ret":56}')
81
- expect(last_response.status).to eq(201)
55
+ context "with a required non-empty string" do
56
+ before do
57
+ subject.params do
58
+ requires :email, type: String, regexp: /^\S+$/
59
+ end
60
+ subject.get do
61
+ "Hello"
62
+ end
63
+ end
64
+ it 'requires when missing' do
65
+ get '/'
66
+ expect(last_response.status).to eq(400)
67
+ expect(last_response.body).to eq('{"error":"email is missing, email is invalid"}')
68
+ end
69
+ it 'requires when empty' do
70
+ get '/', email: ""
71
+ expect(last_response.status).to eq(400)
72
+ expect(last_response.body).to eq('{"error":"email is invalid"}')
73
+ end
74
+ it "valid when set" do
75
+ get '/', email: "bob@example.com"
76
+ expect(last_response.status).to eq(200)
77
+ expect(last_response.body).to eq("Hello".to_json)
78
+ end
82
79
  end
83
80
 
84
- it 'validates name, company' do
85
- get '/'
86
- expect(last_response.status).to eq(400)
87
- expect(last_response.body).to eq('{"error":"name is missing"}')
88
-
89
- get '/', name: "Bob"
90
- expect(last_response.status).to eq(400)
91
- expect(last_response.body).to eq('{"error":"company is missing"}')
92
-
93
- get '/', name: "Bob", company: "TestCorp"
94
- expect(last_response.status).to eq(200)
95
- expect(last_response.body).to eq("Hello".to_json)
81
+ context "with required parameters and no type" do
82
+ before do
83
+ subject.params do
84
+ requires :name, :company
85
+ end
86
+ subject.get do
87
+ "Hello"
88
+ end
89
+ end
90
+ it 'validates name, company' do
91
+ get '/'
92
+ expect(last_response.status).to eq(400)
93
+ expect(last_response.body).to eq('{"error":"name is missing"}')
94
+
95
+ get '/', name: "Bob"
96
+ expect(last_response.status).to eq(400)
97
+ expect(last_response.body).to eq('{"error":"company is missing"}')
98
+
99
+ get '/', name: "Bob", company: "TestCorp"
100
+ expect(last_response.status).to eq(200)
101
+ expect(last_response.body).to eq("Hello".to_json)
102
+ end
96
103
  end
97
104
 
98
- it 'validates nested parameters' do
99
- get '/nested'
100
- expect(last_response.status).to eq(400)
101
- expect(last_response.body).to eq('{"error":"user is missing, user[first_name] is missing, user[last_name] is missing"}')
102
-
103
- get '/nested', user: { first_name: "Billy" }
104
- expect(last_response.status).to eq(400)
105
- expect(last_response.body).to eq('{"error":"user[last_name] is missing"}')
106
-
107
- get '/nested', user: { first_name: "Billy", last_name: "Bob" }
108
- expect(last_response.status).to eq(200)
109
- expect(last_response.body).to eq("Nested".to_json)
105
+ context "with nested parameters" do
106
+ before do
107
+ subject.params do
108
+ requires :user, type: Hash do
109
+ requires :first_name
110
+ requires :last_name
111
+ end
112
+ end
113
+ subject.get '/nested' do
114
+ "Nested"
115
+ end
116
+ end
117
+ it 'validates nested parameters' do
118
+ get '/nested'
119
+ expect(last_response.status).to eq(400)
120
+ expect(last_response.body).to eq('{"error":"user is missing, user[first_name] is missing, user[last_name] is missing"}')
121
+
122
+ get '/nested', user: { first_name: "Billy" }
123
+ expect(last_response.status).to eq(400)
124
+ expect(last_response.body).to eq('{"error":"user[last_name] is missing"}')
125
+
126
+ get '/nested', user: { first_name: "Billy", last_name: "Bob" }
127
+ expect(last_response.status).to eq(200)
128
+ expect(last_response.body).to eq("Nested".to_json)
129
+ end
110
130
  end
111
131
 
112
- it 'validates triple nested parameters' do
113
- get '/nested_triple'
114
- expect(last_response.status).to eq(400)
115
- expect(last_response.body).to include '{"error":"admin is missing'
116
-
117
- get '/nested_triple', user: { first_name: "Billy" }
118
- expect(last_response.status).to eq(400)
119
- expect(last_response.body).to include '{"error":"admin is missing'
120
-
121
- get '/nested_triple', admin: { super: { first_name: "Billy" } }
122
- expect(last_response.status).to eq(400)
123
- expect(last_response.body).to eq('{"error":"admin[admin_name] is missing, admin[super][user] is missing, admin[super][user][first_name] is missing, admin[super][user][last_name] is missing"}')
124
-
125
- get '/nested_triple', super: { user: { first_name: "Billy", last_name: "Bob" } }
126
- expect(last_response.status).to eq(400)
127
- expect(last_response.body).to include '{"error":"admin is missing'
128
-
129
- get '/nested_triple', admin: { super: { user: { first_name: "Billy" } } }
130
- expect(last_response.status).to eq(400)
131
- expect(last_response.body).to eq('{"error":"admin[admin_name] is missing, admin[super][user][last_name] is missing"}')
132
-
133
- get '/nested_triple', admin: { admin_name: 'admin', super: { user: { first_name: "Billy" } } }
134
- expect(last_response.status).to eq(400)
135
- expect(last_response.body).to eq('{"error":"admin[super][user][last_name] is missing"}')
136
-
137
- get '/nested_triple', admin: { admin_name: 'admin', super: { user: { first_name: "Billy", last_name: "Bob" } } }
138
- expect(last_response.status).to eq(200)
139
- expect(last_response.body).to eq("Nested triple".to_json)
132
+ context "with triply nested required parameters" do
133
+ before do
134
+ subject.params do
135
+ requires :admin, type: Hash do
136
+ requires :admin_name
137
+ requires :super, type: Hash do
138
+ requires :user, type: Hash do
139
+ requires :first_name
140
+ requires :last_name
141
+ end
142
+ end
143
+ end
144
+ end
145
+ subject.get '/nested_triple' do
146
+ "Nested triple"
147
+ end
148
+ end
149
+ it 'validates triple nested parameters' do
150
+ get '/nested_triple'
151
+ expect(last_response.status).to eq(400)
152
+ expect(last_response.body).to include '{"error":"admin is missing'
153
+
154
+ get '/nested_triple', user: { first_name: "Billy" }
155
+ expect(last_response.status).to eq(400)
156
+ expect(last_response.body).to include '{"error":"admin is missing'
157
+
158
+ get '/nested_triple', admin: { super: { first_name: "Billy" } }
159
+ expect(last_response.status).to eq(400)
160
+ expect(last_response.body).to eq('{"error":"admin[admin_name] is missing, admin[super][user] is missing, admin[super][user][first_name] is missing, admin[super][user][last_name] is missing"}')
161
+
162
+ get '/nested_triple', super: { user: { first_name: "Billy", last_name: "Bob" } }
163
+ expect(last_response.status).to eq(400)
164
+ expect(last_response.body).to include '{"error":"admin is missing'
165
+
166
+ get '/nested_triple', admin: { super: { user: { first_name: "Billy" } } }
167
+ expect(last_response.status).to eq(400)
168
+ expect(last_response.body).to eq('{"error":"admin[admin_name] is missing, admin[super][user][last_name] is missing"}')
169
+
170
+ get '/nested_triple', admin: { admin_name: 'admin', super: { user: { first_name: "Billy" } } }
171
+ expect(last_response.status).to eq(400)
172
+ expect(last_response.body).to eq('{"error":"admin[super][user][last_name] is missing"}')
173
+
174
+ get '/nested_triple', admin: { admin_name: 'admin', super: { user: { first_name: "Billy", last_name: "Bob" } } }
175
+ expect(last_response.status).to eq(200)
176
+ expect(last_response.body).to eq("Nested triple".to_json)
177
+ end
140
178
  end
141
-
142
179
  end