rage-rb 1.18.0 → 1.19.1

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.
@@ -4,262 +4,371 @@ require "yaml"
4
4
  require "erb"
5
5
 
6
6
  ##
7
- # `Rage.configure` can be used to adjust the behavior of your Rage application:
7
+ # Configuration class for Rage framework.
8
8
  #
9
+ # Use {Rage.configure Rage.configure} to access and modify the configuration.
10
+ #
11
+ # **Example:**
9
12
  # ```ruby
10
13
  # Rage.configure do
11
- # config.logger = Rage::Logger.new(STDOUT)
12
- # config.server.workers_count = 2
13
- # end
14
- # ```
15
- #
16
- # # General Configuration
17
- #
18
- # • _config.logger_
19
- #
20
- # > The logger that will be used for `Rage.logger` and any related `Rage` logging. Custom loggers should implement Ruby's {https://ruby-doc.org/3.2.2/stdlibs/logger/Logger.html#class-Logger-label-Entries Logger} interface.
21
- #
22
- # • _config.log_formatter_
23
- #
24
- # > The formatter of the Rage logger. Built in options include `Rage::TextFormatter` and `Rage::JSONFormatter`. Defaults to an instance of `Rage::TextFormatter`.
25
- #
26
- # • _config.log_level_
27
- #
28
- # > Defines the verbosity of the Rage logger. This option defaults to `:debug` for all environments except production, where it defaults to `:info`. The available log levels are: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, and `:unknown`.
29
- #
30
- # • _config.secret_key_base_
31
- #
32
- # > The `secret_key_base` is used as the input secret to the application's key generator, which is used to encrypt cookies. Rage will fall back to the `SECRET_KEY_BASE` environment variable if this is not set.
33
- #
34
- # • _config.fallback_secret_key_base_
35
- #
36
- # > Defines one or several old secrets that need to be rotated. Can accept a single key or an array of keys. Rage will fall back to the `FALLBACK_SECRET_KEY_BASE` environment variable if this is not set.
37
- #
38
- # • _config.after_initialize_
39
- #
40
- # > Schedule a block of code to run after Rage has finished loading the application code. Use this to reference application-level constants during the initialization process.
41
- # > ```
42
- # Rage.config.after_initialize do
43
- # SUPER_USER = User.find_by!(super: true)
44
- # end
45
- # > ```
46
- #
47
- # # Middleware Configuration
48
- #
49
- # • _config.middleware.use_
50
- #
51
- # > Adds a middleware to the top of the middleware stack. **This is the recommended way of adding a middleware.**
52
- # > ```
53
- # config.middleware.use Rack::Cors do
54
- # allow do
55
- # origins "*"
56
- # resource "*", headers: :any
57
- # end
58
- # end
59
- # > ```
60
- #
61
- # • _config.middleware.insert_before_
62
- #
63
- # > Adds middleware at a specified position before another middleware. The position can be either an index or another middleware.
64
- #
65
- # > **_❗️Heads up:_** By default, Rage always uses the `Rage::FiberWrapper` middleware, which wraps every request in a separate fiber. Make sure to always have this middleware in the top of the stack. Placing other middlewares in front may lead to undefined behavior.
66
- #
67
- # > ```
68
- # config.middleware.insert_before Rack::Head, Magical::Unicorns
69
- # config.middleware.insert_before 0, Magical::Unicorns
70
- # > ```
71
- #
72
- # • _config.middleware.insert_after_
73
- #
74
- # > Adds middleware at a specified position after another middleware. The position can be either an index or another middleware.
75
- #
76
- # > ```
77
- # config.middleware.insert_after Rack::Head, Magical::Unicorns
78
- # > ```
79
- #
80
- # # Server Configuration
81
- #
82
- # _• config.server.max_clients_
83
- #
84
- # > Limits the number of simultaneous connections the server can accept. Defaults to the maximum number of open files.
85
- #
86
- # > **_❗️Heads up:_** Decreasing this number is almost never a good idea. Depending on your application specifics, you are encouraged to use other methods to limit the number of concurrent connections:
87
- #
88
- # > 1. If your application is exposed to the public, you may want to use a cloud rate limiter, like {https://developers.cloudflare.com/waf Cloudflare WAF} or {https://docs.fastly.com/en/ngwaf Fastly WAF}.
89
- # > 2. Otherwise, consider using tools like {https://github.com/rack/rack-attack Rack::Attack} or {https://github.com/mperham/connection_pool connection_pool}.
90
- #
91
- # > ```
92
- # # Limit the amount of connections your application can accept
93
- # config.middleware.use Rack::Attack
94
- # Rack::Attack.throttle("req/ip", limit: 300, period: 5.minutes) do |req|
95
- # req.ip
96
- # end
97
- # #
98
- # # Limit the amount of connections to a specific resource
99
- # HTTP = ConnectionPool.new(size: 5, timeout: 5) { Net::HTTP }
100
- # HTTP.with do |conn|
101
- # conn.get("/my-resource")
102
- # end
103
- # > ```
104
- #
105
- # • _config.server.port_
106
- #
107
- # > Specifies what port the server will listen on.
108
- #
109
- # • _config.server.workers_count_
110
- #
111
- # > Specifies the number of server processes to run. Defaults to 1 in development and to the number of available CPU cores in other environments.
112
- #
113
- # • _config.server.timeout_
114
- #
115
- # > Specifies connection timeout.
116
- #
117
- # # Static file server
118
- #
119
- # • _config.public_file_server.enabled_
120
- #
121
- # > Configures whether Rage should serve static files from the public directory. Defaults to `false`.
122
- #
123
- # # Cable Configuration
124
- #
125
- # • _config.cable.protocol_
126
- #
127
- # > Specifies the protocol the server will use. Supported values include {Rage::Cable::Protocols::ActioncableV1Json :actioncable_v1_json} and {Rage::Cable::Protocols::RawWebSocketJson :raw_websocket_json}. Defaults to {Rage::Cable::Protocols::ActioncableV1Json :actioncable_v1_json}.
128
- #
129
- # • _config.cable.allowed_request_origins_
130
- #
131
- # > Restricts the server to only accept requests from specified origins. The origins can be instances of strings or regular expressions, against which a check for the match will be performed.
132
- #
133
- # • _config.cable.disable_request_forgery_protection_
134
- #
135
- # > Allows requests from any origin.
136
- #
137
- # # OpenAPI Configuration
138
- # • _config.openapi.tag_resolver_
139
- #
140
- # > Specifies the proc to build tags for API operations. The proc accepts the controller class, the symbol name of the action, and the default tag built by Rage.
141
- #
142
- # > ```ruby
143
- # config.openapi.tag_resolver = proc do |controller, action, default_tag|
144
- # # ...
14
+ # config.log_level = :warn
15
+ # config.server.port = 8080
145
16
  # end
