grape 0.4.1 → 0.5.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.

@@ -19,12 +19,27 @@ module Grape
19
19
  end
20
20
 
21
21
  def validate!(params)
22
- params = @scope.params(params)
22
+ attributes = AttributesIterator.new(self, @scope, params)
23
+ attributes.each do |resource_params, attr_name|
24
+ if @required || resource_params.has_key?(attr_name)
25
+ validate_param!(attr_name, resource_params)
26
+ end
27
+ end
28
+ end
29
+
30
+ class AttributesIterator
31
+ include Enumerable
32
+
33
+ def initialize(validator, scope, params)
34
+ @attrs = validator.attrs
35
+ @params = scope.params(params)
36
+ @params = (@params.is_a?(Array) ? @params : [@params])
37
+ end
23
38
 
24
- (params.is_a?(Array) ? params : [params]).each do |resource_params|
25
- @attrs.each do |attr_name|
26
- if @required || resource_params.has_key?(attr_name)
27
- validate_param!(attr_name, resource_params)
39
+ def each
40
+ @params.each do |resource_params|
41
+ @attrs.each do |attr_name|
42
+ yield resource_params, attr_name
28
43
  end
29
44
  end
30
45
  end
@@ -76,9 +91,13 @@ module Grape
76
91
 
77
92
  def initialize(api, element, parent, &block)
78
93
  @element = element
79
- @parent = parent
80
- @api = api
94
+ @parent = parent
95
+ @api = api
96
+ @declared_params = []
97
+
81
98
  instance_eval(&block)
99
+
100
+ configure_declared_params
82
101
  end
83
102
 
84
103
  def requires(*attrs)
@@ -116,7 +135,24 @@ module Grape
116
135
  name.to_s
117
136
  end
118
137
 
138
+ protected
139
+
140
+ def push_declared_params(attrs)
141
+ @declared_params.concat attrs
142
+ end
143
+
119
144
  private
145
+
146
+ # Pushes declared params to parent or settings
147
+ def configure_declared_params
148
+ if @parent
149
+ @parent.push_declared_params [element => @declared_params]
150
+ else
151
+ @api.settings.peek[:declared_params] ||= []
152
+ @api.settings[:declared_params].concat @declared_params
153
+ end
154
+ end
155
+
120
156
  def validates(attrs, validations)
121
157
  doc_attrs = { :required => validations.keys.include?(:presence) }
122
158
 
@@ -133,6 +169,10 @@ module Grape
133
169
  doc_attrs[:desc] = desc
134
170
  end
135
171
 
172
+ if default = validations[:default]
173
+ doc_attrs[:default] = default
174
+ end
175
+
136
176
  full_attrs = attrs.collect{ |name| { :name => name, :full_name => full_name(name)} }
137
177
  @api.document_attribute(full_attrs, doc_attrs)
138
178
 
@@ -164,10 +204,6 @@ module Grape
164
204
  end
165
205
  end
166
206
 
167
- def push_declared_params(attrs)
168
- @api.settings.peek[:declared_params] ||= []
169
- @api.settings[:declared_params] += attrs
170
- end
171
207
  end
172
208
 
173
209
  # This module is mixed into the API Class.
@@ -0,0 +1,24 @@
1
+ module Grape
2
+ module Validations
3
+ class DefaultValidator < Validator
4
+ def initialize(attrs, options, required, scope)
5
+ @default = options
6
+ super
7
+ end
8
+
9
+ def validate_param!(attr_name, params)
10
+ params[attr_name] = @default unless params.has_key?(attr_name)
11
+ end
12
+
13
+ def validate!(params)
14
+ params = AttributesIterator.new(self, @scope, params)
15
+ params.each do |resource_params, attr_name|
16
+ if resource_params[attr_name].nil?
17
+ validate_param!(attr_name, resource_params)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
data/lib/grape/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Grape
2
- VERSION = '0.4.1'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -97,6 +97,16 @@ describe Grape::API do
97
97
  # pending 'routes if any media type is allowed'
98
98
  end
99
99
 
100
+ describe '.version using accept_version_header' do
101
+ it_should_behave_like 'versioning' do
102
+ let(:macro_options) do
103
+ {
104
+ :using => :accept_version_header
105
+ }
106
+ end
107
+ end
108
+ end
109
+
100
110
  describe '.represent' do
101
111
  it 'requires a :with option' do
102
112
  expect{ subject.represent Object, {} }.to raise_error(Grape::Exceptions::InvalidWithOptionForRepresent)
