grape 0.2.0 → 0.2.1
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/CHANGELOG.markdown +68 -0
- data/README.markdown +274 -18
- data/lib/grape.rb +1 -0
- data/lib/grape/api.rb +8 -3
- data/lib/grape/endpoint.rb +53 -6
- data/lib/grape/entity.rb +88 -1
- data/lib/grape/middleware/base.rb +4 -4
- data/lib/grape/middleware/error.rb +2 -2
- data/lib/grape/middleware/formatter.rb +13 -14
- data/lib/grape/middleware/versioner.rb +2 -0
- data/lib/grape/middleware/versioner/param.rb +44 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +166 -95
- data/spec/grape/endpoint_spec.rb +108 -3
- data/spec/grape/entity_spec.rb +74 -3
- data/spec/grape/middleware/formatter_spec.rb +6 -6
- data/spec/grape/middleware/versioner/param_spec.rb +58 -0
- data/spec/grape/middleware/versioner_spec.rb +4 -0
- data/spec/support/versioned_helpers.rb +9 -1
- metadata +31 -27
data/CHANGELOG.markdown
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
0.2.1 (7/11/2012)
|
2
|
+
=================
|
3
|
+
|
4
|
+
* [#186](https://github.com/intridea/grape/issues/186): Fix: helpers allow multiple calls with modules and blocks - [@ppadron](https://github.com/ppadron).
|
5
|
+
* [#188](https://github.com/intridea/grape/pull/188): Fix: multi-method routes append '(.:format)' only once - [@kainosnoema](https://github.com/kainosnoema).
|
6
|
+
* [#64](https://github.com/intridea/grape/issues/64), [#180](https://github.com/intridea/grape/pull/180): Added support to get request bodies as parameters - [@bobbytables](https://github.com/bobbytables).
|
7
|
+
* [#175](https://github.com/intridea/grape/pull/175): Added support for API versioning based on a request parameter - [@jackcasey](https://github.com/jackcasey).
|
8
|
+
* [#168](https://github.com/intridea/grape/pull/168): Fix: Formatter can parse symbol keys in the headers hash - [@netmask](https://github.com/netmask).
|
9
|
+
* [#169](https://github.com/intridea/grape/pull/169): Silence multi_json deprecation warnings - [@whiteley](https://github.com/whiteley).
|
10
|
+
* [#166](https://github.com/intridea/grape/pull/166): Added support for `redirect`, including permanent and temporary - [@allenwei](https://github.com/allenwei).
|
11
|
+
* [#159](https://github.com/intridea/grape/pull/159): Added `:requirements` to routes, allowing to use reserved characters in paths - [@gaiottino](https://github.com/gaiottino).
|
12
|
+
* [#156](https://github.com/intridea/grape/pull/156): Added support for adding formatters to entities - [@bobbytables](https://github.com/bobbytables).
|
13
|
+
* [#183](https://github.com/intridea/grape/pull/183): Added ability to include documentation in entities - [@flah00](https://github.com/flah00).
|
14
|
+
* [#189](https://github.com/intridea/grape/pull/189): `HEAD` requests no longer return a body - [@stephencelis](https://github.com/stephencelis).
|
15
|
+
* [#97](https://github.com/intridea/grape/issues/97): Allow overriding `Content-Type` - [@dblock](https://github.com/dblock).
|
16
|
+
|
17
|
+
0.2.0 (3/28/2012)
|
18
|
+
=================
|
19
|
+
|
20
|
+
* Added support for inheriting exposures from entities - [@bobbytables](https://github.com/bobbytables).
|
21
|
+
* Extended formatting with `default_format` - [@dblock](https://github.com/dblock).
|
22
|
+
* Added support for cookies - [@lukaszsliwa](https://github.com/lukaszsliwa).
|
23
|
+
* Added support for declaring additional content-types - [@joeyAghion](https://github.com/joeyAghion).
|
24
|
+
* Added support for HTTP PATCH - [@LTe](https://github.com/LTe).
|
25
|
+
* Added support for describing, documenting and reflecting APIs - [@dblock](https://github.com/dblock).
|
26
|
+
* Added support for anchoring and vendoring - [@jwkoelewijn](https://github.com/jwkoelewijn).
|
27
|
+
* Added support for HTTP OPTIONS - [@grimen](https://github.com/grimen).
|
28
|
+
* Added support for silencing logger - [@evansj](https://github.com/evansj).
|
29
|
+
* Added support for helper modules - [@freelancing-god](https://github.com/freelancing-god).
|
30
|
+
* Added support for Accept header-based versioning - [@jch](https://github.com/jch), [@rodzyn](https://github.com/rodzyn).
|
31
|
+
* Added support for mounting APIs and other Rack applications within APIs - [@mbleigh](https://github.com/mbleigh).
|
32
|
+
* Added entities, multiple object representations - [@mbleigh](https://github.com/mbleigh).
|
33
|
+
* Added ability to handle XML in the incoming request body - [@jwillis](https://github.com/jwillis).
|
34
|
+
* Added support for a configurable logger - [@mbleigh](https://github.com/mbleigh).
|
35
|
+
* Added support for before and after filters - [@mbleigh](https://github.com/mbleigh).
|
36
|
+
* Extended `rescue_from`, which can now take a block - [@dblock](https://github.com/dblock).
|
37
|
+
|
38
|
+
|
39
|
+
0.1.5 (6/14/2011)
|
40
|
+
==================
|
41
|
+
|
42
|
+
* Extended exception handling to all exceptions - [@dblock](https://github.com/dblock).
|
43
|
+
* Added support for returning JSON objects from within error blocks - [@dblock](https://github.com/dblock).
|
44
|
+
* Added support for handling incoming JSON in body - [@tedkulp](https://github.com/tedkulp).
|
45
|
+
* Added support for HTTP digest authentication - [@daddz](https://github.com/daddz).
|
46
|
+
|
47
|
+
0.1.4 (4/8/2011)
|
48
|
+
==================
|
49
|
+
|
50
|
+
* Allow multiple definitions of the same endpoint under multiple versions - [@chrisrhoden](https://github.com/chrisrhoden).
|
51
|
+
* Added support for multipart URL parameters - [@mcastilho](https://github.com/mcastilho).
|
52
|
+
* Added support for custom formatters - [@spraints](https://github.com/spraints).
|
53
|
+
|
54
|
+
0.1.3 (1/10/2011)
|
55
|
+
==================
|
56
|
+
|
57
|
+
* Added support for JSON format in route matching - [@aiwilliams](https://github.com/aiwilliams).
|
58
|
+
* Added suport for custom middleware - [@mbleigh](https://github.com/mbleigh).
|
59
|
+
|
60
|
+
0.1.1 (11/14/2010)
|
61
|
+
==================
|
62
|
+
|
63
|
+
* Endpoints properly reset between each request - [@mbleigh](https://github.com/mbleigh).
|
64
|
+
|
65
|
+
0.1.0 (11/13/2010)
|
66
|
+
==================
|
67
|
+
|
68
|
+
* Initial public release - [@mbleigh](https://github.com/mbleigh).
|
data/README.markdown
CHANGED
@@ -77,9 +77,18 @@ class Twitter::API < Grape::API
|
|
77
77
|
end
|
78
78
|
```
|
79
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
|
+
end
|
87
|
+
```
|
88
|
+
|
80
89
|
## Mounting
|
81
90
|
|
82
|
-
The above sample creates a Rack application that can be run from a rackup *config.ru* file
|
91
|
+
The above sample creates a Rack application that can be run from a rackup *config.ru* file
|
83
92
|
with `rackup`:
|
84
93
|
|
85
94
|
``` ruby
|
@@ -110,12 +119,16 @@ end
|
|
110
119
|
|
111
120
|
## Versioning
|
112
121
|
|
113
|
-
There are
|
114
|
-
|
122
|
+
There are three strategies in which clients can reach your API's endpoints: `:header`, `:path` and `:param`. The default strategy is `:header`.
|
123
|
+
|
115
124
|
|
116
|
-
|
125
|
+
### Header
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
version 'v1', :using => :header
|
129
|
+
```
|
117
130
|
|
118
|
-
Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.
|
131
|
+
Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.
|
119
132
|
|
120
133
|
curl -H Accept=application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
|
121
134
|
|
@@ -124,23 +137,59 @@ supplied. This behavior is similar to routing in Rails. To circumvent this defau
|
|
124
137
|
one could use the `:strict` option. When this option is set to `true`, a `404 Not found` error
|
125
138
|
is returned when no correct Accept header is supplied.
|
126
139
|
|
127
|
-
|
140
|
+
### Path
|
141
|
+
|
142
|
+
``` ruby
|
143
|
+
version 'v1', :using => :path
|
144
|
+
```
|
128
145
|
|
129
146
|
Using this versioning strategy, clients should pass the desired version in the URL.
|
130
147
|
|
131
148
|
curl -H http://localhost:9292/v1/statuses/public_timeline
|
132
149
|
|
133
|
-
Serialization takes place automatically.
|
150
|
+
Serialization takes place automatically.
|
151
|
+
|
152
|
+
### Param
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
version 'v1', :using => :param
|
156
|
+
```
|
157
|
+
|
158
|
+
Using this versioning strategy, clients should pass the desired version as a request parameter, either in the URL query string or in the request body.
|
159
|
+
|
160
|
+
curl -H http://localhost:9292/events?apiver=v1
|
161
|
+
|
162
|
+
The default name for the query parameter is 'apiver' but can be specified using the :parameter option.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
version 'v1', :using => :param, :parameter => "v"
|
166
|
+
```
|
167
|
+
|
168
|
+
curl -H http://localhost:9292/events?v=v1
|
134
169
|
|
135
170
|
## Parameters
|
136
171
|
|
137
|
-
Parameters are available through the `params` hash object. This includes `GET` and `POST` parameters,
|
172
|
+
Parameters are available through the `params` hash object. This includes `GET` and `POST` parameters,
|
138
173
|
along with any named parameters you specify in your route strings.
|
139
174
|
|
140
175
|
```ruby
|
141
|
-
|
176
|
+
get do
|
142
177
|
Article.order(params[:sort_by])
|
143
|
-
|
178
|
+
end
|
179
|
+
```
|
180
|
+
|
181
|
+
Parameters are also populated from the request body on POST and PUT for JSON and XML content-types.
|
182
|
+
|
183
|
+
The Request:
|
184
|
+
|
185
|
+
```curl -d '{"some_key": "some_value"}' 'http://localhost:9292/json_endpoint' -H Content-Type:application/json -v```
|
186
|
+
|
187
|
+
The Grape Endpoint:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
post '/json_endpoint' do
|
191
|
+
params[:some_key]
|
192
|
+
end
|
144
193
|
```
|
145
194
|
|
146
195
|
## Headers
|
@@ -148,10 +197,10 @@ along with any named parameters you specify in your route strings.
|
|
148
197
|
Headers are available through the `env` hash object.
|
149
198
|
|
150
199
|
```ruby
|
151
|
-
|
200
|
+
get do
|
152
201
|
error! 'Unauthorized', 401 unless env['HTTP_SECRET_PASSWORD'] == 'swordfish'
|
153
202
|
...
|
154
|
-
|
203
|
+
end
|
155
204
|
```
|
156
205
|
|
157
206
|
## Helpers
|
@@ -213,6 +262,19 @@ cookies[:counter] = {
|
|
213
262
|
}
|
214
263
|
cookies[:counter][:value] +=1
|
215
264
|
```
|
265
|
+
## Redirect
|
266
|
+
|
267
|
+
You can redirect to a new url
|
268
|
+
|
269
|
+
``` ruby
|
270
|
+
redirect "/new_url"
|
271
|
+
```
|
272
|
+
|
273
|
+
use permanent redirect
|
274
|
+
|
275
|
+
``` ruby
|
276
|
+
redirect "/new_url", :permanent => true
|
277
|
+
```
|
216
278
|
|
217
279
|
## Raising Errors
|
218
280
|
|
@@ -292,6 +354,51 @@ class Twitter::API < Grape::API
|
|
292
354
|
end
|
293
355
|
```
|
294
356
|
|
357
|
+
## Logging
|
358
|
+
|
359
|
+
`Grape::API` provides a `logger` method which by default will return an instance of the `Logger`
|
360
|
+
class from Ruby's standard library.
|
361
|
+
|
362
|
+
To log messages from within an endpoint, you need to define a helper to make the logger
|
363
|
+
available in the endpoint context:
|
364
|
+
|
365
|
+
``` ruby
|
366
|
+
class API < Grape::API
|
367
|
+
helpers do
|
368
|
+
def logger
|
369
|
+
API.logger
|
370
|
+
end
|
371
|
+
end
|
372
|
+
get '/hello' do
|
373
|
+
logger.info "someone said hello"
|
374
|
+
"hey there"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
```
|
378
|
+
|
379
|
+
You can also set your own logger:
|
380
|
+
|
381
|
+
``` ruby
|
382
|
+
class MyLogger
|
383
|
+
def warning(message)
|
384
|
+
puts "this is a warning: #{message}"
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
class API < Grape::API
|
389
|
+
logger MyLogger.new
|
390
|
+
helpers do
|
391
|
+
def logger
|
392
|
+
API.logger
|
393
|
+
end
|
394
|
+
end
|
395
|
+
get '/hello' do
|
396
|
+
logger.warning "someone said hello"
|
397
|
+
"hey there"
|
398
|
+
end
|
399
|
+
end
|
400
|
+
```
|
401
|
+
|
295
402
|
## Content-Types
|
296
403
|
|
297
404
|
By default, Grape supports _XML_, _JSON_, _Atom_, _RSS_, and _text_ content-types.
|
@@ -319,9 +426,20 @@ class Twitter::API < Grape::API
|
|
319
426
|
end
|
320
427
|
```
|
321
428
|
|
429
|
+
You can override the content-type by setting the `Content-Type` header.
|
430
|
+
|
431
|
+
``` ruby
|
432
|
+
class API < Grape::API
|
433
|
+
get '/script' do
|
434
|
+
content_type "application/javascript"
|
435
|
+
"var x = 1;"
|
436
|
+
end
|
437
|
+
end
|
438
|
+
```
|
439
|
+
|
322
440
|
## Writing Tests
|
323
441
|
|
324
|
-
You can test a Grape API with RSpec by making HTTP requests and examining the response.
|
442
|
+
You can test a Grape API with RSpec by making HTTP requests and examining the response.
|
325
443
|
|
326
444
|
### Writing Tests with Rack
|
327
445
|
|
@@ -349,7 +467,7 @@ describe Twitter::API do
|
|
349
467
|
it "returns a status by id" do
|
350
468
|
status = Status.create!
|
351
469
|
get "/api/v1/statuses/#{status.id}"
|
352
|
-
|
470
|
+
last_response.body.should == status.to_json
|
353
471
|
end
|
354
472
|
end
|
355
473
|
end
|
@@ -390,11 +508,122 @@ RSpec.configure do |config|
|
|
390
508
|
end
|
391
509
|
```
|
392
510
|
|
511
|
+
## Reusable Responses with Entities
|
512
|
+
|
513
|
+
Entities are a reusable means for converting Ruby objects to API responses.
|
514
|
+
Entities can be used to conditionally include fields, nest other entities, and build
|
515
|
+
ever larger responses, using inheritance.
|
516
|
+
|
517
|
+
### Defining Entities
|
518
|
+
|
519
|
+
Entities inherit from Grape::Entity, and define a simple DSL. Exposures can use
|
520
|
+
runtime options to determine which fields should be visible, these options are
|
521
|
+
available to :if, :unless, and :proc. The option keys :version and :collection
|
522
|
+
will always be defined. The :version key is defined as api.version. The
|
523
|
+
:collection key is boolean, and defined as true if the object presented is an
|
524
|
+
array.
|
525
|
+
|
526
|
+
* `expose SYMBOLS`
|
527
|
+
* define a list of fields which will always be exposed
|
528
|
+
* `expose SYMBOLS, HASH`
|
529
|
+
* HASH keys include :if, :unless, :proc, :as, :using, :format_with, :documentation
|
530
|
+
* :if and :unless accept hashes (passed during runtime) or procs (arguments are object and options)
|
531
|
+
* `expose SYMBOL, {:format_with => :formatter}`
|
532
|
+
* expose a value, formatting it first
|
533
|
+
* :format_with can only be applied to one exposure at a time
|
534
|
+
* `expose SYMBOL, {:as => "alias"}`
|
535
|
+
* Expose a value, changing its hash key from SYMBOL to alias
|
536
|
+
* :as can only be applied to one exposure at a time
|
537
|
+
* `expose SYMBOL BLOCK`
|
538
|
+
* block arguments are object and options
|
539
|
+
* expose the value returned by the block
|
540
|
+
* block can only be applied to one exposure at a time
|
541
|
+
|
542
|
+
``` ruby
|
543
|
+
module API
|
544
|
+
module Entities
|
545
|
+
class User < Grape::Entity
|
546
|
+
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(' ')}
|
551
|
+
expose :latest_status, :using => API::Status, :as => :status
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
module API
|
557
|
+
module Entities
|
558
|
+
class UserDetailed < API::Entities::User
|
559
|
+
expose :account_id
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
```
|
564
|
+
|
565
|
+
### Using Entities
|
566
|
+
|
567
|
+
Once an entity is defined, it can be used within endpoints, by calling #present. The #present
|
568
|
+
method accepts two arguments, the object to be presented and the options associated with it. The
|
569
|
+
options hash must always include :with, which defines the entity to expose.
|
570
|
+
|
571
|
+
If the entity includes documentation it can be included in an endpoint's description.
|
572
|
+
|
573
|
+
``` ruby
|
574
|
+
module API
|
575
|
+
class Users < Grape::API
|
576
|
+
version 'v1'
|
577
|
+
|
578
|
+
desc 'User index', {
|
579
|
+
:object_fields => API::Entities::User.documentation
|
580
|
+
}
|
581
|
+
get '/users' do
|
582
|
+
@users = User.all
|
583
|
+
type = current_user.admin? ? :full : :default
|
584
|
+
present @users, with: API::Entities::User, :type => type
|
585
|
+
end
|
586
|
+
end
|
587
|
+
end
|
588
|
+
```
|
589
|
+
|
590
|
+
### Caveats
|
591
|
+
|
592
|
+
Entities with duplicate exposure names and conditions will silently overwrite one another.
|
593
|
+
In the following example, when object#check equals "foo", only afield will be exposed.
|
594
|
+
However, when object#check equals "bar" both bfield and foo will be exposed.
|
595
|
+
|
596
|
+
```ruby
|
597
|
+
module API
|
598
|
+
module Entities
|
599
|
+
class User < Grape::Entity
|
600
|
+
expose :afield, :foo, :if => lambda{|object,options| object.check=="foo"}
|
601
|
+
expose :bfield, :foo, :if => lambda{|object,options| object.check=="bar"}
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
605
|
+
```
|
606
|
+
|
607
|
+
This can be problematic, when you have mixed collections. Using #respond_to? is safer.
|
608
|
+
|
609
|
+
```ruby
|
610
|
+
module API
|
611
|
+
module Entities
|
612
|
+
class User < Grape::Entity
|
613
|
+
expose :afield, :if => lambda{|object,options| object.check=="foo"}
|
614
|
+
expose :bfield, :if => lambda{|object,options| object.check=="bar"}
|
615
|
+
expose :foo, :if => lambda{object,options| object.respond_to?(:foo)}
|
616
|
+
end
|
617
|
+
end
|
618
|
+
end
|
619
|
+
```
|
620
|
+
|
393
621
|
## Describing and Inspecting an API
|
394
622
|
|
395
623
|
Grape lets you add a description to an API along with any other optional
|
396
624
|
elements that can also be inspected at runtime.
|
397
|
-
This can be useful for generating documentation.
|
625
|
+
This can be useful for generating documentation. If the response
|
626
|
+
requires documentation, consider using an entity.
|
398
627
|
|
399
628
|
``` ruby
|
400
629
|
class TwitterAPI < Grape::API
|
@@ -432,13 +661,13 @@ Parameters can also be tagged to the method declaration itself.
|
|
432
661
|
|
433
662
|
``` ruby
|
434
663
|
class StringAPI < Grape::API
|
435
|
-
get "split/:string", { :params =>
|
664
|
+
get "split/:string", { :params => { "token" => "a token" }, :optional_params => { "limit" => "the limit" } } do
|
436
665
|
params[:string].split(params[:token], (params[:limit] || 0))
|
437
666
|
end
|
438
667
|
end
|
439
668
|
|
440
|
-
StringAPI::routes[0].route_params # yields
|
441
|
-
StringAPI::routes[0].route_optional_params # yields
|
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"}
|
442
671
|
```
|
443
672
|
|
444
673
|
It's possible to retrieve the information about the current route from within an API call with `route`.
|
@@ -452,6 +681,33 @@ class MyAPI < Grape::API
|
|
452
681
|
end
|
453
682
|
```
|
454
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
|
700
|
+
end
|
701
|
+
|
702
|
+
before { validate_request! }
|
703
|
+
|
704
|
+
desc "creates a new item resource", :params => { :name => 'name is a required parameter' }
|
705
|
+
post :items do
|
706
|
+
...
|
707
|
+
end
|
708
|
+
end
|
709
|
+
```
|
710
|
+
|
455
711
|
## Anchoring
|
456
712
|
|
457
713
|
Grape by default anchors all request paths, which means that the request URL
|