grape 0.2.1.1 → 0.2.2
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.
- data/.gitignore +1 -0
- data/CHANGELOG.markdown +23 -2
- data/Gemfile +2 -0
- data/README.markdown +402 -227
- data/grape.gemspec +5 -2
- data/lib/grape.rb +6 -0
- data/lib/grape/api.rb +59 -2
- data/lib/grape/endpoint.rb +49 -9
- data/lib/grape/entity.rb +75 -8
- data/lib/grape/exceptions/base.rb +17 -0
- data/lib/grape/exceptions/validation_error.rb +10 -0
- data/lib/grape/middleware/base.rb +28 -19
- data/lib/grape/middleware/error.rb +11 -3
- data/lib/grape/middleware/formatter.rb +11 -18
- data/lib/grape/middleware/versioner/header.rb +76 -17
- data/lib/grape/util/deep_merge.rb +23 -0
- data/lib/grape/util/hash_stack.rb +12 -3
- data/lib/grape/validations.rb +202 -0
- data/lib/grape/validations/coerce.rb +61 -0
- data/lib/grape/validations/presence.rb +11 -0
- data/lib/grape/validations/regexp.rb +13 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +281 -123
- data/spec/grape/endpoint_spec.rb +69 -4
- data/spec/grape/entity_spec.rb +204 -16
- data/spec/grape/middleware/exception_spec.rb +21 -0
- data/spec/grape/middleware/formatter_spec.rb +19 -0
- data/spec/grape/middleware/versioner/header_spec.rb +159 -88
- data/spec/grape/validations/coerce_spec.rb +129 -0
- data/spec/grape/validations/presence_spec.rb +138 -0
- data/spec/grape/validations/regexp_spec.rb +33 -0
- data/spec/grape/validations_spec.rb +185 -0
- metadata +65 -74
- data/spec/grape_spec.rb +0 -1
data/.gitignore
CHANGED
data/CHANGELOG.markdown
CHANGED
@@ -1,6 +1,27 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.2 (Next Release)
|
2
2
|
====================
|
3
|
-
|
3
|
+
|
4
|
+
Features
|
5
|
+
--------
|
6
|
+
|
7
|
+
* [#201](https://github.com/intridea/grape/pull/201), [#236](https://github.com/intridea/grape/pull/236), [#221](https://github.com/intridea/grape/pull/221): Added coercion and validations support to `params` DSL - [@schmurfy](https://github.com/schmurfy), [@tim-vandecasteele](https://github.com/tim-vandecasteele), [@adamgotterer](https://github.com/adamgotterer).
|
8
|
+
* [#204](https://github.com/intridea/grape/pull/204): Added ability to declare shared `params` at `namespace` level - [@tim-vandecasteele](https://github.com/tim-vandecasteele).
|
9
|
+
* [#234](https://github.com/intridea/grape/pull/234): Added a DSL for creating entities via mixin - [@mbleigh](https://github.com/mbleigh).
|
10
|
+
* [#240](https://github.com/intridea/grape/pull/240): Define API response format from a query string `format` parameter, if specified - [@neetiraj](https://github.com/neetiraj).
|
11
|
+
* Adds Endpoint#declared to easily filter out unexpected params. - [@mbleigh](https://github.com/mbleigh)
|
12
|
+
|
13
|
+
Fixes
|
14
|
+
-----
|
15
|
+
|
16
|
+
* [#248](https://github.com/intridea/grape/pull/248): Fix: API `version` returns last version set - [@narkoz](https://github.com/narkoz).
|
17
|
+
* [#242](https://github.com/intridea/grape/issues/242): Fix: permanent redirect status should be `301`, was `304` - [@adamgotterer](https://github.com/adamgotterer).
|
18
|
+
* [#211](https://github.com/intridea/grape/pull/211): Fix: custom validations are no longer triggered when optional and parameter is not present - [@adamgotterer](https://github.com/adamgotterer).
|
19
|
+
* [#210](https://github.com/intridea/grape/pull/210): Fix: `Endpoint#body_params` causing undefined method 'size' - [@adamgotterer](https://github.com/adamgotterer).
|
20
|
+
* [#205](https://github.com/intridea/grape/pull/205): Fix: Corrected parsing of empty JSON body on POST/PUT - [@tim-vandecasteele](https://github.com/tim-vandecasteele).
|
21
|
+
* [#181](https://github.com/intridea/grape/pull/181): Fix: Corrected JSON serialization of nested hashes containing `Grape::Entity` instances - [@benrosenblum](https://github.com/benrosenblum).
|
22
|
+
* [#203](https://github.com/intridea/grape/pull/203): Added a check to `Entity#serializable_hash` that verifies an entity exists on an object - [@adamgotterer](https://github.com/adamgotterer).
|
23
|
+
* [#208](https://github.com/intridea/grape/pull/208): `Entity#serializable_hash` must also check if attribute is generated by a user supplied block - [@ppadron](https://github.com/ppadron).
|
24
|
+
* [#252](https://github.com/intridea/grape/pull/252): Resources that don't respond to a requested HTTP method return 405 (Method Not Allowed) instead of 404 (Not Found) [@simulacre](https://github.com/simulacre)
|
4
25
|
|
5
26
|
0.2.1 (7/11/2012)
|
6
27
|
=================
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
|
1
|
+
![grape logo](https://github.com/intridea/grape/wiki/grape_logo.png)
|
2
2
|
|
3
3
|
## What is Grape?
|
4
4
|
|
5
|
-
Grape is a REST-like API micro-framework for Ruby. It
|
6
|
-
existing web application frameworks such as Rails and Sinatra by
|
7
|
-
simple DSL to easily
|
8
|
-
conventions
|
9
|
-
versioning.
|
5
|
+
Grape is a REST-like API micro-framework for Ruby. It's designed to run on Rack
|
6
|
+
or complement existing web application frameworks such as Rails and Sinatra by
|
7
|
+
providing a simple DSL to easily develop RESTful APIs. It has built-in support
|
8
|
+
for common conventions, including multiple formats, subdomain/prefix restriction,
|
9
|
+
content negotiation, versioning and much more.
|
10
|
+
|
11
|
+
[![Build Status](https://travis-ci.org/intridea/grape.png?branch=master)](http://travis-ci.org/intridea/grape)
|
10
12
|
|
11
13
|
## Project Tracking
|
12
14
|
|
@@ -23,6 +25,8 @@ If you're using Bundler, add the gem to Gemfile.
|
|
23
25
|
|
24
26
|
gem 'grape'
|
25
27
|
|
28
|
+
Run `bundle install`.
|
29
|
+
|
26
30
|
## Basic Usage
|
27
31
|
|
28
32
|
Grape APIs are Rack applications that are created by subclassing `Grape::API`.
|
@@ -32,6 +36,7 @@ the context of recreating parts of the Twitter API.
|
|
32
36
|
``` ruby
|
33
37
|
class Twitter::API < Grape::API
|
34
38
|
version 'v1', :using => :header, :vendor => 'twitter'
|
39
|
+
format :json
|
35
40
|
|
36
41
|
helpers do
|
37
42
|
def current_user
|
@@ -44,19 +49,30 @@ class Twitter::API < Grape::API
|
|
44
49
|
end
|
45
50
|
|
46
51
|
resource :statuses do
|
52
|
+
|
53
|
+
desc "Returns a public timeline."
|
47
54
|
get :public_timeline do
|
48
55
|
Tweet.limit(20)
|
49
56
|
end
|
50
57
|
|
58
|
+
desc "Returns a personal timeline."
|
51
59
|
get :home_timeline do
|
52
60
|
authenticate!
|
53
61
|
current_user.home_timeline
|
54
62
|
end
|
55
63
|
|
64
|
+
desc "Returns a tweet."
|
65
|
+
params do
|
66
|
+
requires :id, :type => Integer, :desc => "Tweet id."
|
67
|
+
end
|
56
68
|
get '/show/:id' do
|
57
69
|
Tweet.find(params[:id])
|
58
70
|
end
|
59
|
-
|
71
|
+
|
72
|
+
desc "Creates a tweet."
|
73
|
+
params do
|
74
|
+
requires :status, :type => String, :desc => "Your status."
|
75
|
+
end
|
60
76
|
post :update do
|
61
77
|
authenticate!
|
62
78
|
Tweet.create(
|
@@ -66,28 +82,13 @@ class Twitter::API < Grape::API
|
|
66
82
|
end
|
67
83
|
end
|
68
84
|
|
69
|
-
resource :account do
|
70
|
-
before { authenticate! }
|
71
|
-
|
72
|
-
get '/private' do
|
73
|
-
"Congratulations, you found the secret!"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
78
|
-
```
|
79
|
-
|
80
|
-
Optionally, you can define requirements for your named route parameters using regular expressions. The route will match only if
|
81
|
-
all requirements are met.
|
82
|
-
|
83
|
-
```ruby
|
84
|
-
get '/show/:id', :requirements => { :id => /[0-9]*/ } do
|
85
|
-
Tweet.find(params[:id])
|
86
85
|
end
|
87
86
|
```
|
88
87
|
|
89
88
|
## Mounting
|
90
89
|
|
90
|
+
### Rack
|
91
|
+
|
91
92
|
The above sample creates a Rack application that can be run from a rackup *config.ru* file
|
92
93
|
with `rackup`:
|
93
94
|
|
@@ -102,13 +103,21 @@ And would respond to the following routes:
|
|
102
103
|
GET /statuses/show/:id(.json)
|
103
104
|
POST /statuses/update(.json)
|
104
105
|
|
106
|
+
### Rails
|
107
|
+
|
105
108
|
In a Rails application, modify *config/routes*:
|
106
109
|
|
107
110
|
``` ruby
|
108
111
|
mount Twitter::API => "/"
|
109
112
|
```
|
110
113
|
|
111
|
-
|
114
|
+
Note that you will need to restart Rails to pick up changes in your API classes
|
115
|
+
(see [Issue 131](https://github.com/intridea/grape/issues/131)).
|
116
|
+
|
117
|
+
### Modules
|
118
|
+
|
119
|
+
You can mount multiple API implementations inside another one. These don't have to be
|
120
|
+
different versions, but may be components of the same API.
|
112
121
|
|
113
122
|
```ruby
|
114
123
|
class Twitter::API < Grape::API
|
@@ -119,23 +128,23 @@ end
|
|
119
128
|
|
120
129
|
## Versioning
|
121
130
|
|
122
|
-
There are three strategies in which clients can reach your API's endpoints: `:header`,
|
123
|
-
|
131
|
+
There are three strategies in which clients can reach your API's endpoints: `:header`,
|
132
|
+
`:path` and `:param`. The default strategy is `:path`.
|
124
133
|
|
125
134
|
### Header
|
126
135
|
|
127
136
|
```ruby
|
128
|
-
version 'v1', :using => :header
|
137
|
+
version 'v1', :using => :header, :vendor => 'twitter'
|
129
138
|
```
|
130
139
|
|
131
|
-
Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.
|
140
|
+
Using this versioning strategy, clients should pass the desired version in the HTTP `Accept` head.
|
132
141
|
|
133
142
|
curl -H Accept=application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
|
134
143
|
|
135
|
-
By default, the first matching version is used when no Accept header is
|
144
|
+
By default, the first matching version is used when no `Accept` header is
|
136
145
|
supplied. This behavior is similar to routing in Rails. To circumvent this default behavior,
|
137
|
-
one could use the `:strict` option. When this option is set to `true`, a `
|
138
|
-
is returned when no correct Accept header is supplied.
|
146
|
+
one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error
|
147
|
+
is returned when no correct `Accept` header is supplied.
|
139
148
|
|
140
149
|
### Path
|
141
150
|
|
@@ -147,19 +156,18 @@ Using this versioning strategy, clients should pass the desired version in the U
|
|
147
156
|
|
148
157
|
curl -H http://localhost:9292/v1/statuses/public_timeline
|
149
158
|
|
150
|
-
Serialization takes place automatically.
|
151
|
-
|
152
159
|
### Param
|
153
160
|
|
154
161
|
```ruby
|
155
162
|
version 'v1', :using => :param
|
156
163
|
```
|
157
164
|
|
158
|
-
Using this versioning strategy, clients should pass the desired version as a request parameter,
|
165
|
+
Using this versioning strategy, clients should pass the desired version as a request parameter,
|
166
|
+
either in the URL query string or in the request body.
|
159
167
|
|
160
168
|
curl -H http://localhost:9292/events?apiver=v1
|
161
169
|
|
162
|
-
The default name for the query parameter is 'apiver' but can be specified using the
|
170
|
+
The default name for the query parameter is 'apiver' but can be specified using the `:parameter` option.
|
163
171
|
|
164
172
|
```ruby
|
165
173
|
version 'v1', :using => :param, :parameter => "v"
|
@@ -167,14 +175,25 @@ version 'v1', :using => :param, :parameter => "v"
|
|
167
175
|
|
168
176
|
curl -H http://localhost:9292/events?v=v1
|
169
177
|
|
178
|
+
## Describing Methods
|
179
|
+
|
180
|
+
You can add a description to API methods and namespaces.
|
181
|
+
|
182
|
+
``` ruby
|
183
|
+
desc "Returns a reticulated spline."
|
184
|
+
get "spline/:id" do
|
185
|
+
Spline.find(params[:id])
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
170
189
|
## Parameters
|
171
190
|
|
172
|
-
|
191
|
+
Request parameters are available through the `params` hash object. This includes `GET` and `POST` parameters,
|
173
192
|
along with any named parameters you specify in your route strings.
|
174
193
|
|
175
194
|
```ruby
|
176
195
|
get do
|
177
|
-
|
196
|
+
Article.order(params[:sort_by])
|
178
197
|
end
|
179
198
|
```
|
180
199
|
|
@@ -192,9 +211,113 @@ post '/json_endpoint' do
|
|
192
211
|
end
|
193
212
|
```
|
194
213
|
|
214
|
+
## Parameter Validation and Coercion
|
215
|
+
|
216
|
+
You can define validations and coercion options for your parameters using a `params` block.
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
params do
|
220
|
+
requires :id, type: Integer
|
221
|
+
optional :name, type: String, regexp: /^[a-z]+$/
|
222
|
+
|
223
|
+
group :user do
|
224
|
+
requires :first_name
|
225
|
+
requires :last_name
|
226
|
+
end
|
227
|
+
end
|
228
|
+
get ':id' do
|
229
|
+
# params[:id] is an Integer
|
230
|
+
end
|
231
|
+
```
|
232
|
+
|
233
|
+
When a type is specified an implicit validation is done after the coercion to ensure
|
234
|
+
the output type is the one declared.
|
235
|
+
|
236
|
+
Parameters can be nested using `group`. In the above example, this means both
|
237
|
+
`params[:user][:first_name]` and `params[:user][:last_name]` are required next to `params[:id]`.
|
238
|
+
|
239
|
+
### Namespace Validation and Coercion
|
240
|
+
Namespaces allow parameter definitions and apply to every method within the namespace.
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
namespace :shelves do
|
244
|
+
params do
|
245
|
+
requires :shelf_id, type: Integer, desc: "A shelf."
|
246
|
+
end
|
247
|
+
namespace ":shelf_id" do
|
248
|
+
desc "Retrieve a book from a shelf."
|
249
|
+
params do
|
250
|
+
requires :book_id, type: Integer, desc: "A book."
|
251
|
+
end
|
252
|
+
get ":book_id" do
|
253
|
+
# params[:shelf_id] defines a shelf
|
254
|
+
# params[:book_id] defines a book
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
```
|
259
|
+
|
260
|
+
### Custom Validators
|
261
|
+
```ruby
|
262
|
+
class AlphaNumeric < Grape::Validations::Validator
|
263
|
+
def validate_param!(attr_name, params)
|
264
|
+
unless params[attr_name] =~ /^[[:alnum:]]+$/
|
265
|
+
throw :error, :status => 400, :message => "#{attr_name}: must consist of alpha-numeric characters"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
```
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
params do
|
273
|
+
requires :username, :alpha_numeric => true
|
274
|
+
end
|
275
|
+
```
|
276
|
+
|
277
|
+
You can also create custom classes that take parameters.
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
class Length < Grape::Validations::SingleOptionValidator
|
281
|
+
def validate_param!(attr_name, params)
|
282
|
+
unless params[attr_name].length == @option
|
283
|
+
throw :error, :status => 400, :message => "#{attr_name}: must be #{@option} characters long"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
```
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
params do
|
291
|
+
requires :name, :length => 5
|
292
|
+
end
|
293
|
+
```
|
294
|
+
|
295
|
+
### Validation Errors
|
296
|
+
|
297
|
+
When validation and coercion erros occur an exception of type `ValidationError` is raised.
|
298
|
+
If the exception goes uncaught it will respond with a status of 400 and an error message.
|
299
|
+
You can rescue a `ValidationError` and respond with a custom response.
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
rescue_from ValidationError do |e|
|
303
|
+
Rack::Response.new({
|
304
|
+
'status' => e.status,
|
305
|
+
'message' => e.message,
|
306
|
+
'param' => e.param
|
307
|
+
}.to_json, e.status)
|
308
|
+
end
|
309
|
+
```
|
310
|
+
|
195
311
|
## Headers
|
196
312
|
|
197
|
-
Headers are available through the `env` hash object.
|
313
|
+
Headers are available through the `header` helper or the `env` hash object.
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
get do
|
317
|
+
content_type = header['Content-type']
|
318
|
+
...
|
319
|
+
end
|
320
|
+
```
|
198
321
|
|
199
322
|
```ruby
|
200
323
|
get do
|
@@ -203,10 +326,21 @@ get do
|
|
203
326
|
end
|
204
327
|
```
|
205
328
|
|
329
|
+
## Routes
|
330
|
+
|
331
|
+
Optionally, you can define requirements for your named route parameters using regular
|
332
|
+
expressions. The route will match only if all requirements are met.
|
333
|
+
|
334
|
+
```ruby
|
335
|
+
get '/show/:id', :requirements => { :id => /[0-9]*/ } do
|
336
|
+
Tweet.find(params[:id])
|
337
|
+
end
|
338
|
+
```
|
339
|
+
|
206
340
|
## Helpers
|
207
341
|
|
208
342
|
You can define helper methods that your endpoints can use with the `helpers`
|
209
|
-
macro by either giving a block or a module
|
343
|
+
macro by either giving a block or a module.
|
210
344
|
|
211
345
|
``` ruby
|
212
346
|
module MyHelpers
|
@@ -235,7 +369,7 @@ end
|
|
235
369
|
|
236
370
|
## Cookies
|
237
371
|
|
238
|
-
You can set, get and delete your cookies very simply using `cookies` method
|
372
|
+
You can set, get and delete your cookies very simply using `cookies` method.
|
239
373
|
|
240
374
|
``` ruby
|
241
375
|
class API < Grape::API
|
@@ -251,7 +385,7 @@ class API < Grape::API
|
|
251
385
|
end
|
252
386
|
```
|
253
387
|
|
254
|
-
To set more than value use hash-based syntax
|
388
|
+
To set more than value use hash-based syntax.
|
255
389
|
|
256
390
|
``` ruby
|
257
391
|
cookies[:counter] = {
|
@@ -262,30 +396,73 @@ cookies[:counter] = {
|
|
262
396
|
}
|
263
397
|
cookies[:counter][:value] +=1
|
264
398
|
```
|
265
|
-
## Redirect
|
266
399
|
|
267
|
-
|
400
|
+
## Redirecting
|
401
|
+
|
402
|
+
You can redirect to a new url temporarily (302) or permanently (301).
|
268
403
|
|
269
404
|
``` ruby
|
270
405
|
redirect "/new_url"
|
271
406
|
```
|
272
407
|
|
273
|
-
use permanent redirect
|
274
|
-
|
275
408
|
``` ruby
|
276
409
|
redirect "/new_url", :permanent => true
|
277
410
|
```
|
278
411
|
|
279
|
-
##
|
412
|
+
## Allowed Methods
|
280
413
|
|
281
|
-
|
414
|
+
When you add a route for a resource, a route for the HTTP OPTIONS
|
415
|
+
method will also be added. The response to an OPTIONS request will
|
416
|
+
include an Allow header listing the supported methods.
|
417
|
+
|
418
|
+
``` ruby
|
419
|
+
class API < Grape::API
|
420
|
+
get '/counter' do
|
421
|
+
{ :counter => Counter.count }
|
422
|
+
end
|
423
|
+
|
424
|
+
params do
|
425
|
+
requires :value, :type => Integer, :desc => 'value to add to counter'
|
426
|
+
end
|
427
|
+
put '/counter' do
|
428
|
+
{ :counter => Counter.incr(params.value) }
|
429
|
+
end
|
430
|
+
end
|
431
|
+
```
|
432
|
+
|
433
|
+
``` shell
|
434
|
+
curl -v -X OPTIONS http://localhost:3000/counter
|
435
|
+
|
436
|
+
> OPTIONS /counter HTTP/1.1
|
437
|
+
>
|
438
|
+
< HTTP/1.1 204 No Content
|
439
|
+
< Allow: OPTIONS, GET, PUT
|
440
|
+
```
|
441
|
+
|
442
|
+
|
443
|
+
If a request for a resource is made with an unsupported HTTP method, an
|
444
|
+
HTTP 405 (Method Not Allowed) response will be returned.
|
445
|
+
|
446
|
+
``` shell
|
447
|
+
curl -X DELETE -v http://localhost:3000/counter/
|
448
|
+
|
449
|
+
> DELETE /counter/ HTTP/1.1
|
450
|
+
> Host: localhost:3000
|
451
|
+
>
|
452
|
+
< HTTP/1.1 405 Method Not Allowed
|
453
|
+
< Allow: OPTIONS, GET, PUT
|
454
|
+
```
|
455
|
+
|
456
|
+
## Raising Exceptions
|
457
|
+
|
458
|
+
You can abort the execution of an API method by raising errors with `error!`.
|
282
459
|
|
283
460
|
``` ruby
|
284
461
|
error!("Access Denied", 401)
|
285
462
|
```
|
286
463
|
|
287
|
-
You can also return JSON formatted objects
|
288
|
-
|
464
|
+
You can also return JSON formatted objects by raising error! and passing a hash
|
465
|
+
instead of a message.
|
289
466
|
|
290
467
|
``` ruby
|
291
468
|
error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
|
@@ -294,7 +471,7 @@ error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
|
|
294
471
|
## Exception Handling
|
295
472
|
|
296
473
|
Grape can be told to rescue all exceptions and instead return them in
|
297
|
-
|
474
|
+
txt or json formats.
|
298
475
|
|
299
476
|
``` ruby
|
300
477
|
class Twitter::API < Grape::API
|
@@ -360,7 +537,7 @@ end
|
|
360
537
|
class from Ruby's standard library.
|
361
538
|
|
362
539
|
To log messages from within an endpoint, you need to define a helper to make the logger
|
363
|
-
available in the endpoint context
|
540
|
+
available in the endpoint context.
|
364
541
|
|
365
542
|
``` ruby
|
366
543
|
class API < Grape::API
|
@@ -376,7 +553,7 @@ class API < Grape::API
|
|
376
553
|
end
|
377
554
|
```
|
378
555
|
|
379
|
-
You can also set your own logger
|
556
|
+
You can also set your own logger.
|
380
557
|
|
381
558
|
``` ruby
|
382
559
|
class MyLogger
|
@@ -402,8 +579,10 @@ end
|
|
402
579
|
## Content-Types
|
403
580
|
|
404
581
|
By default, Grape supports _XML_, _JSON_, _Atom_, _RSS_, and _text_ content-types.
|
582
|
+
Serialization takes place automatically.
|
583
|
+
|
405
584
|
Your API can declare additional types to support. Response format is determined by the
|
406
|
-
request's extension or `Accept` header.
|
585
|
+
request's extension, an explicit `format` parameter in the query string, or `Accept` header.
|
407
586
|
|
408
587
|
``` ruby
|
409
588
|
class Twitter::API < Grape::API
|
@@ -414,7 +593,8 @@ end
|
|
414
593
|
You can also set the default format. The order for choosing the format is the following.
|
415
594
|
|
416
595
|
* Use the file extension, if specified. If the file is .json, choose the JSON format.
|
417
|
-
* Use the
|
596
|
+
* Use the value of the `format` parameter in the query string, if specified.
|
597
|
+
* Use the format set by the `format` option, if specified.
|
418
598
|
* Attempt to find an acceptable format from the `Accept` header.
|
419
599
|
* Use the default format, if specified by the `default_format` option.
|
420
600
|
* Default to `:txt` otherwise.
|
@@ -422,6 +602,11 @@ You can also set the default format. The order for choosing the format is the fo
|
|
422
602
|
``` ruby
|
423
603
|
class Twitter::API < Grape::API
|
424
604
|
format :json
|
605
|
+
end
|
606
|
+
```
|
607
|
+
|
608
|
+
``` ruby
|
609
|
+
class Twitter::API < Grape::API
|
425
610
|
default_format :json
|
426
611
|
end
|
427
612
|
```
|
@@ -437,77 +622,6 @@ class API < Grape::API
|
|
437
622
|
end
|
438
623
|
```
|
439
624
|
|
440
|
-
## Writing Tests
|
441
|
-
|
442
|
-
You can test a Grape API with RSpec by making HTTP requests and examining the response.
|
443
|
-
|
444
|
-
### Writing Tests with Rack
|
445
|
-
|
446
|
-
Use `rack-test` and define your API as `app`.
|
447
|
-
|
448
|
-
```ruby
|
449
|
-
require 'spec_helper'
|
450
|
-
|
451
|
-
describe Twitter::API do
|
452
|
-
include Rack::Test::Methods
|
453
|
-
|
454
|
-
def app
|
455
|
-
Twitter::API
|
456
|
-
end
|
457
|
-
|
458
|
-
describe Twitter::API do
|
459
|
-
describe "GET /api/v1/statuses" do
|
460
|
-
it "returns an empty array of statuses" do
|
461
|
-
get "/api/v1/statuses"
|
462
|
-
last_response.status.should == 200
|
463
|
-
JSON.parse(response.body).should == []
|
464
|
-
end
|
465
|
-
end
|
466
|
-
describe "GET /api/v1/statuses/:id" do
|
467
|
-
it "returns a status by id" do
|
468
|
-
status = Status.create!
|
469
|
-
get "/api/v1/statuses/#{status.id}"
|
470
|
-
last_response.body.should == status.to_json
|
471
|
-
end
|
472
|
-
end
|
473
|
-
end
|
474
|
-
end
|
475
|
-
```
|
476
|
-
|
477
|
-
### Writing Tests with Rails
|
478
|
-
|
479
|
-
``` ruby
|
480
|
-
require 'spec_helper'
|
481
|
-
|
482
|
-
describe Twitter::API do
|
483
|
-
describe "GET /api/v1/statuses" do
|
484
|
-
it "returns an empty array of statuses" do
|
485
|
-
get "/api/v1/statuses"
|
486
|
-
response.status.should == 200
|
487
|
-
JSON.parse(response.body).should == []
|
488
|
-
end
|
489
|
-
end
|
490
|
-
describe "GET /api/v1/statuses/:id" do
|
491
|
-
it "returns a status by id" do
|
492
|
-
status = Status.create!
|
493
|
-
get "/api/v1/statuses/#{status.id}"
|
494
|
-
resonse.body.should == status.to_json
|
495
|
-
end
|
496
|
-
end
|
497
|
-
end
|
498
|
-
```
|
499
|
-
|
500
|
-
In Rails, HTTP request tests would go into the `spec/request` group. You may want your API code to go into
|
501
|
-
`app/api` - you can match that layout under `spec` by adding the following in `spec/spec_helper.rb`.
|
502
|
-
|
503
|
-
```ruby
|
504
|
-
RSpec.configure do |config|
|
505
|
-
config.include RSpec::Rails::RequestExampleGroup, :type => :request, :example_group => {
|
506
|
-
:file_path => /spec\/api/
|
507
|
-
}
|
508
|
-
end
|
509
|
-
```
|
510
|
-
|
511
625
|
## Reusable Responses with Entities
|
512
626
|
|
513
627
|
Entities are a reusable means for converting Ruby objects to API responses.
|
@@ -518,22 +632,22 @@ ever larger responses, using inheritance.
|
|
518
632
|
|
519
633
|
Entities inherit from Grape::Entity, and define a simple DSL. Exposures can use
|
520
634
|
runtime options to determine which fields should be visible, these options are
|
521
|
-
available to
|
522
|
-
will always be defined. The
|
523
|
-
|
635
|
+
available to `:if`, `:unless`, and `:proc`. The option keys `:version` and `:collection`
|
636
|
+
will always be defined. The `:version` key is defined as `api.version`. The
|
637
|
+
`:collection` key is boolean, and defined as `true` if the object presented is an
|
524
638
|
array.
|
525
639
|
|
526
640
|
* `expose SYMBOLS`
|
527
641
|
* define a list of fields which will always be exposed
|
528
642
|
* `expose SYMBOLS, HASH`
|
529
|
-
* HASH keys include
|
530
|
-
*
|
531
|
-
* `expose SYMBOL, {:format_with => :formatter}`
|
643
|
+
* HASH keys include `:if`, `:unless`, `:proc`, `:as`, `:using`, `:format_with`, `:documentation`
|
644
|
+
* `:if` and `:unless` accept hashes (passed during runtime) or procs (arguments are object and options)
|
645
|
+
* `expose SYMBOL, { :format_with => :formatter }`
|
532
646
|
* expose a value, formatting it first
|
533
|
-
*
|
534
|
-
* `expose SYMBOL, {:as => "alias"}`
|
647
|
+
* `:format_with` can only be applied to one exposure at a time
|
648
|
+
* `expose SYMBOL, { :as => "alias" }`
|
535
649
|
* Expose a value, changing its hash key from SYMBOL to alias
|
536
|
-
*
|
650
|
+
* `:as` can only be applied to one exposure at a time
|
537
651
|
* `expose SYMBOL BLOCK`
|
538
652
|
* block arguments are object and options
|
539
653
|
* expose the value returned by the block
|
@@ -544,10 +658,10 @@ module API
|
|
544
658
|
module Entities
|
545
659
|
class User < Grape::Entity
|
546
660
|
expose :first_name, :last_name
|
547
|
-
expose :field, :documentation => {:type => "string", :desc => "words go here"}
|
548
|
-
expose :email, :if => {:type => :full}
|
549
|
-
expose :user_type, user_id, :if => lambda{|user,options| user.confirmed?}
|
550
|
-
expose(:name){|user,options| [user.first_name, user.last_name].join(' ')}
|
661
|
+
expose :field, :documentation => { :type => "string", :desc => "words go here" }
|
662
|
+
expose :email, :if => { :type => :full }
|
663
|
+
expose :user_type, user_id, :if => lambda{ |user,options| user.confirmed? }
|
664
|
+
expose(:name) { |user,options| [ user.first_name, user.last_name ].join(' ')}
|
551
665
|
expose :latest_status, :using => API::Status, :as => :status
|
552
666
|
end
|
553
667
|
end
|
@@ -562,11 +676,32 @@ module API
|
|
562
676
|
end
|
563
677
|
```
|
564
678
|
|
679
|
+
#### Using the Exposure DSL
|
680
|
+
|
681
|
+
Grape ships with a DSL to easily define entities within the context
|
682
|
+
of an existing class:
|
683
|
+
|
684
|
+
```ruby
|
685
|
+
class User
|
686
|
+
include Grape::Entity::DSL
|
687
|
+
|
688
|
+
entity :name, :email do
|
689
|
+
expose :advanced, if: :conditional
|
690
|
+
end
|
691
|
+
end
|
692
|
+
```
|
693
|
+
|
694
|
+
The above will automatically create a `User::Entity` class and
|
695
|
+
define properties on it according to the same rules as above. If
|
696
|
+
you only want to define simple exposures you don't have to supply
|
697
|
+
a block and can instead simply supply a list of comma-separated
|
698
|
+
symbols.
|
699
|
+
|
565
700
|
### Using Entities
|
566
701
|
|
567
|
-
Once an entity is defined, it can be used within endpoints, by calling
|
702
|
+
Once an entity is defined, it can be used within endpoints, by calling `present`. The `present`
|
568
703
|
method accepts two arguments, the object to be presented and the options associated with it. The
|
569
|
-
options hash must always include
|
704
|
+
options hash must always include `:with`, which defines the entity to expose.
|
570
705
|
|
571
706
|
If the entity includes documentation it can be included in an endpoint's description.
|
572
707
|
|
@@ -587,32 +722,58 @@ module API
|
|
587
722
|
end
|
588
723
|
```
|
589
724
|
|
725
|
+
### Entity Organization
|
726
|
+
|
727
|
+
In addition to separately organizing entities, it may be useful to
|
728
|
+
put them as namespaced classes underneath the model they represent.
|
729
|
+
For example:
|
730
|
+
|
731
|
+
```ruby
|
732
|
+
class User
|
733
|
+
def entity
|
734
|
+
Entity.new(self)
|
735
|
+
end
|
736
|
+
|
737
|
+
class Entity < Grape::Entity
|
738
|
+
expose :name, :email
|
739
|
+
end
|
740
|
+
end
|
741
|
+
```
|
742
|
+
|
743
|
+
If you organize your entities this way, Grape will automatically
|
744
|
+
detect the `Entity` class and use it to present your models. In
|
745
|
+
this example, if you added `present User.new` to your endpoint,
|
746
|
+
Grape would automatically detect that there is a `User::Entity`
|
747
|
+
class and use that as the representative entity. This can still
|
748
|
+
be overridden by using the `:with` option or an explicit
|
749
|
+
`represents` call.
|
750
|
+
|
590
751
|
### Caveats
|
591
752
|
|
592
753
|
Entities with duplicate exposure names and conditions will silently overwrite one another.
|
593
|
-
In the following example, when object
|
594
|
-
However, when object
|
754
|
+
In the following example, when `object.check` equals "foo", only `field_a` will be exposed.
|
755
|
+
However, when `object.check` equals "bar" both `field_b` and `foo` will be exposed.
|
595
756
|
|
596
757
|
```ruby
|
597
758
|
module API
|
598
759
|
module Entities
|
599
760
|
class User < Grape::Entity
|
600
|
-
expose :
|
601
|
-
expose :
|
761
|
+
expose :field_a, :foo, :if => lambda { |object, options| object.check == "foo" }
|
762
|
+
expose :field_b, :foo, :if => lambda { |object, options| object.check == "bar" }
|
602
763
|
end
|
603
764
|
end
|
604
765
|
end
|
605
766
|
```
|
606
767
|
|
607
|
-
This can be problematic, when you have mixed collections. Using
|
768
|
+
This can be problematic, when you have mixed collections. Using `respond_to?` is safer.
|
608
769
|
|
609
770
|
```ruby
|
610
771
|
module API
|
611
772
|
module Entities
|
612
773
|
class User < Grape::Entity
|
613
|
-
expose :
|
614
|
-
expose :
|
615
|
-
expose :foo, :if => lambda{object,options| object.respond_to?(:foo)}
|
774
|
+
expose :field_a, :if => lambda { |object, options| object.check == "foo" }
|
775
|
+
expose :field_b, :if => lambda { |object, options| object.check == "bar" }
|
776
|
+
expose :foo, :if => lambda { |object, options| object.respond_to?(:foo) }
|
616
777
|
end
|
617
778
|
end
|
618
779
|
end
|
@@ -620,31 +781,10 @@ end
|
|
620
781
|
|
621
782
|
## Describing and Inspecting an API
|
622
783
|
|
623
|
-
Grape
|
624
|
-
|
625
|
-
This can be useful for generating documentation. If the response
|
626
|
-
requires documentation, consider using an entity.
|
627
|
-
|
628
|
-
``` ruby
|
629
|
-
class TwitterAPI < Grape::API
|
630
|
-
|
631
|
-
version 'v1'
|
632
|
-
|
633
|
-
desc "Retrieves the API version number."
|
634
|
-
get "version" do
|
635
|
-
api.version
|
636
|
-
end
|
637
|
-
|
638
|
-
desc "Reverses a string.", { :params =>
|
639
|
-
{ "s" => { :desc => "string to reverse", :type => "string" }}
|
640
|
-
}
|
641
|
-
get "reverse" do
|
642
|
-
params[:s].reverse
|
643
|
-
end
|
644
|
-
end
|
645
|
-
```
|
784
|
+
Grape routes can be reflected at runtime. This can notably be useful for generating
|
785
|
+
documentation.
|
646
786
|
|
647
|
-
Grape
|
787
|
+
Grape exposes arrays of API versions and compiled routes. Each route
|
648
788
|
contains a `route_prefix`, `route_version`, `route_namespace`, `route_method`,
|
649
789
|
`route_path` and `route_params`. The description and the optional hash that
|
650
790
|
follows the API path may contain any number of keys and its values are also
|
@@ -654,56 +794,20 @@ accessible via dynamically-generated `route_[name]` functions.
|
|
654
794
|
TwitterAPI::versions # yields [ 'v1', 'v2' ]
|
655
795
|
TwitterAPI::routes # yields an array of Grape::Route objects
|
656
796
|
TwitterAPI::routes[0].route_version # yields 'v1'
|
657
|
-
TwitterAPI::routes[0].route_description #
|
797
|
+
TwitterAPI::routes[0].route_description # etc.
|
658
798
|
```
|
659
799
|
|
660
|
-
|
661
|
-
|
662
|
-
``` ruby
|
663
|
-
class StringAPI < Grape::API
|
664
|
-
get "split/:string", { :params => { "token" => "a token" }, :optional_params => { "limit" => "the limit" } } do
|
665
|
-
params[:string].split(params[:token], (params[:limit] || 0))
|
666
|
-
end
|
667
|
-
end
|
668
|
-
|
669
|
-
StringAPI::routes[0].route_params # yields a hash {"string" => "", "token" => "a token"}
|
670
|
-
StringAPI::routes[0].route_optional_params # yields a hash {"limit" => "the limit"}
|
671
|
-
```
|
672
|
-
|
673
|
-
It's possible to retrieve the information about the current route from within an API call with `route`.
|
800
|
+
It's possible to retrieve the information about the current route from within an API
|
801
|
+
call with `route`.
|
674
802
|
|
675
803
|
``` ruby
|
676
804
|
class MyAPI < Grape::API
|
677
|
-
desc "Returns a description of a parameter."
|
678
|
-
|
679
|
-
|
680
|
-
end
|
681
|
-
end
|
682
|
-
```
|
683
|
-
|
684
|
-
You can use this information to create a helper that will check if the request has
|
685
|
-
all required parameters:
|
686
|
-
|
687
|
-
``` ruby
|
688
|
-
class MyAPI < Grape::API
|
689
|
-
|
690
|
-
helpers do
|
691
|
-
def validate_request!
|
692
|
-
# skip validation if no parameter is declared
|
693
|
-
return unless route.route_params
|
694
|
-
route.route_params.each do |k, v|
|
695
|
-
if !params.has_key? k
|
696
|
-
error!("Missing field: #{k}", 400)
|
697
|
-
end
|
698
|
-
end
|
699
|
-
end
|
805
|
+
desc "Returns a description of a parameter."
|
806
|
+
params do
|
807
|
+
requires :id, :type => Integer, :desc => "Identity."
|
700
808
|
end
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
desc "creates a new item resource", :params => { :name => 'name is a required parameter' }
|
705
|
-
post :items do
|
706
|
-
...
|
809
|
+
get "params/:id" do
|
810
|
+
route.route_params[params[:id]] # yields the parameter description
|
707
811
|
end
|
708
812
|
end
|
709
813
|
```
|
@@ -712,12 +816,10 @@ end
|
|
712
816
|
|
713
817
|
Grape by default anchors all request paths, which means that the request URL
|
714
818
|
should match from start to end to match, otherwise a `404 Not Found` is
|
715
|
-
returned.
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
to the end, or not at all. Rails solves this problem by using a `:anchor =>
|
720
|
-
false` option in your routes.
|
819
|
+
returned. However, this is sometimes not what you want, because it is not always
|
820
|
+
known upfront what can be expected from the call. This is because Rack-mount by
|
821
|
+
default anchors requests to match from the start to the end, or not at all.
|
822
|
+
Rails solves this problem by using a `:anchor => false` option in your routes.
|
721
823
|
In Grape this option can be used as well when a method is defined.
|
722
824
|
|
723
825
|
For instance when you're API needs to get part of an URL, for instance:
|
@@ -739,13 +841,87 @@ specification and using the `PATH_INFO` Rack environment variable, using
|
|
739
841
|
`env["PATH_INFO"]`. This will hold everything that comes after the '/urls/'
|
740
842
|
part.
|
741
843
|
|
742
|
-
##
|
844
|
+
## Writing Tests
|
845
|
+
|
846
|
+
You can test a Grape API with RSpec by making HTTP requests and examining the response.
|
847
|
+
|
848
|
+
### Writing Tests with Rack
|
849
|
+
|
850
|
+
Use `rack-test` and define your API as `app`.
|
851
|
+
|
852
|
+
```ruby
|
853
|
+
require 'spec_helper'
|
854
|
+
|
855
|
+
describe Twitter::API do
|
856
|
+
include Rack::Test::Methods
|
857
|
+
|
858
|
+
def app
|
859
|
+
Twitter::API
|
860
|
+
end
|
861
|
+
|
862
|
+
describe Twitter::API do
|
863
|
+
describe "GET /api/v1/statuses" do
|
864
|
+
it "returns an empty array of statuses" do
|
865
|
+
get "/api/v1/statuses"
|
866
|
+
last_response.status.should == 200
|
867
|
+
JSON.parse(response.body).should == []
|
868
|
+
end
|
869
|
+
end
|
870
|
+
describe "GET /api/v1/statuses/:id" do
|
871
|
+
it "returns a status by id" do
|
872
|
+
status = Status.create!
|
873
|
+
get "/api/v1/statuses/#{status.id}"
|
874
|
+
last_response.body.should == status.to_json
|
875
|
+
end
|
876
|
+
end
|
877
|
+
end
|
878
|
+
end
|
879
|
+
```
|
880
|
+
|
881
|
+
### Writing Tests with Rails
|
882
|
+
|
883
|
+
``` ruby
|
884
|
+
require 'spec_helper'
|
885
|
+
|
886
|
+
describe Twitter::API do
|
887
|
+
describe "GET /api/v1/statuses" do
|
888
|
+
it "returns an empty array of statuses" do
|
889
|
+
get "/api/v1/statuses"
|
890
|
+
response.status.should == 200
|
891
|
+
JSON.parse(response.body).should == []
|
892
|
+
end
|
893
|
+
end
|
894
|
+
describe "GET /api/v1/statuses/:id" do
|
895
|
+
it "returns a status by id" do
|
896
|
+
status = Status.create!
|
897
|
+
get "/api/v1/statuses/#{status.id}"
|
898
|
+
resonse.body.should == status.to_json
|
899
|
+
end
|
900
|
+
end
|
901
|
+
end
|
902
|
+
```
|
903
|
+
|
904
|
+
In Rails, HTTP request tests would go into the `spec/request` group. You may want your API code to go into
|
905
|
+
`app/api` - you can match that layout under `spec` by adding the following in `spec/spec_helper.rb`.
|
906
|
+
|
907
|
+
```ruby
|
908
|
+
RSpec.configure do |config|
|
909
|
+
config.include RSpec::Rails::RequestExampleGroup, :type => :request, :example_group => {
|
910
|
+
:file_path => /spec\/api/
|
911
|
+
}
|
912
|
+
end
|
913
|
+
```
|
914
|
+
|
915
|
+
## Contributing to Grape
|
916
|
+
|
917
|
+
Grape is work of dozens of contributors. You're encouraged to submit pull requests, propose
|
918
|
+
features and discuss issues.
|
743
919
|
|
744
920
|
* Fork the project
|
745
921
|
* Write tests for your new feature or a test that reproduces a bug
|
746
922
|
* Implement your feature or make a bug fix
|
747
923
|
* Do not mess with Rakefile, version or history
|
748
|
-
* Commit, push and make a pull request. Bonus points for
|
924
|
+
* Commit, push and make a pull request. Bonus points for topic branches.
|
749
925
|
|
750
926
|
## License
|
751
927
|
|
@@ -753,5 +929,4 @@ MIT License. See LICENSE for details.
|
|
753
929
|
|
754
930
|
## Copyright
|
755
931
|
|
756
|
-
Copyright (c) 2010-2012 Michael Bleigh and Intridea, Inc.
|
757
|
-
|
932
|
+
Copyright (c) 2010-2012 Michael Bleigh, and Intridea, Inc.
|