rackr 0.0.65 → 0.0.68
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 +255 -86
- 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} +11 -8
- 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 +1 -0
- data/lib/rackr/router.rb +182 -116
- data/lib/rackr/utils.rb +28 -0
- data/lib/rackr.rb +106 -33
- metadata +8 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f73af417da9875005db2b3ee4f61d9c8a646721d97791b1737ec5e3868145cee
|
|
4
|
+
data.tar.gz: 984c3a38fa157758f04414c4d963ee901771c535e5dd1e3047a403c649c13a3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f5c8dfaa7a2329474ac15f27a8ba4330f2b53df85b0ddf141e053dfa0b5efb14c6af7bee05c3f8fc70fae968563c33a2b08a7f693db4a3e7e769e9b51a17561
|
|
7
|
+
data.tar.gz: 2455b933241dcbae25f6c4ad60ffbd3aab03a1121d16a57b681318461a5e08e9b3c70d54102a2a1869b7f20d041d9383c4f73e1988d7e4137afde586d7cf328d
|
data/lib/rackr/action.rb
CHANGED
|
@@ -3,95 +3,279 @@
|
|
|
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
|
|
9
|
-
|
|
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|
|
|
10
87
|
{
|
|
11
88
|
'content-type' => content_type,
|
|
12
89
|
'content-length' => content.bytesize.to_s
|
|
13
90
|
}.merge(headers)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
]
|
|
91
|
+
end).freeze
|
|
92
|
+
|
|
93
|
+
RENDER = {
|
|
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]]
|
|
97
|
+
end,
|
|
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]]
|
|
101
|
+
end,
|
|
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
|
|
23
110
|
end,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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))
|
|
30
127
|
end,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
status,
|
|
35
|
-
@@default_headers_for.call('application/json', headers, val),
|
|
36
|
-
[val]
|
|
37
|
-
]
|
|
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))
|
|
38
131
|
end,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
headers.each { |h, v| val.set_header(h, v) } if headers
|
|
42
|
-
val.finish
|
|
132
|
+
head: proc do |status, _empty, headers|
|
|
133
|
+
Rack::Response.new(nil, status, headers)
|
|
43
134
|
end,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
135
|
+
redirect_to: proc do |content, _status, headers|
|
|
136
|
+
Rack::Response.new(
|
|
137
|
+
nil,
|
|
138
|
+
302,
|
|
139
|
+
{ 'location' => content }.merge(headers)
|
|
140
|
+
)
|
|
48
141
|
end
|
|
49
142
|
}.freeze
|
|
50
143
|
|
|
51
144
|
def self.included(base)
|
|
52
145
|
base.class_eval do
|
|
53
|
-
|
|
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')
|
|
54
154
|
|
|
55
155
|
def initialize(routes: nil, config: nil)
|
|
56
156
|
@routes = routes
|
|
57
157
|
@config = config
|
|
58
|
-
@deps = config
|
|
59
|
-
@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)
|
|
60
170
|
end
|
|
61
171
|
|
|
62
|
-
def render(**opts)
|
|
172
|
+
def render(content = nil, **opts)
|
|
173
|
+
if content
|
|
174
|
+
return RENDER[:html].call(
|
|
175
|
+
content.to_s,
|
|
176
|
+
opts[:status],
|
|
177
|
+
opts[:headers] || {},
|
|
178
|
+
opts[:charset] || 'utf-8',
|
|
179
|
+
content_security_policy
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
63
183
|
type = opts.keys.first
|
|
64
184
|
content = opts[type]
|
|
65
185
|
|
|
66
|
-
|
|
186
|
+
if (renderer = RENDER[type])
|
|
187
|
+
return renderer.call(
|
|
188
|
+
content,
|
|
189
|
+
opts[:status],
|
|
190
|
+
opts[:headers] || {},
|
|
191
|
+
opts[:charset] || 'utf-8',
|
|
192
|
+
content_security_policy
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
if (mime = MIME_TYPES[type])
|
|
197
|
+
return [
|
|
198
|
+
opts[:status] || 200,
|
|
199
|
+
DEFAULT_HEADERS.call(
|
|
200
|
+
"#{mime}; charset=#{opts[:charset] || 'utf-8'}",
|
|
201
|
+
opts[:headers] || {},
|
|
202
|
+
content
|
|
203
|
+
),
|
|
204
|
+
[content]
|
|
205
|
+
]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
_render_view(
|
|
209
|
+
content,
|
|
210
|
+
status: opts[:status] || 200,
|
|
211
|
+
headers: opts[:headers] || {},
|
|
212
|
+
layout_path: opts[:layout_path] || 'layout',
|
|
213
|
+
view: opts[:view],
|
|
214
|
+
response_instance: opts[:response_instance] || false,
|
|
215
|
+
charset: opts[:charset] || 'utf-8'
|
|
216
|
+
)
|
|
67
217
|
end
|
|
68
218
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
219
|
+
def build_response(content = nil, **opts)
|
|
220
|
+
if content
|
|
221
|
+
return BUILD_RESPONSE[:html].call(
|
|
222
|
+
content.to_s,
|
|
223
|
+
opts[:status] || 200,
|
|
224
|
+
opts[:headers] || {},
|
|
225
|
+
opts[:charset] || 'utf-8',
|
|
226
|
+
content_security_policy
|
|
227
|
+
)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
type = opts.keys.first
|
|
231
|
+
content = opts[type]
|
|
232
|
+
|
|
233
|
+
if (builder = BUILD_RESPONSE[type])
|
|
234
|
+
return builder.call(
|
|
235
|
+
content,
|
|
236
|
+
opts[:status] || 200,
|
|
237
|
+
opts[:headers] || {},
|
|
238
|
+
opts[:charset] || 'utf-8',
|
|
239
|
+
content_security_policy
|
|
240
|
+
)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
if (mime = MIME_TYPES[type])
|
|
244
|
+
return Rack::Response.new(
|
|
245
|
+
content,
|
|
246
|
+
opts[:status] || 200,
|
|
247
|
+
DEFAULT_HEADERS.call(
|
|
248
|
+
"#{mime}; charset=#{opts[:charset] || 'utf-8'}",
|
|
249
|
+
opts[:headers] || {},
|
|
250
|
+
content
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
end
|
|
254
|
+
|
|
75
255
|
_render_view(
|
|
76
|
-
|
|
77
|
-
status: status,
|
|
78
|
-
headers: headers,
|
|
79
|
-
layout_path: layout_path,
|
|
80
|
-
|
|
256
|
+
content,
|
|
257
|
+
status: opts[:status] || 200,
|
|
258
|
+
headers: opts[:headers] || {},
|
|
259
|
+
layout_path: opts[:layout_path] || 'layout',
|
|
260
|
+
view: opts[:view],
|
|
261
|
+
response_instance: true,
|
|
262
|
+
charset: opts[:charset] || 'utf-8'
|
|
81
263
|
)
|
|
82
264
|
end
|
|
83
265
|
|
|
84
266
|
def _render_view(
|
|
85
267
|
paths,
|
|
86
|
-
status
|
|
87
|
-
headers
|
|
88
|
-
layout_path
|
|
89
|
-
response_instance
|
|
90
|
-
view
|
|
268
|
+
status:,
|
|
269
|
+
headers:,
|
|
270
|
+
layout_path:,
|
|
271
|
+
response_instance:,
|
|
272
|
+
view:,
|
|
273
|
+
charset:
|
|
91
274
|
)
|
|
92
275
|
base_path = config.dig(:views, :path) || 'views'
|
|
276
|
+
headers['content-security-policy'] ||= content_security_policy
|
|
93
277
|
|
|
94
|
-
file_or_nil =
|
|
278
|
+
file_or_nil = proc do |path|
|
|
95
279
|
::File.read(path)
|
|
96
280
|
rescue Errno::ENOENT
|
|
97
281
|
nil
|
|
@@ -118,56 +302,31 @@ class Rackr
|
|
|
118
302
|
return Rack::Response.new(
|
|
119
303
|
parsed_erb,
|
|
120
304
|
status,
|
|
121
|
-
|
|
305
|
+
DEFAULT_HEADERS.call("text/html; charset=#{charset}", headers, parsed_erb)
|
|
122
306
|
)
|
|
123
307
|
end
|
|
124
308
|
|
|
125
|
-
[status,
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def not_found!
|
|
129
|
-
raise Rackr::NotFound
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def load_json(val)
|
|
133
|
-
return Oj.load(val.body.read) if val.is_a?(Rack::Request)
|
|
134
|
-
|
|
135
|
-
Oj.load(val)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def html_response(content = '', status: 200, headers: {})
|
|
139
|
-
Rack::Response.new(content, status, @@default_headers_for.call('text/html; charset=utf-8', headers, content))
|
|
309
|
+
[status, DEFAULT_HEADERS.call("text/html; charset=#{charset}", headers, parsed_erb), [parsed_erb]]
|
|
140
310
|
end
|
|
141
311
|
|
|
142
|
-
def
|
|
143
|
-
|
|
144
|
-
Rack::Response.new(content, status, @@default_headers_for.call('application/json', headers, content))
|
|
312
|
+
def d(content)
|
|
313
|
+
raise Rackr::Dump, content
|
|
145
314
|
end
|
|
146
315
|
|
|
147
|
-
def
|
|
148
|
-
|
|
316
|
+
def not_found!
|
|
317
|
+
raise Rackr::NotFound
|
|
149
318
|
end
|
|
150
319
|
|
|
320
|
+
# rubocop:disable Security/Eval
|
|
151
321
|
def load_erb(content, binding_context: nil)
|
|
152
322
|
eval(Erubi::Engine.new(content).src, binding_context)
|
|
153
323
|
end
|
|
324
|
+
# rubocop:enable Security/Eval
|
|
154
325
|
|
|
155
326
|
def head(status, headers: {})
|
|
156
327
|
[status, headers, []]
|
|
157
328
|
end
|
|
158
329
|
|
|
159
|
-
def head_response(status, headers: {})
|
|
160
|
-
Rack::Response.new(nil, status, headers)
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def redirect_response(url, headers: {})
|
|
164
|
-
Rack::Response.new(
|
|
165
|
-
nil,
|
|
166
|
-
302,
|
|
167
|
-
{ 'location' => url }.merge(headers)
|
|
168
|
-
)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
330
|
def redirect_to(url, headers: {})
|
|
172
331
|
[302, { 'location' => url }.merge(headers), []]
|
|
173
332
|
end
|
|
@@ -175,7 +334,17 @@ class Rackr
|
|
|
175
334
|
def response(body = nil, status = 200, headers = {})
|
|
176
335
|
Rack::Response.new(body, status, headers)
|
|
177
336
|
end
|
|
337
|
+
|
|
338
|
+
def content_security_policy
|
|
339
|
+
@content_security_policy ||=
|
|
340
|
+
DEFAULT_CSP_HEADERS
|
|
341
|
+
.merge(config&.dig(:csp_headers) || {})
|
|
342
|
+
.map { |k, v| "#{k.to_s.tr('_', '-')} #{v}" }
|
|
343
|
+
.join('; ')
|
|
344
|
+
end
|
|
178
345
|
end
|
|
179
346
|
end
|
|
180
347
|
end
|
|
348
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
349
|
+
# rubocop:enable Metrics/MethodLength
|
|
181
350
|
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,7 +61,7 @@ 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
67
|
traceback << "<p>#{tail.join('<br>')}</p>"
|
|
@@ -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}'")
|