rage-rb 0.3.0 → 0.5.0
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/CHANGELOG.md +35 -5
- data/Gemfile +3 -0
- data/README.md +14 -12
- data/lib/rage/all.rb +27 -0
- data/lib/rage/application.rb +35 -6
- data/lib/rage/cli.rb +43 -44
- data/lib/rage/configuration.rb +22 -6
- data/lib/rage/controller/api.rb +52 -4
- data/lib/rage/errors.rb +4 -0
- data/lib/rage/fiber.rb +58 -10
- data/lib/rage/fiber_scheduler.rb +59 -32
- data/lib/rage/logger/logger.rb +164 -0
- data/lib/rage/logger/text_formatter.rb +46 -0
- data/lib/rage/params_parser.rb +45 -0
- data/lib/rage/request.rb +44 -0
- data/lib/rage/router/backend.rb +37 -4
- data/lib/rage/router/dsl.rb +236 -6
- data/lib/rage/router/handler_storage.rb +8 -5
- data/lib/rage/sidekiq_session.rb +72 -0
- data/lib/rage/templates/Gemfile +5 -1
- data/lib/rage/templates/Rakefile +1 -0
- data/lib/rage/templates/config-application.rb +6 -1
- data/lib/rage/templates/config-environments-development.rb +6 -3
- data/lib/rage/templates/config-environments-production.rb +7 -3
- data/lib/rage/templates/config-environments-test.rb +6 -3
- data/lib/rage/uploaded_file.rb +70 -0
- data/lib/rage/version.rb +1 -1
- data/lib/rage-rb.rb +7 -16
- data/rage.gemspec +1 -1
- metadata +13 -4
data/lib/rage/fiber_scheduler.rb
CHANGED
@@ -3,41 +3,50 @@
|
|
3
3
|
require "resolv"
|
4
4
|
|
5
5
|
class Rage::FiberScheduler
|
6
|
+
MAX_READ = 65536
|
7
|
+
|
6
8
|
def initialize
|
7
9
|
@root_fiber = Fiber.current
|
8
10
|
end
|
9
11
|
|
10
12
|
def io_wait(io, events, timeout = nil)
|
11
13
|
f = Fiber.current
|
12
|
-
::Iodine::Scheduler.attach(io.fileno, events, timeout&.ceil || 0) { f.resume }
|
13
|
-
Fiber.yield
|
14
|
+
::Iodine::Scheduler.attach(io.fileno, events, timeout&.ceil || 0) { |err| f.resume(err) }
|
14
15
|
|
15
|
-
|
16
|
+
err = Fiber.yield
|
17
|
+
if err == Errno::ETIMEDOUT::Errno
|
18
|
+
0
|
19
|
+
else
|
20
|
+
events
|
21
|
+
end
|
16
22
|
end
|
17
23
|
|
18
|
-
# TODO: this is more synchronous than asynchronous right now
|
19
24
|
def io_read(io, buffer, length, offset = 0)
|
20
|
-
|
21
|
-
|
25
|
+
length_to_read = if length == 0
|
26
|
+
buffer.size > MAX_READ ? MAX_READ : buffer.size
|
27
|
+
else
|
28
|
+
length
|
29
|
+
end
|
30
|
+
|
31
|
+
while true
|
32
|
+
string = ::Iodine::Scheduler.read(io.fileno, length_to_read, offset)
|
22
33
|
|
23
34
|
if string.nil?
|
24
35
|
return offset
|
25
36
|
end
|
26
37
|
|
27
38
|
if string.empty?
|
28
|
-
|
29
|
-
next
|
39
|
+
return -Errno::EAGAIN::Errno
|
30
40
|
end
|
31
41
|
|
32
42
|
buffer.set_string(string, offset)
|
33
|
-
offset += string.bytesize
|
34
43
|
|
35
44
|
size = string.bytesize
|
36
|
-
|
37
|
-
|
38
|
-
end
|
45
|
+
offset += size
|
46
|
+
return offset if size < length_to_read || size >= buffer.size
|
39
47
|
|
40
|
-
|
48
|
+
Fiber.pause
|
49
|
+
end
|
41
50
|
end
|
42
51
|
|
43
52
|
def io_write(io, buffer, length, offset = 0)
|
@@ -46,15 +55,11 @@ class Rage::FiberScheduler
|
|
46
55
|
|
47
56
|
::Iodine::Scheduler.write(io.fileno, buffer.get_string, bytes_to_write, offset)
|
48
57
|
|
49
|
-
|
58
|
+
bytes_to_write - offset
|
50
59
|
end
|
51
60
|
|
52
61
|
def kernel_sleep(duration = nil)
|
53
|
-
|
54
|
-
f = Fiber.current
|
55
|
-
::Iodine.run_after((duration * 1000).to_i) { f.resume }
|
56
|
-
Fiber.yield
|
57
|
-
end
|
62
|
+
block(nil, duration || 0)
|
58
63
|
end
|
59
64
|
|
60
65
|
# TODO: GC works a little strange with this closure;
|
@@ -75,13 +80,22 @@ class Rage::FiberScheduler
|
|
75
80
|
Resolv.getaddresses(hostname)
|
76
81
|
end
|
77
82
|
|
78
|
-
def block(
|
79
|
-
f = Fiber.current
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
+
def block(_blocker, timeout = nil)
|
84
|
+
f, fulfilled, channel = Fiber.current, false, "unblock:#{Fiber.current.object_id}"
|
85
|
+
|
86
|
+
resume_fiber_block = proc do
|
87
|
+
unless fulfilled
|
88
|
+
fulfilled = true
|
89
|
+
::Iodine.defer { ::Iodine.unsubscribe(channel) }
|
90
|
+
f.resume
|
91
|
+
end
|
83
92
|
end
|
84
|
-
|
93
|
+
|
94
|
+
::Iodine.subscribe(channel, &resume_fiber_block)
|
95
|
+
if timeout
|
96
|
+
::Iodine.run_after((timeout * 1000).to_i, &resume_fiber_block)
|
97
|
+
end
|
98
|
+
|
85
99
|
Fiber.yield
|
86
100
|
end
|
87
101
|
|
@@ -90,15 +104,28 @@ class Rage::FiberScheduler
|
|
90
104
|
end
|
91
105
|
|
92
106
|
def fiber(&block)
|
93
|
-
|
94
|
-
inner_schedule = f != @root_fiber
|
107
|
+
parent = Fiber.current
|
95
108
|
|
96
|
-
fiber =
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
109
|
+
fiber = if parent == @root_fiber
|
110
|
+
# the fiber to wrap a request in
|
111
|
+
Fiber.new(blocking: false) do
|
112
|
+
Fiber.current.__set_result(block.call)
|
113
|
+
end
|
114
|
+
else
|
115
|
+
# the fiber was created in the user code
|
116
|
+
logger = Thread.current[:rage_logger]
|
117
|
+
|
118
|
+
Fiber.new(blocking: false) do
|
119
|
+
Thread.current[:rage_logger] = logger
|
120
|
+
Fiber.current.__set_result(block.call)
|
121
|
+
# send a message for `Fiber.await` to work
|
122
|
+
Iodine.publish("await:#{parent.object_id}", "") if parent.alive?
|
123
|
+
rescue Exception => e
|
124
|
+
Fiber.current.__set_err(e)
|
125
|
+
Iodine.publish("await:#{parent.object_id}", Fiber::AWAIT_ERROR_MESSAGE) if parent.alive?
|
126
|
+
end
|
101
127
|
end
|
128
|
+
|
102
129
|
fiber.resume
|
103
130
|
|
104
131
|
fiber
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
|
5
|
+
##
|
6
|
+
# All logs in `rage` consist of two parts: keys and tags. A sample log entry might look like this:
|
7
|
+
# ```
|
8
|
+
# [fecbba0735355738] timestamp=2023-10-19T11:12:56+00:00 pid=1825 level=info message=hello
|
9
|
+
# ```
|
10
|
+
# In the log entry above, `timestamp`, `pid`, `level`, and `message` are keys, while `fecbba0735355738` is a tag.
|
11
|
+
#
|
12
|
+
# Use {tagged} to add custom tags to an entry:
|
13
|
+
# ```ruby
|
14
|
+
# Rage.logger.tagged("ApiCall") do
|
15
|
+
# perform_api_call
|
16
|
+
# Rage.logger.info "success"
|
17
|
+
# end
|
18
|
+
# # => [fecbba0735355738][ApiCall] timestamp=2023-10-19T11:12:56+00:00 pid=1825 level=info message=success
|
19
|
+
# ```
|
20
|
+
#
|
21
|
+
# {with_context} can be used to add custom keys:
|
22
|
+
# ```ruby
|
23
|
+
# cache_key = "mykey"
|
24
|
+
# Rage.logger.with_context(cache_key: cache_key) do
|
25
|
+
# get_from_cache(cache_key)
|
26
|
+
# Rage.logger.info "cache miss"
|
27
|
+
# end
|
28
|
+
# # => [fecbba0735355738] timestamp=2023-10-19T11:12:56+00:00 pid=1825 level=info cache_key=mykey message=cache miss
|
29
|
+
# ```
|
30
|
+
#
|
31
|
+
# `Rage::Logger` also implements the interface of Ruby's native {https://ruby-doc.org/3.2.2/stdlibs/logger/Logger.html Logger}:
|
32
|
+
# ```ruby
|
33
|
+
# Rage.logger.info("Initializing")
|
34
|
+
# Rage.logger.debug { "This is a " + potentially + " expensive operation" }
|
35
|
+
# ```
|
36
|
+
class Rage::Logger
|
37
|
+
METHODS_MAP = {
|
38
|
+
"debug" => Logger::DEBUG,
|
39
|
+
"info" => Logger::INFO,
|
40
|
+
"warn" => Logger::WARN,
|
41
|
+
"error" => Logger::ERROR,
|
42
|
+
"fatal" => Logger::FATAL,
|
43
|
+
"unknown" => Logger::UNKNOWN
|
44
|
+
}
|
45
|
+
private_constant :METHODS_MAP
|
46
|
+
|
47
|
+
attr_reader :level, :formatter
|
48
|
+
|
49
|
+
# Create a new logger.
|
50
|
+
#
|
51
|
+
# @param log [Object] a filename (`String`), IO object (typically `STDOUT`, `STDERR`, or an open file), `nil` (it writes nothing) or `File::NULL` (same as `nil`)
|
52
|
+
# @param level [Integer] logging severity threshold
|
53
|
+
# @param formatter [#call] logging formatter
|
54
|
+
# @param shift_age [Integer, String] number of old log files to keep, or frequency of rotation (`"daily"`, `"weekly"` or `"monthly"`). Default value is `0`, which disables log file rotation
|
55
|
+
# @param shift_size [Integer] maximum log file size in bytes (only applies when `shift_age` is a positive Integer)
|
56
|
+
# @param shift_period_suffix [String] the log file suffix format for daily, weekly or monthly rotation
|
57
|
+
# @param binmode sets whether the logger writes in binary mode
|
58
|
+
def initialize(log, level: Logger::DEBUG, formatter: Rage::TextFormatter.new, shift_age: 0, shift_size: 104857600, shift_period_suffix: "%Y%m%d", binmode: false)
|
59
|
+
if log && log != File::NULL
|
60
|
+
@logdev = Logger::LogDevice.new(log, shift_age:, shift_size:, shift_period_suffix:, binmode:)
|
61
|
+
end
|
62
|
+
|
63
|
+
@formatter = formatter
|
64
|
+
@level = level
|
65
|
+
define_log_methods
|
66
|
+
end
|
67
|
+
|
68
|
+
def level=(level)
|
69
|
+
@level = level
|
70
|
+
define_log_methods
|
71
|
+
end
|
72
|
+
|
73
|
+
def formatter=(formatter)
|
74
|
+
@formatter = formatter
|
75
|
+
define_log_methods
|
76
|
+
end
|
77
|
+
|
78
|
+
# Add custom keys to an entry.
|
79
|
+
#
|
80
|
+
# @param context [Hash] a hash of custom keys
|
81
|
+
# @example
|
82
|
+
# Rage.logger.with_context(key: "mykey") do
|
83
|
+
# Rage.logger.info "cache miss"
|
84
|
+
# end
|
85
|
+
def with_context(context)
|
86
|
+
old_context = Thread.current[:rage_logger][:context]
|
87
|
+
|
88
|
+
if old_context.empty? # there's nothing in the context yet
|
89
|
+
Thread.current[:rage_logger][:context] = context
|
90
|
+
else # it's not the first `with_context` call in the chain
|
91
|
+
Thread.current[:rage_logger][:context] = old_context.merge(context)
|
92
|
+
end
|
93
|
+
|
94
|
+
yield(self)
|
95
|
+
true
|
96
|
+
|
97
|
+
ensure
|
98
|
+
Thread.current[:rage_logger][:context] = old_context
|
99
|
+
end
|
100
|
+
|
101
|
+
# Add a custom tag to an entry.
|
102
|
+
#
|
103
|
+
# @param tag [String] the tag to add to an entry
|
104
|
+
# @example
|
105
|
+
# Rage.logger.tagged("ApiCall") do
|
106
|
+
# Rage.logger.info "success"
|
107
|
+
# end
|
108
|
+
def tagged(tag)
|
109
|
+
Thread.current[:rage_logger][:tags] << tag
|
110
|
+
|
111
|
+
yield(self)
|
112
|
+
true
|
113
|
+
|
114
|
+
ensure
|
115
|
+
Thread.current[:rage_logger][:tags].pop
|
116
|
+
end
|
117
|
+
|
118
|
+
alias_method :with_tag, :tagged
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def define_log_methods
|
123
|
+
methods = METHODS_MAP.map do |level_name, level_val|
|
124
|
+
if @logdev.nil? || level_val < @level
|
125
|
+
# logging is disabled or the log level is higher than the current one
|
126
|
+
<<-RUBY
|
127
|
+
def #{level_name}(msg = nil)
|
128
|
+
false
|
129
|
+
end
|
130
|
+
RUBY
|
131
|
+
elsif defined?(IRB)
|
132
|
+
# the call was made from IRB - don't use the formatter
|
133
|
+
<<-RUBY
|
134
|
+
def #{level_name}(msg = nil)
|
135
|
+
@logdev.write((msg || yield) + "\n")
|
136
|
+
end
|
137
|
+
RUBY
|
138
|
+
elsif @formatter.class.name.start_with?("Rage::")
|
139
|
+
# the call was made from within the application and a built-in formatter is used;
|
140
|
+
# in such case we use the `gen_timestamp` method which is much faster than `Time.now.strftime`;
|
141
|
+
# it's not a standard approach however, so it's used with built-in formatters only
|
142
|
+
<<-RUBY
|
143
|
+
def #{level_name}(msg = nil)
|
144
|
+
@logdev.write(
|
145
|
+
@formatter.call("#{level_name}".freeze, Iodine::Rack::Utils.gen_timestamp, nil, msg || yield)
|
146
|
+
)
|
147
|
+
end
|
148
|
+
RUBY
|
149
|
+
else
|
150
|
+
# the call was made from within the application and a custom formatter is used;
|
151
|
+
# stick to the standard approach of using one of the Log Level constants as sevetiry and `Time.now` as time
|
152
|
+
<<-RUBY
|
153
|
+
def #{level_name}(msg = nil)
|
154
|
+
@logdev.write(
|
155
|
+
@formatter.call(#{level_val}, Time.now, nil, msg || yield)
|
156
|
+
)
|
157
|
+
end
|
158
|
+
RUBY
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
self.class.class_eval(methods.join("\n"))
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Rage::TextFormatter
|
2
|
+
def initialize
|
3
|
+
@pid = Process.pid
|
4
|
+
Iodine.on_state(:on_start) do
|
5
|
+
@pid = Process.pid
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(severity, timestamp, _, message)
|
10
|
+
logger = Thread.current[:rage_logger]
|
11
|
+
tags = logger[:tags]
|
12
|
+
|
13
|
+
if final = logger[:final]
|
14
|
+
params, env = final[:params], final[:env]
|
15
|
+
if params
|
16
|
+
return "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} controller=#{params[:controller]} action=#{params[:action]} status=#{final[:response][0]} duration=#{final[:duration]}\n"
|
17
|
+
else
|
18
|
+
# no controller/action keys are written if there are no params
|
19
|
+
return "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} status=#{final[:response][0]} duration=#{final[:duration]}\n"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if tags.length == 1
|
24
|
+
tags_msg = "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=#{severity}"
|
25
|
+
elsif tags.length == 2
|
26
|
+
tags_msg = "[#{tags[0]}][#{tags[1]}] timestamp=#{timestamp} pid=#{@pid} level=#{severity}"
|
27
|
+
else
|
28
|
+
tags_msg = "[#{tags[0]}][#{tags[1]}]"
|
29
|
+
i = 2
|
30
|
+
while i < tags.length
|
31
|
+
tags_msg << "[#{tags[i]}]"
|
32
|
+
i += 1
|
33
|
+
end
|
34
|
+
tags_msg << " timestamp=#{timestamp} pid=#{@pid} level=#{severity}"
|
35
|
+
end
|
36
|
+
|
37
|
+
context = logger[:context]
|
38
|
+
|
39
|
+
if !context.empty?
|
40
|
+
context_msg = ""
|
41
|
+
context.each { |k, v| context_msg << "#{k}=#{v} " }
|
42
|
+
end
|
43
|
+
|
44
|
+
"#{tags_msg} #{context_msg}message=#{message}\n"
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::ParamsParser
|
4
|
+
def self.prepare(env, url_params)
|
5
|
+
has_body, query_string, content_type = env["IODINE_HAS_BODY"], env["QUERY_STRING"], env["CONTENT_TYPE"]
|
6
|
+
|
7
|
+
query_params = Iodine::Rack::Utils.parse_nested_query(query_string) if query_string != ""
|
8
|
+
unless has_body
|
9
|
+
if query_params
|
10
|
+
return query_params.merge!(url_params)
|
11
|
+
else
|
12
|
+
return url_params
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
request_params = if content_type.start_with?("application/json")
|
17
|
+
json_parse(env["rack.input"].read)
|
18
|
+
elsif content_type.start_with?("application/x-www-form-urlencoded")
|
19
|
+
Iodine::Rack::Utils.parse_urlencoded_nested_query(env["rack.input"].read)
|
20
|
+
else
|
21
|
+
Iodine::Rack::Utils.parse_multipart(env["rack.input"], content_type)
|
22
|
+
end
|
23
|
+
|
24
|
+
if request_params && !query_params
|
25
|
+
request_params.merge!(url_params)
|
26
|
+
elsif request_params && query_params
|
27
|
+
request_params.merge!(query_params, url_params)
|
28
|
+
else
|
29
|
+
url_params
|
30
|
+
end
|
31
|
+
|
32
|
+
rescue => e
|
33
|
+
raise Rage::Errors::BadRequest
|
34
|
+
end
|
35
|
+
|
36
|
+
if defined?(::FastJsonparser)
|
37
|
+
def self.json_parse(json)
|
38
|
+
FastJsonparser.parse(json, symbolize_keys: true)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
def self.json_parse(json)
|
42
|
+
JSON.parse(json, symbolize_names: true)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/rage/request.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::Request
|
4
|
+
# @private
|
5
|
+
def initialize(env)
|
6
|
+
@env = env
|
7
|
+
end
|
8
|
+
|
9
|
+
# Get the request headers.
|
10
|
+
# @example
|
11
|
+
# request.headers["Content-Type"] # => "application/json"
|
12
|
+
# request.headers["Connection"] # => "keep-alive"
|
13
|
+
def headers
|
14
|
+
@headers ||= Headers.new(@env)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @private
|
18
|
+
class Headers
|
19
|
+
HTTP = "HTTP_"
|
20
|
+
|
21
|
+
def initialize(env)
|
22
|
+
@env = env
|
23
|
+
end
|
24
|
+
|
25
|
+
def [](requested_header)
|
26
|
+
if requested_header.start_with?(HTTP)
|
27
|
+
@env[requested_header]
|
28
|
+
else
|
29
|
+
(requested_header = requested_header.tr("-", "_")).upcase!
|
30
|
+
|
31
|
+
if "CONTENT_TYPE" == requested_header || "CONTENT_LENGTH" == requested_header
|
32
|
+
@env[requested_header]
|
33
|
+
else
|
34
|
+
@env["#{HTTP}#{requested_header}"]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
headers = @env.select { |k| k == "CONTENT_TYPE" || k == "CONTENT_LENGTH" || k.start_with?(HTTP) }
|
41
|
+
"#<#{self.class.name} @headers=#{headers.inspect}"
|
42
|
+
end
|
43
|
+
end # class Headers
|
44
|
+
end
|
data/lib/rage/router/backend.rb
CHANGED
@@ -14,8 +14,36 @@ class Rage::Router::Backend
|
|
14
14
|
@constrainer = Rage::Router::Constrainer.new({})
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
17
|
+
def mount(path, handler, methods)
|
18
|
+
raise "Mount handler should respond to `call`" unless handler.respond_to?(:call)
|
19
|
+
|
18
20
|
raw_handler = handler
|
21
|
+
is_sidekiq = handler.respond_to?(:name) && handler.name == "Sidekiq::Web"
|
22
|
+
|
23
|
+
handler = ->(env, _params) do
|
24
|
+
env["SCRIPT_NAME"] = path
|
25
|
+
sub_path = env["PATH_INFO"].delete_prefix!(path)
|
26
|
+
env["PATH_INFO"] = "/" if sub_path == ""
|
27
|
+
|
28
|
+
if is_sidekiq
|
29
|
+
Rage::SidekiqSession.with_session(env) do
|
30
|
+
raw_handler.call(env)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
raw_handler.call(env)
|
34
|
+
end
|
35
|
+
|
36
|
+
ensure
|
37
|
+
env["PATH_INFO"] = "#{env["SCRIPT_NAME"]}#{sub_path}"
|
38
|
+
end
|
39
|
+
|
40
|
+
methods.each do |method|
|
41
|
+
__on(method, path, handler, {}, {}, { raw_handler:, mount: true })
|
42
|
+
__on(method, "#{path}/*", handler, {}, {}, { raw_handler:, mount: true })
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def on(method, path, handler, constraints: {}, defaults: nil)
|
19
47
|
raise "Path could not be empty" if path&.empty?
|
20
48
|
|
21
49
|
if match_index = (path =~ OPTIONAL_PARAM_REGEXP)
|
@@ -29,12 +57,17 @@ class Rage::Router::Backend
|
|
29
57
|
return
|
30
58
|
end
|
31
59
|
|
60
|
+
meta = { raw_handler: handler }
|
61
|
+
|
32
62
|
if handler.is_a?(String)
|
33
63
|
raise "Invalid route handler format, expected to match the 'controller#action' pattern" unless handler =~ STRING_HANDLER_REGEXP
|
34
64
|
|
35
65
|
controller, action = to_controller_class($1), $2
|
36
66
|
run_action_method_name = controller.__register_action(action.to_sym)
|
37
67
|
|
68
|
+
meta[:controller] = $1
|
69
|
+
meta[:action] = $2
|
70
|
+
|
38
71
|
handler = eval("->(env, params) { #{controller}.new(env, params).#{run_action_method_name} }")
|
39
72
|
else
|
40
73
|
raise "Non-string route handler should respond to `call`" unless handler.respond_to?(:call)
|
@@ -45,7 +78,7 @@ class Rage::Router::Backend
|
|
45
78
|
handler = ->(env, _params) { orig_handler.call(env) }
|
46
79
|
end
|
47
80
|
|
48
|
-
__on(method, path, handler,
|
81
|
+
__on(method, path, handler, constraints, defaults, meta)
|
49
82
|
end
|
50
83
|
|
51
84
|
def lookup(env)
|
@@ -55,7 +88,7 @@ class Rage::Router::Backend
|
|
55
88
|
|
56
89
|
private
|
57
90
|
|
58
|
-
def __on(method, path, handler,
|
91
|
+
def __on(method, path, handler, constraints, defaults, meta)
|
59
92
|
@constrainer.validate_constraints(constraints)
|
60
93
|
# Let the constrainer know if any constraints are being used now
|
61
94
|
@constrainer.note_usage(constraints)
|
@@ -162,7 +195,7 @@ class Rage::Router::Backend
|
|
162
195
|
end
|
163
196
|
end
|
164
197
|
|
165
|
-
route = { method
|
198
|
+
route = { method:, path:, pattern:, params:, constraints:, handler:, defaults:, meta: }
|
166
199
|
@routes << route
|
167
200
|
current_node.add_route(route, @constrainer)
|
168
201
|
end
|