Skip to content

Commit 7910a98

Browse files
committed
Populate the env route information when calling the generated allow_headers endpoints
1 parent 9198f7e commit 7910a98

File tree

4 files changed

+94
-24
lines changed

4 files changed

+94
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#### Fixes
1111

1212
* Your contribution here.
13+
* [#2076](https://github.com/ruby-grape/grape/pull/2076): Make route information available for hooks when the automatically generated endpoints are invoked - [@anakinj](https://github.com/anakinj).
1314
* [#2067](https://github.com/ruby-grape/grape/pull/2067): Coerce empty string to nil for all primitive types except String - [@petekinnecom](https://github.com/petekinnecom).
1415
* [#2064](https://github.com/ruby-grape/grape/pull/2064): Fix Ruby 2.7 deprecation warning in `Grape::Middleware::Base#initialize` - [@skarger](https://github.com/skarger).
1516

lib/grape/api/instance.rb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,19 +198,21 @@ def add_head_not_allowed_methods_and_options_methods
198198
routes = endpoint.routes
199199
routes.each do |route|
200200
# using the :any shorthand produces [nil] for route methods, substitute all manually
201-
route_key = route.pattern.to_regexp
202-
routes_map[route_key] ||= {}
203-
route_settings = routes_map[route_key]
204-
route_settings[:pattern] = route.pattern
205-
route_settings[:requirements] = route.requirements
206-
route_settings[:path] = route.origin
201+
route_settings = routes_map[route.pattern.to_regexp] ||= {}
202+
203+
route_settings.merge!(options: {},
204+
pattern: route.pattern,
205+
requirements: route.requirements,
206+
path: route.origin,
207+
endpoint: route.app)
208+
207209
route_settings[:methods] ||= []
210+
208211
if route.request_method == '*' || route_settings[:methods].include?('*')
209212
route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS
210213
else
211214
route_settings[:methods] << route.request_method
212215
end
213-
route_settings[:endpoint] = route.app
214216
end
215217
end
216218

@@ -252,7 +254,6 @@ def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
252254
end
253255
not_allowed_methods = supported_methods - allowed_methods
254256
return if not_allowed_methods.empty?
255-
256257
@router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
257258
end
258259

lib/grape/router.rb

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,37 +99,34 @@ def transaction(env)
9999
response = yield(input, method)
100100

101101
return response if response && !(cascade = cascade?(response))
102-
neighbor = greedy_match?(input)
102+
last_neighbor_route = greedy_match?(input)
103103

104-
# If neighbor exists and request method is OPTIONS,
104+
# If last_neighbor_route exists and request method is OPTIONS,
105105
# return response by using #call_with_allow_headers.
106-
return call_with_allow_headers(
107-
env,
108-
neighbor.allow_header,
109-
neighbor.endpoint
110-
) if neighbor && method == Grape::Http::Headers::OPTIONS && !cascade
106+
return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Grape::Http::Headers::OPTIONS && !cascade
111107

112108
route = match?(input, '*')
113-
return neighbor.endpoint.call(env) if neighbor && cascade && route
109+
110+
return last_neighbor_route.endpoint.call(env) if last_neighbor_route && cascade && route
114111

115112
if route
116113
response = process_route(route, env)
117114
return response if response && !(cascade = cascade?(response))
118115
end
119116

120-
!cascade && neighbor ? call_with_allow_headers(env, neighbor.allow_header, neighbor.endpoint) : nil
117+
return call_with_allow_headers(env, last_neighbor_route) if !cascade && last_neighbor_route
118+
119+
nil
121120
end
122121

123122
def process_route(route, env)
124-
input, = *extract_input_and_method(env)
125-
routing_args = env[Grape::Env::GRAPE_ROUTING_ARGS]
126-
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(routing_args, route, input)
123+
prepare_env_from_route(env, route)
127124
route.exec(env)
128125
end
129126

130127
def make_routing_args(default_args, route, input)
131128
args = default_args || { route_info: route }
132-
args.merge(route.params(input))
129+
args.merge(route.params(input) || {})
133130
end
134131

135132
def extract_input_and_method(env)
@@ -160,9 +157,15 @@ def greedy_match?(input)
160157
@neutral_map.detect { |route| last_match["_#{route.index}"] }
161158
end
162159

163-
def call_with_allow_headers(env, methods, endpoint)
164-
env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ').freeze
165-
endpoint.call(env)
160+
def call_with_allow_headers(env, route)
161+
prepare_env_from_route(env, route)
162+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze
163+
route.endpoint.call(env)
164+
end
165+
166+
def prepare_env_from_route(env, route)
167+
input, = *extract_input_and_method(env)
168+
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
166169
end
167170

168171
def cascade?(response)

spec/grape/api_spec.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,71 @@ class DummyFormatClass
816816
end
817817
end
818818

819+
describe 'when helper behaviour is controlled by attributes on the route ' do
820+
before do
821+
subject.before do
822+
error!('Access Denied', 401) unless route.options[:secret] == params[:secret]
823+
end
824+
825+
subject.namespace 'example' do
826+
before do
827+
error!('Access Denied', 401) unless route.options[:namespace_secret] == params[:namespace_secret]
828+
end
829+
830+
desc 'it gets with secret', secret: 'password'
831+
get { status(params[:id] == '504' ? 200 : 404) }
832+
833+
desc 'it post with secret', secret: 'password', namespace_secret: 'namespace_password'
834+
post {}
835+
end
836+
end
837+
838+
context 'when HTTP method is not defined' do
839+
let(:response) { delete('/example') }
840+
841+
it 'responds with a 405 status' do
842+
expect(response.status).to eql 405
843+
end
844+
end
845+
846+
context 'when HTTP method is defined with attribute' do
847+
let(:response) { post('/example?secret=incorrect_password') }
848+
it 'responds with the defined error in the before hook' do
849+
expect(response.status).to eql 401
850+
end
851+
end
852+
853+
context 'when HTTP method is defined and the underlying before hook expectation is not met' do
854+
let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') }
855+
it 'ends up in the endpoint' do
856+
expect(response.status).to eql 401
857+
end
858+
end
859+
860+
context 'when HTTP method is defined and everything is like the before hooks expect' do
861+
let(:response) { post('/example?secret=password&namespace_secret=namespace_password') }
862+
it 'ends up in the endpoint' do
863+
expect(response.status).to eql 201
864+
end
865+
end
866+
867+
context 'when HEAD is called for the defined GET' do
868+
let(:response) { head('/example?id=504') }
869+
870+
it 'responds with 401 because before expectations in before hooks are not met' do
871+
expect(response.status).to eql 401
872+
end
873+
end
874+
875+
context 'when HEAD is called for the defined GET' do
876+
let(:response) { head('/example?id=504&secret=password') }
877+
878+
it 'responds with 200 because before hooks are not called' do
879+
expect(response.status).to eql 200
880+
end
881+
end
882+
end
883+
819884
context 'allows HEAD on a GET request that' do
820885
before do
821886
subject.get 'example' do

0 commit comments

Comments
 (0)