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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a2c53919b2365343fd55636d5d3a29b41e93975a60ef3d2af74416e12808f27a
|
|
4
|
+
data.tar.gz: 32f5c5da65e14bf318c07427fd5bb0f9c978c04d75e94877ddab0793da4a8005
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8e0decd638c32afe394950c7b45bf44539704b136bde9d9e9dfc2a698e37c427317e16fb310bb6515d0f4a339fd224228d9d5ee424ddbaca7f191ec8f40f4b27
|
|
7
|
+
data.tar.gz: 04117f475a16165a813cac337f72880e6d461ffe41e3ce085ab06690fbc4b21a85f1f22d343fa4a4b070f4a7d9898f26eec29d231df7c8b9469b4d2d60f34848
|
data/lib/rackr/action.rb
CHANGED
|
@@ -3,76 +3,259 @@
|
|
|
3
3
|
require 'erubi'
|
|
4
4
|
require 'oj'
|
|
5
5
|
require 'rack'
|
|
6
|
+
require_relative 'action/callbacks'
|
|
6
7
|
|
|
7
8
|
class Rackr
|
|
9
|
+
# This module provides the action functions available inside the routes context or
|
|
10
|
+
# specific action class that included the Rackr::Action.
|
|
8
11
|
module Action
|
|
12
|
+
MIME_TYPES = {
|
|
13
|
+
text: 'text/plain',
|
|
14
|
+
html: 'text/html',
|
|
15
|
+
json: 'application/json',
|
|
16
|
+
manifest: 'text/cache-manifest',
|
|
17
|
+
atom: 'application/atom+xml',
|
|
18
|
+
avi: 'video/x-msvideo',
|
|
19
|
+
bmp: 'image/bmp',
|
|
20
|
+
bz: 'application/x-bzip',
|
|
21
|
+
bz2: 'application/x-bzip2',
|
|
22
|
+
chm: 'application/vnd.ms-htmlhelp',
|
|
23
|
+
css: 'text/css',
|
|
24
|
+
csv: 'text/csv',
|
|
25
|
+
flv: 'video/x-flv',
|
|
26
|
+
gif: 'image/gif',
|
|
27
|
+
gz: 'application/x-gzip',
|
|
28
|
+
h264: 'video/h264',
|
|
29
|
+
ico: 'image/vnd.microsoft.icon',
|
|
30
|
+
ics: 'text/calendar',
|
|
31
|
+
jpg: 'image/jpeg',
|
|
32
|
+
js: 'application/javascript',
|
|
33
|
+
mp4: 'video/mp4',
|
|
34
|
+
mov: 'video/quicktime',
|
|
35
|
+
mp3: 'audio/mpeg',
|
|
36
|
+
mp4a: 'audio/mp4',
|
|
37
|
+
mpg: 'video/mpeg',
|
|
38
|
+
oga: 'audio/ogg',
|
|
39
|
+
ogg: 'application/ogg',
|
|
40
|
+
ogv: 'video/ogg',
|
|
41
|
+
pdf: 'application/pdf',
|
|
42
|
+
pgp: 'application/pgp-encrypted',
|
|
43
|
+
png: 'image/png',
|
|
44
|
+
psd: 'image/vnd.adobe.photoshop',
|
|
45
|
+
rss: 'application/rss+xml',
|
|
46
|
+
rtf: 'application/rtf',
|
|
47
|
+
sh: 'application/x-sh',
|
|
48
|
+
svg: 'image/svg+xml',
|
|
49
|
+
swf: 'application/x-shockwave-flash',
|
|
50
|
+
tar: 'application/x-tar',
|
|
51
|
+
torrent: 'application/x-bittorrent',
|
|
52
|
+
tsv: 'text/tab-separated-values',
|
|
53
|
+
uri: 'text/uri-list',
|
|
54
|
+
vcs: 'text/x-vcalendar',
|
|
55
|
+
wav: 'audio/x-wav',
|
|
56
|
+
webm: 'video/webm',
|
|
57
|
+
wmv: 'video/x-ms-wmv',
|
|
58
|
+
woff: 'application/font-woff',
|
|
59
|
+
woff2: 'application/font-woff2',
|
|
60
|
+
wsdl: 'application/wsdl+xml',
|
|
61
|
+
xhtml: 'application/xhtml+xml',
|
|
62
|
+
xml: 'application/xml',
|
|
63
|
+
xslt: 'application/xslt+xml',
|
|
64
|
+
yml: 'text/yaml',
|
|
65
|
+
zip: 'application/zip'
|
|
66
|
+
}.freeze
|
|
67
|
+
|
|
68
|
+
DEFAULT_CSP_HEADERS = {
|
|
69
|
+
base_uri: "'self'",
|
|
70
|
+
child_src: "'self'",
|
|
71
|
+
connect_src: "'self'",
|
|
72
|
+
default_src: "'none'",
|
|
73
|
+
font_src: "'self'",
|
|
74
|
+
form_action: "'self'",
|
|
75
|
+
frame_ancestors: "'self'",
|
|
76
|
+
frame_src: "'self'",
|
|
77
|
+
img_src: "'self' https: data:",
|
|
78
|
+
media_src: "'self'",
|
|
79
|
+
object_src: "'none'",
|
|
80
|
+
script_src: "'self'",
|
|
81
|
+
style_src: "'self' 'unsafe-inline' https:"
|
|
82
|
+
}.freeze
|
|
83
|
+
|
|
84
|
+
# These are constant (not methods) for better performance
|
|
85
|
+
|
|
86
|
+
DEFAULT_HEADERS = (proc do |content_type, headers, content|
|
|
87
|
+
{
|
|
88
|
+
'content-type' => content_type,
|
|
89
|
+
'content-length' => content.bytesize.to_s
|
|
90
|
+
}.merge(headers)
|
|
91
|
+
end).freeze
|
|
92
|
+
|
|
9
93
|
RENDER = {
|
|
10
|
-
|
|
11
|
-
|
|
94
|
+
json: proc do |content, status, headers, charset|
|
|
95
|
+
content = Oj.dump(content, mode: :compat) unless content.is_a?(String)
|
|
96
|
+
[status || 200, DEFAULT_HEADERS.call("application/json; charset=#{charset}", headers, content), [content]]
|
|
12
97
|
end,
|
|
13
|
-
|
|
14
|
-
[
|
|
98
|
+
html: proc do |content, status, headers, charset, content_security_policy|
|
|
99
|
+
headers['content-security-policy'] = content_security_policy
|
|
100
|
+
[status || 200, DEFAULT_HEADERS.call("text/html; charset=#{charset}", headers, content), [content]]
|
|
15
101
|
end,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
102
|
+
res: proc do |content, status, _headers, charset|
|
|
103
|
+
content.status = status if status
|
|
104
|
+
if charset
|
|
105
|
+
content.content_type =
|
|
106
|
+
(content.content_type || 'charset=utf-8').sub(/charset=\S+/, "charset=#{charset}")
|
|
107
|
+
end
|
|
108
|
+
content.headers['content-length'] ||= content.body.join.bytesize.to_s
|
|
109
|
+
content.finish
|
|
19
110
|
end,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
111
|
+
response: proc do |content, status, _headers, charset|
|
|
112
|
+
content.status = status if status
|
|
113
|
+
if charset
|
|
114
|
+
content.content_type =
|
|
115
|
+
(content.content_type || 'charset=utf-8').sub(/charset=\S+/, "charset=#{charset}")
|
|
116
|
+
end
|
|
117
|
+
content.headers['content-length'] ||= content.body.join.bytesize.to_s
|
|
118
|
+
content.finish
|
|
119
|
+
end
|
|
120
|
+
}.freeze
|
|
121
|
+
|
|
122
|
+
BUILD_RESPONSE = {
|
|
123
|
+
json: proc do |content, status, headers, charset|
|
|
124
|
+
content = Oj.dump(content, mode: :compat) unless content.is_a?(String)
|
|
125
|
+
Rack::Response.new(content, status,
|
|
126
|
+
DEFAULT_HEADERS.call("application/json; charset=#{charset}", headers, content))
|
|
127
|
+
end,
|
|
128
|
+
html: proc do |content, status, headers, charset, content_security_policy|
|
|
129
|
+
headers['content-security-policy'] = content_security_policy
|
|
130
|
+
Rack::Response.new(content, status, DEFAULT_HEADERS.call("text/html; charset=#{charset}", headers, content))
|
|
24
131
|
end,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
132
|
+
head: proc do |status, _empty, headers|
|
|
133
|
+
Rack::Response.new(nil, status, headers)
|
|
134
|
+
end,
|
|
135
|
+
redirect_to: proc do |content, _status, headers|
|
|
136
|
+
Rack::Response.new(
|
|
137
|
+
nil,
|
|
138
|
+
302,
|
|
139
|
+
{ 'location' => content }.merge(headers)
|
|
140
|
+
)
|
|
29
141
|
end
|
|
30
142
|
}.freeze
|
|
31
143
|
|
|
32
144
|
def self.included(base)
|
|
33
145
|
base.class_eval do
|
|
34
|
-
|
|
146
|
+
if self != Rackr
|
|
147
|
+
attr_reader :routes, :config, :deps, :db, :log, :cache
|
|
148
|
+
|
|
149
|
+
include Callbacks unless included_modules.include?(Rackr::Callback)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
include HtmlSlice if Object.const_defined?('HtmlSlice')
|
|
153
|
+
include Stimulux if Object.const_defined?('Stimulux')
|
|
35
154
|
|
|
36
155
|
def initialize(routes: nil, config: nil)
|
|
37
156
|
@routes = routes
|
|
38
157
|
@config = config
|
|
39
|
-
@deps = config
|
|
40
|
-
@db = config
|
|
158
|
+
@deps = config&.dig(:deps)
|
|
159
|
+
@db = config&.dig(:deps, :db)
|
|
160
|
+
@log = config&.dig(:deps, :log)
|
|
161
|
+
@cache = config&.dig(:deps, :cache)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def url_for(method, name)
|
|
165
|
+
"#{config&.dig(:host)}#{path_for(method, name)}"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def path_for(method, name)
|
|
169
|
+
routes.send(method)&.dig(name)
|
|
41
170
|
end
|
|
42
171
|
|
|
43
172
|
def render(**opts)
|
|
44
173
|
type = opts.keys.first
|
|
45
174
|
content = opts[type]
|
|
46
175
|
|
|
47
|
-
|
|
176
|
+
if (renderer = RENDER[type])
|
|
177
|
+
return renderer.call(
|
|
178
|
+
content,
|
|
179
|
+
opts[:status],
|
|
180
|
+
opts[:headers] || {},
|
|
181
|
+
opts[:charset] || 'utf-8',
|
|
182
|
+
content_security_policy
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
if (mime = MIME_TYPES[type])
|
|
187
|
+
return [
|
|
188
|
+
opts[:status] || 200,
|
|
189
|
+
DEFAULT_HEADERS.call(
|
|
190
|
+
"#{mime}; charset=#{opts[:charset] || 'utf-8'}",
|
|
191
|
+
opts[:headers] || {},
|
|
192
|
+
content
|
|
193
|
+
),
|
|
194
|
+
[content]
|
|
195
|
+
]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
_render_view(
|
|
199
|
+
content,
|
|
200
|
+
status: opts[:status] || 200,
|
|
201
|
+
headers: opts[:headers] || {},
|
|
202
|
+
layout_path: opts[:layout_path] || 'layout',
|
|
203
|
+
view: opts[:view],
|
|
204
|
+
response_instance: opts[:response_instance] || false,
|
|
205
|
+
charset: opts[:charset] || 'utf-8'
|
|
206
|
+
)
|
|
48
207
|
end
|
|
49
208
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
209
|
+
def build_response(**opts)
|
|
210
|
+
type = opts.keys.first
|
|
211
|
+
content = opts[type]
|
|
212
|
+
|
|
213
|
+
if (builder = BUILD_RESPONSE[type])
|
|
214
|
+
return builder.call(
|
|
215
|
+
content,
|
|
216
|
+
opts[:status] || 200,
|
|
217
|
+
opts[:headers] || {},
|
|
218
|
+
opts[:charset] || 'utf-8',
|
|
219
|
+
content_security_policy
|
|
220
|
+
)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
if (mime = MIME_TYPES[type])
|
|
224
|
+
return Rack::Response.new(
|
|
225
|
+
content,
|
|
226
|
+
opts[:status] || 200,
|
|
227
|
+
DEFAULT_HEADERS.call(
|
|
228
|
+
"#{mime}; charset=#{opts[:charset] || 'utf-8'}",
|
|
229
|
+
opts[:headers] || {},
|
|
230
|
+
content
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
end
|
|
234
|
+
|
|
56
235
|
_render_view(
|
|
57
|
-
|
|
58
|
-
status: status,
|
|
59
|
-
headers: headers,
|
|
60
|
-
layout_path: layout_path,
|
|
61
|
-
|
|
236
|
+
content,
|
|
237
|
+
status: opts[:status] || 200,
|
|
238
|
+
headers: opts[:headers] || {},
|
|
239
|
+
layout_path: opts[:layout_path] || 'layout',
|
|
240
|
+
view: opts[:view],
|
|
241
|
+
response_instance: true,
|
|
242
|
+
charset: opts[:charset] || 'utf-8'
|
|
62
243
|
)
|
|
63
244
|
end
|
|
64
245
|
|
|
65
246
|
def _render_view(
|
|
66
247
|
paths,
|
|
67
|
-
status
|
|
68
|
-
headers
|
|
69
|
-
layout_path
|
|
70
|
-
response_instance
|
|
71
|
-
view
|
|
248
|
+
status:,
|
|
249
|
+
headers:,
|
|
250
|
+
layout_path:,
|
|
251
|
+
response_instance:,
|
|
252
|
+
view:,
|
|
253
|
+
charset:
|
|
72
254
|
)
|
|
73
255
|
base_path = config.dig(:views, :path) || 'views'
|
|
256
|
+
headers['content-security-policy'] ||= content_security_policy
|
|
74
257
|
|
|
75
|
-
file_or_nil =
|
|
258
|
+
file_or_nil = proc do |path|
|
|
76
259
|
::File.read(path)
|
|
77
260
|
rescue Errno::ENOENT
|
|
78
261
|
nil
|
|
@@ -99,62 +282,31 @@ class Rackr
|
|
|
99
282
|
return Rack::Response.new(
|
|
100
283
|
parsed_erb,
|
|
101
284
|
status,
|
|
102
|
-
|
|
285
|
+
DEFAULT_HEADERS.call("text/html; charset=#{charset}", headers, parsed_erb)
|
|
103
286
|
)
|
|
104
287
|
end
|
|
105
288
|
|
|
106
|
-
[status,
|
|
107
|
-
[parsed_erb]]
|
|
289
|
+
[status, DEFAULT_HEADERS.call("text/html; charset=#{charset}", headers, parsed_erb), [parsed_erb]]
|
|
108
290
|
end
|
|
109
291
|
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Oj.load(val)
|
|
292
|
+
def d(content)
|
|
293
|
+
raise Rackr::Dump, content
|
|
114
294
|
end
|
|
115
295
|
|
|
116
|
-
def
|
|
117
|
-
|
|
118
|
-
{ 'content-type' => 'text/html', 'content-length' => content.bytesize.to_s }.merge(headers))
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def json_response(content = {}, status: 200, headers: {})
|
|
122
|
-
content = Oj.dump(content, mode: :compat) unless content.is_a?(String)
|
|
123
|
-
Rack::Response.new(
|
|
124
|
-
content,
|
|
125
|
-
status,
|
|
126
|
-
{ 'content-type' => 'application/json', 'content-length' => content.bytesize.to_s }.merge(headers)
|
|
127
|
-
)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def text_response(content, status: 200, headers: {})
|
|
131
|
-
Rack::Response.new(
|
|
132
|
-
content,
|
|
133
|
-
status,
|
|
134
|
-
{ 'content-type' => 'text/plain', 'content-length' => content.bytesize.to_s }.merge(headers)
|
|
135
|
-
)
|
|
296
|
+
def not_found!
|
|
297
|
+
raise Rackr::NotFound
|
|
136
298
|
end
|
|
137
299
|
|
|
300
|
+
# rubocop:disable Security/Eval
|
|
138
301
|
def load_erb(content, binding_context: nil)
|
|
139
302
|
eval(Erubi::Engine.new(content).src, binding_context)
|
|
140
303
|
end
|
|
304
|
+
# rubocop:enable Security/Eval
|
|
141
305
|
|
|
142
306
|
def head(status, headers: {})
|
|
143
307
|
[status, headers, []]
|
|
144
308
|
end
|
|
145
309
|
|
|
146
|
-
def head_response(status, headers: {})
|
|
147
|
-
Rack::Response.new(nil, status, headers)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def redirect_response(url, headers: {})
|
|
151
|
-
Rack::Response.new(
|
|
152
|
-
nil,
|
|
153
|
-
302,
|
|
154
|
-
{ 'location' => url }.merge(headers)
|
|
155
|
-
)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
310
|
def redirect_to(url, headers: {})
|
|
159
311
|
[302, { 'location' => url }.merge(headers), []]
|
|
160
312
|
end
|
|
@@ -162,7 +314,17 @@ class Rackr
|
|
|
162
314
|
def response(body = nil, status = 200, headers = {})
|
|
163
315
|
Rack::Response.new(body, status, headers)
|
|
164
316
|
end
|
|
317
|
+
|
|
318
|
+
def content_security_policy
|
|
319
|
+
@content_security_policy ||=
|
|
320
|
+
DEFAULT_CSP_HEADERS
|
|
321
|
+
.merge(config&.dig(:csp_headers) || {})
|
|
322
|
+
.map { |k, v| "#{k.to_s.tr('_', '-')} #{v}" }
|
|
323
|
+
.join('; ')
|
|
324
|
+
end
|
|
165
325
|
end
|
|
166
326
|
end
|
|
167
327
|
end
|
|
328
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
329
|
+
# rubocop:enable Metrics/MethodLength
|
|
168
330
|
end
|
data/lib/rackr/callback.rb
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rack/utils'
|
|
4
|
+
|
|
5
|
+
class Rackr
|
|
6
|
+
class Router
|
|
7
|
+
module DevHtml
|
|
8
|
+
# Pretty print the content of the dump
|
|
9
|
+
class PrettyPrint
|
|
10
|
+
def self.call(content, level = 0)
|
|
11
|
+
content = content.inspect if content.instance_of?(String)
|
|
12
|
+
content = 'nil' if content.instance_of?(nil)
|
|
13
|
+
content = 'false' if content.instance_of?(false)
|
|
14
|
+
|
|
15
|
+
return "<pre>#{Rack::Utils.escape_html(content)}</pre>" if level >= 2
|
|
16
|
+
|
|
17
|
+
case content
|
|
18
|
+
when String, Symbol, Numeric, TrueClass, FalseClass, NilClass
|
|
19
|
+
"<pre>#{Rack::Utils.escape_html(content)}</pre>"
|
|
20
|
+
when Array
|
|
21
|
+
pretty_print_array(content, level)
|
|
22
|
+
when Hash
|
|
23
|
+
pretty_print_hash(content, level)
|
|
24
|
+
else
|
|
25
|
+
pretty_print_object(content, level)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.pretty_print_array(array, level)
|
|
30
|
+
list_items = array.map do |item|
|
|
31
|
+
"<li>#{call(item, level + 1)}</li>"
|
|
32
|
+
end.join
|
|
33
|
+
|
|
34
|
+
"<ul>#{list_items}</ul>"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.pretty_print_hash(hash, level)
|
|
38
|
+
list_items = hash.map do |key, value|
|
|
39
|
+
"<li><strong>#{key.inspect} =></strong> #{call(value, level + 1)}</li>"
|
|
40
|
+
end.join
|
|
41
|
+
|
|
42
|
+
"<ul>#{list_items}</ul>"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.pretty_print_object(content, level)
|
|
46
|
+
instance_vars = content.instance_variables.map do |var|
|
|
47
|
+
value = content.instance_variable_get(var)
|
|
48
|
+
"<li><strong>#{var}:</strong> #{call(value, level + 1)}</li>"
|
|
49
|
+
end.join
|
|
50
|
+
|
|
51
|
+
"<h3>#{content.class}</h3><ul>#{instance_vars}</ul>"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# This is the action that is called when a dump is raised
|
|
56
|
+
class Dump
|
|
57
|
+
include Rackr::Action
|
|
58
|
+
|
|
59
|
+
def call(env)
|
|
60
|
+
res = response(<<-HTML
|
|
61
|
+
<!DOCTYPE html>
|
|
62
|
+
<html>
|
|
63
|
+
<head>
|
|
64
|
+
<title>Application dump</title>
|
|
65
|
+
<style>
|
|
66
|
+
body {
|
|
67
|
+
padding: 0px;
|
|
68
|
+
margin: 0px;
|
|
69
|
+
font:small sans-serif;
|
|
70
|
+
background-color: #2b2b2b;
|
|
71
|
+
color: white;
|
|
72
|
+
}
|
|
73
|
+
h2 {
|
|
74
|
+
margin: 0px;
|
|
75
|
+
padding: 0.2em;
|
|
76
|
+
background-color: #353535;
|
|
77
|
+
}
|
|
78
|
+
li {
|
|
79
|
+
padding: 0px;
|
|
80
|
+
}
|
|
81
|
+
div {
|
|
82
|
+
margin: 1em;
|
|
83
|
+
}
|
|
84
|
+
</style>
|
|
85
|
+
</head>
|
|
86
|
+
<body>
|
|
87
|
+
<h2>#{env['dump'].content.class}</h2>
|
|
88
|
+
<div>
|
|
89
|
+
<h3>Methods</h3>
|
|
90
|
+
#{env['dump'].content.methods}
|
|
91
|
+
<h3>Content</h3>
|
|
92
|
+
#{PrettyPrint.call(env['dump'].content)}
|
|
93
|
+
</div>
|
|
94
|
+
</body>
|
|
95
|
+
</html>
|
|
96
|
+
HTML
|
|
97
|
+
)
|
|
98
|
+
res.status = 200
|
|
99
|
+
res.headers['content-type'] = 'text/html'
|
|
100
|
+
render res:
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
class Rackr
|
|
4
4
|
class Router
|
|
5
|
-
module
|
|
6
|
-
|
|
5
|
+
module DevHtml
|
|
6
|
+
# This is the action that is called when an error is raised
|
|
7
|
+
class Errors
|
|
7
8
|
include Rackr::Action
|
|
8
9
|
|
|
9
10
|
def call(env)
|
|
10
|
-
|
|
11
|
+
res = build_response(html: <<-HTML
|
|
11
12
|
<!DOCTYPE html>
|
|
12
13
|
<html>
|
|
13
14
|
<head>
|
|
@@ -20,9 +21,9 @@ class Rackr
|
|
|
20
21
|
body>div { border-bottom:1px solid #ddd; }
|
|
21
22
|
h1 { font-weight:normal; }
|
|
22
23
|
h2 { margin-bottom:.8em; }
|
|
23
|
-
h2 span { font-size:80%; color:#
|
|
24
|
+
h2 span { font-size:80%; color:#555; font-weight:normal; }
|
|
24
25
|
#summary { background: #ffc; }
|
|
25
|
-
#summary h2 { font-weight: normal; color: #
|
|
26
|
+
#summary h2 { font-weight: normal; color: #555; }
|
|
26
27
|
#backtrace { background: #eee; }
|
|
27
28
|
pre {
|
|
28
29
|
background: #ddd;
|
|
@@ -45,12 +46,14 @@ class Rackr
|
|
|
45
46
|
</body>
|
|
46
47
|
</html>
|
|
47
48
|
HTML
|
|
48
|
-
|
|
49
|
+
)
|
|
50
|
+
res.status = 500
|
|
51
|
+
render res:
|
|
49
52
|
end
|
|
50
53
|
|
|
51
54
|
def backtrace(env)
|
|
52
55
|
first, *tail = env['error'].backtrace
|
|
53
|
-
traceback =
|
|
56
|
+
traceback = +'<h2>Traceback <span>(innermost first)</span></h2>'
|
|
54
57
|
traceback << "<p class=\"first-p\">#{first}</p><br/>"
|
|
55
58
|
|
|
56
59
|
line_number = extract_line_number(first)
|
|
@@ -58,10 +61,10 @@ class Rackr
|
|
|
58
61
|
file_path = (match ? match[1] : nil)
|
|
59
62
|
unless file_path.nil?
|
|
60
63
|
lines = File.readlines(file_path).map.with_index { |line, i| "#{i + 1}: #{line}" }
|
|
61
|
-
traceback << "<pre>#{slice_around_index(lines, line_number).join
|
|
64
|
+
traceback << "<pre>#{slice_around_index(lines, line_number).join}</pre>"
|
|
62
65
|
end
|
|
63
66
|
|
|
64
|
-
traceback << "<p>#{tail.join(
|
|
67
|
+
traceback << "<p>#{tail.join('<br>')}</p>"
|
|
65
68
|
traceback
|
|
66
69
|
end
|
|
67
70
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Rackr
|
|
4
|
+
class Router
|
|
5
|
+
# A endpoint in Rackr is all objects that respond do .call, or .new.call
|
|
6
|
+
module Endpoint
|
|
7
|
+
def self.call(endpoint, content, routes = nil, config = nil, error = nil)
|
|
8
|
+
instance =
|
|
9
|
+
if endpoint.respond_to?(:call)
|
|
10
|
+
endpoint
|
|
11
|
+
elsif endpoint < Rackr::Action || endpoint < Rackr::Callback
|
|
12
|
+
endpoint.new(routes:, config:)
|
|
13
|
+
else
|
|
14
|
+
endpoint.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return instance.call(content, error) if error
|
|
18
|
+
|
|
19
|
+
instance.call(content)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/rackr/router/errors.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
class Rackr
|
|
4
4
|
class Router
|
|
5
|
+
# Errors for the router
|
|
5
6
|
module Errors
|
|
6
7
|
class Error < StandardError; end
|
|
7
8
|
class InvalidNamedRouteError < Error; end
|
|
@@ -10,8 +11,15 @@ class Rackr
|
|
|
10
11
|
class InvalidCallbackError < Error; end
|
|
11
12
|
class InvalidPathError < Error; end
|
|
12
13
|
class InvalidBranchNameError < Error; end
|
|
14
|
+
class InvalidRackResponseError < StandardError; end
|
|
13
15
|
|
|
14
16
|
class << self
|
|
17
|
+
def check_rack_response(response, where)
|
|
18
|
+
return if response.is_a?(Array)
|
|
19
|
+
|
|
20
|
+
raise(InvalidRackResponseError, "Invalid Rack response in #{where}, received: #{response}")
|
|
21
|
+
end
|
|
22
|
+
|
|
15
23
|
def check_scope_name(path)
|
|
16
24
|
return if path.is_a?(String) || path.is_a?(Symbol) || path.nil?
|
|
17
25
|
|
|
@@ -39,7 +47,7 @@ class Rackr
|
|
|
39
47
|
|
|
40
48
|
def check_callbacks(callbacks, path)
|
|
41
49
|
check = lambda { |callback|
|
|
42
|
-
unless callback.nil? || callback.respond_to?(:call) || (callback.respond_to?(:new) && callback.
|
|
50
|
+
unless callback.nil? || callback.respond_to?(:call) || (callback.respond_to?(:new) && callback.method_defined?(:call))
|
|
43
51
|
raise(InvalidCallbackError,
|
|
44
52
|
"Callbacks must respond to a `call` method or be a class with a `call` instance method, got: '#{callback.inspect}' for '#{path}'")
|
|
45
53
|
end
|
|
@@ -49,9 +57,7 @@ class Rackr
|
|
|
49
57
|
end
|
|
50
58
|
|
|
51
59
|
def check_endpoint(endpoint, path)
|
|
52
|
-
if endpoint.respond_to?(:call) || (endpoint.respond_to?(:new) && endpoint.
|
|
53
|
-
return
|
|
54
|
-
end
|
|
60
|
+
return if endpoint.respond_to?(:call) || (endpoint.respond_to?(:new) && endpoint.method_defined?(:call))
|
|
55
61
|
|
|
56
62
|
raise(InvalidEndpointError,
|
|
57
63
|
"Endpoints must respond to a `call` method or be a class with a `call` instance method, got: '#{endpoint.inspect}' for '#{path}'")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Rackr
|
|
4
|
+
class Router
|
|
5
|
+
# Path route is a route that has a path value that can be matched, and may have path params
|
|
6
|
+
class PathRoute < Route
|
|
7
|
+
attr_reader :splitted_path,
|
|
8
|
+
:has_params
|
|
9
|
+
|
|
10
|
+
def initialize(path, endpoint, befores: [], afters: [], wildcard: false)
|
|
11
|
+
super(endpoint, befores:, afters:)
|
|
12
|
+
|
|
13
|
+
@path = path
|
|
14
|
+
@splitted_path = @path.split('/')
|
|
15
|
+
@params = fetch_params
|
|
16
|
+
@has_params = @params != []
|
|
17
|
+
@path_regex = /\A#{path.gsub(/(:\w+)/, '([^/]+)')}\z/
|
|
18
|
+
@wildcard = wildcard
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def match?(path_info)
|
|
22
|
+
return path_info.match?(@path_regex) if @has_params
|
|
23
|
+
return true if @wildcard
|
|
24
|
+
|
|
25
|
+
path_info == @path
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def fetch_params
|
|
31
|
+
@splitted_path.select { |value| value.start_with? ':' }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|