Skip to content

Commit db85017

Browse files
committed
Merged from frontier.
2 parents bcf5ad8 + ade2d26 commit db85017

File tree

13 files changed

+178
-54
lines changed

13 files changed

+178
-54
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,7 @@ pkg
2727
dist
2828
Gemfile.lock
2929

30+
## Rubinius
31+
.rbx
32+
3033
## PROJECT::SPECIFIC

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ group :development, :test do
99
gem 'guard-bundler'
1010
gem 'rb-fsevent'
1111
gem 'growl'
12+
gem 'json'
1213
end

README.markdown

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
## What is Grape?
66

7-
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.
7+
Grape is a REST-like API micro-framework for Ruby. It is built to complement
8+
existing web application frameworks such as Rails and Sinatra by providing a
9+
simple DSL to easily provide APIs. It has built-in support for common
10+
conventions such as multiple formats, subdomain/prefix restriction, and
11+
versioning.
812

913
## Project Tracking
1014

@@ -19,7 +23,9 @@ Grape is available as a gem, to install it just install the gem:
1923

2024
## Basic Usage
2125

22-
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.
26+
Grape APIs are Rack applications that are created by subclassing `Grape::API`.
27+
Below is a simple example showing some of the more common features of Grape in
28+
the context of recreating parts of the Twitter API.
2329

2430
```ruby
2531
class Twitter::API < Grape::API
@@ -68,7 +74,8 @@ class Twitter::API < Grape::API
6874
end
6975
```
7076

71-
This would create a Rack application that could be used like so (in a Rackup config.ru file):
77+
This would create a Rack application that could be used like so (in a Rackup
78+
config.ru file):
7279

7380
```ruby
7481
run Twitter::API
@@ -89,10 +96,14 @@ request:
8996

9097
curl -H Accept=application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
9198

92-
By default, the first matching version is used when no Accept header is supplied. This behavior is similar to routing in Rails.
93-
To circumvent this default behaviour, one could use the `:strict` option. When this option is set to `true`, a `404 Not found` error is returned when no correct Accept header is supplied.
99+
By default, the first matching version is used when no Accept header is
100+
supplied. This behavior is similar to routing in Rails.
101+
To circumvent this default behaviour, one could use the `:strict` option. When
102+
this option is set to `true`, a `404 Not found` error is returned when no
103+
correct Accept header is supplied.
94104

95-
Serialization takes place automatically. For more detailed usage information, please visit the [Grape Wiki](http://github.com/intridea/grape/wiki).
105+
Serialization takes place automatically. For more detailed usage information,
106+
please visit the [Grape Wiki](http://github.com/intridea/grape/wiki).
96107

97108
## Helpers
98109

@@ -132,7 +143,8 @@ You can raise errors explicitly.
132143
error!("Access Denied", 401)
133144
```
134145

135-
You can also return JSON formatted objects explicitly by raising error! and passing a hash instead of a message.
146+
You can also return JSON formatted objects explicitly by raising error! and
147+
passing a hash instead of a message.
136148

137149
```ruby
138150
error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
@@ -157,15 +169,17 @@ class Twitter::API < Grape::API
157169
end
158170
```
159171

160-
The error format can be specified using `error_format`. Available formats are `:json` and `:txt` (default).
172+
The error format can be specified using `error_format`. Available formats are
173+
`:json` and `:txt` (default).
161174

162175
```ruby
163176
class Twitter::API < Grape::API
164177
error_format :json
165178
end
166179
```
167180

168-
You can rescue all exceptions with a code block. The `rack_response` wrapper automatically sets the default error code and content-type.
181+
You can rescue all exceptions with a code block. The `rack_response` wrapper
182+
automatically sets the default error code and content-type.
169183

170184
```ruby
171185
class Twitter::API < Grape::API
@@ -175,7 +189,8 @@ class Twitter::API < Grape::API
175189
end
176190
```
177191

178-
You can also rescue specific exceptions with a code block and handle the Rack response at the lowest level.
192+
You can also rescue specific exceptions with a code block and handle the Rack
193+
response at the lowest level.
179194