146
- # > ```
147
- #
148
- # # Deferred Configuration
149
- # • _config.deferred.backend_
150
- #
151
- # > Specifies the backend for deferred tasks. Supported values are `:disk`, which uses disk storage, or `nil`, which disables persistence of deferred tasks.
152
- # > The `:disk` backend accepts the following options:
153
- # >
154
- # > - `:path` - the path to the directory where deferred tasks will be stored. Defaults to `storage`.
155
- # > - `:prefix` - the prefix for the deferred task files. Defaults to `deferred-`.
156
- # > - `:fsync_frequency` - the frequency of `fsync` calls in seconds. Defaults to `0.5`.
157
- #
158
- # > ```ruby
159
- # config.deferred.backend = :disk, { path: "storage" }
160
- # > ```
161
- #
162
- # • _config.deferred.backpressure_
163
- #
164
- # > Enables the backpressure for deferred tasks. The backpressure is used to limit the number of pending tasks in the queue. It accepts a hash with the following options:
165
- # >
166
- # > - `:high_water_mark` - the maximum number of pending tasks in the queue. Defaults to `1000`.
167
- # > - `:low_water_mark` - the minimum number of pending tasks in the queue before the backpressure is released. Defaults to `high_water_mark * 0.8`.
168
- # > - `:timeout` - the timeout for the backpressure in seconds. Defaults to `2`.
169
- #
170
- # > ```ruby
171
- # config.deferred.backpressure = { high_water_mark: 1000, low_water_mark: 800, timeout: 2 }
172
- # > ```
173
- #
174
- # > Additionally, you can set the backpressure value to `true` to use the default values:
175
- #
176
- # > ```ruby
177
- # config.deferred.backpressure = true
178
17
  # ```
179
18
  #
180
- # # Transient Settings
19
+ # ## Transient Settings
181
20
  #
182
21
  # The settings described in this section should be configured using **environment variables** and are either temporary or will become the default in the future.
183
22
  #
184
- # _RAGE_DISABLE_IO_WRITE_
185
- #
186
- # > Disables the `io_write` hook to fix the ["zero-length iov"](https://bugs.ruby-lang.org/issues/19640) error on Ruby < 3.3.
187
- #
188
- # • _RAGE_DISABLE_AR_POOL_PATCH_
189
- #
190
- # > Disables the `ActiveRecord::ConnectionPool` patch and makes Rage use the original ActiveRecord implementation.
191
- #
192
- # • _RAGE_DISABLE_AR_WEAK_CONNECTIONS_
193
- #
194
- # > Instructs Rage to not reuse Active Record connections between different fibers.
23
+ # - _RAGE_DISABLE_IO_WRITE_ - disables the `io_write` hook to fix the ["zero-length iov"](https://bugs.ruby-lang.org/issues/19640) error on Ruby < 3.3.
24
+ # - _RAGE_DISABLE_AR_POOL_PATCH_ - disables the `ActiveRecord::ConnectionPool` patch and makes Rage use the original ActiveRecord implementation.
25
+ # - _RAGE_DISABLE_AR_WEAK_CONNECTIONS_ - instructs Rage to not reuse Active Record connections between different fibers. Only applies to Active Record < 7.2.
195
26
  #
196
27
  class Rage::Configuration
28
+ # @private
197
29
  include Hooks
198
30
 
199
- attr_accessor :logger
200
- attr_reader :log_formatter, :log_level
201
- attr_writer :secret_key_base, :fallback_secret_key_base
202
-
31
+ # @private
203
32
  # used in DSL
204
33
  def config = self
205
34
 