@@ -181,6 +191,36 @@ describe Grape::API do
181
191
  end
182
192
  end
183
193
 
194
+ describe '.route_param' do
195
+ it 'adds a parameterized route segment namespace' do
196
+ subject.namespace :users do
197
+ route_param :id do
198
+ get do
199
+ params[:id]
200
+ end
201
+ end
202
+ end
203
+
204
+ get '/users/23'
205
+ last_response.body.should == '23'
206
+ end
207
+
208
+ it 'should be able to define requirements with a single hash' do
209
+ subject.namespace :users do
210
+ route_param :id, :requirements => /[0-9]+/ do
211
+ get do
212
+ params[:id]
213
+ end
214
+ end
215
+ end
216
+
217
+ get '/users/michael'
218
+ last_response.status.should == 404
219
+ get '/users/23'
220
+ last_response.status.should == 200
221
+ end
222
+ end
223
+
184
224
  describe '.route' do
185
225
  it 'allows for no path' do
186
226
  subject.namespace :votes do
@@ -245,6 +285,13 @@ describe Grape::API do
245
285
  versioned_get "/", "v1", :using => :param
246
286
  end
247
287
 
288
+ it 'Accept-Version header versioned APIs' do
289
+ subject.version 'v1', :using => :accept_version_header
290
+ subject.enable_root_route!
291
+
292
+ versioned_get "/", "v1", :using => :accept_version_header
293
+ end
294
+
248
295
  it 'unversioned APIs' do
249
296
  subject.enable_root_route!
250
297
 
@@ -329,6 +376,7 @@ describe Grape::API do
329
376
  send verb, '/', MultiJson.dump(object), { 'CONTENT_TYPE' => 'application/json' }
330
377
  last_response.status.should == (verb == :post ? 201 : 200)
331
378
  last_response.body.should eql MultiJson.dump(object)
379
+ last_request.params.should eql Hash.new
332
380
  end
333
381
  it "stores input in api.request.input" do
334
382
  subject.format :json
@@ -339,6 +387,17 @@ describe Grape::API do
339
387
  last_response.status.should == (verb == :post ? 201 : 200)
340
388
  last_response.body.should eql MultiJson.dump(object).to_json
341
389
  end
390
+ context "chunked transfer encoding" do
391
+ it "stores input in api.request.input" do
392
+ subject.format :json
393
+ subject.send(verb) do
394
+ env['api.request.input']
395
+ end
396
+ send verb, '/', MultiJson.dump(object), { 'CONTENT_TYPE' => 'application/json', 'HTTP_TRANSFER_ENCODING' => 'chunked', 'CONTENT_LENGTH' => nil }
397
+ last_response.status.should == (verb == :post ? 201 : 200)
398
+ last_response.body.should eql MultiJson.dump(object).to_json
399
+ end
400
+ end
342
401
  end
343
402
  end
344
403
  end
@@ -928,6 +987,21 @@ describe Grape::API do
928
987
  last_response.status.should == 400
929
988
  last_response.body.should == 'New Error'
930
989
  end
990
+
991
+ it 'can rescue exceptions raised in the formatter' do
992
+ formatter = stub(:formatter)
993
+ formatter.stub(:call) { raise StandardError }
994
+ Grape::Formatter::Base.stub(:formatter_for) { formatter }
995
+
996
+ subject.rescue_from :all do |e|
997
+ rack_response('Formatter Error', 500)
998
+ end
999
+ subject.get('/formatter_exception') { 'Hello world' }
1000
+
1001
+ get '/formatter_exception'
1002
+ last_response.status.should eql 500
1003
+ last_response.body.should == 'Formatter Error'
1004
+ end
931
1005
  end
932
1006
 
933
1007
  describe '.rescue_from klass, block' do
@@ -1193,6 +1267,15 @@ describe Grape::API do
1193
1267
  end
1194
1268
 
1195
1269
  describe '.parser' do
1270
+ it 'parses data in format requested by content-type' do
1271
+ subject.format :json
1272
+ subject.post '/data' do
1273
+ { :x => params[:x] }
1274
+ end
1275
+ post "/data", '{"x":42}', { 'CONTENT_TYPE' => 'application/json' }
1276
+ last_response.status.should == 201
1277
+ last_response.body.should == '{"x":42}'
1278
+ end
1196
1279
  context 'lambda parser' do
1197
1280
  before :each do
1198
1281
  subject.content_type :txt, "text/plain"
