rage-rb 1.26.1 → 1.27.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 +14 -1
- data/CONTRIBUTING.md +1 -1
- data/README.md +1 -1
- data/lib/rage/configuration.rb +1 -0
- data/lib/rage/cookies.rb +11 -9
- data/lib/rage/fiber_scheduler.rb +17 -13
- data/lib/rage/openapi/index.html.erb +1 -1
- data/lib/rage/openapi/openapi.rb +3 -5
- data/lib/rage/openapi/parsers/ext/blueprinter.rb +20 -12
- data/lib/rage/router/README.md +1 -1
- data/lib/rage/router/backend.rb +8 -4
- data/lib/rage/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 158f3d2cea7753f15be62035f7b196117284a4112159400ecf57759fb719ed08
|
|
4
|
+
data.tar.gz: 002ef2cc3b75084f0393b254f3c2b61e2e6be83d1a5cf0e64b7529aa3452e1a7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3f194f003221636c0fe3120a31d5b60a38d9a8a496fc873e13fdc6c2020f8a59f4348073a06a019f7e3de6609e4b73e2bb493ffce12bb63f8f317365a720fdcc
|
|
7
|
+
data.tar.gz: 3af06d4347e71056ca9bd26e9b0edfc84e554399ed2b37aedce6afbea6b11247091929874b76540a63b3041c8b1edd3f93cc197904b68d19e4ef81a94ccc2ffd
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.27.0] - 2026-08-06
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- [OpenAPI] Add support for the `root:` option in Blueprinter response annotations (#343).
|
|
8
|
+
- Add `FiberScheduler#timeout_after` (#374).
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- [OpenAPI] Avoid caching a request-specific spec URL in the HTML docs page.
|
|
13
|
+
- [Router] Compose and restore `SCRIPT_NAME` correctly for mounted Rack apps.
|
|
14
|
+
- [OpenAPI] Fix schema registry key collisions for Blueprinter classes referenced under different views.
|
|
15
|
+
- Fixed RbNaCl dependency check to produce correct error message (#361).
|
|
16
|
+
|
|
3
17
|
## [1.26.1] - 2026-07-17
|
|
4
18
|
|
|
5
19
|
### Fixed
|
|
@@ -22,7 +36,6 @@
|
|
|
22
36
|
|
|
23
37
|
### Fixed
|
|
24
38
|
|
|
25
|
-
- [CI] Check all changed PR file pages when requiring changelog updates.
|
|
26
39
|
- [API] Ignore `If-Modified-Since` when `If-None-Match` is present.
|
|
27
40
|
- [API] Use weak comparison for `If-None-Match` validation.
|
|
28
41
|
- [Deferred] Ignore missing temp files during async disk storage cleanup.
|
data/CONTRIBUTING.md
CHANGED
|
@@ -223,7 +223,7 @@ end
|
|
|
223
223
|
1. Wraps every request in a Fiber
|
|
224
224
|
2. Implements the defer protocol for pausing/resuming async requests
|
|
225
225
|
|
|
226
|
-
When a request encounters blocking I/O (database query, HTTP request, etc.), the fiber yields. `FiberWrapper` detects this (`fiber.alive?`) and returns
|
|
226
|
+
When a request encounters blocking I/O (database query, HTTP request, etc.), the fiber yields. `FiberWrapper` detects this (`fiber.alive?`) and returns the special `[:__http_defer__, fiber]` tuple to Iodine, which pauses the connection.
|
|
227
227
|
|
|
228
228
|
When the I/O completes, the fiber resumes and publishes a message to notify Iodine that the response is ready. This is the mechanism that enables transparent, non-blocking concurrency.
|
|
229
229
|
|
data/README.md
CHANGED
|
@@ -145,7 +145,7 @@ Rage prioritizes stable public APIs, long deprecation cycles, and minimal extern
|
|
|
145
145
|
|
|
146
146
|
## Learn More
|
|
147
147
|
|
|
148
|
-
- Documentation: [https://rage-rb.dev](https://rage-rb.dev/docs/intro)
|
|
148
|
+
- Documentation: [https://rage-rb.dev/docs/intro](https://rage-rb.dev/docs/intro)
|
|
149
149
|
- API Reference: [https://rage-rb.dev/api](https://rage-rb.dev/api)
|
|
150
150
|
- Architecture: [ARCHITECTURE.md](https://github.com/rage-rb/rage/blob/main/ARCHITECTURE.md)
|
|
151
151
|
- Contributing: [CONTRIBUTING.md](https://github.com/rage-rb/rage/blob/main/CONTRIBUTING.md)
|
data/lib/rage/configuration.rb
CHANGED
|
@@ -23,6 +23,7 @@ require "erb"
|
|
|
23
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
24
|
# - _RAGE_DISABLE_AR_POOL_PATCH_ - disables the `ActiveRecord::ConnectionPool` patch and makes Rage use the original ActiveRecord implementation.
|
|
25
25
|
# - _RAGE_DISABLE_AR_WEAK_CONNECTIONS_ - instructs Rage to not reuse Active Record connections between different fibers. Only applies to Active Record < 7.2.
|
|
26
|
+
# - _RAGE_ENABLE_NON_BLOCKING_TIMEOUT_ - enables the non-blocking `timeout_after` method on the fiber scheduler, which allows timeouts to work cooperatively with fiber-based concurrency.
|
|
26
27
|
#
|
|
27
28
|
class Rage::Configuration
|
|
28
29
|
# @private
|
data/lib/rage/cookies.rb
CHANGED
|
@@ -278,24 +278,22 @@ class Rage::Cookies
|
|
|
278
278
|
|
|
279
279
|
private
|
|
280
280
|
|
|
281
|
-
def ensure_rbnacl!
|
|
281
|
+
def ensure_rbnacl!
|
|
282
282
|
return if defined?(RbNaCl) &&
|
|
283
283
|
Gem::Version.create(RbNaCl::VERSION) >= RBNACL_MIN_VERSION &&
|
|
284
284
|
Gem::Version.create(RbNaCl::VERSION) < RBNACL_MAX_VERSION
|
|
285
285
|
|
|
286
286
|
fail <<~ERR
|
|
287
287
|
|
|
288
|
-
Rage depends on `rbnacl` [>= #{RBNACL_MIN_VERSION}, < #{RBNACL_MAX_VERSION}] to support
|
|
288
|
+
Rage depends on `rbnacl` [>= #{RBNACL_MIN_VERSION}, < #{RBNACL_MAX_VERSION}] to support encrypted and signed cookies. Ensure the following line is added to your Gemfile:
|
|
289
289
|
gem "rbnacl"
|
|
290
290
|
|
|
291
291
|
ERR
|
|
292
292
|
end
|
|
293
293
|
|
|
294
294
|
def build_key(secret, purpose:)
|
|
295
|
-
ensure_rbnacl!(purpose: purpose)
|
|
296
|
-
|
|
297
295
|
if !secret
|
|
298
|
-
raise "Rage.config.secret_key_base should be set to use
|
|
296
|
+
raise "Rage.config.secret_key_base should be set to use encrypted or signed cookies"
|
|
299
297
|
end
|
|
300
298
|
|
|
301
299
|
RbNaCl::Hash.blake2b("", key: [secret].pack("H*"), digest_size: 32, personal: purpose)
|
|
@@ -337,7 +335,10 @@ class Rage::Cookies
|
|
|
337
335
|
private
|
|
338
336
|
|
|
339
337
|
def primary_box
|
|
340
|
-
@primary_box ||=
|
|
338
|
+
@primary_box ||= begin
|
|
339
|
+
ensure_rbnacl!
|
|
340
|
+
RbNaCl::SimpleBox.from_secret_key(build_key(Rage.config.secret_key_base, purpose: PURPOSE))
|
|
341
|
+
end
|
|
341
342
|
end
|
|
342
343
|
|
|
343
344
|
def fallback_boxes
|
|
@@ -401,9 +402,10 @@ class Rage::Cookies
|
|
|
401
402
|
end
|
|
402
403
|
|
|
403
404
|
def primary_signer
|
|
404
|
-
@primary_signer ||=
|
|
405
|
-
|
|
406
|
-
|
|
405
|
+
@primary_signer ||= begin
|
|
406
|
+
ensure_rbnacl!
|
|
407
|
+
RbNaCl::HMAC::SHA512256.new(build_key(Rage.config.secret_key_base, purpose: PURPOSE))
|
|
408
|
+
end
|
|
407
409
|
end
|
|
408
410
|
|
|
409
411
|
def fallback_signers
|
data/lib/rage/fiber_scheduler.rb
CHANGED
|
@@ -75,19 +75,23 @@ class Rage::FiberScheduler
|
|
|
75
75
|
Fiber.pause if duration.nil? || duration < 1
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
if ENV["RAGE_ENABLE_NON_BLOCKING_TIMEOUT"]
|
|
79
|
+
def timeout_after(duration, exception_class = Timeout::Error, *exception_arguments, &block)
|
|
80
|
+
f, fulfilled = Fiber.current, false
|
|
81
|
+
|
|
82
|
+
::Iodine.run_after((duration * 1000).to_i) do
|
|
83
|
+
if !fulfilled && f.alive?
|
|
84
|
+
fulfilled = true
|
|
85
|
+
f.__wait_generation += 1
|
|
86
|
+
f.raise(exception_class, *exception_arguments)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
block.call
|
|
91
|
+
ensure
|
|
92
|
+
fulfilled = true
|
|
93
|
+
end
|
|
94
|
+
end
|
|
91
95
|
|
|
92
96
|
# Resolve a hostname to IP addresses, caching results for 60 seconds.
|
|
93
97
|
def address_resolve(hostname)
|
data/lib/rage/openapi/openapi.rb
CHANGED
|
@@ -42,11 +42,9 @@ module Rage::OpenAPI
|
|
|
42
42
|
# run Rage.openapi.application(namespace: "Api::V2")
|
|
43
43
|
# end
|
|
44
44
|
def self.application(namespace: nil)
|
|
45
|
-
html_app = ->(
|
|
45
|
+
html_app = ->(_) do
|
|
46
46
|
__data_cache[[:page, namespace]] ||= begin
|
|
47
|
-
|
|
48
|
-
spec_url = "#{scheme}://#{host}#{path}/json"
|
|
49
|
-
page = ERB.new(File.read("#{__dir__}/index.html.erb")).result(binding)
|
|
47
|
+
page = ERB.new(File.read("#{__dir__}/index.html.erb")).result
|
|
50
48
|
|
|
51
49
|
[200, { "content-type" => "text/html; charset=UTF-8" }, [page]]
|
|
52
50
|
end
|
|
@@ -120,7 +118,7 @@ module Rage::OpenAPI
|
|
|
120
118
|
|
|
121
119
|
# @private
|
|
122
120
|
def self.__try_parse_collection(str)
|
|
123
|
-
if str =~ /^Array<([\w\s:\(\),]+)>$/ || str =~ /^\[([\w\s:\(\),]+)\]$/
|
|
121
|
+
if str =~ /^Array<([\w\s:\(\),'\"]+)>$/ || str =~ /^\[([\w\s:\(\),'\"]+)\]$/
|
|
124
122
|
[true, $1]
|
|
125
123
|
else
|
|
126
124
|
[false, str]
|
|
@@ -20,9 +20,10 @@ class Rage::OpenAPI::Parsers::Ext::Blueprinter
|
|
|
20
20
|
is_collection, raw_klass_str, serializer_options = Rage::OpenAPI.__parse_serializer_args(klass_str)
|
|
21
21
|
klass = @namespace.const_get(raw_klass_str)
|
|
22
22
|
schema = build_schema(klass, is_collection, serializer_options)
|
|
23
|
+
registry_key = "#{raw_klass_str}_#{serializer_options[:view] || :default}"
|
|
23
24
|
|
|
24
|
-
if @root.schema_registry.key?(
|
|
25
|
-
@root.schema_registry[
|
|
25
|
+
if @root.schema_registry.key?(registry_key)
|
|
26
|
+
@root.schema_registry[registry_key] = is_collection ? schema["items"] : schema
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
schema
|
|
@@ -32,26 +33,31 @@ class Rage::OpenAPI::Parsers::Ext::Blueprinter
|
|
|
32
33
|
|
|
33
34
|
private
|
|
34
35
|
|
|
35
|
-
def build_schema(klass, is_collection, serializer_options
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
view_name = serializer_options&.key?(:view) ? serializer_options[:view] : :default
|
|
36
|
+
def build_schema(klass, is_collection, serializer_options)
|
|
37
|
+
view_name = serializer_options&.key?(:view) ? serializer_options[:view].to_sym : :default
|
|
39
38
|
reflections = klass.reflections
|
|
40
39
|
view = reflections[view_name]
|
|
41
40
|
raise InvalidViewError, "invalid view #{view_name}" unless view
|
|
42
41
|
|
|
42
|
+
@parsing_stack.add([klass.name, view_name])
|
|
43
|
+
|
|
43
44
|
identifier_fields = extract_fields(reflections[:identifier])
|
|
44
45
|
default_fields = extract_fields(view)
|
|
45
46
|
association_fields = extract_associations(view)
|
|
46
47
|
|
|
47
|
-
@parsing_stack.delete(klass.name)
|
|
48
|
+
@parsing_stack.delete([klass.name, view_name])
|
|
48
49
|
|
|
49
50
|
schema = identifier_fields.merge(default_fields.merge(association_fields).sort.to_h)
|
|
50
51
|
|
|
51
52
|
result = { "type" => "object" }
|
|
52
53
|
result["properties"] = schema if schema.any?
|
|
53
54
|
result = { "type" => "array", "items" => result } if is_collection
|
|
54
|
-
|
|
55
|
+
|
|
56
|
+
if serializer_options&.key?(:root)
|
|
57
|
+
{ "type" => "object", "properties" => { serializer_options[:root].to_s => result } }
|
|
58
|
+
else
|
|
59
|
+
result
|
|
60
|
+
end
|
|
55
61
|
end
|
|
56
62
|
|
|
57
63
|
def extract_fields(view)
|
|
@@ -65,14 +71,16 @@ class Rage::OpenAPI::Parsers::Ext::Blueprinter
|
|
|
65
71
|
blueprint = association.blueprint
|
|
66
72
|
name, display_name = association.name.to_s, association.display_name.to_s
|
|
67
73
|
is_collection = collection_association?(name)
|
|
74
|
+
serializer_options = { view: association.view }
|
|
68
75
|
|
|
69
76
|
item_schema = if blueprint.is_a?(Proc)
|
|
70
77
|
{ "type" => "object" }
|
|
71
|
-
elsif @parsing_stack.include?(blueprint.name)
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
elsif @parsing_stack.include?([blueprint.name, association.view])
|
|
79
|
+
schema_key = "#{blueprint.name}_#{association.view}"
|
|
80
|
+
@root.schema_registry[schema_key] ||= nil
|
|
81
|
+
{ "$ref" => "#/components/schemas/#{schema_key}" }
|
|
74
82
|
else
|
|
75
|
-
build_schema(blueprint, false)
|
|
83
|
+
build_schema(blueprint, false, serializer_options)
|
|
76
84
|
end
|
|
77
85
|
|
|
78
86
|
properties[display_name] = is_collection ? { "type" => "array", "items" => item_schema } : item_schema
|
data/lib/rage/router/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
This is an almost complete rewrite of https://github.com/delvedor/find-my-way.
|
|
2
2
|
|
|
3
|
-
Currently, the only constraint supported is the `host` constraint.
|
|
3
|
+
Currently, the only constraint supported is the `host` constraint. The constraint value can be either a string or a regular expression. Custom/lambda constraints are unlikely to be added.
|
|
4
4
|
|
|
5
5
|
Compared to the Rails router, the most notable difference except constraints is that a wildcard segment can only be in the last section of the path and cannot be named.
|
|
6
6
|
|
data/lib/rage/router/backend.rb
CHANGED
|
@@ -35,13 +35,17 @@ class Rage::Router::Backend
|
|
|
35
35
|
# by the time the app is called, `rack.input` is already consumed in `Rage::ParamsParser`
|
|
36
36
|
env["rack.input"].rewind
|
|
37
37
|
|
|
38
|
-
env["SCRIPT_NAME"]
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
script_name = env["SCRIPT_NAME"]
|
|
39
|
+
path_info = env["PATH_INFO"]
|
|
40
|
+
|
|
41
|
+
env["SCRIPT_NAME"] = "#{script_name}#{path}"
|
|
42
|
+
sub_path = path_info.delete_prefix(path)
|
|
43
|
+
env["PATH_INFO"] = sub_path == "" ? "/" : sub_path
|
|
41
44
|
|
|
42
45
|
handler.call(env)
|
|
43
46
|
ensure
|
|
44
|
-
env["
|
|
47
|
+
env["SCRIPT_NAME"] = script_name
|
|
48
|
+
env["PATH_INFO"] = path_info
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
methods.each do |method|
|
data/lib/rage/version.rb
CHANGED