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.

Files changed (142) hide show
  1. data/.gitignore +13 -1
  2. data/.rspec +1 -1
  3. data/.travis.yml +1 -0
  4. data/Gemfile +10 -0
  5. data/Guardfile +15 -0
  6. data/README.markdown +472 -67
  7. data/grape.gemspec +4 -4
  8. data/lib/grape.rb +17 -5
  9. data/lib/grape/api.rb +227 -124
  10. data/lib/grape/cookies.rb +41 -0
  11. data/lib/grape/endpoint.rb +256 -27
  12. data/lib/grape/entity.rb +227 -0
  13. data/lib/grape/middleware/auth/oauth2.rb +1 -2
  14. data/lib/grape/middleware/base.rb +59 -6
  15. data/lib/grape/middleware/error.rb +15 -6
  16. data/lib/grape/middleware/filter.rb +17 -0
  17. data/lib/grape/middleware/formatter.rb +25 -31
  18. data/lib/grape/middleware/versioner.rb +20 -20
  19. data/lib/grape/middleware/versioner/header.rb +59 -0
  20. data/lib/grape/middleware/versioner/path.rb +42 -0
  21. data/lib/grape/route.rb +23 -0
  22. data/lib/grape/util/hash_stack.rb +100 -0
  23. data/lib/grape/version.rb +1 -1
  24. data/spec/grape/api_spec.rb +651 -162
  25. data/spec/grape/endpoint_spec.rb +216 -18
  26. data/spec/grape/entity_spec.rb +320 -0
  27. data/spec/grape/middleware/auth/basic_spec.rb +2 -2
  28. data/spec/grape/middleware/auth/digest_spec.rb +4 -6
  29. data/spec/grape/middleware/exception_spec.rb +1 -0
  30. data/spec/grape/middleware/formatter_spec.rb +81 -27
  31. data/spec/grape/middleware/versioner/header_spec.rb +148 -0
  32. data/spec/grape/middleware/versioner/path_spec.rb +40 -0
  33. data/spec/grape/middleware/versioner_spec.rb +6 -34
  34. data/spec/grape/util/hash_stack_spec.rb +133 -0
  35. data/spec/shared/versioning_examples.rb +77 -0
  36. data/spec/spec_helper.rb +11 -3
  37. data/spec/support/basic_auth_encode_helpers.rb +4 -0
  38. data/spec/support/rack_patch.rb +25 -0
  39. data/spec/support/versioned_helpers.rb +34 -0
  40. metadata +140 -241
  41. data/.yardoc/checksums +0 -13
  42. data/.yardoc/objects/Grape.dat +0 -0
  43. data/.yardoc/objects/Grape/API.dat +0 -0
  44. data/.yardoc/objects/Grape/API/auth_c.dat +0 -0
  45. data/.yardoc/objects/Grape/API/build_endpoint_c.dat +0 -0
  46. data/.yardoc/objects/Grape/API/call_c.dat +0 -0
  47. data/.yardoc/objects/Grape/API/compile_path_c.dat +0 -0
  48. data/.yardoc/objects/Grape/API/default_format_c.dat +0 -0
  49. data/.yardoc/objects/Grape/API/delete_c.dat +0 -0
  50. data/.yardoc/objects/Grape/API/get_c.dat +0 -0
  51. data/.yardoc/objects/Grape/API/group_c.dat +0 -0
  52. data/.yardoc/objects/Grape/API/head_c.dat +0 -0
  53. data/.yardoc/objects/Grape/API/helpers_c.dat +0 -0
  54. data/.yardoc/objects/Grape/API/http_basic_c.dat +0 -0
  55. data/.yardoc/objects/Grape/API/inherited_c.dat +0 -0
  56. data/.yardoc/objects/Grape/API/logger_c.dat +0 -0
  57. data/.yardoc/objects/Grape/API/namespace_c.dat +0 -0
  58. data/.yardoc/objects/Grape/API/nest_c.dat +0 -0
  59. data/.yardoc/objects/Grape/API/post_c.dat +0 -0
  60. data/.yardoc/objects/Grape/API/prefix_c.dat +0 -0
  61. data/.yardoc/objects/Grape/API/put_c.dat +0 -0
  62. data/.yardoc/objects/Grape/API/reset_21_c.dat +0 -0
  63. data/.yardoc/objects/Grape/API/resource_c.dat +0 -0
  64. data/.yardoc/objects/Grape/API/resources_c.dat +0 -0
  65. data/.yardoc/objects/Grape/API/route_c.dat +0 -0
  66. data/.yardoc/objects/Grape/API/route_set_c.dat +0 -0
  67. data/.yardoc/objects/Grape/API/scope_c.dat +0 -0
  68. data/.yardoc/objects/Grape/API/set_c.dat +0 -0
  69. data/.yardoc/objects/Grape/API/settings_c.dat +0 -0
  70. data/.yardoc/objects/Grape/API/settings_stack_c.dat +0 -0
  71. data/.yardoc/objects/Grape/API/version_c.dat +0 -0
  72. data/.yardoc/objects/Grape/Endpoint.dat +0 -0
  73. data/.yardoc/objects/Grape/Endpoint/block_3D_c.dat +0 -0
  74. data/.yardoc/objects/Grape/Endpoint/block_c.dat +0 -0
  75. data/.yardoc/objects/Grape/Endpoint/call_c.dat +0 -0
  76. data/.yardoc/objects/Grape/Endpoint/call_i.dat +0 -0
  77. data/.yardoc/objects/Grape/Endpoint/env_i.dat +0 -0
  78. data/.yardoc/objects/Grape/Endpoint/error_21_i.dat +0 -0
  79. data/.yardoc/objects/Grape/Endpoint/generate_c.dat +0 -0
  80. data/.yardoc/objects/Grape/Endpoint/header_i.dat +0 -0
  81. data/.yardoc/objects/Grape/Endpoint/params_i.dat +0 -0
  82. data/.yardoc/objects/Grape/Endpoint/request_i.dat +0 -0
  83. data/.yardoc/objects/Grape/Endpoint/status_i.dat +0 -0
  84. data/.yardoc/objects/Grape/Endpoint/version_i.dat +0 -0
  85. data/.yardoc/objects/Grape/Middleware.dat +0 -0
  86. data/.yardoc/objects/Grape/Middleware/Auth.dat +0 -0
  87. data/.yardoc/objects/Grape/Middleware/Auth/Basic.dat +0 -0
  88. data/.yardoc/objects/Grape/Middleware/Auth/Basic/authenticator_i.dat +0 -0
  89. data/.yardoc/objects/Grape/Middleware/Auth/Basic/basic_request_i.dat +0 -0
  90. data/.yardoc/objects/Grape/Middleware/Auth/Basic/before_i.dat +0 -0
  91. data/.yardoc/objects/Grape/Middleware/Auth/Basic/credentials_i.dat +0 -0
  92. data/.yardoc/objects/Grape/Middleware/Auth/Basic/initialize_i.dat +0 -0
  93. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2.dat +0 -0
  94. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/before_i.dat +0 -0
  95. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/default_options_i.dat +0 -0
  96. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/error_out_i.dat +0 -0
  97. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/parse_authorization_header_i.dat +0 -0
  98. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/token_class_i.dat +0 -0
  99. data/.yardoc/objects/Grape/Middleware/Auth/OAuth2/verify_token_i.dat +0 -0
  100. data/.yardoc/objects/Grape/Middleware/Base.dat +0 -0
  101. data/.yardoc/objects/Grape/Middleware/Base/after_i.dat +0 -0
  102. data/.yardoc/objects/Grape/Middleware/Base/app_i.dat +0 -0
  103. data/.yardoc/objects/Grape/Middleware/Base/before_i.dat +0 -0
  104. data/.yardoc/objects/Grape/Middleware/Base/call_21_i.dat +0 -0
  105. data/.yardoc/objects/Grape/Middleware/Base/call_i.dat +0 -0
  106. data/.yardoc/objects/Grape/Middleware/Base/default_options_i.dat +0 -0
  107. data/.yardoc/objects/Grape/Middleware/Base/env_i.dat +0 -0
  108. data/.yardoc/objects/Grape/Middleware/Base/initialize_i.dat +0 -0
  109. data/.yardoc/objects/Grape/Middleware/Base/options_i.dat +0 -0
  110. data/.yardoc/objects/Grape/Middleware/Base/request_i.dat +0 -0
  111. data/.yardoc/objects/Grape/Middleware/Base/response_i.dat +0 -0
  112. data/.yardoc/objects/Grape/Middleware/Error.dat +0 -0
  113. data/.yardoc/objects/Grape/Middleware/Error/call_21_i.dat +0 -0
  114. data/.yardoc/objects/Grape/Middleware/Error/error_response_i.dat +0 -0
  115. data/.yardoc/objects/Grape/Middleware/Formatter.dat +0 -0
  116. data/.yardoc/objects/Grape/Middleware/Formatter/CONTENT_TYPES.dat +0 -0
  117. data/.yardoc/objects/Grape/Middleware/Formatter/after_i.dat +0 -0
  118. data/.yardoc/objects/Grape/Middleware/Formatter/before_i.dat +0 -0
  119. data/.yardoc/objects/Grape/Middleware/Formatter/content_types_i.dat +0 -0
  120. data/.yardoc/objects/Grape/Middleware/Formatter/default_options_i.dat +0 -0
  121. data/.yardoc/objects/Grape/Middleware/Formatter/encode_json_i.dat +0 -0
  122. data/.yardoc/objects/Grape/Middleware/Formatter/encode_txt_i.dat +0 -0
  123. data/.yardoc/objects/Grape/Middleware/Formatter/format_from_extension_i.dat +0 -0
  124. data/.yardoc/objects/Grape/Middleware/Formatter/format_from_header_i.dat +0 -0
  125. data/.yardoc/objects/Grape/Middleware/Formatter/headers_i.dat +0 -0
  126. data/.yardoc/objects/Grape/Middleware/Formatter/mime_array_i.dat +0 -0
  127. data/.yardoc/objects/Grape/Middleware/Formatter/mime_types_i.dat +0 -0
  128. data/.yardoc/objects/Grape/Middleware/Prefixer.dat +0 -0
  129. data/.yardoc/objects/Grape/Middleware/Prefixer/before_i.dat +0 -0
  130. data/.yardoc/objects/Grape/Middleware/Prefixer/prefix_i.dat +0 -0
  131. data/.yardoc/objects/Grape/Middleware/Versioner.dat +0 -0
  132. data/.yardoc/objects/Grape/Middleware/Versioner/before_i.dat +0 -0
  133. data/.yardoc/objects/Grape/Middleware/Versioner/default_options_i.dat +0 -0
  134. data/.yardoc/objects/Grape/MiddlewareStack.dat +0 -0
  135. data/.yardoc/objects/Grape/MiddlewareStack/initialize_i.dat +0 -0
  136. data/.yardoc/objects/Grape/MiddlewareStack/stack_i.dat +0 -0
  137. data/.yardoc/objects/Grape/MiddlewareStack/to_app_i.dat +0 -0
  138. data/.yardoc/objects/Grape/MiddlewareStack/use_i.dat +0 -0
  139. data/.yardoc/objects/root.dat +0 -0
  140. data/.yardoc/proxy_types +0 -2
  141. data/Gemfile.lock +0 -52
  142. 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
