1
+ require 'grape/router'
2
+
1
3
module Grape
2
4
# The API class is the primary entry point for creating Grape APIs. Users
3
5
# should subclass this class in order to build an API.
4
6
class API
5
7
include Grape ::DSL ::API
6
8
7
9
class << self
8
- attr_reader :instance
10
+ attr_reader :instance , :router
9
11
10
12
# A class-level lock to ensure the API is not compiled by multiple
11
13
# threads simultaneously within the same process.
@@ -87,24 +89,25 @@ def inherit_settings(other_settings)
87
89
# Builds the routes from the defined endpoints, effectively compiling
88
90
# this API into a usable form.
89
91
def initialize
90
- @route_set = Rack :: Mount :: RouteSet . new
92
+ @router = Router . new
91
93
add_head_not_allowed_methods_and_options_methods
92
94
self . class . endpoints . each do |endpoint |
93
- endpoint . mount_in ( @route_set )
95
+ endpoint . mount_in ( @router )
94
96
end
95
97
96
- @route_set . freeze
98
+ @router . compile!
99
+ @router . freeze
97
100
end
98
101
99
102
# Handle a request. See Rack documentation for what `env` is.
100
103
def call ( env )
101
- result = @route_set . call ( env )
104
+ result = @router . call ( env )
102
105
result [ 1 ] . delete ( Grape ::Http ::Headers ::X_CASCADE ) unless cascade?
103
106
result
104
107
end
105
108
106
109
# Some requests may return a HTTP 404 error if grape cannot find a matching
107
- # route. In this case, Rack::Mount adds a X-Cascade header to the response
110
+ # route. In this case, Grape::Router adds a X-Cascade header to the response
108
111
# and sets it to 'pass', indicating to grape's parents they should keep
109
112
# looking for a matching route on other resources.
110
113
#
@@ -126,20 +129,23 @@ def cascade?
126
129
# will return an HTTP 405 response for any HTTP method that the resource
127
130
# cannot handle.
128
131
def add_head_not_allowed_methods_and_options_methods
129
- methods_per_path = { }
132
+ routes_map = { }
130
133
131
134
self . class . endpoints . each do |endpoint |
132
135
routes = endpoint . routes
133
136
routes . each do |route |
134
- route_path = route . route_path
135
- . gsub ( /\( .*\) / , '' ) # ignore any optional portions
136
- . gsub ( %r{\: [^\/ .?]+} , ':x' ) # substitute variable names to avoid conflicts
137
-
138
- methods_per_path [ route_path ] ||= [ ]
139
- methods_per_path [ route_path ] << route . route_method
137
+ # using the :any shorthand produces [nil] for route methods, substitute all manually
138
+ route_key = route . pattern . to_regexp
139
+ routes_map [ route_key ] ||= { }
140
+ route_settings = routes_map [ route_key ]
141
+ route_settings [ :requirements ] = route . requirements
142
+ route_settings [ :path ] = route . origin
143
+ route_settings [ :methods ] ||= [ ]
144
+ route_settings [ :methods ] << route . request_method
145
+ route_settings [ :endpoint ] = route . app
140
146
141
147
# using the :any shorthand produces [nil] for route methods, substitute all manually
142
- methods_per_path [ route_path ] = %w( GET PUT POST DELETE PATCH HEAD OPTIONS ) if methods_per_path [ route_path ] . compact . empty?
148
+ route_settings [ :methods ] = %w( GET PUT POST DELETE PATCH HEAD OPTIONS ) if route_settings [ :methods ] . include? ( 'ANY' )
143
149
end
144
150
end
145
151
@@ -149,7 +155,9 @@ def add_head_not_allowed_methods_and_options_methods
149
155
# informations again.
150
156
without_root_prefix do
151
157
without_versioning do
152
- methods_per_path . each do |path , methods |
158
+ routes_map . each do |regexp , config |
159
+ methods = config [ :methods ]
160
+ path = config [ :path ]
153
161
allowed_methods = methods . dup
154
162
155
163
unless self . class . namespace_inheritable ( :do_not_route_head )
@@ -159,18 +167,18 @@ def add_head_not_allowed_methods_and_options_methods
159
167
allow_header = ( self . class . namespace_inheritable ( :do_not_route_options ) ? allowed_methods : [ Grape ::Http ::Headers ::OPTIONS ] | allowed_methods ) . join ( ', ' )
160
168
161
169
unless self . class . namespace_inheritable ( :do_not_route_options )
162
- generate_options_method ( path , allow_header ) unless allowed_methods . include? ( Grape ::Http ::Headers ::OPTIONS )
170
+ generate_options_method ( path , allow_header , config ) unless allowed_methods . include? ( Grape ::Http ::Headers ::OPTIONS )
163
171
end
164
172
165
- generate_not_allowed_method ( path , allowed_methods , allow_header )
173
+ generate_not_allowed_method ( regexp , allowed_methods , allow_header , config [ :endpoint ] )
166
174
end
167
175
end
168
176
end
169
177
end
170
178
171
179
# Generate an 'OPTIONS' route for a pre-exisiting user defined route
172
- def generate_options_method ( path , allow_header )
173
- self . class . options ( path , { } ) do
180
+ def generate_options_method ( path , allow_header , options = { } )
181
+ self . class . options ( path , options ) do
174
182
header 'Allow' , allow_header
175
183
status 204
176
184
''
@@ -179,15 +187,13 @@ def generate_options_method(path, allow_header)
179
187
180
188
# Generate a route that returns an HTTP 405 response for a user defined
181
189
# path on methods not specified
182
- def generate_not_allowed_method ( path , allowed_methods , allow_header )
190
+ def generate_not_allowed_method ( path , allowed_methods , allow_header , endpoint = nil )
183
191
not_allowed_methods = %w( GET PUT POST DELETE PATCH HEAD ) - allowed_methods
184
192
not_allowed_methods << Grape ::Http ::Headers ::OPTIONS if self . class . namespace_inheritable ( :do_not_route_options )
185
193
186
194
return if not_allowed_methods . empty?
187
195
188
- self . class . route ( not_allowed_methods , path ) do
189
- fail Grape ::Exceptions ::MethodNotAllowed , header . merge ( 'Allow' => allow_header )
190
- end
196
+ @router . associate_routes ( path , not_allowed_methods , allow_header , endpoint )
191
197
end
192
198
193
199
# Allows definition of endpoints that ignore the versioning configuration
0 commit comments