@@ -1240,6 +1323,42 @@ describe Grape::API do
1240
1323
  last_response.body.should eql 'Disallowed type attribute: "symbol"'
1241
1324
  end
1242
1325
  end
1326
+ context "none parser class" do
1327
+ before :each do
1328
+ subject.parser :json, nil
1329
+ subject.put "data" do
1330
+ "body: #{env['api.request.body']}"
1331
+ end
1332
+ end
1333
+ it "does not parse data" do
1334
+ put '/data', 'not valid json', "CONTENT_TYPE" => "application/json"
1335
+ last_response.status.should == 200
1336
+ last_response.body.should == "body: not valid json"
1337
+ end
1338
+ end
1339
+ end
1340
+
1341
+ describe '.default_format' do
1342
+ before :each do
1343
+ subject.format :json
1344
+ subject.default_format :json
1345
+ end
1346
+ it 'returns data in default format' do
1347
+ subject.get '/data' do
1348
+ { :x => 42 }
1349
+ end
1350
+ get "/data"
1351
+ last_response.status.should == 200
1352
+ last_response.body.should == '{"x":42}'
1353
+ end
1354
+ it 'parses data in default format' do
1355
+ subject.post '/data' do
1356
+ { :x => params[:x] }
1357
+ end
1358
+ post "/data", '{"x":42}', "CONTENT_TYPE" => ""
1359
+ last_response.status.should == 201
1360
+ last_response.body.should == '{"x":42}'
1361
+ end
1243
1362
  end
1244
1363
 
1245
1364
  describe '.default_error_status' do
@@ -1601,6 +1720,24 @@ describe Grape::API do
1601
1720
  last_response.body.should == 'yo'
1602
1721
  end
1603
1722
 
1723
+ it 'applies the settings to nested mounted apis' do
1724
+ subject.version 'v1', :using => :path
1725
+
1726
+ subject.namespace :cool do
1727
+ inner_app = Class.new(Grape::API)
1728
+ inner_app.get('/awesome') do
1729
+ "yo"
1730
+ end
1731
+
1732
+ app = Class.new(Grape::API)
1733
+ app.mount inner_app
1734
+ mount app
1735
+ end
1736
+
1737
+ get '/v1/cool/awesome'
1738
+ last_response.body.should == 'yo'
1739
+ end
1740
+
1604
1741
  it 'inherits rescues even when some defined by mounted' do
1605
1742
  subject.rescue_from :all do |e|
1606
1743
  rack_response("rescued from #{e.message}", 202)
@@ -1801,6 +1938,14 @@ describe Grape::API do
1801
1938
  get '/meaning_of_life', {}, { 'HTTP_ACCEPT' => 'text/html' }
1802
1939
  last_response.body.should == { :meaning_of_life => 42 }.to_json
1803
1940
  end
1941
+ it 'can be overwritten with an explicit content type' do
1942
+ subject.get '/meaning_of_life_with_content_type' do
1943
+ content_type "text/plain"
1944
+ { :meaning_of_life => 42 }.to_s
1945
+ end
1946
+ get '/meaning_of_life_with_content_type'
1947
+ last_response.body.should == { :meaning_of_life => 42 }.to_s
1948
+ end
1804
1949
  it 'raised :error from middleware' do
1805
1950
  middleware = Class.new(Grape::Middleware::Base) do
1806
1951
  def before
@@ -1952,18 +2097,33 @@ XML
1952
2097
  end
1953
2098
 
1954
2099
  context "cascading" do
1955
- it "cascades" do
1956
- subject.version 'v1', :using => :path, :cascade => true
1957
- get "/v1/hello"
1958
- last_response.status.should == 404
1959
- last_response.headers["X-Cascade"].should == "pass"
2100
+ context "via version" do
2101
+ it "cascades" do
2102
+ subject.version 'v1', :using => :path, :cascade => true
2103
+ get "/v1/hello"
2104
+ last_response.status.should == 404
2105
+ last_response.headers["X-Cascade"].should == "pass"
2106
+ end
2107
+ it "does not cascade" do
2108
+ subject.version 'v2', :using => :path, :cascade => false
2109
+ get "/v2/hello"
2110
+ last_response.status.should == 404
2111
+ last_response.headers.keys.should_not include "X-Cascade"
2112
+ end
1960
2113
  end
