grape 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- data/.gitignore +13 -1
- data/.rspec +1 -1
- data/.travis.yml +1 -0
- data/Gemfile +10 -0
- data/Guardfile +15 -0
- data/README.markdown +472 -67
- data/grape.gemspec +4 -4
- data/lib/grape.rb +17 -5
- data/lib/grape/api.rb +227 -124
- data/lib/grape/cookies.rb +41 -0
- data/lib/grape/endpoint.rb +256 -27
- data/lib/grape/entity.rb +227 -0
- data/lib/grape/middleware/auth/oauth2.rb +1 -2
- data/lib/grape/middleware/base.rb +59 -6
- data/lib/grape/middleware/error.rb +15 -6
- data/lib/grape/middleware/filter.rb +17 -0
- data/lib/grape/middleware/formatter.rb +25 -31
- data/lib/grape/middleware/versioner.rb +20 -20
- data/lib/grape/middleware/versioner/header.rb +59 -0
- data/lib/grape/middleware/versioner/path.rb +42 -0
- data/lib/grape/route.rb +23 -0
- data/lib/grape/util/hash_stack.rb +100 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +651 -162
- data/spec/grape/endpoint_spec.rb +216 -18
- data/spec/grape/entity_spec.rb +320 -0
- data/spec/grape/middleware/auth/basic_spec.rb +2 -2
- data/spec/grape/middleware/auth/digest_spec.rb +4 -6
- data/spec/grape/middleware/exception_spec.rb +1 -0
- data/spec/grape/middleware/formatter_spec.rb +81 -27
- data/spec/grape/middleware/versioner/header_spec.rb +148 -0
- data/spec/grape/middleware/versioner/path_spec.rb +40 -0
- data/spec/grape/middleware/versioner_spec.rb +6 -34
- data/spec/grape/util/hash_stack_spec.rb +133 -0
- data/spec/shared/versioning_examples.rb +77 -0
- data/spec/spec_helper.rb +11 -3
- data/spec/support/basic_auth_encode_helpers.rb +4 -0
- data/spec/support/rack_patch.rb +25 -0
- data/spec/support/versioned_helpers.rb +34 -0
- metadata +140 -241
- data/.yardoc/checksums +0 -13
- data/.yardoc/objects/Grape.dat +0 -0
- data/.yardoc/objects/Grape/API.dat +0 -0
- data/.yardoc/objects/Grape/API/auth_c.dat +0 -0
- data/.yardoc/objects/Grape/API/build_endpoint_c.dat +0 -0
- data/.yardoc/objects/Grape/API/call_c.dat +0 -0
- data/.yardoc/objects/Grape/API/compile_path_c.dat +0 -0
- data/.yardoc/objects/Grape/API/default_format_c.dat +0 -0
- data/.yardoc/objects/Grape/API/delete_c.dat +0 -0
- data/.yardoc/objects/Grape/API/get_c.dat +0 -0
- data/.yardoc/objects/Grape/API/group_c.dat +0 -0
- data/.yardoc/objects/Grape/API/head_c.dat +0 -0
- data/.yardoc/objects/Grape/API/helpers_c.dat +0 -0
- data/.yardoc/objects/Grape/API/http_basic_c.dat +0 -0
- data/.yardoc/objects/Grape/API/inherited_c.dat +0 -0
- data/.yardoc/objects/Grape/API/logger_c.dat +0 -0
- data/.yardoc/objects/Grape/API/namespace_c.dat +0 -0
- data/.yardoc/objects/Grape/API/nest_c.dat +0 -0
- data/.yardoc/objects/Grape/API/post_c.dat +0 -0
- data/.yardoc/objects/Grape/API/prefix_c.dat +0 -0
- data/.yardoc/objects/Grape/API/put_c.dat +0 -0
- data/.yardoc/objects/Grape/API/reset_21_c.dat +0 -0
- data/.yardoc/objects/Grape/API/resource_c.dat +0 -0
- data/.yardoc/objects/Grape/API/resources_c.dat +0 -0
- data/.yardoc/objects/Grape/API/route_c.dat +0 -0
- data/.yardoc/objects/Grape/API/route_set_c.dat +0 -0
- data/.yardoc/objects/Grape/API/scope_c.dat +0 -0
- data/.yardoc/objects/Grape/API/set_c.dat +0 -0
- data/.yardoc/objects/Grape/API/settings_c.dat +0 -0
- data/.yardoc/objects/Grape/API/settings_stack_c.dat +0 -0
- data/.yardoc/objects/Grape/API/version_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/block_3D_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/block_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/call_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/call_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/env_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/error_21_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/generate_c.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/header_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/params_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/request_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/status_i.dat +0 -0
- data/.yardoc/objects/Grape/Endpoint/version_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/authenticator_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/basic_request_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/credentials_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/Basic/initialize_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/error_out_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/parse_authorization_header_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/token_class_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/verify_token_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/after_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/app_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/call_21_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/call_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/env_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/initialize_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/request_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Base/response_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Error.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Error/call_21_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Error/error_response_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/CONTENT_TYPES.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/after_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/content_types_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/encode_json_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/encode_txt_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/format_from_extension_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/format_from_header_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/headers_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/mime_array_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Formatter/mime_types_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Prefixer.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Prefixer/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Prefixer/prefix_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Versioner.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Versioner/before_i.dat +0 -0
- data/.yardoc/objects/Grape/Middleware/Versioner/default_options_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/initialize_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/stack_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/to_app_i.dat +0 -0
- data/.yardoc/objects/Grape/MiddlewareStack/use_i.dat +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -2
- data/Gemfile.lock +0 -52
- data/autotest/discover.rb +0 -1
data/.gitignore
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
## MAC OS
|
2
2
|
.DS_Store
|
3
|
+
.com.apple.timemachine.supported
|
3
4
|
|
4
5
|
## TEXTMATE
|
5
6
|
*.tmproj
|
@@ -10,15 +11,26 @@ tmtags
|
|
10
11
|
\#*
|
11
12
|
.\#*
|
12
13
|
|
14
|
+
## REDCAR
|
15
|
+
.redcar
|
16
|
+
|
13
17
|
## VIM
|
14
18
|
*.swp
|
15
19
|
|
20
|
+
## RUBYMINE
|
21
|
+
.idea
|
22
|
+
|
16
23
|
## PROJECT::GENERAL
|
17
24
|
coverage
|
18
|
-
|
25
|
+
doc
|
19
26
|
pkg
|
20
27
|
.rvmrc
|
21
28
|
.bundle
|
29
|
+
.yardoc/*
|
22
30
|
dist
|
31
|
+
Gemfile.lock
|
32
|
+
|
33
|
+
## Rubinius
|
34
|
+
.rbx
|
23
35
|
|
24
36
|
## PROJECT::SPECIFIC
|
data/.rspec
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
--color
|
2
|
-
--format=
|
2
|
+
--format=progress
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
|
+
watch(%r{^spec/support/shared_versioning_examples.rb$}) { |m| "spec/" }
|
8
|
+
watch('spec/spec_helper.rb') { "spec/" }
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
guard 'bundler' do
|
13
|
+
watch('Gemfile')
|
14
|
+
watch(/^.+\.gemspec/)
|
15
|
+
end
|
data/README.markdown
CHANGED
@@ -1,96 +1,501 @@
|
|
1
|
-
# Grape
|
2
|
-
[![Build Status](http://travis-ci.org/intridea/grape.png)](http://travis-ci.org/intridea/grape)
|
1
|
+
# Grape [![Build Status](http://travis-ci.org/intridea/grape.png?branch=frontier)](http://travis-ci.org/intridea/grape)
|
3
2
|
|
4
|
-
|
3
|
+
## What is Grape?
|
4
|
+
|
5
|
+
Grape is a REST-like API micro-framework for Ruby. It is built to complement
|
6
|
+
existing web application frameworks such as Rails and Sinatra by providing a
|
7
|
+
simple DSL to easily provide APIs. It has built-in support for common
|
8
|
+
conventions such as multiple formats, subdomain/prefix restriction, and
|
9
|
+
versioning.
|
10
|
+
|
11
|
+
## Project Tracking
|
12
|
+
|
13
|
+
* [Grape Google Group](http://groups.google.com/group/ruby-grape)
|
14
|
+
* [Grape Wiki](https://github.com/intridea/grape/wiki)
|
5
15
|
|
6
16
|
## Installation
|
7
17
|
|
8
18
|
Grape is available as a gem, to install it just install the gem:
|
9
19
|
|
10
20
|
gem install grape
|
11
|
-
|
21
|
+
|
22
|
+
If you're using Bundler, add the gem to Gemfile.
|
23
|
+
|
24
|
+
gem 'grape'
|
25
|
+
|
12
26
|
## Basic Usage
|
13
27
|
|
14
|
-
Grape APIs are Rack applications that are created by subclassing `Grape::API`.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
28
|
+
Grape APIs are Rack applications that are created by subclassing `Grape::API`.
|
29
|
+
Below is a simple example showing some of the more common features of Grape in
|
30
|
+
the context of recreating parts of the Twitter API.
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
class Twitter::API < Grape::API
|
34
|
+
version 'v1', :using => :header, :vendor => 'twitter'
|
35
|
+
|
36
|
+
helpers do
|
37
|
+
def current_user
|
38
|
+
@current_user ||= User.authorize!(env)
|
39
|
+
end
|
40
|
+
|
41
|
+
def authenticate!
|
42
|
+
error!('401 Unauthorized', 401) unless current_user
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
resource :statuses do
|
47
|
+
get :public_timeline do
|
48
|
+
Tweet.limit(20)
|
49
|
+
end
|
50
|
+
|
51
|
+
get :home_timeline do
|
52
|
+
authenticate!
|
53
|
+
current_user.home_timeline
|
54
|
+
end
|
55
|
+
|
56
|
+
get '/show/:id' do
|
57
|
+
Tweet.find(params[:id])
|
58
|
+
end
|
59
|
+
|
60
|
+
post :update do
|
61
|
+
authenticate!
|
62
|
+
Tweet.create(
|
63
|
+
:user => current_user,
|
64
|
+
:text => params[:status]
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
resource :account do
|
70
|
+
before { authenticate! }
|
71
|
+
|
72
|
+
get '/private' do
|
73
|
+
"Congratulations, you found the secret!"
|
51
74
|
end
|
52
|
-
|
53
|
-
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
## Mounting
|
81
|
+
|
82
|
+
The above sample creates a Rack application that can be run from a rackup *config.ru* file
|
83
|
+
with `rackup`:
|
84
|
+
|
85
|
+
``` ruby
|
86
|
+
run Twitter::API
|
87
|
+
```
|
54
88
|
|
55
|
-
run Twitter::API
|
56
|
-
|
57
89
|
And would respond to the following routes:
|
58
90
|
|
59
|
-
GET /
|
60
|
-
GET /
|
61
|
-
GET /
|
62
|
-
POST /
|
63
|
-
|
64
|
-
|
65
|
-
|
91
|
+
GET /statuses/public_timeline(.json)
|
92
|
+
GET /statuses/home_timeline(.json)
|
93
|
+
GET /statuses/show/:id(.json)
|
94
|
+
POST /statuses/update(.json)
|
95
|
+
|
96
|
+
In a Rails application, modify *config/routes*:
|
97
|
+
|
98
|
+
``` ruby
|
99
|
+
mount Twitter::API => "/"
|
100
|
+
```
|
101
|
+
|
102
|
+
You can mount multiple API implementations inside another one.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
class Twitter::API < Grape::API
|
106
|
+
mount Twitter::APIv1
|
107
|
+
mount Twitter::APIv2
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
## Versioning
|
112
|
+
|
113
|
+
There are two stragies in which clients can reach your API's endpoints: `:header`
|
114
|
+
and `:path`. The default strategy is `:header`.
|
115
|
+
|
116
|
+
version 'v1', :using => :header
|
117
|
+
|
118
|
+
Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.
|
119
|
+
|
120
|
+
curl -H Accept=application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
|
121
|
+
|
122
|
+
By default, the first matching version is used when no Accept header is
|
123
|
+
supplied. This behavior is similar to routing in Rails. To circumvent this default behavior,
|
124
|
+
one could use the `:strict` option. When this option is set to `true`, a `404 Not found` error
|
125
|
+
is returned when no correct Accept header is supplied.
|
126
|
+
|
127
|
+
version 'v1', :using => :path
|
128
|
+
|
129
|
+
Using this versioning strategy, clients should pass the desired version in the URL.
|
130
|
+
|
131
|
+
curl -H http://localhost:9292/v1/statuses/public_timeline
|
132
|
+
|
133
|
+
Serialization takes place automatically.
|
134
|
+
|
135
|
+
## Parameters
|
136
|
+
|
137
|
+
Parameters are available through the `params` hash object. This includes `GET` and `POST` parameters,
|
138
|
+
along with any named parameters you specify in your route strings.
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
get do
|
142
|
+
Article.order(params[:sort_by])
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
## Headers
|
147
|
+
|
148
|
+
Headers are available through the `env` hash object.
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
get do
|
152
|
+
error! 'Unauthorized', 401 unless env['HTTP_SECRET_PASSWORD'] == 'swordfish'
|
153
|
+
...
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
## Helpers
|
158
|
+
|
159
|
+
You can define helper methods that your endpoints can use with the `helpers`
|
160
|
+
macro by either giving a block or a module:
|
161
|
+
|
162
|
+
``` ruby
|
163
|
+
module MyHelpers
|
164
|
+
def say_hello(user)
|
165
|
+
"hey there #{user.name}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class API < Grape::API
|
170
|
+
# define helpers with a block
|
171
|
+
helpers do
|
172
|
+
def current_user
|
173
|
+
User.find(params[:user_id])
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# or mix in a module
|
178
|
+
helpers MyHelpers
|
179
|
+
|
180
|
+
get '/hello' do
|
181
|
+
# helpers available in your endpoint and filters
|
182
|
+
say_hello(current_user)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
## Cookies
|
188
|
+
|
189
|
+
You can set, get and delete your cookies very simply using `cookies` method:
|
190
|
+
|
191
|
+
``` ruby
|
192
|
+
class API < Grape::API
|
193
|
+
get '/counter' do
|
194
|
+
cookies[:counter] ||= 0
|
195
|
+
cookies[:counter] += 1
|
196
|
+
{ :counter => cookies[:counter] }
|
197
|
+
end
|
198
|
+
|
199
|
+
delete '/counter' do
|
200
|
+
{ :result => cookies.delete(:counter) }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
```
|
204
|
+
|
205
|
+
To set more than value use hash-based syntax:
|
206
|
+
|
207
|
+
``` ruby
|
208
|
+
cookies[:counter] = {
|
209
|
+
:value => 0,
|
210
|
+
:expires => Time.tomorrow,
|
211
|
+
:domain => '.example.com',
|
212
|
+
:path => '/'
|
213
|
+
}
|
214
|
+
cookies[:counter][:value] +=1
|
215
|
+
```
|
216
|
+
|
66
217
|
## Raising Errors
|
67
218
|
|
68
219
|
You can raise errors explicitly.
|
69
220
|
|
70
|
-
|
221
|
+
``` ruby
|
222
|
+
error!("Access Denied", 401)
|
223
|
+
```
|
71
224
|
|
72
|
-
You can also return JSON formatted objects explicitly by raising error! and
|
225
|
+
You can also return JSON formatted objects explicitly by raising error! and
|
226
|
+
passing a hash instead of a message.
|
73
227
|
|
74
|
-
|
228
|
+
``` ruby
|
229
|
+
error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
|
230
|
+
```
|
75
231
|
|
76
232
|
## Exception Handling
|
77
233
|
|
78
|
-
Grape can be told to rescue
|
79
|
-
|
80
|
-
|
234
|
+
Grape can be told to rescue all exceptions and instead return them in
|
235
|
+
text or json formats.
|
236
|
+
|
237
|
+
``` ruby
|
238
|
+
class Twitter::API < Grape::API
|
239
|
+
rescue_from :all
|
240
|
+
end
|
241
|
+
```
|
242
|
+
|
243
|
+
You can also rescue specific exceptions.
|
244
|
+
|
245
|
+
``` ruby
|
246
|
+
class Twitter::API < Grape::API
|
247
|
+
rescue_from ArgumentError, NotImplementedError
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
The error format can be specified using `error_format`. Available formats are
|
252
|
+
`:json` and `:txt` (default).
|
253
|
+
|
254
|
+
``` ruby
|
255
|
+
class Twitter::API < Grape::API
|
256
|
+
error_format :json
|
257
|
+
end
|
258
|
+
```
|
259
|
+
|
260
|
+
You can rescue all exceptions with a code block. The `rack_response` wrapper
|
261
|
+
automatically sets the default error code and content-type.
|
262
|
+
|
263
|
+
``` ruby
|
264
|
+
class Twitter::API < Grape::API
|
265
|
+
rescue_from :all do |e|
|
266
|
+
rack_response({ :message => "rescued from #{e.class.name}" })
|
267
|
+
end
|
268
|
+
end
|
269
|
+
```
|
270
|
+
|
271
|
+
You can also rescue specific exceptions with a code block and handle the Rack
|
272
|
+
response at the lowest level.
|
273
|
+
|
274
|
+
``` ruby
|
275
|
+
class Twitter::API < Grape::API
|
276
|
+
rescue_from :all do |e|
|
277
|
+
Rack::Response.new([ e.message ], 500, { "Content-type" => "text/error" }).finish
|
278
|
+
end
|
279
|
+
end
|
280
|
+
```
|
281
|
+
|
282
|
+
Or rescue specific exceptions.
|
283
|
+
|
284
|
+
``` ruby
|
285
|
+
class Twitter::API < Grape::API
|
286
|
+
rescue_from ArgumentError do |e|
|
287
|
+
Rack::Response.new([ "ArgumentError: #{e.message}" ], 500)
|
288
|
+
end
|
289
|
+
rescue_from NotImplementedError do |e|
|
290
|
+
Rack::Response.new([ "NotImplementedError: #{e.message}" ], 500)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
```
|
294
|
+
|
295
|
+
## Content-Types
|
296
|
+
|
297
|
+
By default, Grape supports _XML_, _JSON_, _Atom_, _RSS_, and _text_ content-types.
|
298
|
+
Your API can declare additional types to support. Response format is determined by the
|
299
|
+
request's extension or `Accept` header.
|
81
300
|
|
82
|
-
|
83
|
-
|
301
|
+
``` ruby
|
302
|
+
class Twitter::API < Grape::API
|
303
|
+
content_type :xls, "application/vnd.ms-excel"
|
304
|
+
end
|
305
|
+
```
|
306
|
+
|
307
|
+
You can also set the default format. The order for choosing the format is the following.
|
308
|
+
|
309
|
+
* Use the file extension, if specified. If the file is .json, choose the JSON format.
|
310
|
+
* Use the format, if specified by the `format` option.
|
311
|
+
* Attempt to find an acceptable format from the `Accept` header.
|
312
|
+
* Use the default format, if specified by the `default_format` option.
|
313
|
+
* Default to `:txt` otherwise.
|
314
|
+
|
315
|
+
``` ruby
|
316
|
+
class Twitter::API < Grape::API
|
317
|
+
format :json
|
318
|
+
default_format :json
|
319
|
+
end
|
320
|
+
```
|
321
|
+
|
322
|
+
## Writing Tests
|
323
|
+
|
324
|
+
You can test a Grape API with RSpec by making HTTP requests and examining the response.
|
325
|
+
|
326
|
+
### Writing Tests with Rack
|
327
|
+
|
328
|
+
Use `rack-test` and define your API as `app`.
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
require 'spec_helper'
|
332
|
+
|
333
|
+
describe Twitter::API do
|
334
|
+
include Rack::Test::Methods
|
335
|
+
|
336
|
+
def app
|
337
|
+
Twitter::API
|
338
|
+
end
|
339
|
+
|
340
|
+
describe Twitter::API do
|
341
|
+
describe "GET /api/v1/statuses" do
|
342
|
+
it "returns an empty array of statuses" do
|
343
|
+
get "/api/v1/statuses"
|
344
|
+
last_response.status.should == 200
|
345
|
+
JSON.parse(response.body).should == []
|
346
|
+
end
|
84
347
|
end
|
348
|
+
describe "GET /api/v1/statuses/:id" do
|
349
|
+
it "returns a status by id" do
|
350
|
+
status = Status.create!
|
351
|
+
get "/api/v1/statuses/#{status.id}"
|
352
|
+
last_resonse.body.should == status.to_json
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
```
|
358
|
+
|
359
|
+
### Writing Tests with Rails
|
360
|
+
|
361
|
+
``` ruby
|
362
|
+
require 'spec_helper'
|
363
|
+
|
364
|
+
describe Twitter::API do
|
365
|
+
describe "GET /api/v1/statuses" do
|
366
|
+
it "returns an empty array of statuses" do
|
367
|
+
get "/api/v1/statuses"
|
368
|
+
response.status.should == 200
|
369
|
+
JSON.parse(response.body).should == []
|
370
|
+
end
|
371
|
+
end
|
372
|
+
describe "GET /api/v1/statuses/:id" do
|
373
|
+
it "returns a status by id" do
|
374
|
+
status = Status.create!
|
375
|
+
get "/api/v1/statuses/#{status.id}"
|
376
|
+
resonse.body.should == status.to_json
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
```
|
381
|
+
|
382
|
+
In Rails, HTTP request tests would go into the `spec/request` group. You may want your API code to go into
|
383
|
+
`app/api` - you can match that layout under `spec` by adding the following in `spec/spec_helper.rb`.
|
384
|
+
|
385
|
+
```ruby
|
386
|
+
RSpec.configure do |config|
|
387
|
+
config.include RSpec::Rails::RequestExampleGroup, :type => :request, :example_group => {
|
388
|
+
:file_path => /spec\/api/
|
389
|
+
}
|
390
|
+
end
|
391
|
+
```
|
392
|
+
|
393
|
+
## Describing and Inspecting an API
|
394
|
+
|
395
|
+
Grape lets you add a description to an API along with any other optional
|
396
|
+
elements that can also be inspected at runtime.
|
397
|
+
This can be useful for generating documentation.
|
398
|
+
|
399
|
+
``` ruby
|
400
|
+
class TwitterAPI < Grape::API
|
401
|
+
|
402
|
+
version 'v1'
|
403
|
+
|
404
|
+
desc "Retrieves the API version number."
|
405
|
+
get "version" do
|
406
|
+
api.version
|
407
|
+
end
|
408
|
+
|
409
|
+
desc "Reverses a string.", { :params =>
|
410
|
+
{ "s" => { :desc => "string to reverse", :type => "string" }}
|
411
|
+
}
|
412
|
+
get "reverse" do
|
413
|
+
params[:s].reverse
|
414
|
+
end
|
415
|
+
end
|
416
|
+
```
|
417
|
+
|
418
|
+
Grape then exposes arrays of API versions and compiled routes. Each route
|
419
|
+
contains a `route_prefix`, `route_version`, `route_namespace`, `route_method`,
|
420
|
+
`route_path` and `route_params`. The description and the optional hash that
|
421
|
+
follows the API path may contain any number of keys and its values are also
|
422
|
+
accessible via dynamically-generated `route_[name]` functions.
|
423
|
+
|
424
|
+
``` ruby
|
425
|
+
TwitterAPI::versions # yields [ 'v1', 'v2' ]
|
426
|
+
TwitterAPI::routes # yields an array of Grape::Route objects
|
427
|
+
TwitterAPI::routes[0].route_version # yields 'v1'
|
428
|
+
TwitterAPI::routes[0].route_description # yields [ { "s" => { :desc => "string to reverse", :type => "string" }} ]
|
429
|
+
```
|
430
|
+
|
431
|
+
Parameters can also be tagged to the method declaration itself.
|
432
|
+
|
433
|
+
``` ruby
|
434
|
+
class StringAPI < Grape::API
|
435
|
+
get "split/:string", { :params => [ "token" ], :optional_params => [ "limit" ] } do
|
436
|
+
params[:string].split(params[:token], (params[:limit] || 0))
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
StringAPI::routes[0].route_params # yields an array [ "string", "token" ]
|
441
|
+
StringAPI::routes[0].route_optional_params # yields an array [ "limit" ]
|
442
|
+
```
|
443
|
+
|
444
|
+
It's possible to retrieve the information about the current route from within an API call with `route`.
|
445
|
+
|
446
|
+
``` ruby
|
447
|
+
class MyAPI < Grape::API
|
448
|
+
desc "Returns a description of a parameter.", { :params => { "id" => "a required id" } }
|
449
|
+
get "params/:id" do
|
450
|
+
route.route_params[params[:id]] # returns "a required id"
|
451
|
+
end
|
452
|
+
end
|
453
|
+
```
|
454
|
+
|
455
|
+
## Anchoring
|
456
|
+
|
457
|
+
Grape by default anchors all request paths, which means that the request URL
|
458
|
+
should match from start to end to match, otherwise a `404 Not Found` is
|
459
|
+
returned.
|
460
|
+
However, this is sometimes not what you want, because it is not always known up
|
461
|
+
front what can be expected from the call.
|
462
|
+
This is because Rack-mount by default anchors requests to match from the start
|
463
|
+
to the end, or not at all. Rails solves this problem by using a `:anchor =>
|
464
|
+
false` option in your routes.
|
465
|
+
In Grape this option can be used as well when a method is defined.
|
466
|
+
|
467
|
+
For instance when you're API needs to get part of an URL, for instance:
|
468
|
+
|
469
|
+
``` ruby
|
470
|
+
class UrlAPI < Grape::API
|
471
|
+
namespace :urls do
|
472
|
+
get '/(*:url)', :anchor => false do
|
473
|
+
some_data
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
```
|
478
|
+
|
479
|
+
This will match all paths starting with '/urls/'. There is one caveat though:
|
480
|
+
the `params[:url]` parameter only holds the first part of the request url.
|
481
|
+
Luckily this can be circumvented by using the described above syntax for path
|
482
|
+
specification and using the `PATH_INFO` Rack environment variable, using
|
483
|
+
`env["PATH_INFO"]`. This will hold everything that comes after the '/urls/'
|
484
|
+
part.
|
85
485
|
|
86
486
|
## Note on Patches/Pull Requests
|
87
|
-
|
88
|
-
* Fork the project
|
89
|
-
*
|
90
|
-
*
|
91
|
-
*
|
92
|
-
*
|
487
|
+
|
488
|
+
* Fork the project
|
489
|
+
* Write tests for your new feature or a test that reproduces a bug
|
490
|
+
* Implement your feature or make a bug fix
|
491
|
+
* Do not mess with Rakefile, version or history
|
492
|
+
* Commit, push and make a pull request. Bonus points for topical branches.
|
493
|
+
|
494
|
+
## License
|
495
|
+
|
496
|
+
MIT License. See LICENSE for details.
|
93
497
|
|
94
498
|
## Copyright
|
95
499
|
|
96
|
-
Copyright (c) 2010 Michael Bleigh and Intridea, Inc.
|
500
|
+
Copyright (c) 2010-2012 Michael Bleigh and Intridea, Inc.
|
501
|
+
|