rackr 0.0.64 → 0.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/rackr/action.rb +237 -75
- data/lib/rackr/callback.rb +1 -0
- data/lib/rackr/router/build_request.rb +1 -0
- data/lib/rackr/router/dev_html/dump.rb +105 -0
- data/lib/rackr/router/{errors/dev_html.rb → dev_html/errors.rb} +12 -9
- data/lib/rackr/router/endpoint.rb +23 -0
- data/lib/rackr/router/errors.rb +10 -4
- data/lib/rackr/router/path_route.rb +35 -0
- data/lib/rackr/router/route.rb +8 -24
- data/lib/rackr/router.rb +204 -104
- data/lib/rackr/utils.rb +28 -0
- data/lib/rackr.rb +104 -33
- metadata +10 -9
data/lib/rackr/router/route.rb
CHANGED
|
@@ -2,41 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
class Rackr
|
|
4
4
|
class Router
|
|
5
|
+
# A Route for Rackr is an object that has endpoint and callbacks (before and after)
|
|
5
6
|
class Route
|
|
6
7
|
attr_reader :endpoint,
|
|
7
|
-
:splitted_path,
|
|
8
|
-
:has_params,
|
|
9
|
-
:has_befores,
|
|
10
8
|
:befores,
|
|
11
|
-
:
|
|
12
|
-
:afters
|
|
9
|
+
:has_befores,
|
|
10
|
+
:afters,
|
|
11
|
+
:has_afters
|
|
13
12
|
|
|
14
|
-
def initialize(
|
|
15
|
-
@path = path
|
|
16
|
-
@splitted_path = @path.split('/')
|
|
13
|
+
def initialize(endpoint, befores: [], afters: [])
|
|
17
14
|
@endpoint = endpoint
|
|
18
|
-
@params = fetch_params
|
|
19
|
-
@has_params = @params != []
|
|
20
15
|
@befores = befores
|
|
21
16
|
@has_befores = befores != []
|
|
22
17
|
@afters = afters
|
|
23
18
|
@has_afters = afters != []
|
|
24
|
-
@path_regex = /\A#{path.gsub(/(:\w+)/, '([^/]+)')}\z/
|
|
25
|
-
@wildcard = wildcard
|
|
26
19
|
end
|
|
27
20
|
|
|
28
|
-
def match?
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
path_info == @path
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def fetch_params
|
|
38
|
-
@splitted_path.select { |value| value.start_with? ':' }
|
|
39
|
-
end
|
|
21
|
+
def match? = true
|
|
22
|
+
def splitted_path = []
|
|
23
|
+
def has_params = false
|
|
40
24
|
end
|
|
41
25
|
end
|
|
42
26
|
end
|
data/lib/rackr/router.rb
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'router/errors'
|
|
4
|
+
require_relative 'router/endpoint'
|
|
4
5
|
require_relative 'router/route'
|
|
6
|
+
require_relative 'router/path_route'
|
|
5
7
|
require_relative 'router/build_request'
|
|
8
|
+
require_relative 'router/dev_html/errors'
|
|
9
|
+
require_relative 'router/dev_html/dump'
|
|
6
10
|
|
|
7
11
|
class Rackr
|
|
12
|
+
# This is the core class of Rackr. This class aggregate the route instance tree, callbacks (before and after) and scopes
|
|
13
|
+
# then, using the building blocks, match the request and call the endpoints
|
|
8
14
|
class Router
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
include Rackr::Utils
|
|
16
|
+
|
|
17
|
+
attr_writer :default_not_found
|
|
18
|
+
attr_reader :routes, :config, :not_found_tree, :error_tree, :specific_error_tree
|
|
11
19
|
|
|
12
20
|
def initialize(config = {}, before: [], after: [])
|
|
13
|
-
@instance_routes = {}
|
|
14
|
-
%w[GET POST DELETE PUT TRACE OPTIONS PATCH].each do |method|
|
|
15
|
-
@instance_routes[method] = { __instances: [] }
|
|
16
|
-
end
|
|
17
21
|
http_methods = HTTP_METHODS.map { |m| m.downcase.to_sym }
|
|
18
22
|
@routes = Struct.new(*http_methods).new
|
|
19
23
|
http_methods.each do |method|
|
|
@@ -28,55 +32,121 @@ class Rackr
|
|
|
28
32
|
@scopes_befores = {}
|
|
29
33
|
@afters = ensure_array(after)
|
|
30
34
|
@scopes_afters = {}
|
|
31
|
-
@
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
@path_routes_tree = {}
|
|
36
|
+
%w[GET POST DELETE PUT TRACE OPTIONS PATCH].each do |method|
|
|
37
|
+
@path_routes_tree[method] = { __instances: [] }
|
|
38
|
+
end
|
|
39
|
+
@not_found_tree = {}
|
|
40
|
+
@default_not_found =
|
|
41
|
+
Route.new(
|
|
42
|
+
proc { [404, {}, ['Not found']] },
|
|
43
|
+
befores: @befores,
|
|
44
|
+
afters: @afters
|
|
45
|
+
)
|
|
46
|
+
@error_tree = {}
|
|
47
|
+
@default_error =
|
|
48
|
+
Route.new(
|
|
49
|
+
proc { |_req, _e| [500, {}, ['Internal server error']] },
|
|
50
|
+
befores: @befores,
|
|
51
|
+
afters: @afters
|
|
52
|
+
)
|
|
53
|
+
@specific_error_tree = {}
|
|
54
|
+
@specific_errors = {}
|
|
35
55
|
end
|
|
36
56
|
|
|
37
57
|
def call(env)
|
|
38
58
|
path_info = env['PATH_INFO']
|
|
59
|
+
request_method = env['REQUEST_METHOD'] == 'HEAD' ? 'GET' : env['REQUEST_METHOD']
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
splitted_request_path_info = path_info.split('/')
|
|
62
|
+
current_request_path_info =
|
|
42
63
|
path_info == '/' ? path_info : path_info.chomp('/') # remove trailing "/"
|
|
43
64
|
|
|
44
|
-
|
|
45
|
-
|
|
65
|
+
route_instance, found_scopes = match_path_route(
|
|
66
|
+
request_method,
|
|
67
|
+
splitted_request_path_info,
|
|
68
|
+
current_request_path_info
|
|
69
|
+
)
|
|
70
|
+
rack_request = BuildRequest.new(env, splitted_request_path_info).call(route_instance)
|
|
71
|
+
befores = route_instance.befores
|
|
72
|
+
before_result = nil
|
|
46
73
|
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
begin
|
|
75
|
+
i = 0
|
|
76
|
+
while i < befores.size
|
|
77
|
+
before_result = Endpoint.call(befores[i], rack_request, @routes, @config)
|
|
78
|
+
unless before_result.is_a?(Rack::Request)
|
|
79
|
+
Errors.check_rack_response(before_result, 'before callback')
|
|
49
80
|
|
|
50
|
-
|
|
81
|
+
return before_result
|
|
82
|
+
end
|
|
51
83
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
i = 0
|
|
55
|
-
while i < befores.size
|
|
56
|
-
before_result = call_endpoint(befores[i], rack_request)
|
|
57
|
-
return before_result unless before_result.is_a?(Rack::Request)
|
|
84
|
+
i += 1
|
|
85
|
+
end
|
|
58
86
|
|
|
59
|
-
|
|
87
|
+
endpoint_result = Endpoint.call(route_instance.endpoint, before_result || rack_request, @routes, @config)
|
|
60
88
|
|
|
61
|
-
|
|
89
|
+
call_afters(route_instance, endpoint_result)
|
|
90
|
+
rescue Rackr::NotFound
|
|
91
|
+
return not_found_fallback(found_scopes, route_instance, before_result || rack_request)
|
|
92
|
+
rescue Rackr::Dump => e
|
|
93
|
+
return Endpoint.call(DevHtml::Dump, env.merge({ 'dump' => e }))
|
|
94
|
+
# rubocop:disable Lint/RescueException
|
|
95
|
+
rescue Exception => e
|
|
96
|
+
return error_fallback(found_scopes, route_instance, before_result || rack_request, e, env)
|
|
62
97
|
end
|
|
98
|
+
# rubocop:enable Lint/RescueException
|
|
99
|
+
|
|
100
|
+
Errors.check_rack_response(endpoint_result, 'action')
|
|
101
|
+
endpoint_result
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def not_found_fallback(found_scopes, route_instance, request)
|
|
105
|
+
endpoint_result = Endpoint.call(
|
|
106
|
+
match_route(
|
|
107
|
+
found_scopes,
|
|
108
|
+
not_found_tree,
|
|
109
|
+
@default_not_found
|
|
110
|
+
).endpoint,
|
|
111
|
+
request,
|
|
112
|
+
@routes,
|
|
113
|
+
@config
|
|
114
|
+
)
|
|
63
115
|
|
|
64
|
-
|
|
116
|
+
call_afters(route_instance, endpoint_result)
|
|
65
117
|
|
|
118
|
+
endpoint_result
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def error_fallback(found_scopes, route_instance, request, error, env)
|
|
122
|
+
error_route = match_route(
|
|
123
|
+
found_scopes,
|
|
124
|
+
specific_error_tree[error.class] || error_tree,
|
|
125
|
+
@specific_errors[error.class] || @default_error
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return Endpoint.call(DevHtml::Errors, env.merge({ 'error' => error })) if @dev_mode && error_route == @default_error
|
|
129
|
+
|
|
130
|
+
endpoint_result = Endpoint.call(error_route.endpoint, request, @routes, @config, error)
|
|
131
|
+
|
|
132
|
+
if endpoint_result.nil?
|
|
133
|
+
return Endpoint.call(DevHtml::Errors, env.merge({ 'error' => error })) if @dev_mode
|
|
134
|
+
|
|
135
|
+
endpoint_result = Endpoint.call(@default_error.endpoint, request, @routes, @config, error)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
call_afters(route_instance, endpoint_result)
|
|
139
|
+
|
|
140
|
+
endpoint_result
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def call_afters(route_instance, endpoint_result)
|
|
66
144
|
afters = route_instance.afters
|
|
67
145
|
i = 0
|
|
68
146
|
while i < afters.size
|
|
69
|
-
|
|
147
|
+
Endpoint.call(afters[i], endpoint_result, @routes, @config)
|
|
70
148
|
i += 1
|
|
71
149
|
end
|
|
72
|
-
|
|
73
|
-
endpoint_result
|
|
74
|
-
rescue Rackr::NotFound
|
|
75
|
-
call_endpoint(@not_found, request_builder.call)
|
|
76
|
-
rescue Exception => e
|
|
77
|
-
return @error.call(request_builder.call, e) if !@dev_mode || ENV['RACKR_ERROR_DEV']
|
|
78
|
-
|
|
79
|
-
call_endpoint(Errors::DevHtml, env.merge({ 'error' => e }))
|
|
80
150
|
end
|
|
81
151
|
|
|
82
152
|
def add(method, path, endpoint, as: nil, route_befores: [], route_afters: [])
|
|
@@ -89,34 +159,65 @@ class Rackr
|
|
|
89
159
|
method = :get if method == :head
|
|
90
160
|
|
|
91
161
|
wildcard = path == '*'
|
|
92
|
-
path = path.is_a?(Symbol) ? path.inspect : path.
|
|
162
|
+
path = path.is_a?(Symbol) ? path.inspect : path.delete_prefix('/')
|
|
93
163
|
path_with_scopes = "/#{not_empty_scopes.join('/')}#{put_path_slash(path)}"
|
|
94
164
|
add_named_route(method, path_with_scopes, as)
|
|
165
|
+
action_befores, action_afters = fetch_endpoint_callbacks(endpoint)
|
|
95
166
|
|
|
96
167
|
route_instance =
|
|
97
|
-
|
|
168
|
+
PathRoute.new(
|
|
98
169
|
path_with_scopes,
|
|
99
170
|
endpoint,
|
|
100
|
-
befores: @befores + ensure_array(route_befores),
|
|
101
|
-
afters: @afters + ensure_array(route_afters),
|
|
171
|
+
befores: @befores + ensure_array(route_befores) + action_befores,
|
|
172
|
+
afters: @afters + ensure_array(route_afters) + action_afters,
|
|
102
173
|
wildcard: wildcard
|
|
103
174
|
)
|
|
104
175
|
|
|
105
|
-
|
|
176
|
+
path_segments = path_with_scopes.split('/').reject(&:empty?)
|
|
106
177
|
|
|
107
|
-
|
|
178
|
+
if path_segments.empty?
|
|
179
|
+
@path_routes_tree[method.to_s.upcase][:__instances].push(route_instance)
|
|
180
|
+
else
|
|
181
|
+
deep_hash_push(@path_routes_tree[method.to_s.upcase], *(path_segments + [:__instances]), route_instance)
|
|
182
|
+
end
|
|
108
183
|
end
|
|
109
184
|
|
|
110
185
|
def add_not_found(endpoint)
|
|
111
|
-
Errors.check_endpoint(endpoint, '
|
|
186
|
+
Errors.check_endpoint(endpoint, 'not found')
|
|
112
187
|
|
|
113
|
-
|
|
188
|
+
action_befores, action_afters = fetch_endpoint_callbacks(endpoint)
|
|
189
|
+
route_instance =
|
|
190
|
+
Route.new(
|
|
191
|
+
endpoint,
|
|
192
|
+
befores: @befores + action_befores,
|
|
193
|
+
afters: @afters + action_afters
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return set_to_scope(not_found_tree, route_instance) if @scopes.size >= 1
|
|
197
|
+
|
|
198
|
+
@default_not_found = route_instance
|
|
114
199
|
end
|
|
115
200
|
|
|
116
|
-
def add_error(endpoint)
|
|
201
|
+
def add_error(endpoint, error_class = nil)
|
|
117
202
|
Errors.check_endpoint(endpoint, 'error')
|
|
118
203
|
|
|
119
|
-
|
|
204
|
+
action_befores, action_afters = fetch_endpoint_callbacks(endpoint)
|
|
205
|
+
route_instance =
|
|
206
|
+
Route.new(
|
|
207
|
+
endpoint,
|
|
208
|
+
befores: @befores + action_befores,
|
|
209
|
+
afters: @afters + action_afters
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if error_class
|
|
213
|
+
return set_to_scope(specific_error_tree[error_class] ||= {}, route_instance) if @scopes.size >= 1
|
|
214
|
+
|
|
215
|
+
@specific_errors[error_class] = route_instance
|
|
216
|
+
else
|
|
217
|
+
return set_to_scope(error_tree, route_instance) if @scopes.size >= 1
|
|
218
|
+
|
|
219
|
+
@default_error = route_instance
|
|
220
|
+
end
|
|
120
221
|
end
|
|
121
222
|
|
|
122
223
|
def append_scope(name, scope_befores: [], scope_afters: [])
|
|
@@ -144,45 +245,34 @@ class Rackr
|
|
|
144
245
|
@scopes = @scopes.first(@scopes.size - 1)
|
|
145
246
|
end
|
|
146
247
|
|
|
147
|
-
|
|
248
|
+
def not_empty_scopes
|
|
249
|
+
@scopes.reject { |v| (v == '') }
|
|
250
|
+
end
|
|
148
251
|
|
|
149
|
-
|
|
150
|
-
return endpoint.call(content) if endpoint.respond_to?(:call)
|
|
252
|
+
private
|
|
151
253
|
|
|
152
|
-
|
|
153
|
-
|
|
254
|
+
def fetch_endpoint_callbacks(endpoint)
|
|
255
|
+
action_befores = []
|
|
256
|
+
action_afters = []
|
|
257
|
+
if endpoint.is_a?(Class) && endpoint.ancestors.include?(Rackr::Action)
|
|
258
|
+
action_instance = endpoint.new
|
|
259
|
+
action_befores = action_instance.befores
|
|
260
|
+
action_afters = action_instance.afters
|
|
154
261
|
end
|
|
155
262
|
|
|
156
|
-
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def ensure_array(list)
|
|
160
|
-
return [] if list.nil?
|
|
161
|
-
return list if list.is_a?(Array)
|
|
162
|
-
|
|
163
|
-
[list]
|
|
263
|
+
[action_befores, action_afters]
|
|
164
264
|
end
|
|
165
265
|
|
|
166
266
|
def add_named_route(method, path_with_scopes, as)
|
|
167
267
|
return @routes.send(method.downcase)[:root] = path_with_scopes if path_with_scopes == '/'
|
|
168
268
|
return @routes.send(method.downcase)[as] = path_with_scopes unless as.nil?
|
|
169
269
|
|
|
170
|
-
key = path_with_scopes.sub('/', '').
|
|
270
|
+
key = path_with_scopes.sub('/', '').delete(':').tr('/', '_')
|
|
171
271
|
@routes.send(method.downcase)[key.to_s.to_sym] = path_with_scopes
|
|
172
272
|
end
|
|
173
273
|
|
|
174
|
-
def
|
|
175
|
-
|
|
176
|
-
push_it(@instance_routes[method], *scopes_with_slash, route_instance)
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def push_it(hash, first_key, *rest_keys, val)
|
|
180
|
-
if rest_keys.empty?
|
|
181
|
-
(hash[first_key] ||= []) << val
|
|
182
|
-
else
|
|
183
|
-
hash[first_key] = push_it(hash[first_key] ||= {}, *rest_keys, val)
|
|
184
|
-
end
|
|
185
|
-
hash
|
|
274
|
+
def set_to_scope(instances, route_instance)
|
|
275
|
+
deep_hash_set(instances, not_empty_scopes + %i[__instance], route_instance)
|
|
186
276
|
end
|
|
187
277
|
|
|
188
278
|
def put_path_slash(path)
|
|
@@ -195,52 +285,62 @@ class Rackr
|
|
|
195
285
|
path
|
|
196
286
|
end
|
|
197
287
|
|
|
198
|
-
def
|
|
199
|
-
|
|
200
|
-
|
|
288
|
+
def match_path_route(request_method, splitted_request_path_info, current_request_path_info)
|
|
289
|
+
path_routes_tree = @path_routes_tree[request_method]
|
|
290
|
+
found_scopes = []
|
|
201
291
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
292
|
+
i = 1
|
|
293
|
+
while i < splitted_request_path_info.size
|
|
294
|
+
segment = splitted_request_path_info[i]
|
|
295
|
+
|
|
296
|
+
if path_routes_tree.key?(segment)
|
|
297
|
+
found_scopes << segment
|
|
298
|
+
path_routes_tree = path_routes_tree[segment]
|
|
299
|
+
elsif (param_key = path_routes_tree.keys.find { |k| k.start_with?(':') })
|
|
300
|
+
found_scopes << param_key
|
|
301
|
+
path_routes_tree = path_routes_tree[param_key]
|
|
302
|
+
elsif path_routes_tree.key?('*')
|
|
303
|
+
path_routes_tree = path_routes_tree['*']
|
|
304
|
+
break
|
|
305
|
+
else
|
|
306
|
+
break
|
|
307
|
+
end
|
|
308
|
+
i += 1
|
|
207
309
|
end
|
|
208
310
|
|
|
209
|
-
|
|
210
|
-
|
|
311
|
+
route_instance = path_routes_tree[:__instances]&.detect do |route|
|
|
312
|
+
route.match?(current_request_path_info)
|
|
313
|
+
end
|
|
211
314
|
|
|
212
|
-
|
|
315
|
+
route_instance = find_not_found_route(found_scopes) if route_instance.nil?
|
|
213
316
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
found_route = nil
|
|
317
|
+
[route_instance, found_scopes]
|
|
318
|
+
end
|
|
217
319
|
|
|
218
|
-
|
|
219
|
-
|
|
320
|
+
def find_not_found_route(found_scopes)
|
|
321
|
+
not_found_route = nil
|
|
220
322
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
break
|
|
225
|
-
elsif scope.start_with?(':')
|
|
226
|
-
found_route = find_instance_in_scope.(request_method, found_scopes)
|
|
227
|
-
return found_route if found_route
|
|
323
|
+
while not_found_route.nil? && !found_scopes.empty?
|
|
324
|
+
not_found_route = @not_found_tree.dig(*found_scopes, :__instance)
|
|
325
|
+
break if not_found_route
|
|
228
326
|
|
|
229
|
-
|
|
230
|
-
instance_routes = @instance_routes[request_method].dig(*found_scopes)
|
|
231
|
-
break
|
|
232
|
-
end
|
|
233
|
-
end
|
|
327
|
+
found_scopes.pop
|
|
234
328
|
end
|
|
235
329
|
|
|
236
|
-
|
|
330
|
+
not_found_route || @default_not_found
|
|
331
|
+
end
|
|
237
332
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
333
|
+
def match_route(found_scopes, instances, default_instance)
|
|
334
|
+
route_instance = nil
|
|
335
|
+
|
|
336
|
+
while route_instance.nil? && found_scopes != []
|
|
337
|
+
route_instance = instances&.dig(*(found_scopes + [:__instance]))
|
|
338
|
+
found_scopes.pop
|
|
241
339
|
end
|
|
242
340
|
|
|
243
|
-
|
|
341
|
+
return default_instance if route_instance.nil?
|
|
342
|
+
|
|
343
|
+
route_instance
|
|
244
344
|
end
|
|
245
345
|
end
|
|
246
346
|
end
|
data/lib/rackr/utils.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Rackr
|
|
4
|
+
# Utils methods for Rackr
|
|
5
|
+
module Utils
|
|
6
|
+
def deep_hash_push(hash, first_key, *rest_keys, val)
|
|
7
|
+
if rest_keys.empty?
|
|
8
|
+
(hash[first_key] ||= []) << val
|
|
9
|
+
else
|
|
10
|
+
hash[first_key] = deep_hash_push(hash[first_key] ||= {}, *rest_keys, val)
|
|
11
|
+
end
|
|
12
|
+
hash
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ensure_array(list)
|
|
16
|
+
return [] if list.nil?
|
|
17
|
+
return list if list.is_a?(Array)
|
|
18
|
+
|
|
19
|
+
[list]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def deep_hash_set(hash, keys, value)
|
|
23
|
+
*path, last = keys
|
|
24
|
+
node = path.inject(hash) { |h, k| h[k] ||= {} }
|
|
25
|
+
node[last] = value
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|