35
+ # @!group General Configuration
36
+
37
+ # Returns the logger used by Rage.
38
+ # @return [Rage::Logger, nil]
39
+ def logger
40
+ @logger
41
+ end
42
+
43
+ # Set the logger used by Rage.
44
+ # Accepts a logger object that implements the `#debug`, `#info`, `#warn`, `#error`, `#fatal`, and `#unknown` methods, or `nil`. If set to `nil`, logging will be disabled.
45
+ # `Rage.logger` always returns an instance of {Rage::Logger Rage::Logger}, but if you provide a custom object, it will be used internally by `Rage.logger`.
46
+ #
47
+ # @overload logger=(logger)
48
+ # Set a standard logger
49
+ # @param logger [#debug, #info, #warn, #error, #fatal, #unknown]
50
+ # @example
51
+ # config.logger = Rage::Logger.new(STDOUT)
52
+ # @overload logger=(callable)
53
+ # Set an external logger. This allows you to send Rage's raw structured logging data directly to external observability platforms without serializing it to text first.
54
+ #
55
+ # The external logger receives pre-parsed structured data (severity, tags, context) rather than formatted strings. This differs from `config.log_formatter` in that formatters control how logs are formatted (text vs JSON), while the external logger controls where logs are sent and how they integrate with external platforms.
56
+ # @param callable [ExternalLoggerInterface]
57
+ # @example
58
+ # config.logger = proc do |severity:, tags:, context:, message:, request_info:|
59
+ # # Custom logging logic here
60
+ # end
61
+ # @overload logger=(nil)
62
+ # Disable logging
63
+ # @example
64
+ # config.logger = nil
65
+ def logger=(logger)
66
+ @logger = if logger.nil? || logger.is_a?(Rage::Logger)
67
+ logger
68
+ elsif Rage::Logger::METHODS_MAP.keys.all? { |method| logger.respond_to?(method) }
69
+ Rage::Logger.new(Rage::Logger::External::Static[logger])
70
+ elsif logger.respond_to?(:call)
71
+ Rage::Logger.new(Rage::Logger::External::Dynamic[logger])
72
+ else
73
+ raise ArgumentError, "Invalid logger: must be an instance of `Rage::Logger`, respond to `#call`, or implement all standard Ruby Logger methods (`#debug`, `#info`, `#warn`, `#error`, `#fatal`, `#unknown`)"
74
+ end
75
+ end
76
+
77
+ # Returns the log formatter used by Rage.
78
+ # @return [#call, nil]
79
+ def log_formatter
80
+ @log_formatter
81
+ end
82
+
83
+ # Set the log formatter used by Rage.
84
+ # Built in options include {Rage::TextFormatter Rage::TextFormatter} and {Rage::JSONFormatter Rage::JSONFormatter}.
85
+ #
86
+ # @param formatter [#call] a callable object that formats log messages
87
+ # @example
88
+ # config.log_formatter = proc do |severity, datetime, progname, msg|
89
+ # "[#{datetime}] #{severity} -- #{progname}: #{msg}\n"
90
+ # end
206
91
  def log_formatter=(formatter)
207
92
  raise ArgumentError, "Custom log formatter should respond to `#call`" unless formatter.respond_to?(:call)
208
93
  @log_formatter = formatter
209
94
  end
210
95
 
96
+ # Returns the log level used by Rage.
97
+ # @return [Integer, nil]
98
+ def log_level
99
+ @log_level
100
+ end
101
+
102
+ # Set the log level used by Rage.
103
+ # @param level [:debug, :info, :warn, :error, :fatal, :unknown, Integer] the log level
104
+ # @example
105
+ # config.log_level = :info
211
106
  def log_level=(level)
212
107
  @log_level = level.is_a?(Symbol) ? Logger.const_get(level.to_s.upcase) : level
213
108
  end
214
109
 
110
+ # The secret key base is used as the input secret to the application's key generator, which is used to encrypt cookies. Rage will fall back to the `SECRET_KEY_BASE` environment variable if this is not set.
111
+ # @param key [String] the secret key base
112
+ def secret_key_base=(key)
113
+ @secret_key_base = key
114
+ end
115
+
116
+ # Returns the secret key base used for encrypting cookies.
117
+ # @return [String, nil]
215
118
  def secret_key_base
216
119
  @secret_key_base || ENV["SECRET_KEY_BASE"]
217
120
  end
218
121
 
122
+ # Set one or several old secrets that need to be rotated. Can accept a single key or an array of keys. Rage will fall back to the `FALLBACK_SECRET_KEY_BASE` environment variable if this is not set.
123
+ # @param key [String, Array<String>] the fallback secret key base(s)
124
+ def fallback_secret_key_base=(key)
125
+ @fallback_secret_key_base = key
126
+ end
127
+
128
+ # Returns the fallback secret key base(s) used for decrypting cookies encrypted with old secrets.
129
+ # @return [Array<String>]
219
130
  def fallback_secret_key_base
220
131
  Array(@fallback_secret_key_base || ENV["FALLBACK_SECRET_KEY_BASE"])
221
132
  end
222
133
 
223
- def server
224
- @server ||= Server.new
134
+ # Schedule a block of code to run after Rage has finished loading the application code. Use this to reference application-level constants during the initialization process.
135
+ # @example
136
+ # Rage.config.after_initialize do
137
+ # SUPER_USER = User.find_by!(super: true)
138
+ # end
139
+ def after_initialize(&block)
140
+ push_hook(block, :after_initialize)
225
141
  end
