Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend :cascade to work with all versioning strategies #342

Merged
merged 1 commit into from
Feb 20, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 30 additions & 20 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,18 @@ end

## Versioning

There are three strategies in which clients can reach your API's endpoints: `:header`,
`:path` and `:param`. The default strategy is `:path`.
There are three strategies in which clients can reach your API's endpoints: `:path`,
`:header` and `:param`. The default strategy is `:path`.

### Path

```ruby
version 'v1', :using => :path
```

Using this versioning strategy, clients should pass the desired version in the URL.

curl -H http://localhost:9292/v1/statuses/public_timeline

### Header

Expand All @@ -192,24 +202,7 @@ Using this versioning strategy, clients should pass the desired version in the H
By default, the first matching version is used when no `Accept` header is
supplied. This behavior is similar to routing in Rails. To circumvent this default behavior,
one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error
is returned when no correct `Accept` header is supplied. By default this error contains an
`X-Cascade` header set to `pass`, allowing nesting and stacking of routes (see
[Rack::Mount](https://github.com/josh/rack-mount) for more information). To prevent this behavior,
and not add the `X-Cascade` header, set the `:cascade` option to `false`.

```ruby
version 'v1', :using => :header, :vendor => 'twitter', :cascade => false
```

### Path

```ruby
version 'v1', :using => :path
```

Using this versioning strategy, clients should pass the desired version in the URL.

curl -H http://localhost:9292/v1/statuses/public_timeline
is returned when no correct `Accept` header is supplied.

### Param

Expand All @@ -230,6 +223,7 @@ version 'v1', :using => :param, :parameter => "v"

curl -H http://localhost:9292/statuses/public_timeline?v=v1


## Describing Methods

You can add a description to API methods and namespaces.
Expand Down Expand Up @@ -662,6 +656,22 @@ class Twitter::API < Grape::API
end
```

#### Rails 3.x

When mounted inside Rails 3.x, errors like "404 Not Found" or "406 Not Acceptable" will likely be
handled and rendered by Rails handlers. For instance, accessing a nonexistent route "/api/foo"
raises a 404, which inside rails will ultimately be translated to a ActionController::RoutingError,
which most likely will get rendered to a HTML error page.

Most APIs will enjoy avoiding Rails exceptions and have their own exceptions reaching the client.
In that case, the `:cascade` option can be set to `false` on the versioning definition.

```ruby
version 'v1', :using => :header, :vendor => 'twitter', :cascade => false
```

The `:cascade` option can also be used with the other versioning strategies (`:param` and `:path`).

## Logging

`Grape::API` provides a `logger` method which by default will return an instance of the `Logger`
Expand Down
19 changes: 17 additions & 2 deletions lib/grape/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ def nest(*blocks, &block)
end
end

def inherited(subclass)
def inherited(subclass)
subclass.reset!
subclass.logger = logger.clone
end
Expand All @@ -457,7 +457,22 @@ def initialize
end

def call(env)
@route_set.call(env)
status, headers, body = @route_set.call(env)
headers['X-Cascade'] = '' unless cascade?
[status, headers, body]
end

# Some requests may return a HTTP 404 error if grape cannot find a matching
# route. In this case, Rack::Mount adds a X-Cascade header to the response
# and sets it to 'pass', indicating to grape's parents they should keep
# looking for a matching route on other resources.
#
# In some applications (e.g. mounting grape on rails), one might need to trap
# errors from reaching upstream. This is effectivelly done by unsetting
# X-Cascade. Default :cascade is true.
def cascade?
cascade = ((self.class.settings || {})[:version_options] || {})[:cascade]
cascade.nil? ? true : cascade
end

reset!
Expand Down
3 changes: 3 additions & 0 deletions lib/grape/middleware/versioner/header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ def strict?
options[:version_options] && options[:version_options][:strict]
end

# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
# of routes (see [Rack::Mount](https://github.com/josh/rack-mount) for more information). To prevent
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
def cascade?
options[:version_options] && (options[:version_options].has_key?(:cascade) ? options[:version_options][:cascade] : true)
end
Expand Down
15 changes: 15 additions & 0 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1855,4 +1855,19 @@ def serializable_hash
end
end

context "cascading" do
it "cascades" do
subject.version 'v1', :using => :path, :cascade => true
get "/v1/hello"
last_response.status.should == 404
last_response.headers["X-Cascade"].should == "pass"
end

it "does not cascade" do
subject.version 'v2', :using => :path, :cascade => false
get "/v2/hello"
last_response.status.should == 404
last_response.headers["X-Cascade"].should == ""
end
end
end