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

Allow rack/mount cascading to be skipped on header #339

Merged
merged 1 commit into from
Feb 18, 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
1 change: 1 addition & 0 deletions CHANGELOG.markdown
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Next Release
============

* [#340](https://github.com/intridea/grape/pull/339): Allow rack/mount cascading to be skipped on header - [@dieb](https://github.com/dieb).
* [#333](https://github.com/intridea/grape/pull/333): Validation for array in params - [@flyerhzm](https://github.com/flyerhzm).
* [#306](https://github.com/intridea/grape/issues/306): Added I18n support for all Grape exceptions - [@niedhui](https://github.com/niedhui).
* [#294](https://github.com/intridea/grape/issues/294): Extracted `Grape::Entity` into a [grape-entity](https://github.com/agileanimal/grape-entity) gem - [@agileanimal](https://github.com/agileanimal).
Expand Down
10 changes: 7 additions & 3 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,11 @@ 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.
is returned when no correct `Accept` header is supplied. By default this error contains a
`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 circumvent this default
behavior, one can set the `:cascade` option to `false`, indicating the `X-Cascade` header may not
be passed.

### Path

Expand Down Expand Up @@ -500,7 +504,7 @@ redirect "/statuses", :permanent => true
## Allowed Methods

When you add a `GET` route for a resource, a route for the `HEAD`
method will also be added automatically. You can disable this
method will also be added automatically. You can disable this
behavior with `do_not_route_head!`.

``` ruby
Expand Down Expand Up @@ -907,7 +911,7 @@ formatter.

## Authentication

### Basic and Digest Auth
### Basic and Digest Auth

Grape has built-in Basic and Digest authentication.

Expand Down
16 changes: 12 additions & 4 deletions lib/grape/middleware/versioner/header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ def before
if strict?
# If no Accept header:
if header.qvalues.empty?
throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => 'Accept header must be set.'
throw :error, :status => 406, :headers => error_headers, :message => 'Accept header must be set.'
end
# Remove any acceptable content types with ranges.
header.qvalues.reject! do |media_type,_|
Rack::Accept::Header.parse_media_type(media_type).find{|s| s == '*'}
end
# If all Accept headers included a range:
if header.qvalues.empty?
throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => 'Accept header must not contain ranges ("*").'
throw :error, :status => 406, :headers => error_headers, :message => 'Accept header must not contain ranges ("*").'
end
end

Expand All @@ -55,10 +55,10 @@ def before
end
# If none of the available content types are acceptable:
elsif strict?
throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => '406 Not Acceptable'
throw :error, :status => 406, :headers => error_headers, :message => '406 Not Acceptable'
# If all acceptable content types specify a vendor or version that doesn't exist:
elsif header.values.all?{ |media_type| has_vendor?(media_type) || has_version?(media_type)}
throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => 'API vendor or version not found.'
throw :error, :status => 406, :headers => error_headers, :message => 'API vendor or version not found.'
end
end

Expand Down Expand Up @@ -95,6 +95,14 @@ def strict?
options[:version_options] && options[:version_options][:strict]
end

def cascade?
options[:version_options] && (options[:version_options][:cascade].nil? ? true : options[:version_options][:cascade])
end

def error_headers
cascade? ? {'X-Cascade' => 'pass'} : {}
end

# @param [String] media_type a content type
# @return [Boolean] whether the content type sets a vendor
def has_vendor?(media_type)
Expand Down
56 changes: 56 additions & 0 deletions spec/grape/middleware/versioner/header_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,60 @@
subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first.should == 200
end
end

context 'when :strict and :cascade=>false are set' do
before do
@options[:versions] = ['v1']
@options[:version_options][:strict] = true
@options[:version_options][:cascade] = false
end

it 'fails with 406 Not Acceptable if header is not set' do
expect {
env = subject.call({}).last
}.to throw_symbol(
:error,
:status => 406,
:headers => {},
:message => 'Accept header must be set.'
)
end

it 'fails with 406 Not Acceptable if header is empty' do
expect {
env = subject.call('HTTP_ACCEPT' => '').last
}.to throw_symbol(
:error,
:status => 406,
:headers => {},
:message => 'Accept header must be set.'
)
end

it 'fails with 406 Not Acceptable if type is a range' do
expect {
env = subject.call('HTTP_ACCEPT' => '*/*').last
}.to throw_symbol(
:error,
:status => 406,
:headers => {},
:message => 'Accept header must not contain ranges ("*").'
)
end

it 'fails with 406 Not Acceptable if subtype is a range' do
expect {
env = subject.call('HTTP_ACCEPT' => 'application/*').last
}.to throw_symbol(
:error,
:status => 406,
:headers => {},
:message => 'Accept header must not contain ranges ("*").'
)
end

it 'succeeds if proper header is set' do
subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first.should == 200
end
end
end