142
+ # @!endgroup
226
143
 
144
+ # @!group Middleware Configuration
145
+ # Allows configuring the middleware stack used by Rage.
146
+ # @return [Rage::Configuration::Middleware]
227
147
  def middleware
228
148
  @middleware ||= Middleware.new
229
149
  end
150
+ # @!endgroup
230
151
 
231
- def cable
232
- @cable ||= Cable.new
152
+ # @!group Server Configuration
153
+ # Allows configuring the built-in Rage server.
154
+ # @return [Rage::Configuration::Server]
155
+ def server
156
+ @server ||= Server.new
233
157
  end
158
+ # @!endgroup
234
159
 
160
+ # @!group Static File Server
161
+ # Allows configuring the static file server used by Rage.
162
+ # @return [Rage::Configuration::PublicFileServer]
235
163
  def public_file_server
236
164
  @public_file_server ||= PublicFileServer.new
237
165
  end
166
+ # @!endgroup
167
+
168
+ # @!group Cable Configuration
169
+ # Allows configuring Cable settings.
170
+ # @return [Rage::Configuration::Cable]
171
+ def cable
172
+ @cable ||= Cable.new
173
+ end
174
+ # @!endgroup
238
175
 
176
+ # @!group OpenAPI Configuration
177
+ # Allows configuring OpenAPI settings.
178
+ # @return [Rage::Configuration::OpenAPI]
239
179
  def openapi
240
180
  @openapi ||= OpenAPI.new
241
181
  end
182
+ # @!endgroup
242
183
 
184
+ # @!group Deferred Configuration
185
+ # Allows configuring Deferred settings.
186
+ # @return [Rage::Configuration::Deferred]
243
187
  def deferred
244
188
  @deferred ||= Deferred.new
245
189
  end
190
+ # @!endgroup
246
191
 
247
- def internal
248
- @internal ||= Internal.new
192
+ # @!group Logging Context and Tags Configuration
193
+ # Allows configuring custom log context objects that will be included in every log entry.
194
+ # @return [Rage::Configuration::LogContext]
195
+ def log_context
196
+ @log_context ||= LogContext.new
249
197
  end
250
198
 
251
- def after_initialize(&block)
252
- push_hook(block, :after_initialize)
199
+ # Allows configuring custom log tags that will be included in every log entry.
200
+ # @return [Rage::Configuration::LogTags]
201
+ def log_tags
202
+ @log_tags ||= LogTags.new
253
203
  end
204
+ # @!endgroup
254
205
 
206
+ # @!group Session Configuration
207
+ # Allows configuring session settings.
208
+ # @return [Rage::Configuration::Session]
209
+ def session
210
+ @session ||= Session.new
211
+ end
212
+ # @!endgroup
213
+
214
+ # @private
215
+ def internal
216
+ @internal ||= Internal.new
217
+ end
218
+
219
+ # @private
255
220
  def run_after_initialize!
256
221
  run_hooks_for!(:after_initialize, self)
257
222
  end
258
223
 
224
+ class LogContext
225
+ # @private
226
+ def initialize
227
+ @objects = []
228
+ end
229
+
230
+ # @private
231
+ def objects
232
+ @objects.dup
233
+ end
234
+
235
+ # Add a new custom log context object. Each context object is evaluated independently and the results are merged into the final log entry.
236
+ # @overload <<(hash)
237
+ # Add a static log context entry.
238
+ # @param hash [Hash] a hash representing the log context
239
+ # @example
240
+ # Rage.configure do
241
+ # config.log_context << { version: ENV["APP_VERSION"] }
242
+ # end
243
+ # @overload <<(callable)
244
+ # Add a dynamic log context entry. Dynamic context entries are executed on every log call to capture dynamic state like changing span IDs during request processing.
245
+ # @param callable [#call] a callable object that returns a hash representing the log context or nil
246
+ # @example
247
+ # Rage.configure do
248
+ # config.log_context << proc { { trace_id: MyObservabilitySDK.trace_id } if MyObservabilitySDK.active? }
249
+ # end
250
+ # @note Exceptions from dynamic context callables will cause the entire request to fail. Make sure to handle exceptions inside the callable if necessary.
251
+ def <<(block_or_hash)
252
+ validate_input!(block_or_hash)
253
+ @objects << block_or_hash
254
+ @objects.tap(&:flatten!).tap(&:uniq!)
255
+
256
+ self
257
+ end
258
+
259
+ alias_method :push, :<<
260
+
261
+ # Remove a custom log context object.
262
+ # @param block_or_hash [Hash, #call] the context object to remove
263
+ # @example
264
+ # Rage.configure do
265
+ # config.log_context.delete(MyObservabilitySDK::LOG_CONTEXT)
266
+ # end
267
+ def delete(block_or_hash)
268
+ @objects.delete(block_or_hash)
269
+ end
270
+
271
+ private
272
+
273
+ def validate_input!(obj)
274
+ if obj.is_a?(Array)
275
+ obj.each { |item| validate_input!(item) }
276
+ elsif !obj.is_a?(Hash) && !obj.respond_to?(:call)
277
+ raise ArgumentError, "custom log context has to be a hash, an array of hashes, or respond to `#call`"
278
+ end
279
+ end
280
+ end
281
+
282
+ class LogTags < LogContext
283
+ # @!method <<(block_or_string)
284
+ # Add a new custom log tag. Each tag is evaluated independently and the results are merged into the final log entry.
285
+ # @overload <<(string)
286
+ # Add a static log tag.
287
+ # @param string [String] the log tag
288
+ # @example
289
+ # Rage.configure do
290
+ # config.log_tags << Rage.env
291
+ # end
292
+ # @overload <<(callable)
293
+ # Add a dynamic log tag. Dynamic tags are executed on every log call.
294
+ # @param callable [#call] a callable object that returns a string representing the log tag, an array of log tags, or nil
295
+ # @example
296
+ # Rage.configure do
297
+ # config.log_tags << proc { Current.tenant.slug }
298
+ # end
299
+ # @note Exceptions from dynamic tag callables will cause the entire request to fail. Make sure to handle exceptions inside the callable if necessary.
300
+
301
+ # @!method delete(block_or_string)
302
+ # Remove a custom log tag object.
303
+ # @param block_or_string [String, #call] the tag object to remove
304
+ # @example
305
+ # Rage.configure do
306
+ # config.log_tags.delete(MyObservabilitySDK::LOG_TAGS)
307
+ # end
308
+
309
+ # @private
310
+ private
311
+
312
+ def validate_input!(obj)
313
+ if obj.is_a?(Array)
314
+ obj.each { |item| validate_input!(item) }
315
+ elsif !obj.respond_to?(:to_str) && !obj.respond_to?(:call)
316
+ raise ArgumentError, "custom log tag has to be a string, an array of strings, or respond to `#call`"
317
+ end
318
+ end
319
+ end
320
+
259
321
  class Server