180195
```ruby
181196
class Twitter::API < Grape::API
@@ -196,7 +211,10 @@ end
196211

197212
## Writing Tests
198213

199-
You can test a Grape API with RSpec. Tests make HTTP requests, therefore they must go into the `spec/request` group. You may want your API code to go into `app/api` - you can match that layout under `spec` by adding the following in `spec/spec_helper.rb`.
214+
You can test a Grape API with RSpec. Tests make HTTP requests, therefore they
215+
must go into the `spec/request` group. You may want your API code to go into
216+
`app/api` - you can match that layout under `spec` by adding the following in
217+
`spec/spec_helper.rb`.
200218

201219
```ruby
202220
RSpec.configure do |config|
@@ -224,7 +242,8 @@ end
224242

225243
## Describing and Inspecting an API
226244

227-
Grape lets you add a description to an API along with any other optional elements that can also be inspected at runtime.
245+
Grape lets you add a description to an API along with any other optional
246+
elements that can also be inspected at runtime.
228247
This can be useful for generating documentation.
229248

230249
```ruby
@@ -246,7 +265,11 @@ class TwitterAPI < Grape::API
246265
end
247266
```
248267

249-
Grape then exposes arrays of API versions and compiled routes. Each route contains a `route_prefix`, `route_version`, `route_namespace`, `route_method`, `route_path` and `route_params`. The description and the optional hash that follows the API path may contain any number of keys and its values are also accessible via dynamically-generated `route_[name]` functions.
268+
Grape then exposes arrays of API versions and compiled routes. Each route
269+
contains a `route_prefix`, `route_version`, `route_namespace`, `route_method`,
270+
`route_path` and `route_params`. The description and the optional hash that
271+
follows the API path may contain any number of keys and its values are also
272+
accessible via dynamically-generated `route_[name]` functions.
250273

251274
```ruby
252275
TwitterAPI::versions # yields [ 'v1', 'v2' ]
@@ -268,6 +291,37 @@ StringAPI::routes[0].route_params # yields an array [ "string", "token" ]
268291
StringAPI::routes[0].route_optional_params # yields an array [ "limit" ]
269292
```
270293

294+
## Anchoring
295+
296+
Grape by default anchors all request paths, which means that the request URL
297+
should match from start to end to match, otherwise a `404 Not Found` is
298+
returned.
299+
However, this is sometimes not what you want, because it is not always known up
300+
front what can be expected from the call.
301+
This is because Rack-mount by default anchors requests to match from the start
302+
to the end, or not at all. Rails solves this problem by using a `:anchor =>
303+
false` option in your routes.
304+
In Grape this option can be used as well when a method is defined.
305+
306+
For instance when you're API needs to get part of an URL, for instance:
307+
308+
```ruby
309+
class UrlAPI < Grape::API
310+
namespace :urls do
311+
get '/(*:url)', :anchor => false do
312+
some_data
313+
end
314+
end
315+
end
316+
```
317+
318+
This will match all paths starting with '/urls/'. There is one caveat though:
319+
the `params[:url]` parameter only holds the first part of the request url.
320+
Luckily this can be circumvented by using the described above syntax for path
321+
specification and using the `PATH_INFO` Rack environment variable, using
322+
`env["PATH_INFO"]`. This will hold everyting that comes after the '/urls/'
323+
part.
324+
271325
## Note on Patches/Pull Requests
272326

273327
* Fork the project

lib/grape/endpoint.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ def prepare_routes
4848
options[:method].each do |method|
4949
options[:path].each do |path|
5050
prepared_path = prepare_path(path)
51-
path = compile_path(prepared_path, !options[:app])
51+
52+
anchor = options[:route_options][:anchor]
53+
anchor = anchor.nil? ? true : anchor
54+
55+
path = compile_path(prepared_path, anchor && !options[:app])
5256
regex = Rack::Mount::RegexpWithNamedGroups.new(path)
5357
path_params = {}
5458
# named parameters in the api path

