Skip to content

Commit e54b8b0

Browse files
arempe93dblock
authored andcommitted
Fixes route :any, '*path' breaking generated OPTIONS and Method Not Allowed routes.
1 parent 7d1640e commit e54b8b0

4 files changed

Lines changed: 91 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* [#1216](https://github.com/ruby-grape/grape/pull/1142): Fix JSON error response when calling `error!` with non-Strings - [@jrforrest](https://github.com/jrforrest).
1919
* [#1225](https://github.com/ruby-grape/grape/pull/1225): Fix `given` with nested params not returning correct declared params - [@JanStevens](https://github.com/JanStevens).
2020
* [#1249](https://github.com/ruby-grape/grape/pull/1249): Don't fail even if invalid type value is passed to default validator - [@namusyaka](https://github.com/namusyaka).
21+
* [#1263](https://github.com/ruby-grape/grape/pull/1263): Fix `route :any, '*path'` breaking generated `OPTIONS`, Method Not Allowed routes - [@arempe93](https://github.com/arempe93).
2122

2223
0.14.0 (12/07/2015)
2324
===================

UPGRADING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ end
3232

3333
See [#1147](https://github.com/ruby-grape/grape/issues/1147) and [#1240](https://github.com/ruby-grape/grape/issues/1240) for discussion of the issues.
3434

35+
#### Changes to generated OPTIONS and Method Not Allowed routes
36+
37+
Using `route :any, '*path'` no longer blocks generated `OPTIONS` and Method Not Allowed methods from other routes. Use `do_not_route_options!` to prevent `OPTIONS` routes from being created.
38+
3539
### Upgrading to >= 0.14.0
3640

3741
#### Changes to availability of DSL methods in filters

lib/grape/api.rb

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ def add_head_not_allowed_methods_and_options_methods
146146
routes.each do |route|
147147
methods_per_path[route.route_path] ||= []
148148
methods_per_path[route.route_path] << route.route_method
149+
150+
# using the :any shorthand produces [nil] for route methods, substitute all manually
151+
methods_per_path[route.route_path] = %w(GET PUT POST DELETE PATCH HEAD OPTIONS) if methods_per_path[route.route_path].compact.empty?
149152
end
150153
end
151154

@@ -157,33 +160,53 @@ def add_head_not_allowed_methods_and_options_methods
157160
without_versioning do
158161
methods_per_path.each do |path, methods|
159162
allowed_methods = methods.dup
163+
160164
unless self.class.namespace_inheritable(:do_not_route_head)
161165
allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
162166
end
163167

164168
allow_header = ([Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
169+
165170
unless self.class.namespace_inheritable(:do_not_route_options)
166-
unless allowed_methods.include?(Grape::Http::Headers::OPTIONS)
167-
self.class.options(path, {}) do
168-
header 'Allow', allow_header
169-
status 204
170-
''
171-
end
172-
end
171+
generate_options_method(path, allow_header) unless allowed_methods.include?(Grape::Http::Headers::OPTIONS)
173172
end
174173

175-
not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - allowed_methods
176-
not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
177-
self.class.route(not_allowed_methods, path) do
178-
header 'Allow', allow_header
179-
status 405
180-
''
181-
end
174+
generate_not_allowed_method(path, allowed_methods, allow_header)
182175
end
183176
end
184177
end
185178
end
186179

180+
# Generate an 'OPTIONS' route for a pre-exisiting user defined route
181+
def generate_options_method(path, allow_header)
182+
self.class.options(path, {}) do
183+
header 'Allow', allow_header
184+
status 204
185+
''
186+
end
187+
188+
# move options endpoint to top of defined endpoints
189+
self.class.endpoints.unshift self.class.endpoints.pop
190+
end
191+
192+
# Generate a route that returns an HTTP 405 response for a user defined
193+
# path on methods not specified
194+
def generate_not_allowed_method(path, allowed_methods, allow_header)
195+
not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - allowed_methods
196+
not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
197+
198+
return if not_allowed_methods.empty?
199+
200+
self.class.route(not_allowed_methods, path) do
201+
header 'Allow', allow_header
202+
status 405
203+
''
204+
end
205+
206+
# move options endpoint to top of defined endpoints
207+
self.class.endpoints.unshift self.class.endpoints.pop
208+
end
209+
187210
# Allows definition of endpoints that ignore the versioning configuration
188211
# used by the rest of your API.
189212
def without_versioning(&_block)

spec/grape/api_spec.rb

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,9 @@ def subject.enable_root_route!
555555
subject.get 'example' do
556556
'example'
557557
end
558+
subject.route :any, '*path' do
559+
error! :not_found, 404
560+
end
558561
options '/example'
559562
end
560563

@@ -583,13 +586,53 @@ def subject.enable_root_route!
583586
end
584587
end
585588

586-
it 'allows HEAD on a GET request' do
587-
subject.get 'example' do
588-
'example'
589+
describe 'adds a 405 Not Allowed route that' do
590+
before do
591+
subject.before { header 'X-Custom-Header', 'foo' }
592+
subject.post :example do
593+
'example'
594+
end
595+
subject.route :any, '*path' do
596+
error! :not_found, 404
597+
end
598+
get '/example'
599+
end
600+
601+
it 'returns a 405' do
602+
expect(last_response.status).to eql 405
603+
end
604+
605+
it 'has an empty body' do
606+
expect(last_response.body).to be_blank
607+
end
608+
609+
it 'has an Allow header' do
610+
expect(last_response.headers['Allow']).to eql 'OPTIONS, POST'
611+
end
612+
613+
it 'has a X-Custom-Header' do
614+
expect(last_response.headers['X-Custom-Header']).to eql 'foo'
615+
end
616+
end
617+
618+
context 'allows HEAD on a GET request that' do
619+
before do
620+
subject.get 'example' do
621+
'example'
622+
end
623+
subject.route :any, '*path' do
624+
error! :not_found, 404
625+
end
626+
head '/example'
627+
end
628+
629+
it 'returns a 200' do
630+
expect(last_response.status).to eql 200
631+
end
632+
633+
it 'has an empty body' do
634+
expect(last_response.body).to eql ''
589635
end
590-
head '/example'
591-
expect(last_response.status).to eql 200
592-
expect(last_response.body).to eql ''
593636
end
594637

595638
it 'overwrites the default HEAD request' do

0 commit comments

Comments
 (0)