322
+ # @!attribute port
323
+ # Specify the port the server will listen on.
324
+ # @return [Integer]
325
+ # @example Change the default port
326
+ # Rage.configure do
327
+ # config.server.port = 3001
328
+ # end
329
+ #
330
+ # @!attribute workers_count
331
+ # Specify the number of worker processes to spawn. Use `-1` to spawn one worker per CPU core.
332
+ # @return [Integer]
333
+ # @example Change the number of worker processes
334
+ # Rage.configure do
335
+ # config.server.workers_count = 4
336
+ # end
337
+ #
338
+ # @!attribute timeout
339
+ # Specify the connection timeout in seconds.
340
+ # @return [Integer]
341
+ # @example Change the connection timeout
342
+ # Rage.configure do
343
+ # config.server.timeout = 30
344
+ # end
345
+ #
346
+ # @!attribute max_clients
347
+ # Limit the number of simultaneous connections the server can accept. Defaults to the maximum number of open files.
348
+ # @return [Integer]
349
+ #
350
+ # @note Decreasing this number is almost never a good idea. Depending on your application specifics, you are encouraged to use other methods to limit the number of concurrent connections:
351
+ #
352
+ # - If your application is exposed to the public, you may want to use a cloud rate limiter, like {https://developers.cloudflare.com/waf Cloudflare WAF} or {https://docs.fastly.com/en/ngwaf Fastly WAF}.
353
+ # - Otherwise, consider using tools like {https://github.com/rack/rack-attack Rack::Attack} or {https://github.com/mperham/connection_pool connection_pool}.
354
+ # @example Limit the amount of connections your application can accept
355
+ # Rage.configure do
356
+ # config.middleware.use Rack::Attack
357
+ # Rack::Attack.throttle("req/ip", limit: 300, period: 5.minutes) do |req|
358
+ # req.ip
359
+ # end
360
+ # end
361
+ # @example Limit the amount of connections to a specific resource
362
+ # HTTP = ConnectionPool.new(size: 5, timeout: 5) { Net::HTTP }
363
+ # HTTP.with do |conn|
364
+ # conn.get("/my-resource")
365
+ # end
260
366
  attr_accessor :port, :workers_count, :timeout, :max_clients
367
+
368
+ # @private
261
369
  attr_reader :threads_count
262
370
 
371
+ # @private
263
372
  def initialize
264
373
  @threads_count = 1
265
374
  @workers_count = Rage.env.development? ? 1 : -1
@@ -268,16 +377,47 @@ class Rage::Configuration
268
377
  end
269
378
 
270
379
  class Middleware
380
+ # @private
271
381
  attr_reader :middlewares
272
382
 
383
+ # @private
273
384
  def initialize
274
385
  @middlewares = [[Rage::FiberWrapper]]
275
386
  end
276
387
 
388
+ # Add a new middleware to the end of the stack.
389
+ # @note This is the recommended way of adding a middleware.
390
+ # @param new_middleware [Class] the middleware class
391
+ # @param args [Array] arguments passed to the middleware initializer
392
+ # @param block [Proc] an optional block passed to the middleware initializer
393
+ # @example
394
+ # Rage.configure do
395
+ # config.middleware.use Rack::Cors do
396
+ # allow do
397
+ # origins "*"
398
+ # resource "*", headers: :any
399
+ # end
400
+ # end
401
+ # end
277
402
  def use(new_middleware, *args, &block)