1961
-
1962
- it "does not cascade" do
1963
- subject.version 'v2', :using => :path, :cascade => false
1964
- get "/v2/hello"
1965
- last_response.status.should == 404
1966
- last_response.headers.keys.should_not include "X-Cascade"
2114
+ context "via endpoint" do
2115
+ it "cascades" do
2116
+ subject.cascade true
2117
+ get "/hello"
2118
+ last_response.status.should == 404
2119
+ last_response.headers["X-Cascade"].should == "pass"
2120
+ end
2121
+ it "does not cascade" do
2122
+ subject.cascade false
2123
+ get "/hello"
2124
+ last_response.status.should == 404
2125
+ last_response.headers.keys.should_not include "X-Cascade"
2126
+ end
1967
2127
  end
1968
2128
  end
1969
2129
  end
@@ -64,6 +64,12 @@ describe Grape::Endpoint do
64
64
  get '/headers', nil, { "HTTP_X_GRAPE_CLIENT" => "1" }
65
65
  JSON.parse(last_response.body)["X-Grape-Client"].should == "1"
66
66
  end
67
+ it 'includes headers passed as symbols' do
68
+ env = Rack::MockRequest.env_for("/headers")
69
+ env[:HTTP_SYMBOL_HEADER] = "Goliath passes symbols"
70
+ body = subject.call(env)[2].body.first
71
+ JSON.parse(body)["Symbol-Header"].should == "Goliath passes symbols"
72
+ end
67
73
  end
68
74
 
69
75
  describe '#cookies' do
@@ -167,12 +173,16 @@ describe Grape::Endpoint do
167
173
  subject.params do
168
174
  requires :first
169
175
  optional :second
176
+ optional :third, :default => 'third-default'
177
+ group :nested do
178
+ optional :fourth
179
+ end
170
180
  end
171
181
  end
172
182
 
173
183
  it 'has as many keys as there are declared params' do
174
184
  subject.get '/declared' do
175
- declared(params).keys.size.should == 2
185
+ declared(params).keys.size.should == 4
176
186
  ""
177
187
  end
178
188
 
@@ -180,6 +190,36 @@ describe Grape::Endpoint do
180
190
  last_response.status.should == 200
181
191
  end
182
192
 
193
+ it 'has a optional param with default value all the time' do
194
+ subject.get '/declared' do
195
+ params[:third].should == 'third-default'
196
+ ""
197
+ end
198
+
199
+ get '/declared?first=one'
200
+ last_response.status.should == 200
201
+ end
202
+
203
+ it 'builds nested params' do
204
+ subject.get '/declared' do
205
+ declared(params)[:nested].keys.size.should == 1
206
+ ""
207
+ end
208
+
209
+ get '/declared?first=present&nested[fourth]=1'
210
+ last_response.status.should == 200
211
+ end
212
+
213
+ it 'builds nested params when given array' do
214
+ subject.get '/declared' do
215
+ declared(params)[:nested].size.should == 2
216
+ ""
217
+ end
218
+
219
+ get '/declared?first=present&nested[][fourth]=1&nested[][fourth]=2'
220
+ last_response.status.should == 200
221
+ end
222
+
183
223
  it 'filters out any additional params that are given' do
184
224
  subject.get '/declared' do
185
225
  declared(params).key?(:other).should == false
@@ -238,6 +238,44 @@ XML
238
238
  last_response.body.should == 'abcDef({"example":{"name":"johnnyiller"}})'
239
239
  end
240
240
 
241
+ context "present with multiple entities" do
242
+ let(:user) do
243
+ end
244
+
245
+ before :each do
246
+ end
247
+
248
+ it "present with multiple entities using optional symbol" do
249
+ user = Class.new do
250
+ attr_reader :name
251
+ def initialize(args)
252
+ @name = args[:name] || "no name set"
253
+ end
254
+ end
255
+ user1 = user.new({:name => 'user1'})
256
+ user2 = user.new({:name => 'user2'})
257
+
258
+ entity = Class.new(Grape::Entity)
259
+ entity.expose :name
260
+
261
+ subject.format :json
262
+ subject.get '/example' do
263
+ present :page, 1
264
+ present :user1, user1, :with => entity
265
+ present :user2, user2, :with => entity
266
+ end
267
+ get '/example'
268
+ expect_response_json = {
269
+ "page" => 1,
270
+ "user1" => {"name" => "user1"},
271
+ "user2" => {"name" => "user2"}
272
+ }
273
+ JSON(last_response.body).should == expect_response_json
274
+ end
275
+
276
+ end
277
+
278
+
241
279
  end
242
280
 
243
281
  end