lib/grape/middleware/versioner/header.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ def before
3535
env['api.subtype'] = subtype
3636

3737
subtype.scan(/vnd\.(.+)?-(.+)?\+(.*)?/) do |vendor, version, format|
38-
if options[:versions] && !options[:versions].include?(version)
38+
is_vendored = options[:version_options] && options[:version_options][:vendor]
39+
is_vendored_match = is_vendored ? options[:version_options][:vendor] == vendor : true
40+
41+
if (options[:versions] && !options[:versions].include?(version)) || !is_vendored_match
3942
throw :error, :status => 404, :headers => {'X-Cascade' => 'pass'}, :message => "404 API Version Not Found"
4043
end
4144

spec/grape/api_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require 'spec_helper'
2-
require 'shared_versioning_examples'
2+
require 'shared/versioning_examples'
33

44
describe Grape::API do
55
subject { Class.new(Grape::API) }
@@ -448,7 +448,7 @@ def call(env)
448448
subject.get(:hello){ "Hello, world."}
449449
get '/hello'
450450
last_response.status.should eql 401
451-
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic('allow','whatever')
451+
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow','whatever')
452452
last_response.status.should eql 200
453453
end
454454

@@ -476,7 +476,7 @@ def call(env)
476476
subject.get(:hello){ "Hello, world."}
477477
get '/hello'
478478
last_response.status.should eql 401
479-
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic('allow','whatever')
479+
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow','whatever')
480480
last_response.status.should eql 200
481481
end
482482
end

spec/grape/endpoint_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,35 @@ def memoized
215215
end
216216
end
217217
end
218+
219+
context 'anchoring' do
220+
verbs = %w(post get head delete put options)
221+
222+
verbs.each do |verb|
223+
it "should allow for the anchoring option with a #{verb.upcase} method" do
224+
subject.send(verb, '/example', :anchor => true) do
225+
verb
226+
end
227+
send(verb, '/example/and/some/more')
228+
last_response.status.should eql 404
229+
end
230+
231+
it "should anchor paths by default for the #{verb.upcase} method" do
232+
subject.send(verb, '/example') do
233+
verb
234+
end
235+
send(verb, '/example/and/some/more')
236+
last_response.status.should eql 404
237+
end
238+
239+
it "should respond to /example/and/some/more for the non-anchored #{verb.upcase} method" do
240+
subject.send(verb, '/example', :anchor => false) do
241+
verb
242+
end
243+
send(verb, '/example/and/some/more')
244+
last_response.status.should eql (verb == "post" ? 201 : 200)
245+
last_response.body.should eql verb
246+
end
247+
end
248+
end
218249
end

spec/grape/middleware/auth/basic_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ def app
2020
end
2121

2222
it 'should authenticate if given valid creds' do
23-
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic('admin','admin')
23+
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin','admin')
2424
last_response.status.should == 200
2525
end
2626

2727
it 'should throw a 401 is wrong auth is given' do
28-
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic('admin','wrong')
28+
get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin','wrong')
2929
last_response.status.should == 401
3030
end
3131
end

spec/grape/middleware/versioner/header_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@
8080
end
8181
end
8282

83+
context 'vendors' do
84+
before do
85+
@options = {
86+
:version => ['v1'],
87+
:version_options => {:using => :header, :vendor => 'vendor'}
88+
}
89+
end
90+
91+
it 'should match with correct vendor' do
92+
status = subject.call('HTTP_ACCEPT' => accept).first
93+
status.should == 200
94+
end
95+
96+
it 'should not match with an incorrect vendor' do
97+
expect {
98+
env = subject.call('HTTP_ACCEPT' => 'application/vnd.othervendor-v1+json').last
99+
}.to throw_symbol(:error, :status => 404, :headers => {'X-Cascade' => 'pass'}, :message => "404 API Version Not Found")
100+
end
101+
end
102+
83103
context 'no matched version' do
84104
before do
85105
@options = {
File renamed without changes.

0 commit comments

Comments
 (0)