grape 0.3.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +15 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +70 -0
- data/.travis.yml +7 -6
- data/CHANGELOG.md +134 -4
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +5 -2
- data/README.md +551 -116
- data/RELEASING.md +105 -0
- data/Rakefile +29 -8
- data/UPGRADING.md +124 -0
- data/grape.gemspec +3 -3
- data/lib/grape/api.rb +207 -88
- data/lib/grape/cookies.rb +4 -8
- data/lib/grape/endpoint.rb +198 -144
- data/lib/grape/error_formatter/base.rb +5 -7
- data/lib/grape/error_formatter/json.rb +3 -5
- data/lib/grape/error_formatter/txt.rb +1 -3
- data/lib/grape/error_formatter/xml.rb +4 -6
- data/lib/grape/exceptions/base.rb +9 -9
- data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
- data/lib/grape/exceptions/invalid_formatter.rb +1 -4
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -5
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -6
- data/lib/grape/exceptions/missing_mime_type.rb +1 -5
- data/lib/grape/exceptions/missing_option.rb +1 -4
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -4
- data/lib/grape/exceptions/unknown_options.rb +1 -5
- data/lib/grape/exceptions/unknown_validator.rb +1 -3
- data/lib/grape/exceptions/validation.rb +13 -3
- data/lib/grape/exceptions/validation_errors.rb +43 -0
- data/lib/grape/formatter/base.rb +5 -7
- data/lib/grape/formatter/json.rb +0 -3
- data/lib/grape/formatter/serializable_hash.rb +15 -15
- data/lib/grape/formatter/txt.rb +0 -2
- data/lib/grape/formatter/xml.rb +0 -2
- data/lib/grape/http/request.rb +26 -0
- data/lib/grape/locale/en.yml +8 -5
- data/lib/grape/middleware/auth/base.rb +30 -0
- data/lib/grape/middleware/auth/basic.rb +3 -20
- data/lib/grape/middleware/auth/digest.rb +2 -19
- data/lib/grape/middleware/auth/oauth2.rb +31 -24
- data/lib/grape/middleware/base.rb +7 -7
- data/lib/grape/middleware/error.rb +36 -22
- data/lib/grape/middleware/filter.rb +3 -3
- data/lib/grape/middleware/formatter.rb +99 -61
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
- data/lib/grape/middleware/versioner/header.rb +22 -16
- data/lib/grape/middleware/versioner/param.rb +9 -11
- data/lib/grape/middleware/versioner/path.rb +10 -13
- data/lib/grape/middleware/versioner.rb +3 -1
- data/lib/grape/namespace.rb +23 -0
- data/lib/grape/parser/base.rb +3 -5
- data/lib/grape/parser/json.rb +0 -2
- data/lib/grape/parser/xml.rb +0 -2
- data/lib/grape/path.rb +70 -0
- data/lib/grape/route.rb +10 -6
- data/lib/grape/util/content_types.rb +2 -1
- data/lib/grape/util/deep_merge.rb +5 -5
- data/lib/grape/util/hash_stack.rb +13 -2
- data/lib/grape/validations/coerce.rb +11 -10
- data/lib/grape/validations/default.rb +25 -0
- data/lib/grape/validations/presence.rb +7 -3
- data/lib/grape/validations/regexp.rb +2 -5
- data/lib/grape/validations/values.rb +17 -0
- data/lib/grape/validations.rb +161 -54
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +19 -4
- data/spec/grape/api_spec.rb +897 -268
- data/spec/grape/endpoint_spec.rb +283 -66
- data/spec/grape/entity_spec.rb +132 -29
- data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/basic_spec.rb +8 -8
- data/spec/grape/middleware/auth/digest_spec.rb +5 -5
- data/spec/grape/middleware/auth/oauth2_spec.rb +81 -36
- data/spec/grape/middleware/base_spec.rb +8 -13
- data/spec/grape/middleware/error_spec.rb +13 -17
- data/spec/grape/middleware/exception_spec.rb +47 -27
- data/spec/grape/middleware/formatter_spec.rb +103 -41
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
- data/spec/grape/middleware/versioner/header_spec.rb +76 -51
- data/spec/grape/middleware/versioner/param_spec.rb +18 -18
- data/spec/grape/middleware/versioner/path_spec.rb +6 -6
- data/spec/grape/middleware/versioner_spec.rb +5 -2
- data/spec/grape/path_spec.rb +229 -0
- data/spec/grape/util/hash_stack_spec.rb +31 -32
- data/spec/grape/validations/coerce_spec.rb +116 -51
- data/spec/grape/validations/default_spec.rb +123 -0
- data/spec/grape/validations/presence_spec.rb +42 -44
- data/spec/grape/validations/regexp_spec.rb +9 -9
- data/spec/grape/validations/values_spec.rb +138 -0
- data/spec/grape/validations/zh-CN.yml +4 -3
- data/spec/grape/validations_spec.rb +681 -48
- data/spec/shared/versioning_examples.rb +22 -6
- data/spec/spec_helper.rb +3 -2
- data/spec/support/basic_auth_encode_helpers.rb +0 -1
- data/spec/support/content_type_helpers.rb +11 -0
- data/spec/support/versioned_helpers.rb +13 -5
- metadata +34 -84
data/README.md
CHANGED
@@ -8,16 +8,15 @@ providing a simple DSL to easily develop RESTful APIs. It has built-in support
|
|
8
8
|
for common conventions, including multiple formats, subdomain/prefix restriction,
|
9
9
|
content negotiation, versioning and much more.
|
10
10
|
|
11
|
-
[](http://travis-ci.org/intridea/grape)
|
11
|
+
[](http://travis-ci.org/intridea/grape) [](https://codeclimate.com/github/intridea/grape) [](http://inch-pages.github.io/github/intridea/grape) [](https://www.versioneye.com/ruby/grape/0.6.1)
|
12
12
|
|
13
13
|
## Stable Release
|
14
14
|
|
15
|
-
You're reading the documentation for the
|
16
|
-
The current stable release is [0.2.6](https://github.com/intridea/grape/blob/v0.2.6/README.markdown).
|
15
|
+
You're reading the documentation for the stable release of Grape, 0.7.0.
|
17
16
|
|
18
|
-
## Project
|
17
|
+
## Project Resources
|
19
18
|
|
20
|
-
* [Grape Google Group](http://groups.google.com/group/ruby-grape)
|
19
|
+
* Need help? [Grape Google Group](http://groups.google.com/group/ruby-grape)
|
21
20
|
* [Grape Wiki](https://github.com/intridea/grape/wiki)
|
22
21
|
|
23
22
|
## Installation
|
@@ -41,8 +40,7 @@ the context of recreating parts of the Twitter API.
|
|
41
40
|
```ruby
|
42
41
|
module Twitter
|
43
42
|
class API < Grape::API
|
44
|
-
|
45
|
-
version 'v1', :using => :header, :vendor => 'twitter'
|
43
|
+
version 'v1', using: :header, vendor: 'twitter'
|
46
44
|
format :json
|
47
45
|
|
48
46
|
helpers do
|
@@ -56,7 +54,6 @@ module Twitter
|
|
56
54
|
end
|
57
55
|
|
58
56
|
resource :statuses do
|
59
|
-
|
60
57
|
desc "Return a public timeline."
|
61
58
|
get :public_timeline do
|
62
59
|
Status.limit(20)
|
@@ -70,46 +67,47 @@ module Twitter
|
|
70
67
|
|
71
68
|
desc "Return a status."
|
72
69
|
params do
|
73
|
-
requires :id, :
|
70
|
+
requires :id, type: Integer, desc: "Status id."
|
74
71
|
end
|
75
|
-
|
76
|
-
|
72
|
+
route_param :id do
|
73
|
+
get do
|
74
|
+
Status.find(params[:id])
|
75
|
+
end
|
77
76
|
end
|
78
77
|
|
79
78
|
desc "Create a status."
|
80
79
|
params do
|
81
|
-
requires :status, :
|
80
|
+
requires :status, type: String, desc: "Your status."
|
82
81
|
end
|
83
82
|
post do
|
84
83
|
authenticate!
|
85
84
|
Status.create!({
|
86
|
-
:
|
87
|
-
:
|
85
|
+
user: current_user,
|
86
|
+
text: params[:status]
|
88
87
|
})
|
89
88
|
end
|
90
89
|
|
91
90
|
desc "Update a status."
|
92
91
|
params do
|
93
|
-
requires :id, :
|
94
|
-
requires :status, :
|
92
|
+
requires :id, type: String, desc: "Status ID."
|
93
|
+
requires :status, type: String, desc: "Your status."
|
95
94
|
end
|
96
95
|
put ':id' do
|
97
96
|
authenticate!
|
98
97
|
current_user.statuses.find(params[:id]).update({
|
99
|
-
:
|
100
|
-
:
|
98
|
+
user: current_user,
|
99
|
+
text: params[:status]
|
101
100
|
})
|
102
101
|
end
|
103
102
|
|
104
103
|
desc "Delete a status."
|
105
104
|
params do
|
106
|
-
requires :id, :
|
105
|
+
requires :id, type: String, desc: "Status ID."
|
107
106
|
end
|
108
107
|
delete ':id' do
|
109
108
|
authenticate!
|
110
109
|
current_user.statuses.find(params[:id]).destroy
|
111
110
|
end
|
112
|
-
|
113
111
|
end
|
114
112
|
end
|
115
113
|
end
|
@@ -137,13 +135,40 @@ And would respond to the following routes:
|
|
137
135
|
|
138
136
|
Grape will also automatically respond to HEAD and OPTIONS for all GET, and just OPTIONS for all other routes.
|
139
137
|
|
138
|
+
### Alongside Sinatra (or other frameworks)
|
139
|
+
|
140
|
+
If you wish to mount Grape alongside another Rack framework such as Sinatra, you can do so easily using
|
141
|
+
`Rack::Cascade`:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
# Example config.ru
|
145
|
+
|
146
|
+
require 'sinatra'
|
147
|
+
require 'grape'
|
148
|
+
|
149
|
+
class API < Grape::API
|
150
|
+
get :hello do
|
151
|
+
{ hello: "world" }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class Web < Sinatra::Base
|
156
|
+
get '/' do
|
157
|
+
"Hello world."
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
use Rack::Session::Cookie
|
162
|
+
run Rack::Cascade.new [API, Web]
|
163
|
+
```
|
164
|
+
|
140
165
|
### Rails
|
141
166
|
|
142
167
|
Place API files into `app/api` and modify `application.rb`.
|
143
168
|
|
144
169
|
```ruby
|
145
|
-
config.paths.add
|
146
|
-
config.autoload_paths += Dir[
|
170
|
+
config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
|
171
|
+
config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
|
147
172
|
```
|
148
173
|
|
149
174
|
Modify `config/routes`:
|
@@ -176,13 +201,13 @@ end
|
|
176
201
|
|
177
202
|
## Versioning
|
178
203
|
|
179
|
-
There are
|
180
|
-
`:header` and `:param`. The default strategy is `:path`.
|
204
|
+
There are four strategies in which clients can reach your API's endpoints: `:path`,
|
205
|
+
`:header`, `:accept_version_header` and `:param`. The default strategy is `:path`.
|
181
206
|
|
182
207
|
### Path
|
183
208
|
|
184
209
|
```ruby
|
185
|
-
version 'v1', :
|
210
|
+
version 'v1', using: :path
|
186
211
|
```
|
187
212
|
|
188
213
|
Using this versioning strategy, clients should pass the desired version in the URL.
|
@@ -192,22 +217,41 @@ Using this versioning strategy, clients should pass the desired version in the U
|
|
192
217
|
### Header
|
193
218
|
|
194
219
|
```ruby
|
195
|
-
version 'v1', :
|
220
|
+
version 'v1', using: :header, vendor: 'twitter'
|
196
221
|
```
|
197
222
|
|
198
223
|
Using this versioning strategy, clients should pass the desired version in the HTTP `Accept` head.
|
199
224
|
|
200
|
-
curl -H Accept
|
225
|
+
curl -H Accept:application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
|
201
226
|
|
202
227
|
By default, the first matching version is used when no `Accept` header is
|
203
228
|
supplied. This behavior is similar to routing in Rails. To circumvent this default behavior,
|
204
229
|
one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error
|
205
230
|
is returned when no correct `Accept` header is supplied.
|
206
231
|
|
232
|
+
When an invalid `Accept` header is supplied, a `406 Not Acceptable` error is returned if the `:cascade`
|
233
|
+
option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route
|
234
|
+
matches.
|
235
|
+
|
236
|
+
### Accept-Version Header
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
version 'v1', using: :accept_version_header
|
240
|
+
```
|
241
|
+
|
242
|
+
Using this versioning strategy, clients should pass the desired version in the HTTP `Accept-Version` header.
|
243
|
+
|
244
|
+
curl -H "Accept-Version:v1" http://localhost:9292/statuses/public_timeline
|
245
|
+
|
246
|
+
By default, the first matching version is used when no `Accept-Version` header is
|
247
|
+
supplied. This behavior is similar to routing in Rails. To circumvent this default behavior,
|
248
|
+
one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error
|
249
|
+
is returned when no correct `Accept` header is supplied.
|
250
|
+
|
207
251
|
### Param
|
208
252
|
|
209
253
|
```ruby
|
210
|
-
version 'v1', :
|
254
|
+
version 'v1', using: :param
|
211
255
|
```
|
212
256
|
|
213
257
|
Using this versioning strategy, clients should pass the desired version as a request parameter,
|
@@ -218,7 +262,7 @@ either in the URL query string or in the request body.
|
|
218
262
|
The default name for the query parameter is 'apiver' but can be specified using the `:parameter` option.
|
219
263
|
|
220
264
|
```ruby
|
221
|
-
version 'v1', :
|
265
|
+
version 'v1', using: :param, parameter: "v"
|
222
266
|
```
|
223
267
|
|
224
268
|
curl -H http://localhost:9292/statuses/public_timeline?v=v1
|
@@ -246,7 +290,7 @@ get :public_timeline do
|
|
246
290
|
end
|
247
291
|
```
|
248
292
|
|
249
|
-
Parameters are automatically populated from the request body on POST and PUT for form input, JSON and
|
293
|
+
Parameters are automatically populated from the request body on `POST` and `PUT` for form input, JSON and
|
250
294
|
XML content-types.
|
251
295
|
|
252
296
|
The request:
|
@@ -259,7 +303,7 @@ The Grape endpoint:
|
|
259
303
|
|
260
304
|
```ruby
|
261
305
|
post '/statuses' do
|
262
|
-
Status.create!(
|
306
|
+
Status.create!(text: params[:text])
|
263
307
|
end
|
264
308
|
```
|
265
309
|
|
@@ -268,7 +312,7 @@ Multipart POSTs and PUTs are supported as well.
|
|
268
312
|
The request:
|
269
313
|
|
270
314
|
```
|
271
|
-
curl --form image_file
|
315
|
+
curl --form image_file=@image.jpg http://localhost:9292/upload
|
272
316
|
```
|
273
317
|
|
274
318
|
The Grape endpoint:
|
@@ -279,6 +323,14 @@ post "upload" do
|
|
279
323
|
end
|
280
324
|
```
|
281
325
|
|
326
|
+
In the case of conflict between either of:
|
327
|
+
|
328
|
+
* route string parameters
|
329
|
+
* `GET`, `POST` and `PUT` parameters
|
330
|
+
* the contents of the request body on `POST` and `PUT`
|
331
|
+
|
332
|
+
route string parameters will have precedence.
|
333
|
+
|
282
334
|
## Parameter Validation and Coercion
|
283
335
|
|
284
336
|
You can define validations and coercion options for your parameters using a `params` block.
|
@@ -290,6 +342,9 @@ params do
|
|
290
342
|
group :media do
|
291
343
|
requires :url
|
292
344
|
end
|
345
|
+
optional :audio do
|
346
|
+
requires :format, type: Symbol, values: [:mp3, :wav, :aac, :ogg], default: :mp3
|
347
|
+
end
|
293
348
|
end
|
294
349
|
put ':id' do
|
295
350
|
# params[:id] is an Integer
|
@@ -299,8 +354,57 @@ end
|
|
299
354
|
When a type is specified an implicit validation is done after the coercion to ensure
|
300
355
|
the output type is the one declared.
|
301
356
|
|
302
|
-
|
303
|
-
|
357
|
+
Optional parameters can have a default value.
|
358
|
+
|
359
|
+
```ruby
|
360
|
+
params do
|
361
|
+
optional :color, type: String, default: 'blue'
|
362
|
+
optional :random_number, type: Integer, default: -> { Random.rand(1..100) }
|
363
|
+
optional :non_random_number, type: Integer, default: Random.rand(1..100)
|
364
|
+
end
|
365
|
+
```
|
366
|
+
|
367
|
+
Parameters can be restricted to a specific set of values with the `:values` option.
|
368
|
+
|
369
|
+
Default values are eagerly evaluated. Above `:non_random_number` will evaluate to the same
|
370
|
+
number for each call to the endpoint of this `params` block. To have the default evaluate
|
371
|
+
at calltime use a lambda, like `:random_number` above.
|
372
|
+
|
373
|
+
```ruby
|
374
|
+
params do
|
375
|
+
requires :status, type: Symbol, values: [:not_started, :processing, :done]
|
376
|
+
end
|
377
|
+
```
|
378
|
+
|
379
|
+
The :values option can also be supplied with a `Proc` to be evalutated at runtime. For example, given a status
|
380
|
+
model you may want to restrict by hashtags that you have previously defined in the `HashTag` model.
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
params do
|
384
|
+
required :hashtag, type: String, values: -> { Hashtag.all.map(&:tag) }
|
385
|
+
end
|
386
|
+
```
|
387
|
+
|
388
|
+
Parameters can be nested using `group` or by calling `requires` or `optional` with a block.
|
389
|
+
In the above example, this means `params[:media][:url]` is required along with `params[:id]`,
|
390
|
+
and `params[:audio][:format]` is required only if `params[:audio]` is present.
|
391
|
+
With a block, `group`, `requires` and `optional` accept an additional option `type` which can
|
392
|
+
be either `Array` or `Hash`, and defaults to `Array`. Depending on the value, the nested
|
393
|
+
parameters will be treated either as values of a hash or as values of hashes in an array.
|
394
|
+
|
395
|
+
```ruby
|
396
|
+
params do
|
397
|
+
optional :preferences, type: Array do
|
398
|
+
requires :key
|
399
|
+
requires :value
|
400
|
+
end
|
401
|
+
|
402
|
+
requires :name, type: Hash do
|
403
|
+
requires :first_name
|
404
|
+
requires :last_name
|
405
|
+
end
|
406
|
+
end
|
407
|
+
```
|
304
408
|
|
305
409
|
### Namespace Validation and Coercion
|
306
410
|
|
@@ -326,13 +430,30 @@ end
|
|
326
430
|
The `namespace` method has a number of aliases, including: `group`, `resource`,
|
327
431
|
`resources`, and `segment`. Use whichever reads the best for your API.
|
328
432
|
|
433
|
+
You can conveniently define a route parameter as a namespace using `route_param`.
|
434
|
+
|
435
|
+
```ruby
|
436
|
+
namespace :statuses do
|
437
|
+
route_param :id do
|
438
|
+
desc "Returns all replies for a status."
|
439
|
+
get 'replies' do
|
440
|
+
Status.find(params[:id]).replies
|
441
|
+
end
|
442
|
+
desc "Returns a status."
|
443
|
+
get do
|
444
|
+
Status.find(params[:id])
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
```
|
449
|
+
|
329
450
|
### Custom Validators
|
330
451
|
|
331
452
|
```ruby
|
332
453
|
class AlphaNumeric < Grape::Validations::Validator
|
333
454
|
def validate_param!(attr_name, params)
|
334
455
|
unless params[attr_name] =~ /^[[:alnum:]]+$/
|
335
|
-
|
456
|
+
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message: "must consist of alpha-numeric characters"
|
336
457
|
end
|
337
458
|
end
|
338
459
|
end
|
@@ -340,7 +461,7 @@ end
|
|
340
461
|
|
341
462
|
```ruby
|
342
463
|
params do
|
343
|
-
requires :text, :
|
464
|
+
requires :text, alpha_numeric: true
|
344
465
|
end
|
345
466
|
```
|
346
467
|
|
@@ -350,7 +471,7 @@ You can also create custom classes that take parameters.
|
|
350
471
|
class Length < Grape::Validations::SingleOptionValidator
|
351
472
|
def validate_param!(attr_name, params)
|
352
473
|
unless params[attr_name].length <= @option
|
353
|
-
|
474
|
+
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message: "must be at the most #{@option} characters long"
|
354
475
|
end
|
355
476
|
end
|
356
477
|
end
|
@@ -358,26 +479,33 @@ end
|
|
358
479
|
|
359
480
|
```ruby
|
360
481
|
params do
|
361
|
-
requires :text, :
|
482
|
+
requires :text, length: 140
|
362
483
|
end
|
363
484
|
```
|
364
485
|
|
365
486
|
### Validation Errors
|
366
487
|
|
367
|
-
|
488
|
+
Validation and coercion errors are collected and an exception of type `Grape::Exceptions::ValidationErrors` is raised.
|
368
489
|
If the exception goes uncaught it will respond with a status of 400 and an error message.
|
369
|
-
You can rescue a `Grape::Exceptions::
|
490
|
+
You can rescue a `Grape::Exceptions::ValidationErrors` and respond with a custom response.
|
370
491
|
|
371
492
|
```ruby
|
372
|
-
rescue_from Grape::Exceptions::
|
493
|
+
rescue_from Grape::Exceptions::ValidationErrors do |e|
|
373
494
|
Rack::Response.new({
|
374
|
-
|
375
|
-
|
376
|
-
|
495
|
+
status: e.status,
|
496
|
+
message: e.message,
|
497
|
+
errors: e.errors
|
377
498
|
}.to_json, e.status)
|
378
499
|
end
|
379
500
|
```
|
380
501
|
|
502
|
+
The validation errors are grouped by parameter name and can be accessed via ``Grape::Exceptions::ValidationErrors#errors``.
|
503
|
+
|
504
|
+
### I18n
|
505
|
+
|
506
|
+
Grape supports I18n for parameter-related error messages, but will fallback to English if
|
507
|
+
translations for the default locale have not been provided. See [en.yml](lib/grape/locale/en.yml) for message keys.
|
508
|
+
|
381
509
|
## Headers
|
382
510
|
|
383
511
|
Request headers are available through the `headers` helper or from `env` in their original form.
|
@@ -397,18 +525,32 @@ end
|
|
397
525
|
You can set a response header with `header` inside an API.
|
398
526
|
|
399
527
|
```ruby
|
400
|
-
header
|
528
|
+
header 'X-Robots-Tag', 'noindex'
|
529
|
+
```
|
530
|
+
|
531
|
+
When raising `error!`, pass additional headers as arguments.
|
532
|
+
|
533
|
+
```ruby
|
534
|
+
error! 'Unauthorized', 401, 'X-Error-Detail' => 'Invalid token.'
|
401
535
|
```
|
402
536
|
|
403
537
|
## Routes
|
404
538
|
|
405
539
|
Optionally, you can define requirements for your named route parameters using regular
|
406
|
-
expressions. The route will match only if all requirements are met.
|
540
|
+
expressions on namespace or endpoint. The route will match only if all requirements are met.
|
407
541
|
|
408
542
|
```ruby
|
409
|
-
get ':id', :
|
543
|
+
get ':id', requirements: { id: /[0-9]*/ } do
|
410
544
|
Status.find(params[:id])
|
411
545
|
end
|
546
|
+
|
547
|
+
namespace :outer, requirements: { id: /[0-9]*/ } do
|
548
|
+
get :id do
|
549
|
+
end
|
550
|
+
|
551
|
+
get ":id/edit" do
|
552
|
+
end
|
553
|
+
end
|
412
554
|
```
|
413
555
|
|
414
556
|
## Helpers
|
@@ -441,23 +583,84 @@ class API < Grape::API
|
|
441
583
|
end
|
442
584
|
```
|
443
585
|
|
586
|
+
You can define reusable `params` using `helpers`.
|
587
|
+
|
588
|
+
```ruby
|
589
|
+
class API < Grape::API
|
590
|
+
helpers do
|
591
|
+
params :pagination do
|
592
|
+
optional :page, type: Integer
|
593
|
+
optional :per_page, type: Integer
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
desc "Get collection"
|
598
|
+
params do
|
599
|
+
use :pagination # aliases: includes, use_scope
|
600
|
+
end
|
601
|
+
get do
|
602
|
+
Collection.page(params[:page]).per(params[:per_page])
|
603
|
+
end
|
604
|
+
end
|
605
|
+
```
|
606
|
+
|
607
|
+
You can also define reusable `params` using shared helpers.
|
608
|
+
|
609
|
+
```ruby
|
610
|
+
module SharedParams
|
611
|
+
extend Grape::API::Helpers
|
612
|
+
|
613
|
+
params :period do
|
614
|
+
optional :start_date
|
615
|
+
optional :end_date
|
616
|
+
end
|
617
|
+
|
618
|
+
params :pagination do
|
619
|
+
optional :page, type: Integer
|
620
|
+
optional :per_page, type: Integer
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
class API < Grape::API
|
625
|
+
helpers SharedParams
|
626
|
+
|
627
|
+
desc "Get collection"
|
628
|
+
params do
|
629
|
+
use :period, :pagination
|
630
|
+
end
|
631
|
+
get do
|
632
|
+
Collection.from(params[:start_date]).to(params[:end_date])
|
633
|
+
.page(params[:page]).per(params[:per_page])
|
634
|
+
end
|
635
|
+
end
|
636
|
+
```
|
637
|
+
|
638
|
+
## Parameter Documentation
|
639
|
+
|
640
|
+
You can attach additional documentation to `params` using a `documentation` hash.
|
641
|
+
|
642
|
+
```ruby
|
643
|
+
params do
|
644
|
+
optional :first_name, type: String, documentation: { example: 'Jim' }
|
645
|
+
requires :last_name, type: String, documentation: { example: 'Smith' }
|
646
|
+
end
|
647
|
+
```
|
648
|
+
|
444
649
|
## Cookies
|
445
650
|
|
446
651
|
You can set, get and delete your cookies very simply using `cookies` method.
|
447
652
|
|
448
653
|
```ruby
|
449
654
|
class API < Grape::API
|
450
|
-
|
451
655
|
get 'status_count' do
|
452
656
|
cookies[:status_count] ||= 0
|
453
657
|
cookies[:status_count] += 1
|
454
|
-
{ :
|
658
|
+
{ status_count: cookies[:status_count] }
|
455
659
|
end
|
456
660
|
|
457
661
|
delete 'status_count' do
|
458
|
-
{ :
|
662
|
+
{ status_count: cookies.delete(:status_count) }
|
459
663
|
end
|
460
|
-
|
461
664
|
end
|
462
665
|
```
|
463
666
|
|
@@ -465,10 +668,10 @@ Use a hash-based syntax to set more than one value.
|
|
465
668
|
|
466
669
|
```ruby
|
467
670
|
cookies[:status_count] = {
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
671
|
+
value: 0,
|
672
|
+
expires: Time.tomorrow,
|
673
|
+
domain: '.twitter.com',
|
674
|
+
path: '/'
|
472
675
|
}
|
473
676
|
|
474
677
|
cookies[:status_count][:value] +=1
|
@@ -483,7 +686,7 @@ cookies.delete :status_count
|
|
483
686
|
Specify an optional path.
|
484
687
|
|
485
688
|
```ruby
|
486
|
-
cookies.delete :status_count, :
|
689
|
+
cookies.delete :status_count, path: '/'
|
487
690
|
```
|
488
691
|
|
489
692
|
## Redirecting
|
@@ -491,11 +694,11 @@ cookies.delete :status_count, :path => '/'
|
|
491
694
|
You can redirect to a new url temporarily (302) or permanently (301).
|
492
695
|
|
493
696
|
```ruby
|
494
|
-
redirect
|
697
|
+
redirect '/statuses'
|
495
698
|
```
|
496
699
|
|
497
700
|
```ruby
|
498
|
-
redirect
|
701
|
+
redirect '/statuses', permanent: true
|
499
702
|
```
|
500
703
|
|
501
704
|
## Allowed Methods
|
@@ -506,13 +709,11 @@ behavior with `do_not_route_head!`.
|
|
506
709
|
|
507
710
|
``` ruby
|
508
711
|
class API < Grape::API
|
509
|
-
|
510
712
|
do_not_route_head!
|
511
713
|
|
512
714
|
get '/example' do
|
513
715
|
# only responds to GET
|
514
716
|
end
|
515
|
-
|
516
717
|
end
|
517
718
|
```
|
518
719
|
|
@@ -522,19 +723,17 @@ include an "Allow" header listing the supported methods.
|
|
522
723
|
|
523
724
|
```ruby
|
524
725
|
class API < Grape::API
|
525
|
-
|
526
726
|
get '/rt_count' do
|
527
|
-
{ :
|
727
|
+
{ rt_count: current_user.rt_count }
|
528
728
|
end
|
529
729
|
|
530
730
|
params do
|
531
|
-
requires :value, :
|
731
|
+
requires :value, type: Integer, desc: 'Value to add to the rt count.'
|
532
732
|
end
|
533
733
|
put '/rt_count' do
|
534
734
|
current_user.rt_count += params[:value].to_i
|
535
|
-
{ :
|
735
|
+
{ rt_count: current_user.rt_count }
|
536
736
|
end
|
537
|
-
|
538
737
|
end
|
539
738
|
```
|
540
739
|
|
@@ -567,16 +766,43 @@ curl -X DELETE -v http://localhost:3000/rt_count/
|
|
567
766
|
You can abort the execution of an API method by raising errors with `error!`.
|
568
767
|
|
569
768
|
```ruby
|
570
|
-
error!
|
769
|
+
error! 'Access Denied', 401
|
571
770
|
```
|
572
771
|
|
573
772
|
You can also return JSON formatted objects by raising error! and passing a hash
|
574
773
|
instead of a message.
|
575
774
|
|
576
775
|
```ruby
|
577
|
-
error!
|
776
|
+
error!({ error: "unexpected error", detail: "missing widget" }, 500)
|
578
777
|
```
|
579
778
|
|
779
|
+
### Default Error HTTP Status Code
|
780
|
+
|
781
|
+
By default Grape returns a 500 status code from `error!`. You can change this with `default_error_status`.
|
782
|
+
|
783
|
+
``` ruby
|
784
|
+
class API < Grape::API
|
785
|
+
default_error_status 400
|
786
|
+
get '/example' do
|
787
|
+
error! "This should have http status code 400"
|
788
|
+
end
|
789
|
+
end
|
790
|
+
```
|
791
|
+
|
792
|
+
### Handling 404
|
793
|
+
|
794
|
+
For Grape to handle all the 404s for your API, it can be useful to use a catch-all.
|
795
|
+
In its simplest form, it can be like:
|
796
|
+
|
797
|
+
```ruby
|
798
|
+
route :any, '*path' do
|
799
|
+
error! # or something else
|
800
|
+
end
|
801
|
+
```
|
802
|
+
|
803
|
+
It is very crucial to __define this endpoint at the very end of your API__, as it
|
804
|
+
literally accepts every request.
|
805
|
+
|
580
806
|
## Exception Handling
|
581
807
|
|
582
808
|
Grape can be told to rescue all exceptions and return them in the API format.
|
@@ -591,10 +817,12 @@ You can also rescue specific exceptions.
|
|
591
817
|
|
592
818
|
```ruby
|
593
819
|
class Twitter::API < Grape::API
|
594
|
-
rescue_from ArgumentError,
|
820
|
+
rescue_from ArgumentError, UserDefinedError
|
595
821
|
end
|
596
822
|
```
|
597
823
|
|
824
|
+
In this case ```UserDefinedError``` must be inherited from ```StandardError```.
|
825
|
+
|
598
826
|
The error format will match the request format. See "Content-Types" below.
|
599
827
|
|
600
828
|
Custom error formatters for existing and additional types can be defined with a proc.
|
@@ -621,13 +849,13 @@ class Twitter::API < Grape::API
|
|
621
849
|
end
|
622
850
|
```
|
623
851
|
|
624
|
-
You can rescue all exceptions with a code block. The `
|
852
|
+
You can rescue all exceptions with a code block. The `error_response` wrapper
|
625
853
|
automatically sets the default error code and content-type.
|
626
854
|
|
627
855
|
```ruby
|
628
856
|
class Twitter::API < Grape::API
|
629
857
|
rescue_from :all do |e|
|
630
|
-
|
858
|
+
error_response({ message: "rescued from #{e.class.name}" })
|
631
859
|
end
|
632
860
|
end
|
633
861
|
```
|
@@ -648,29 +876,68 @@ Or rescue specific exceptions.
|
|
648
876
|
```ruby
|
649
877
|
class Twitter::API < Grape::API
|
650
878
|
rescue_from ArgumentError do |e|
|
651
|
-
Rack::Response.new([ "ArgumentError: #{e.message}" ], 500)
|
879
|
+
Rack::Response.new([ "ArgumentError: #{e.message}" ], 500).finish
|
652
880
|
end
|
653
881
|
rescue_from NotImplementedError do |e|
|
654
|
-
Rack::Response.new([ "NotImplementedError: #{e.message}" ], 500)
|
882
|
+
Rack::Response.new([ "NotImplementedError: #{e.message}" ], 500).finish
|
655
883
|
end
|
656
884
|
end
|
657
885
|
```
|
658
886
|
|
887
|
+
By default, `rescue_from` will rescue the exceptions listed and all their subclasses.
|
888
|
+
|
889
|
+
Assume you have the following exception classes defined.
|
890
|
+
|
891
|
+
```ruby
|
892
|
+
module APIErrors
|
893
|
+
class ParentError < StandardError; end
|
894
|
+
class ChildError < ParentError; end
|
895
|
+
end
|
896
|
+
```
|
897
|
+
|
898
|
+
Then the following `rescue_from` clause will rescue exceptions of type `APIErrors::ParentError` and its subclasses (in this case `APIErrors::ChildError`).
|
899
|
+
|
900
|
+
```ruby
|
901
|
+
rescue_from APIErrors::ParentError do |e|
|
902
|
+
Rack::Response.new({
|
903
|
+
error: "#{e.class} error",
|
904
|
+
message: e.message
|
905
|
+
}.to_json, e.status).finish
|
906
|
+
end
|
907
|
+
```
|
908
|
+
|
909
|
+
To only rescue the base exception class, set `rescue_subclasses: false`.
|
910
|
+
The code below will rescue exceptions of type `RuntimeError` but _not_ its subclasses.
|
911
|
+
|
912
|
+
```ruby
|
913
|
+
rescue_from RuntimeError, rescue_subclasses: false do |e|
|
914
|
+
Rack::Response.new(
|
915
|
+
status: e.status,
|
916
|
+
message: e.message,
|
917
|
+
errors: e.errors
|
918
|
+
}.to_json, e.status).finish
|
919
|
+
end
|
920
|
+
```
|
921
|
+
|
659
922
|
#### Rails 3.x
|
660
923
|
|
661
|
-
When mounted inside Rails 3.x, errors like "404 Not Found" or
|
662
|
-
handled and rendered by Rails handlers. For instance,
|
663
|
-
raises a 404, which inside rails will ultimately
|
664
|
-
which most likely will get rendered
|
924
|
+
When mounted inside containers, such as Rails 3.x, errors like "404 Not Found" or
|
925
|
+
"406 Not Acceptable" will likely be handled and rendered by Rails handlers. For instance,
|
926
|
+
accessing a nonexistent route "/api/foo" raises a 404, which inside rails will ultimately
|
927
|
+
be translated to an `ActionController::RoutingError`, which most likely will get rendered
|
928
|
+
to a HTML error page.
|
665
929
|
|
666
|
-
Most APIs will enjoy
|
667
|
-
|
930
|
+
Most APIs will enjoy preventing downstream handlers from handling errors. You may set the
|
931
|
+
`:cascade` option to `false` for the entire API or separately on specific `version` definitions,
|
932
|
+
which will remove the `X-Cascade: true` header from API responses.
|
668
933
|
|
669
934
|
```ruby
|
670
|
-
|
935
|
+
cascade false
|
671
936
|
```
|
672
937
|
|
673
|
-
|
938
|
+
```ruby
|
939
|
+
version 'v1', using: :header, vendor: 'twitter', cascade: false
|
940
|
+
```
|
674
941
|
|
675
942
|
## Logging
|
676
943
|
|
@@ -722,9 +989,8 @@ By default, Grape supports _XML_, _JSON_, and _TXT_ content-types. The default f
|
|
722
989
|
|
723
990
|
Serialization takes place automatically. For example, you do not have to call `to_json` in each JSON API implementation.
|
724
991
|
|
725
|
-
Your API can declare which types to support by using `content_type`. Response format
|
726
|
-
|
727
|
-
string, or `Accept` header.
|
992
|
+
Your API can declare which types to support by using `content_type`. Response format is determined by the
|
993
|
+
request's extension, an explicit `format` parameter in the query string, or `Accept` header.
|
728
994
|
|
729
995
|
The following API will only respond to the JSON content-type and will not parse any other input than `application/json`,
|
730
996
|
`application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and `multipart/mixed`. All other requests
|
@@ -736,6 +1002,16 @@ class Twitter::API < Grape::API
|
|
736
1002
|
end
|
737
1003
|
```
|
738
1004
|
|
1005
|
+
When the content-type is omitted, Grape will return a 406 error code unless `default_format` is specified.
|
1006
|
+
The following API will try to parse any data without a content-type using a JSON parser.
|
1007
|
+
|
1008
|
+
```ruby
|
1009
|
+
class Twitter::API < Grape::API
|
1010
|
+
format :json
|
1011
|
+
default_format :json
|
1012
|
+
end
|
1013
|
+
```
|
1014
|
+
|
739
1015
|
If you combine `format` with `rescue_from :all`, errors will be rendered using the same format.
|
740
1016
|
If you do not want this behavior, set the default error formatter with `default_error_formatter`.
|
741
1017
|
|
@@ -773,7 +1049,7 @@ end
|
|
773
1049
|
|
774
1050
|
Built-in formats are the following.
|
775
1051
|
|
776
|
-
* `:json`: use object's `to_json` when available, otherwise call `MultiJson.dump`
|
1052
|
+
* `:json` and `:jsonapi`: use object's `to_json` when available, otherwise call `MultiJson.dump`
|
777
1053
|
* `:xml`: use object's `to_xml` when available, usually via `MultiXml`, otherwise call `to_s`
|
778
1054
|
* `:txt`: use object's `to_txt` when available, otherwise `to_s`
|
779
1055
|
* `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
|
@@ -796,6 +1072,43 @@ The order for choosing the format is the following.
|
|
796
1072
|
* Use the default format, if specified by the `default_format` option.
|
797
1073
|
* Default to `:txt`.
|
798
1074
|
|
1075
|
+
### JSONP
|
1076
|
+
|
1077
|
+
Grape supports JSONP via [Rack::JSONP](https://github.com/rack/rack-contrib), part of the
|
1078
|
+
[rack-contrib](https://github.com/rack/rack-contrib) gem. Add `rack-contrib` to your `Gemfile`.
|
1079
|
+
|
1080
|
+
```ruby
|
1081
|
+
require 'rack/contrib'
|
1082
|
+
|
1083
|
+
class API < Grape::API
|
1084
|
+
use Rack::JSONP
|
1085
|
+
format :json
|
1086
|
+
get '/' do
|
1087
|
+
'Hello World'
|
1088
|
+
end
|
1089
|
+
end
|
1090
|
+
```
|
1091
|
+
|
1092
|
+
### CORS
|
1093
|
+
|
1094
|
+
Grape supports CORS via [Rack::CORS](https://github.com/cyu/rack-cors), part of the
|
1095
|
+
[rack-cors](https://github.com/cyu/rack-cors) gem. Add `rack-cors` to your `Gemfile`,
|
1096
|
+
then use the middleware in your config.ru file.
|
1097
|
+
|
1098
|
+
```ruby
|
1099
|
+
require 'rack/cors'
|
1100
|
+
|
1101
|
+
use Rack::Cors do
|
1102
|
+
allow do
|
1103
|
+
origins '*'
|
1104
|
+
resource '*', headers: :any, methods: :get
|
1105
|
+
end
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
run Twitter::API
|
1109
|
+
|
1110
|
+
```
|
1111
|
+
|
799
1112
|
## Content-type
|
800
1113
|
|
801
1114
|
Content-type is set by the formatter. You can override the content-type of the response at runtime
|
@@ -817,7 +1130,8 @@ section above. It also supports custom data formats. You must declare additional
|
|
817
1130
|
`content_type` and optionally supply a parser via `parser` unless a parser is already available within
|
818
1131
|
Grape to enable a custom format. Such a parser can be a function or a class.
|
819
1132
|
|
820
|
-
|
1133
|
+
With a parser, parsed data is available "as-is" in `env['api.request.body']`.
|
1134
|
+
Without a parser, data is available "as-is" and in `env['api.request.input']`.
|
821
1135
|
|
822
1136
|
The following example is a trivial parser that will assign any input with the "text/custom" content-type
|
823
1137
|
to `:value`. The parameter will be available via `params[:value]` inside the API call.
|
@@ -825,7 +1139,7 @@ to `:value`. The parameter will be available via `params[:value]` inside the API
|
|
825
1139
|
```ruby
|
826
1140
|
module CustomParser
|
827
1141
|
def self.call(object, env)
|
828
|
-
{ :
|
1142
|
+
{ value: object.to_s }
|
829
1143
|
end
|
830
1144
|
end
|
831
1145
|
```
|
@@ -846,6 +1160,8 @@ You can invoke the above API as follows.
|
|
846
1160
|
curl -X PUT -d 'data' 'http://localhost:9292/value' -H Content-Type:text/custom -v
|
847
1161
|
```
|
848
1162
|
|
1163
|
+
You can disable parsing for a content-type with `nil`. For example, `parser :json, nil` will disable JSON parsing altogether. The request data is then available as-is in `env['api.request.body']`.
|
1164
|
+
|
849
1165
|
## RESTful Model Representations
|
850
1166
|
|
851
1167
|
Grape supports a range of ways to present your data with some help from a generic `present` method,
|
@@ -854,23 +1170,22 @@ hash may include `:with`, which defines the entity to expose.
|
|
854
1170
|
|
855
1171
|
### Grape Entities
|
856
1172
|
|
857
|
-
Add the [grape-entity](https://github.com/
|
858
|
-
Please refer to the [grape-entity documentation](https://github.com/
|
1173
|
+
Add the [grape-entity](https://github.com/intridea/grape-entity) gem to your Gemfile.
|
1174
|
+
Please refer to the [grape-entity documentation](https://github.com/intridea/grape-entity/blob/master/README.md)
|
859
1175
|
for more details.
|
860
1176
|
|
861
1177
|
The following example exposes statuses.
|
862
1178
|
|
863
1179
|
```ruby
|
864
1180
|
module API
|
865
|
-
|
866
1181
|
module Entities
|
867
1182
|
class Status < Grape::Entity
|
868
1183
|
expose :user_name
|
869
|
-
expose :text, :
|
870
|
-
expose :ip, :
|
871
|
-
expose :user_type, user_id, :
|
1184
|
+
expose :text, documentation: { type: "string", desc: "Status update text." }
|
1185
|
+
expose :ip, if: { type: :full }
|
1186
|
+
expose :user_type, user_id, if: lambda { |status, options| status.user.public? }
|
872
1187
|
expose :digest { |status, options| Digest::MD5.hexdigest(status.txt) }
|
873
|
-
expose :replies, :
|
1188
|
+
expose :replies, using: API::Status, as: :replies
|
874
1189
|
end
|
875
1190
|
end
|
876
1191
|
|
@@ -878,24 +1193,62 @@ module API
|
|
878
1193
|
version 'v1'
|
879
1194
|
|
880
1195
|
desc 'Statuses index', {
|
881
|
-
:
|
1196
|
+
params: API::Entities::Status.documentation
|
882
1197
|
}
|
883
1198
|
get '/statuses' do
|
884
1199
|
statuses = Status.all
|
885
1200
|
type = current_user.admin? ? :full : :default
|
886
|
-
present statuses, with: API::Entities::Status, :
|
1201
|
+
present statuses, with: API::Entities::Status, type: type
|
887
1202
|
end
|
888
1203
|
end
|
889
1204
|
end
|
890
1205
|
```
|
891
1206
|
|
1207
|
+
You can use entity documentation directly in the params block with `using: Entity.documentation`.
|
1208
|
+
|
1209
|
+
```ruby
|
1210
|
+
module API
|
1211
|
+
class Statuses < Grape::API
|
1212
|
+
version 'v1'
|
1213
|
+
|
1214
|
+
desc 'Create a status', {
|
1215
|
+
requires :all, except: [:ip], using: API::Entities::Status.documentation.except(:id)
|
1216
|
+
}
|
1217
|
+
post '/status' do
|
1218
|
+
Status.create! params
|
1219
|
+
end
|
1220
|
+
end
|
1221
|
+
end
|
1222
|
+
```
|
1223
|
+
|
1224
|
+
You can present with multiple entities using an optional Symbol argument.
|
1225
|
+
|
1226
|
+
```ruby
|
1227
|
+
get '/statuses' do
|
1228
|
+
statuses = Status.all.page(1).per(20)
|
1229
|
+
present :total_page, 10
|
1230
|
+
present :per_page, 20
|
1231
|
+
present :statuses, statuses, with: API::Entities::Status
|
1232
|
+
end
|
1233
|
+
```
|
1234
|
+
|
1235
|
+
The response will be
|
1236
|
+
|
1237
|
+
```
|
1238
|
+
{
|
1239
|
+
total_page: 10,
|
1240
|
+
per_page: 20,
|
1241
|
+
statuses: []
|
1242
|
+
}
|
1243
|
+
```
|
1244
|
+
|
892
1245
|
In addition to separately organizing entities, it may be useful to put them as namespaced
|
893
1246
|
classes underneath the model they represent.
|
894
1247
|
|
895
1248
|
```ruby
|
896
1249
|
class Status
|
897
1250
|
def entity
|
898
|
-
|
1251
|
+
Entity.new(self)
|
899
1252
|
end
|
900
1253
|
|
901
1254
|
class Entity < Grape::Entity
|
@@ -922,6 +1275,13 @@ You can use [Rabl](https://github.com/nesquena/rabl) templates with the help of
|
|
922
1275
|
[grape-rabl](https://github.com/LTe/grape-rabl) gem, which defines a custom Grape Rabl
|
923
1276
|
formatter.
|
924
1277
|
|
1278
|
+
### Active Model Serializers
|
1279
|
+
|
1280
|
+
You can use [Active Model Serializers](https://github.com/rails-api/active_model_serializers) serializers with the help of the
|
1281
|
+
[grape-active_model_serializers](https://github.com/jrhe/grape-active_model_serializers) gem, which defines a custom Grape AMS
|
1282
|
+
formatter.
|
1283
|
+
|
1284
|
+
|
925
1285
|
## Authentication
|
926
1286
|
|
927
1287
|
### Basic and Digest Auth
|
@@ -936,7 +1296,7 @@ end
|
|
936
1296
|
```
|
937
1297
|
|
938
1298
|
```ruby
|
939
|
-
http_digest({ :
|
1299
|
+
http_digest({ realm: 'Test Api', opaque: 'app secret' }) do |username|
|
940
1300
|
# lookup the user's password here
|
941
1301
|
{ 'user1' => 'password1' }[username]
|
942
1302
|
end
|
@@ -971,7 +1331,7 @@ call with `route`.
|
|
971
1331
|
class MyAPI < Grape::API
|
972
1332
|
desc "Returns a description of a parameter."
|
973
1333
|
params do
|
974
|
-
requires :id, :
|
1334
|
+
requires :id, type: Integer, desc: "Identity."
|
975
1335
|
end
|
976
1336
|
get "params/:id" do
|
977
1337
|
route.route_params[params[:id]] # yields the parameter description
|
@@ -996,7 +1356,21 @@ end
|
|
996
1356
|
|
997
1357
|
## Before and After
|
998
1358
|
|
999
|
-
|
1359
|
+
Blocks can be executed before or after every API call, using `before`, `after`,
|
1360
|
+
`before_validation` and `after_validation`.
|
1361
|
+
|
1362
|
+
Before and after callbacks execute in the following order:
|
1363
|
+
|
1364
|
+
1. `before`
|
1365
|
+
2. `before_validation`
|
1366
|
+
3. _validations_
|
1367
|
+
4. `after_validation`
|
1368
|
+
5. _the API call_
|
1369
|
+
6. `after`
|
1370
|
+
|
1371
|
+
Steps 4, 5 and 6 only happen if validation succeeds.
|
1372
|
+
|
1373
|
+
E.g. using `before`:
|
1000
1374
|
|
1001
1375
|
```ruby
|
1002
1376
|
before do
|
@@ -1004,6 +1378,68 @@ before do
|
|
1004
1378
|
end
|
1005
1379
|
```
|
1006
1380
|
|
1381
|
+
The block applies to every API call within and below the current namespace:
|
1382
|
+
|
1383
|
+
```ruby
|
1384
|
+
class MyAPI < Grape::API
|
1385
|
+
get '/' do
|
1386
|
+
"root - #{@blah}"
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
namespace :foo do
|
1390
|
+
before do
|
1391
|
+
@blah = 'blah'
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
get '/' do
|
1395
|
+
"root - foo - #{@blah}"
|
1396
|
+
end
|
1397
|
+
|
1398
|
+
namespace :bar do
|
1399
|
+
get '/' do
|
1400
|
+
"root - foo - bar - #{@blah}"
|
1401
|
+
end
|
1402
|
+
end
|
1403
|
+
end
|
1404
|
+
end
|
1405
|
+
```
|
1406
|
+
|
1407
|
+
The behaviour is then:
|
1408
|
+
|
1409
|
+
```bash
|
1410
|
+
GET / # 'root - '
|
1411
|
+
GET /foo # 'root - foo - blah'
|
1412
|
+
GET /foo/bar # 'root - foo - bar - blah'
|
1413
|
+
```
|
1414
|
+
|
1415
|
+
Params on a `namespace` (or whatever alias you are using) also work when using
|
1416
|
+
`before_validation` or `after_validation`:
|
1417
|
+
|
1418
|
+
```ruby
|
1419
|
+
class MyAPI < Grape::API
|
1420
|
+
params do
|
1421
|
+
requires :blah, type: Integer
|
1422
|
+
end
|
1423
|
+
resource ':blah' do
|
1424
|
+
after_validation do
|
1425
|
+
# if we reach this point validations will have passed
|
1426
|
+
@blah = declared(params, include_missing: false)[:blah]
|
1427
|
+
end
|
1428
|
+
|
1429
|
+
get '/' do
|
1430
|
+
@blah.class
|
1431
|
+
end
|
1432
|
+
end
|
1433
|
+
end
|
1434
|
+
```
|
1435
|
+
|
1436
|
+
The behaviour is then:
|
1437
|
+
|
1438
|
+
```bash
|
1439
|
+
GET /123 # 'Fixnum'
|
1440
|
+
GET /foo # 400 error - 'blah is invalid'
|
1441
|
+
```
|
1442
|
+
|
1007
1443
|
## Anchoring
|
1008
1444
|
|
1009
1445
|
Grape by default anchors all request paths, which means that the request URL
|
@@ -1011,7 +1447,7 @@ should match from start to end to match, otherwise a `404 Not Found` is
|
|
1011
1447
|
returned. However, this is sometimes not what you want, because it is not always
|
1012
1448
|
known upfront what can be expected from the call. This is because Rack-mount by
|
1013
1449
|
default anchors requests to match from the start to the end, or not at all.
|
1014
|
-
Rails solves this problem by using a
|
1450
|
+
Rails solves this problem by using a `anchor: false` option in your routes.
|
1015
1451
|
In Grape this option can be used as well when a method is defined.
|
1016
1452
|
|
1017
1453
|
For instance when you're API needs to get part of an URL, for instance:
|
@@ -1019,7 +1455,7 @@ For instance when you're API needs to get part of an URL, for instance:
|
|
1019
1455
|
```ruby
|
1020
1456
|
class TwitterAPI < Grape::API
|
1021
1457
|
namespace :statuses do
|
1022
|
-
get '/(*:status)', :
|
1458
|
+
get '/(*:status)', anchor: false do
|
1023
1459
|
|
1024
1460
|
end
|
1025
1461
|
end
|
@@ -1093,13 +1529,13 @@ describe Twitter::API do
|
|
1093
1529
|
end
|
1094
1530
|
```
|
1095
1531
|
|
1096
|
-
In Rails, HTTP request tests would go into the `spec/
|
1532
|
+
In Rails, HTTP request tests would go into the `spec/requests` group. You may want your API code to go into
|
1097
1533
|
`app/api` - you can match that layout under `spec` by adding the following in `spec/spec_helper.rb`.
|
1098
1534
|
|
1099
1535
|
```ruby
|
1100
1536
|
RSpec.configure do |config|
|
1101
|
-
config.include RSpec::Rails::RequestExampleGroup, :
|
1102
|
-
:
|
1537
|
+
config.include RSpec::Rails::RequestExampleGroup, type: :request, example_group: {
|
1538
|
+
file_path: /spec\/api/
|
1103
1539
|
}
|
1104
1540
|
end
|
1105
1541
|
```
|
@@ -1112,15 +1548,17 @@ Add API paths to `config/application.rb`.
|
|
1112
1548
|
|
1113
1549
|
```ruby
|
1114
1550
|
# Auto-load API and its subdirectories
|
1115
|
-
config.paths.add "app
|
1116
|
-
config.autoload_paths += Dir[
|
1551
|
+
config.paths.add File.join("app", "api"), glob: File.join("**", "*.rb")
|
1552
|
+
config.autoload_paths += Dir[Rails.root.join("app", "api", "*")]
|
1117
1553
|
```
|
1118
1554
|
|
1119
1555
|
Create `config/initializers/reload_api.rb`.
|
1120
1556
|
|
1121
1557
|
```ruby
|
1122
1558
|
if Rails.env.development?
|
1123
|
-
|
1559
|
+
ActiveSupport::Dependencies.explicitly_unloadable_constants << "Twitter::API"
|
1560
|
+
|
1561
|
+
api_files = Dir[Rails.root.join('app', 'api', '**', '*.rb')]
|
1124
1562
|
api_reloader = ActiveSupport::FileUpdateChecker.new(api_files) do
|
1125
1563
|
Rails.application.reload_routes!
|
1126
1564
|
end
|
@@ -1132,21 +1570,18 @@ end
|
|
1132
1570
|
|
1133
1571
|
See [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on-rails-3-reload-lib-directory-for-each-request/4368838#4368838) for more information.
|
1134
1572
|
|
1135
|
-
|
1136
1573
|
## Performance Monitoring
|
1137
1574
|
|
1138
|
-
Grape integrates with NewRelic via the
|
1575
|
+
Grape integrates with NewRelic via the
|
1576
|
+
[newrelic-grape](https://github.com/flyerhzm/newrelic-grape) gem, and
|
1577
|
+
with Librato Metrics with the [grape-librato](https://github.com/seanmoon/grape-librato) gem.
|
1139
1578
|
|
1140
1579
|
## Contributing to Grape
|
1141
1580
|
|
1142
|
-
Grape is work of
|
1581
|
+
Grape is work of hundreds of contributors. You're encouraged to submit pull requests, propose
|
1143
1582
|
features and discuss issues.
|
1144
1583
|
|
1145
|
-
|
1146
|
-
* Write tests for your new feature or a test that reproduces a bug
|
1147
|
-
* Implement your feature or make a bug fix
|
1148
|
-
* Add a line to `CHANGELOG.markdown` describing your change
|
1149
|
-
* Commit, push and make a pull request. Bonus points for topic branches.
|
1584
|
+
See [CONTRIBUTING](CONTRIBUTING.md).
|
1150
1585
|
|
1151
1586
|
## License
|
1152
1587
|
|
@@ -1154,4 +1589,4 @@ MIT License. See LICENSE for details.
|
|
1154
1589
|
|
1155
1590
|
## Copyright
|
1156
1591
|
|
1157
|
-
Copyright (c) 2010-
|
1592
|
+
Copyright (c) 2010-2013 Michael Bleigh, and Intridea, Inc.
|