- /doc
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=nested
2
+ --format=progress
@@ -1,6 +1,7 @@
1
1
  rvm:
2
2
  - 1.8.7
3
3
  - 1.9.2
4
+ - 1.9.3
4
5
  - jruby
5
6
  - rbx
6
7
  - ree
data/Gemfile CHANGED
@@ -1,3 +1,13 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ group :development, :test do
6
+ gem 'pry'
7
+ gem 'guard'
8
+ gem 'guard-rspec'
9
+ gem 'guard-bundler'
10
+ gem 'rb-fsevent'
11
+ gem 'growl'
12
+ gem 'json'
13
+ end
@@ -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
@@ -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
- Grape is a REST-like API micro-framework for Ruby. It is built to complement existing web application frameworks such as Rails and Sinatra by providing a simple DSL to easily provide APIs. It has built-in support for common conventions such as multiple formats, subdomain/prefix restriction, and versioning.
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`. Below is a simple example showing some of the more common features of Grape in the context of recreating parts of the Twitter API.
15
-
16
- class Twitter::API < Grape::API
17
- version '1'
18
-
19
- helpers do
20
- def current_user
21
- @current_user ||= User.authorize!(env)
22
- end
23
-
24
- def authenticate!
25
- error!('401 Unauthorized', 401) unless current_user
26
- end
27
- end
28
-
29
- resource :statuses do
30
- get :public_timeline do
31
- Tweet.limit(20)
32
- end
33
-
34
- get :home_timeline do
35
- authenticate!
36
- current_user.home_timeline
37
- end
38
-
39
- get '/show/:id' do
40
- Tweet.find(params[:id])
41
- end
42
-
43
- post :update do
44
- authenticate!
45
- Tweet.create(
46
- :user => current_user,
47
- :text => params[:status]
48
- )
49
- end
50
- end
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
- This would create a Rack application that could be used like so (in a Rackup config.ru file):
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 /1/statuses/public_timeline(.json)
60
- GET /1/statuses/home_timeline(.json)
61
- GET /1/statuses/show/:id(.json)
62
- POST /1/statuses/update(.json)
63
-
64
- Serialization takes place automatically. For more detailed usage information, please visit the [Grape Wiki](http://github.com/intridea/grape/wiki).
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
- error!("Access Denied", 401)
221
+ ``` ruby
222
+ error!("Access Denied", 401)
223
+ ```
71
224
 
72
- You can also return JSON formatted objects explicitly by raising error! and passing a hash instead of a message.
225
+ You can also return JSON formatted objects explicitly by raising error! and
226
+ passing a hash instead of a message.
73
227
 
74
- error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
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 certain (or all) exceptions in your
79
- application and instead display them in text or json form. To do this,
80
- you simply use the `rescue_from` method inside your API declaration:
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
- class Twitter::API < Grape::API
83
- rescue_from ArgumentError, NotImplementedError # :all for all errors
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
- * Make your feature addition or bug fix.
90
- * Add tests for it. This is important so I don't break it in a future version unintentionally.
91
- * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
92
- * Send me a pull request. Bonus points for topic branches.
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. See LICENSE for details.
500
+ Copyright (c) 2010-2012 Michael Bleigh and Intridea, Inc.
501
+