278
403
  insert_after(@middlewares.length - 1, new_middleware, *args, &block)
279
404
  end
280
405
 
406
+ # Insert a new middleware before an existing middleware in the stack.
407
+ # @note Rage always uses the `Rage::FiberWrapper` middleware, which wraps every request in a separate fiber. Make sure to always have this middleware in the top of the stack. Placing other middlewares in front may lead to undefined behavior.
408
+ # @param existing_middleware [Class, Integer] the existing middleware class or its index in the stack
409
+ # @param new_middleware [Class] the new middleware class
410
+ # @param args [Array] arguments passed to the middleware initializer
411
+ # @param block [Proc] an optional block passed to the middleware initializer
412
+ # @example
413
+ # Rage.configure do
414
+ # config.middleware.insert_before Rack::Runtime, Rack::Cors do
415
+ # allow do
416
+ # origins "*"
417
+ # resource "*", headers: :any
418
+ # end
419
+ # end
420
+ # end
281
421
  def insert_before(existing_middleware, new_middleware, *args, &block)
282
422
  index = find_middleware_index(existing_middleware)
283
423
  if index == 0 && @middlewares[0][0] == Rage::FiberWrapper
@@ -286,11 +426,28 @@ class Rage::Configuration
286
426
  @middlewares = (@middlewares[0...index] + [[new_middleware, args, block]] + @middlewares[index..]).uniq(&:first)
287
427
  end
288
428
 
429
+ # Insert a new middleware after an existing middleware in the stack.
430
+ # @param existing_middleware [Class, Integer] the existing middleware class or its index in the stack
431
+ # @param new_middleware [Class] the new middleware class
432
+ # @param args [Array] arguments passed to the middleware initializer
433
+ # @param block [Proc] an optional block passed to the middleware initializer
434
+ # @example
435
+ # Rage.configure do
436
+ # config.middleware.insert_after Rack::Runtime, Rack::Cors do
437
+ # allow do
438
+ # origins "*"
439
+ # resource "*", headers: :any
440
+ # end
441
+ # end
442
+ # end
289
443
  def insert_after(existing_middleware, new_middleware, *args, &block)
290
444
  index = find_middleware_index(existing_middleware)
291
445
  @middlewares = (@middlewares[0..index] + [[new_middleware, args, block]] + @middlewares[index + 1..]).uniq(&:first)
292
446
  end
293
447
 
448
+ # Check if a middleware is included in the stack.
449
+ # @param middleware [Class, Integer] the middleware class or its index in the stack
450
+ # @return [Boolean]
294
451
  def include?(middleware)
295
452
  !!find_middleware_index(middleware) rescue false
296
453
  end
@@ -312,9 +469,24 @@ class Rage::Configuration
312
469
  end
313
470
 
314
471
  class Cable
472
+ # @!attribute allowed_request_origins
473
+ # Restrict the server to only accept requests from specified origins. The origins can be strings or regular expressions. Defaults to `/localhost/` in development and test environments.
474
+ # @return [Array<Regexp>, Regexp, Array<String>, String, nil]
475
+ # @example
476
+ # Rage.configure do
477
+ # config.cable.allowed_request_origins = [/example\.com/, "myapp.com"]
478
+ # end
479
+ #
480
+ # @!attribute disable_request_forgery_protection
481
+ # Disable request forgery protection for WebSocket connections to allow requests from any origin.
482
+ # @return [Boolean]
483
+ # @example
484
+ # Rage.configure do
485
+ # config.cable.disable_request_forgery_protection = true
486
+ # end
315
487
  attr_accessor :allowed_request_origins, :disable_request_forgery_protection
316
- attr_reader :protocol
317
488
 
489
+ # @private
318
490
  def initialize
319
491
  @protocol = Rage::Cable::Protocols::ActioncableV1Json
320
492
  @allowed_request_origins = if Rage.env.development? || Rage.env.test?
@@ -322,6 +494,22 @@ class Rage::Configuration
322
494
  end
323
495
  end
324
496
 
497
+ # Returns the protocol the server will use.
498
+ # @return [Class] the protocol class
499
+ def protocol
500
+ @protocol
501
+ end
502
+
503
+ # Specify the protocol the server will use. Supported values include {Rage::Cable::Protocols::ActioncableV1Json :actioncable_v1_json} and {Rage::Cable::Protocols::RawWebSocketJson :raw_websocket_json}. Defaults to {Rage::Cable::Protocols::ActioncableV1Json :actioncable_v1_json}.
504
+ # @param protocol [:actioncable_v1_json, :raw_websocket_json] the protocol symbol
505
+ # @example Use the built-in ActionCable V1 JSON protocol
506
+ # Rage.configure do
507
+ # config.cable.protocol = :actioncable_v1_json
508
+ # end
509
+ # @example Use the built-in Raw WebSocket JSON protocol
510
+ # Rage.configure do
511
+ # config.cable.protocol = :raw_websocket_json
512
+ # end
325
513
  def protocol=(protocol)
326
514
  @protocol = case protocol
327
515
  when Class
@@ -350,6 +538,7 @@ class Rage::Configuration
350
538
  end
351
539
  end
352
540
 
541
+ # @private
353
542
  def config
