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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d146e39fc587acb31017ef9e5b093aba6a8cecadf8fe8beb038ab7804425015
4
- data.tar.gz: '04787fa986d2d6f4033114d5bf6a134bd6451e2e7cb0c957ef063ddf7fbb9cf3'
3
+ metadata.gz: f73af417da9875005db2b3ee4f61d9c8a646721d97791b1737ec5e3868145cee
4
+ data.tar.gz: 984c3a38fa157758f04414c4d963ee901771c535e5dd1e3047a403c649c13a3a
5
5
  SHA512:
6
- metadata.gz: 57661323fb8e596afd2833f24145716090db3bd1024bf465ae2388f48c5a2c9cbff4384331368d69337a08f7fd977b5f33dcb275421324748eb783f33d6a877e
7
- data.tar.gz: b524b6414b3218cca05f134f81ce03583fac97d38eb4e754c27a754b50fd0d2eaa969b4728304ce6aafe08a45c370ffde59a68d21d76f72634cc43fc74925b9b
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
- @@default_headers_for = lambda { |content_type, headers, content|
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
- @@render = {
17
- html: lambda do |val, status: 200, headers: {}, html: nil|
18
- [
19
- status,
20
- @@default_headers_for.call('text/html; charset=utf-8', headers, val),
21
- [val]
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
- text: lambda do |val, status: 200, headers: {}, text: nil|
25
- [
26
- status,
27
- @@default_headers_for.call('text/plain', headers, val),
28
- [val]
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
- json: lambda do |val, status: 200, headers: {}, json: nil|
32
- val = Oj.dump(val, mode: :compat) unless val.is_a?(String)
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
- res: lambda do |val, status: nil, headers: nil, res: nil|
40
- val.status = status if status
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
- response: lambda do |val, status: nil, headers: nil, response: nil|
45
- val.status = status if status
46
- headers.each { |h, v| val.set_header(h, v) } if headers
47
- val.finish
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
- attr_reader :routes, :config, :deps, :db if self != Rackr
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[:deps]
59
- @db = config.dig(:deps, :db)
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
- @@render[type]&.call(content, **opts) || _render_view(content, **opts)
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 view_response(
70
- paths,
71
- status: 200,
72
- headers: {},
73
- layout_path: 'layout'
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
- paths,
77
- status: status,
78
- headers: headers,
79
- layout_path: layout_path,
80
- response_instance: true
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: 200,
87
- headers: {},
88
- layout_path: 'layout',
89
- response_instance: false,
90
- view: nil
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 = lambda do |path|
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
- @@default_headers_for.call('text/html; charset=utf-8', headers, parsed_erb)
305
+ DEFAULT_HEADERS.call("text/html; charset=#{charset}", headers, parsed_erb)
122
306
  )
123
307
  end
124
308
 
125
- [status, @@default_headers_for.call('text/html; charset=utf-8', headers, parsed_erb), [parsed_erb]]
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 json_response(content = {}, status: 200, headers: {})
143
- content = Oj.dump(content, mode: :compat) unless content.is_a?(String)
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 text_response(content, status: 200, headers: {})
148
- Rack::Response.new(content, status, @@default_headers_for.call('text/plain', headers, content))
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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Rackr
4
+ # A callback is just a Rackr::Action with the assign method
4
5
  module Callback
5
6
  def self.included(base)
6
7
  base.class_eval do
@@ -2,6 +2,7 @@
2
2
 
3
3
  class Rackr
4
4
  class Router
5
+ # This class is responsible for initialize the Rack::Request object and give it the path params
5
6
  class BuildRequest
6
7
  def initialize(env, spplited_request_path)
7
8
  @env = env
@@ -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 Errors
6
- class DevHtml
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
- render(html: <<-HTML
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:#666; font-weight:normal; }
24
+ h2 span { font-size:80%; color:#555; font-weight:normal; }
24
25
  #summary { background: #ffc; }
25
- #summary h2 { font-weight: normal; color: #666; }
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 = String.new('<h2>Traceback <span>(innermost first)</span></h2>')
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('')}</pre>"
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
@@ -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.instance_methods.include?(:call))
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.instance_methods.include?(:call))
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}'")