354
543
  @config ||= begin
355
544
  config_file = Rage.root.join("config/cable.yml")
@@ -363,10 +552,12 @@ class Rage::Configuration
363
552
  end
364
553
  end
365
554
 
555
+ # @private
366
556
  def adapter_config
367
557
  config.except(:adapter)
368
558
  end
369
559
 
560
+ # @private
370
561
  def adapter
371
562
  case config[:adapter]
372
563
  when "redis"
@@ -376,20 +567,54 @@ class Rage::Configuration
376
567
  end
377
568
 
378
569
  class PublicFileServer
570
+ # @!attribute enabled
571
+ # Configure whether Rage should serve static files from the `public` directory. Defaults to `false`.
572
+ # @return [Boolean] whether the static file server is enabled
573
+ # @example
574
+ # Rage.configure do
575
+ # config.public_file_server.enabled = true
576
+ # end
379
577
  attr_accessor :enabled
380
578
  end
381
579
 
382
580
  class OpenAPI
383
- attr_accessor :tag_resolver
581
+ # Specify the rules to customize how OpenAPI tags are generated for API operations.
582
+ # The method accepts a callable object that receives the controller class, the action name (as a symbol), and the original tag generated by Rage.
583
+ # The callable should return a string or an array of strings representing the tags to use for the API operation.
584
+ # This enables grouping endpoints in the OpenAPI documentation according to your application's needs.
585
+ # @param tag_resolver [#call] a callable object that resolves OpenAPI tags
586
+ # @example
587
+ # Rage.configure do
588
+ # config.openapi.tag_resolver = proc do |controller_class, action_name, default_tag|
589
+ # if controller_class.name.start_with?("Admin::")
590
+ # [default_tag, "Admin"]
591
+ # else
592
+ # [default_tag, "Public"]
593
+ # end
594
+ # end
595
+ # end
596
+ def tag_resolver=(tag_resolver)
597
+ unless tag_resolver.respond_to?(:call)
598
+ raise ArgumentError, "Custom tag resolver should respond to `#call`"
599
+ end
600
+
601
+ @tag_resolver = tag_resolver
602
+ end
603
+
604
+ # Returns the OpenAPI tag resolver used by Rage.
605
+ # @return [#call, nil]
606
+ def tag_resolver
607
+ @tag_resolver
608
+ end
384
609
  end
385
610
 
386
611
  class Deferred
387
- attr_reader :backpressure
388
-
612
+ # @private
389
613
  def initialize
390
614
  @configured = false
391
615
  end
392
616
 
617
+ # Returns the backend instance used by `Rage::Deferred`.
393
618
  def backend
394
619
  unless @backend_class
395
620
  @backend_class = Rage::Deferred::Backends::Disk
@@ -399,6 +624,27 @@ class Rage::Configuration
399
624
  @backend_class.new(**@backend_options)
400
625
  end
401
626
 
627
+ # Specify the backend used to persist deferred tasks. Supported values are `:disk`, which uses disk storage, or `nil`, which disables persistence of deferred tasks.
628
+ # @overload backend=(disk, options = {})
629
+ # Use the disk backend.
630
+ # @param options [Hash] additional backend options
631
+ # @option options [Pathname, String] :path the directory where deferred tasks will be stored. Defaults to `storage/`
632
+ # @option options [String] :prefix the prefix used for deferred task files. Defaults to `deferred-`
633
+ # @option options [Integer] :fsync_frequency the frequency of `fsync` calls in seconds. Defaults to `0.5`
634
+ # @example Use the disk backend with default options
635
+ # Rage.configure do
636
+ # config.deferred.backend = :disk
637
+ # end
638
+ # @example Use the disk backend with custom options
639
+ # Rage.configure do
640
+ # config.deferred.backend = :disk, path: "my_storage", fsync_frequency: 1000
641
+ # end
642
+ # @overload backend=(nil)
643
+ # Disable persistence of deferred tasks.
644
+ # @example
645
+ # Rage.configure do
646
+ # config.deferred.backend = nil
647
+ # end
402
648
  def backend=(config)
403
649
  @configured = true
404
650
 
@@ -422,6 +668,7 @@ class Rage::Configuration
422
668
  class Backpressure
423
669
  attr_reader :high_water_mark, :low_water_mark, :timeout, :sleep_interval, :timeout_iterations
424
670
 
671
+ # @private
425
672
  def initialize(high_water_mark = nil, low_water_mark = nil, timeout = nil)
426
673
  @high_water_mark = high_water_mark || 1_000
427
674
  @low_water_mark = low_water_mark || (@high_water_mark * 0.8).round
@@ -432,6 +679,38 @@ class Rage::Configuration
432
679
  end
433
680
  end
434
681
 
682
+ # Returns the backpressure configuration used by `Rage::Deferred`.
683
+ # @return [Backpressure, nil]
684
+ def backpressure
685
+ @backpressure
686
+ end
687
+
688
+ # Configure backpressure settings for `Rage::Deferred`. Backpressure is used to limit the number of pending tasks in the queue and is disabled by default.
689
+ #
690
+ # @overload backpressure=(true)
691
+ # Enable backpressure with default settings.
692
+ # @example
693
+ # Rage.configure do
694
+ # config.deferred.backpressure = true
695
+ # end
696
+ #
697
+ # @overload backpressure=(false)
698
+ # Disable backpressure.
699
+ # @example
700
+ # Rage.configure do
701
+ # config.deferred.backpressure = false
702
+ # end
703
+ #
704
+ # @overload backpressure=(config)
705
+ # Enable backpressure with custom settings.
706
+ # @param config [Hash] backpressure configuration
707
+ # @option config [Integer] :high_water_mark the maximum number of deferred tasks allowed in the queue before applying backpressure. Defaults to `1000`.
708
+ # @option config [Integer] :low_water_mark the minimum number of deferred tasks in the queue at which backpressure is lifted. Defaults to `80%` of `:high_water_mark`.
709
+ # @option config [Integer] :timeout the maximum time in seconds to wait for the queue size to drop below `:low_water_mark` before raising the {Rage::Deferred::PushTimeout Rage::Deferred::PushTimeout} exception. Defaults to 2 seconds.
710
+ # @example
711
+ # Rage.configure do
712
+ # config.deferred.backpressure = { high_water_mark: 2000, low_water_mark: 1500, timeout: 5 }
713
+ # end
435
714
  def backpressure=(config)
436
715
  @configured = true
437
716
 
@@ -451,18 +730,22 @@ class Rage::Configuration
451
730
  @backpressure = Backpressure.new(high_water_mark, low_water_mark, timeout)
452
731
  end
453
732
 
733
+ # @private
454
734
  def default_disk_storage_path
455
735
  Pathname.new("storage")
456
736
  end
457
737
 
738
+ # @private
458
739
  def default_disk_storage_prefix
459
740
  "deferred-"
460
741
  end
461
742
 
743
+ # @private
462
744
  def has_default_disk_storage?
463
745
  default_disk_storage_path.glob("#{default_disk_storage_prefix}*").any?
464
746
  end
465
747
 
748
+ # @private
466
749
  def configured?
467
750
  @configured
468
751
  end
@@ -498,6 +781,17 @@ class Rage::Configuration
498
781
  end
499
782
  end
500
783
 
784
+ class Session
785
+ # @!attribute key
786
+ # Specify the name of the session cookie.
787
+ # @return [String]
788
+ # @example Change the session cookie name
789
+ # Rage.configure do
790
+ # config.session.key = "_myapp_session"
791
+ # end
792
+ attr_accessor :key
793
+ end
794
+
501
795
  # @private
502
796
  class Internal
503
797
  attr_accessor :rails_mode
@@ -539,5 +833,52 @@ class Rage::Configuration
539
833
  else
540
834
  @logger = Rage::Logger.new(nil)
541
835
  end
836
+
837
+ if @log_formatter && @logger.external_logger.is_a?(Rage::Logger::External::Dynamic)
838
+ puts "WARNING: changing the log formatter via `config.log_formatter=` has no effect when using a custom external logger."
839
+ end
840
+
841
+ if @log_context
842
+ Rage.__log_processor.add_custom_context(@log_context.objects)
843
+ @logger.dynamic_context = Rage.__log_processor.dynamic_context
844
+ end
845
+
846
+ if @log_tags
847
+ Rage.__log_processor.add_custom_tags(@log_tags.objects)
848
+ @logger.dynamic_tags = Rage.__log_processor.dynamic_tags
849
+ end
542
850
  end
543
851
  end
852
+
853
+ # @!parse [ruby]
854
+ # # @note This class does not exist at runtime and is used for documentation purposes only. Do not inherit external loggers from it.
855
+ # class ExternalLoggerInterface
856
+ # # Called whenever a log entry is created.
857
+ # #
858
+ # # Rage automatically detects which parameters your external logger's `#call` method accepts, and only passes those parameters. You can omit any of the described parameters in your implementation.
859
+ # #
860
+ # # @param severity [:debug, :info, :warn, :error, :fatal, :unknown] the log severity
861
+ # # @param tags [Array] the log tags submitted via {Rage::Logger#tagged Rage::Logger#tagged}. The first tag is always the request ID
862
+ # # @param context [Hash] the log context submitted via {Rage::Logger#with_context Rage::Logger#with_context}
863
+ # # @param message [String, nil] the log message. For request logs generated by Rage, this is always `nil`
864
+ # # @param request_info [Hash, nil] request-specific information. The value is `nil` for non-request logs; for request logs, contains the following keys:
865
+ # # @option request_info [Hash] :env the Rack env object
866
+ # # @option request_info [Hash] :params the request parameters
867
+ # # @option request_info [Array] :response the Rack response object
868
+ # # @option request_info [Float] :duration the duration of the request in milliseconds
869
+ # # @example
870
+ # # Rage.configure do
871
+ # # config.logger = proc do |severity:, tags:, context:, message:, request_info:|
872
+ # # data = context.merge(tags:)
873
+ # #
874
+ # # if request_info
875
+ # # data[:path] = request_info[:env]["PATH_INFO"]
876
+ # # MyLoggingSDK.info("Request completed", data)
877
+ # # else
878
+ # # MyLoggingSDK.public_send(severity, message, data)
879
+ # # end
880
+ # # end
881
+ # # end
882
+ # def call(severity:, tags:, context:, message:, request_info:)
883
+ # end
884
+ # end