rage-rb 1.14.0 → 1.15.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 +17 -0
- data/lib/rage/cli.rb +20 -0
- data/lib/rage/controller/api.rb +1 -1
- data/lib/rage/errors.rb +6 -0
- data/lib/rage/middleware/cors.rb +1 -1
- data/lib/rage/openapi/converter.rb +45 -6
- data/lib/rage/openapi/nodes/method.rb +1 -1
- data/lib/rage/openapi/openapi.rb +41 -4
- data/lib/rage/openapi/parser.rb +51 -3
- data/lib/rage/openapi/parsers/ext/alba.rb +1 -16
- data/lib/rage/openapi/parsers/yaml.rb +1 -20
- data/lib/rage/request.rb +182 -36
- data/lib/rage/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 537238bd77a6af0c53b59def61a4b94616ca11b8e0bb3f31613d06198c8da2f5
|
4
|
+
data.tar.gz: 4597f16d331862d871aeacf18e701406b825e8208e8d41ec4d29f47d8ce6ce5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c841546ab6547df2e3846000c2f47089e01d08f8702597df835a4b0d6a9920a9be4a65151e017d59e42c0c922d67887ae4d9403f4a2ae2fb048bddc54520c2a
|
7
|
+
data.tar.gz: '0701518f1f90e2bb6e800272a7054aaec3f401b73002733d4b216febf3758aa2a480a1920d19160779efc7450e6dcd12c0ca97392c6c08a325c6617f39147a0e'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.15.0] - 2025-04-02
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Enhance the `Rage::Request` class by [@aaoafk](https://github.com/aaoafk) (#123).
|
8
|
+
- [OpenAPI] Support the `@param` tag (#134).
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
- Fix using `Fiber.schedule` in console by [@lkibbalam](https://github.com/lkibbalam) (#143).
|
13
|
+
- Correctly handle regexp origins in `Rage::Cors` (#138).
|
14
|
+
- [OpenAPI] Correctly handle trailing slash (#141).
|
15
|
+
- [OpenAPI] Correctly handle empty shared components (#139).
|
16
|
+
- [OpenAPI] Explicitly load Prism (#136).
|
17
|
+
- [OpenAPI] Correctly verify available before actions (#144).
|
18
|
+
- [OpenAPI] Correctly handle global comments (#140).
|
19
|
+
|
3
20
|
## [1.14.0] - 2025-03-10
|
4
21
|
|
5
22
|
### Added
|
data/lib/rage/cli.rb
CHANGED
@@ -151,6 +151,7 @@ module Rage
|
|
151
151
|
|
152
152
|
require "irb"
|
153
153
|
environment
|
154
|
+
patch_fiber_for_irb
|
154
155
|
ARGV.clear
|
155
156
|
IRB.start
|
156
157
|
end
|
@@ -235,6 +236,25 @@ module Rage
|
|
235
236
|
|
236
237
|
Rake::Task.tasks.select { |task| !task.comment.nil? && task.name.start_with?("db:") }
|
237
238
|
end
|
239
|
+
|
240
|
+
# Override Fiber.schedule for IRB: Enforce sequential execution of fibers in the IRB environment
|
241
|
+
def patch_fiber_for_irb
|
242
|
+
Fiber.class_eval do
|
243
|
+
def self.schedule(&block)
|
244
|
+
fiber = Fiber.new(blocking: true) do
|
245
|
+
Fiber.current.__set_id
|
246
|
+
Fiber.current.__set_result(block.call)
|
247
|
+
end
|
248
|
+
fiber.resume
|
249
|
+
|
250
|
+
fiber
|
251
|
+
end
|
252
|
+
|
253
|
+
def self.await(fibers)
|
254
|
+
Array(fibers).map(&:__get_result)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
238
258
|
end
|
239
259
|
|
240
260
|
class CLINewAppGenerator < Thor::Group
|
data/lib/rage/controller/api.rb
CHANGED
data/lib/rage/errors.rb
CHANGED
data/lib/rage/middleware/cors.rb
CHANGED
@@ -25,20 +25,23 @@ class Rage::OpenAPI::Converter
|
|
25
25
|
@spec["paths"] = @nodes.leaves.each_with_object({}) do |node, memo|
|
26
26
|
next if node.private || node.parents.any?(&:private)
|
27
27
|
|
28
|
-
|
28
|
+
path_params = []
|
29
29
|
path = node.http_path.gsub(/:(\w+)/) do
|
30
|
-
|
30
|
+
path_params << $1
|
31
31
|
"{#{$1}}"
|
32
32
|
end
|
33
33
|
|
34
34
|
unless memo.key?(path)
|
35
35
|
memo[path] = {}
|
36
|
-
|
36
|
+
path_params.each do |param|
|
37
|
+
documented_path_param = node.parameters.delete(param)
|
38
|
+
|
37
39
|
(memo[path]["parameters"] ||= []) << {
|
38
40
|
"in" => "path",
|
39
|
-
"name" =>
|
41
|
+
"name" => param,
|
40
42
|
"required" => true,
|
41
|
-
"
|
43
|
+
"description" => documented_path_param&.dig(:description) || "",
|
44
|
+
"schema" => get_param_type_spec(param, documented_path_param&.dig(:type))
|
42
45
|
}
|
43
46
|
end
|
44
47
|
end
|
@@ -52,6 +55,10 @@ class Rage::OpenAPI::Converter
|
|
52
55
|
"tags" => build_tags(node)
|
53
56
|
}
|
54
57
|
|
58
|
+
if node.parameters.any?
|
59
|
+
memo[path][method]["parameters"] = build_parameters(node)
|
60
|
+
end
|
61
|
+
|
55
62
|
responses = node.parents.reverse.map(&:responses).reduce(&:merge).merge(node.responses)
|
56
63
|
|
57
64
|
memo[path][method]["responses"] = if responses.any?
|
@@ -85,7 +92,7 @@ class Rage::OpenAPI::Converter
|
|
85
92
|
|
86
93
|
if (shared_components = Rage::OpenAPI.__shared_components["components"])
|
87
94
|
shared_components.each do |definition_type, definitions|
|
88
|
-
(@spec["components"][definition_type] ||= {}).merge!(definitions)
|
95
|
+
(@spec["components"][definition_type] ||= {}).merge!(definitions || {})
|
89
96
|
end
|
90
97
|
end
|
91
98
|
|
@@ -101,6 +108,22 @@ class Rage::OpenAPI::Converter
|
|
101
108
|
basename.capitalize.gsub(/[\s\-_]([a-zA-Z0-9]+)/) { " #{$1.capitalize}" }
|
102
109
|
end
|
103
110
|
|
111
|
+
def build_parameters(node)
|
112
|
+
node.parameters.map do |param_name, param_info|
|
113
|
+
if param_info.key?(:ref)
|
114
|
+
param_info[:ref]
|
115
|
+
else
|
116
|
+
{
|
117
|
+
"name" => param_name,
|
118
|
+
"in" => "query",
|
119
|
+
"required" => param_info[:required],
|
120
|
+
"description" => param_info[:description] || "",
|
121
|
+
"schema" => get_param_type_spec(param_name, param_info[:type])
|
122
|
+
}
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
104
127
|
def build_security(node)
|
105
128
|
available_before_actions = node.controller.__before_actions_for(node.action.to_sym)
|
106
129
|
|
@@ -138,4 +161,20 @@ class Rage::OpenAPI::Converter
|
|
138
161
|
@used_tags += node_tags
|
139
162
|
end
|
140
163
|
end
|
164
|
+
|
165
|
+
def get_param_type_spec(param_name, param_type)
|
166
|
+
unless param_type
|
167
|
+
guessed_type = if param_name == "id" || param_name.end_with?("_id")
|
168
|
+
"Integer"
|
169
|
+
elsif param_name.end_with?("_at")
|
170
|
+
"Time"
|
171
|
+
else
|
172
|
+
"String"
|
173
|
+
end
|
174
|
+
|
175
|
+
return Rage::OpenAPI.__type_to_spec(guessed_type)
|
176
|
+
end
|
177
|
+
|
178
|
+
param_type
|
179
|
+
end
|
141
180
|
end
|
data/lib/rage/openapi/openapi.rb
CHANGED
@@ -4,10 +4,23 @@ require "erb"
|
|
4
4
|
require "yaml"
|
5
5
|
|
6
6
|
if !defined?(Prism)
|
7
|
+
begin
|
8
|
+
require "prism"
|
9
|
+
rescue LoadError
|
10
|
+
fail <<~ERR
|
11
|
+
|
12
|
+
Rage::OpenAPI depends on Prism to build OpenAPI specifications. Add the following line to your Gemfile:
|
13
|
+
gem "prism"
|
14
|
+
|
15
|
+
ERR
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if Gem::Version.create(Prism::VERSION) < Gem::Version.create("0.25.0")
|
7
20
|
fail <<~ERR
|
8
21
|
|
9
|
-
|
10
|
-
gem "prism"
|
22
|
+
Rage::OpenAPI is only compatible with Prism >= 0.25.0. Detected Prism version: #{Prism::VERSION}. Add the following line to your Gemfile:
|
23
|
+
gem "prism", ">= 0.25.0"
|
11
24
|
|
12
25
|
ERR
|
13
26
|
end
|
@@ -45,7 +58,7 @@ module Rage::OpenAPI
|
|
45
58
|
end
|
46
59
|
|
47
60
|
app = ->(env) do
|
48
|
-
if env["PATH_INFO"] == ""
|
61
|
+
if env["PATH_INFO"] == "" || env["PATH_INFO"] == "/"
|
49
62
|
html_app.call(env)
|
50
63
|
elsif env["PATH_INFO"] == "/json"
|
51
64
|
json_app.call(env)
|
@@ -80,7 +93,7 @@ module Rage::OpenAPI
|
|
80
93
|
else
|
81
94
|
case components_file.extname
|
82
95
|
when ".yml", ".yaml"
|
83
|
-
YAML.safe_load(components_file.read)
|
96
|
+
YAML.safe_load(components_file.read) || {}
|
84
97
|
when ".json"
|
85
98
|
JSON.parse(components_file.read)
|
86
99
|
else
|
@@ -117,6 +130,30 @@ module Rage::OpenAPI
|
|
117
130
|
Object
|
118
131
|
end
|
119
132
|
|
133
|
+
# @private
|
134
|
+
def self.__type_to_spec(type, default: false)
|
135
|
+
case type
|
136
|
+
when "Integer"
|
137
|
+
{ "type" => "integer" }
|
138
|
+
when "Float"
|
139
|
+
{ "type" => "number", "format" => "float" }
|
140
|
+
when "Numeric"
|
141
|
+
{ "type" => "number" }
|
142
|
+
when "Boolean"
|
143
|
+
{ "type" => "boolean" }
|
144
|
+
when "Hash"
|
145
|
+
{ "type" => "object" }
|
146
|
+
when "Date"
|
147
|
+
{ "type" => "string", "format" => "date" }
|
148
|
+
when "DateTime", "Time"
|
149
|
+
{ "type" => "string", "format" => "date-time" }
|
150
|
+
when "String"
|
151
|
+
{ "type" => "string" }
|
152
|
+
else
|
153
|
+
{ "type" => "string" } if default
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
120
157
|
# @private
|
121
158
|
def self.__log_warn(log)
|
122
159
|
puts "[OpenAPI] WARNING: #{log}"
|
data/lib/rage/openapi/parser.rb
CHANGED
@@ -132,6 +132,9 @@ class Rage::OpenAPI::Parser
|
|
132
132
|
end
|
133
133
|
end
|
134
134
|
|
135
|
+
elsif expression =~ /@param\s/
|
136
|
+
parse_param_tag(expression, node, comments[i])
|
137
|
+
|
135
138
|
elsif expression =~ /@internal\b/
|
136
139
|
# no-op
|
137
140
|
children = find_children(comments[i + 1..], node)
|
@@ -162,11 +165,10 @@ class Rage::OpenAPI::Parser
|
|
162
165
|
children << expression.strip
|
163
166
|
elsif expression.start_with?("@")
|
164
167
|
break
|
165
|
-
elsif
|
166
|
-
#
|
168
|
+
elsif node.is_a?(Rage::OpenAPI::Nodes::Method) && node.summary
|
169
|
+
Rage::OpenAPI.__log_warn "unrecognized expression detected at #{location_msg(comment)}; use two spaces to mark multi-line expressions"
|
167
170
|
break
|
168
171
|
else
|
169
|
-
Rage::OpenAPI.__log_warn "unrecognized expression detected at #{location_msg(comment)}; use two spaces to mark multi-line expressions"
|
170
172
|
break
|
171
173
|
end
|
172
174
|
end
|
@@ -208,4 +210,50 @@ class Rage::OpenAPI::Parser
|
|
208
210
|
end
|
209
211
|
end
|
210
212
|
end
|
213
|
+
|
214
|
+
def parse_param_tag(expression, node, comment)
|
215
|
+
param = expression[7..].strip
|
216
|
+
|
217
|
+
shared_reference_parser = Rage::OpenAPI::Parsers::SharedReference.new
|
218
|
+
if shared_reference_parser.known_definition?(param)
|
219
|
+
if (ref = shared_reference_parser.parse(param))
|
220
|
+
node.parameters[param] = { ref: }
|
221
|
+
else
|
222
|
+
Rage::OpenAPI.__log_warn "invalid shared reference detected at #{location_msg(comment)}"
|
223
|
+
end
|
224
|
+
return
|
225
|
+
end
|
226
|
+
|
227
|
+
param_name, param_type, param_description = param.split(" ", 3)
|
228
|
+
is_required = true
|
229
|
+
param_type_regexp = /^[{\[]\w+[}\]]$/
|
230
|
+
|
231
|
+
if param_type && !param_type&.match?(param_type_regexp)
|
232
|
+
param_description = if param_description
|
233
|
+
"#{param_type} #{param_description}"
|
234
|
+
else
|
235
|
+
param_type
|
236
|
+
end
|
237
|
+
|
238
|
+
param_type = nil
|
239
|
+
end
|
240
|
+
|
241
|
+
if param_name.end_with?("?")
|
242
|
+
param_name = param_name[0...-1]
|
243
|
+
is_required = false
|
244
|
+
end
|
245
|
+
|
246
|
+
if param_type
|
247
|
+
param_type = param_type[1...-1]
|
248
|
+
parsed_param = Rage::OpenAPI.__type_to_spec(param_type)
|
249
|
+
end
|
250
|
+
|
251
|
+
if node.parameters[param_name]
|
252
|
+
Rage::OpenAPI.__log_warn "duplicate `@param` tag detected at #{location_msg(comment)}"
|
253
|
+
elsif param_type && parsed_param.nil?
|
254
|
+
Rage::OpenAPI.__log_warn "unrecognized type `#{param_type}` detected at #{location_msg(comment)}"
|
255
|
+
else
|
256
|
+
node.parameters[param_name] = { type: parsed_param, description: param_description, required: is_required }
|
257
|
+
end
|
258
|
+
end
|
211
259
|
end
|
@@ -264,22 +264,7 @@ class Rage::OpenAPI::Parsers::Ext::Alba
|
|
264
264
|
end
|
265
265
|
|
266
266
|
def get_type_definition(type_id)
|
267
|
-
|
268
|
-
when "Integer"
|
269
|
-
{ "type" => "integer" }
|
270
|
-
when "Boolean", ":Boolean"
|
271
|
-
{ "type" => "boolean" }
|
272
|
-
when "Numeric"
|
273
|
-
{ "type" => "number" }
|
274
|
-
when "Float"
|
275
|
-
{ "type" => "number", "format" => "float" }
|
276
|
-
when "Date"
|
277
|
-
{ "type" => "string", "format" => "date" }
|
278
|
-
when "DateTime", "Time"
|
279
|
-
{ "type" => "string", "format" => "date-time" }
|
280
|
-
else
|
281
|
-
{ "type" => "string" }
|
282
|
-
end
|
267
|
+
Rage::OpenAPI.__type_to_spec(type_id.delete_prefix(":"), default: true)
|
283
268
|
end
|
284
269
|
end
|
285
270
|
end
|
@@ -42,25 +42,6 @@ class Rage::OpenAPI::Parsers::YAML
|
|
42
42
|
private
|
43
43
|
|
44
44
|
def type_to_spec(type)
|
45
|
-
|
46
|
-
when "Integer"
|
47
|
-
{ "type" => "integer" }
|
48
|
-
when "Float"
|
49
|
-
{ "type" => "number", "format" => "float" }
|
50
|
-
when "Numeric"
|
51
|
-
{ "type" => "number" }
|
52
|
-
when "Boolean"
|
53
|
-
{ "type" => "boolean" }
|
54
|
-
when "Hash"
|
55
|
-
{ "type" => "object" }
|
56
|
-
when "Date"
|
57
|
-
{ "type" => "string", "format" => "date" }
|
58
|
-
when "DateTime", "Time"
|
59
|
-
{ "type" => "string", "format" => "date-time" }
|
60
|
-
when "String"
|
61
|
-
{ "type" => "string" }
|
62
|
-
else
|
63
|
-
{ "type" => "string", "enum" => [type] }
|
64
|
-
end
|
45
|
+
Rage::OpenAPI.__type_to_spec(type) || { "type" => "string", "enum" => [type] }
|
65
46
|
end
|
66
47
|
end
|
data/lib/rage/request.rb
CHANGED
@@ -1,13 +1,154 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "time"
|
4
|
+
require "set" # required for ruby 3.1
|
4
5
|
|
5
6
|
class Rage::Request
|
7
|
+
# @private
|
8
|
+
# regexp to match against a ip address
|
9
|
+
IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
|
10
|
+
# @private
|
11
|
+
# HTTP methods from [RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1](https://www.ietf.org/rfc/rfc2616.txt)
|
12
|
+
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
|
13
|
+
# @private
|
14
|
+
# HTTP methods from [RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV](https://www.ietf.org/rfc/rfc2518.txt)
|
15
|
+
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
|
16
|
+
# @private
|
17
|
+
# HTTP methods from [RFC 3253: Versioning Extensions to WebDAV](https://www.ietf.org/rfc/rfc3253.txt)
|
18
|
+
RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
|
19
|
+
# @private
|
20
|
+
# HTTP methods from [RFC 3648: WebDAV Ordered Collections Protocol](https://www.ietf.org/rfc/rfc3648.txt)
|
21
|
+
RFC3648 = %w(ORDERPATCH)
|
22
|
+
# @private
|
23
|
+
# HTTP methods from [RFC 3744: WebDAV Access Control Protocol](https://www.ietf.org/rfc/rfc3744.txt)
|
24
|
+
RFC3744 = %w(ACL)
|
25
|
+
# @private
|
26
|
+
# HTTP methods from [RFC 5323: WebDAV SEARCH](https://www.ietf.org/rfc/rfc5323.txt)
|
27
|
+
RFC5323 = %w(SEARCH)
|
28
|
+
# @private
|
29
|
+
# HTTP methods from [RFC 4791: Calendaring Extensions to WebDAV](https://www.ietf.org/rfc/rfc4791.txt)
|
30
|
+
RFC4791 = %w(MKCALENDAR)
|
31
|
+
# @private
|
32
|
+
# HTTP methods from [RFC 5789: PATCH Method for HTTP](https://www.ietf.org/rfc/rfc5789.txt)
|
33
|
+
RFC5789 = %w(PATCH)
|
34
|
+
|
35
|
+
# @private
|
36
|
+
# Set data structure of all RFC defined HTTP headers
|
37
|
+
KNOWN_HTTP_METHODS = (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).to_set
|
38
|
+
|
6
39
|
# @private
|
7
40
|
def initialize(env)
|
8
41
|
@env = env
|
9
42
|
end
|
10
43
|
|
44
|
+
# Check if the request was made using TLS/SSL which is if http or https protocol is used inside the URL.
|
45
|
+
# @return [Boolean] true if the request is TLS/SSL, false otherwise
|
46
|
+
def ssl?
|
47
|
+
rack_request.ssl?
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns `https://` if this was an HTTPS request and `http://` otherwise.
|
51
|
+
# @return [String] `https://` if this was an HTTP request over SSL/TLS or `http://` if it was an HTTP request
|
52
|
+
def protocol
|
53
|
+
ssl? ? "https://" : "http://"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get the hostname from the request.
|
57
|
+
# @return [String] the hostname
|
58
|
+
def host
|
59
|
+
rack_request.host
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get the port number from the request.
|
63
|
+
# @return [Integer] the port number
|
64
|
+
def port
|
65
|
+
rack_request.port
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get the query string from the request.
|
69
|
+
# @return [String] the query string (empty string if no query)
|
70
|
+
def query_string
|
71
|
+
rack_request.query_string
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get the Rack environment hash.
|
75
|
+
# @return [Hash] the Rack env
|
76
|
+
def env
|
77
|
+
@env
|
78
|
+
end
|
79
|
+
|
80
|
+
# Check the HTTP request method to see if it was of type `GET`.
|
81
|
+
# @return [Boolean] true if it was a `GET` request
|
82
|
+
def get?
|
83
|
+
rack_request.get?
|
84
|
+
end
|
85
|
+
|
86
|
+
# Check the HTTP request method to see if it was of type `POST`.
|
87
|
+
# @return [Boolean] true if it was a `POST` request
|
88
|
+
def post?
|
89
|
+
rack_request.post?
|
90
|
+
end
|
91
|
+
|
92
|
+
# Check the HTTP request method to see if it was of type `PATCH`.
|
93
|
+
# @return [Boolean] true if it was a `PATCH` request
|
94
|
+
def patch?
|
95
|
+
rack_request.patch?
|
96
|
+
end
|
97
|
+
|
98
|
+
# Check the HTTP request method to see if it was of type `PUT`.
|
99
|
+
# @return [Boolean] true if it was a `PUT` request
|
100
|
+
def put?
|
101
|
+
rack_request.put?
|
102
|
+
end
|
103
|
+
|
104
|
+
# Check the HTTP request method to see if it was of type `DELETE`.
|
105
|
+
# @return [Boolean] true if it was a `DELETE` request
|
106
|
+
def delete?
|
107
|
+
rack_request.delete?
|
108
|
+
end
|
109
|
+
|
110
|
+
# Check the HTTP request method to see if it was of type `HEAD`.
|
111
|
+
# @return [Boolean] true if it was a `HEAD` request
|
112
|
+
def head?
|
113
|
+
rack_request.head?
|
114
|
+
end
|
115
|
+
|
116
|
+
# Get the full request URL.
|
117
|
+
# @return [String] complete URL including protocol, host, path, and query
|
118
|
+
def url
|
119
|
+
rack_request.url
|
120
|
+
end
|
121
|
+
|
122
|
+
# Get the request path.
|
123
|
+
# @return [String] the path component of the URL
|
124
|
+
# @example
|
125
|
+
# request.path # => "/users"
|
126
|
+
def path
|
127
|
+
rack_request.path
|
128
|
+
end
|
129
|
+
|
130
|
+
# Get the request path including the query string.
|
131
|
+
# @return [String] path with query string (if present)
|
132
|
+
# @example
|
133
|
+
# request.fullpath # => "/users?show_archived=true"
|
134
|
+
def fullpath
|
135
|
+
rack_request.fullpath
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get the `User-Agent` header value.
|
139
|
+
# @return [String, nil] the user agent string or nil
|
140
|
+
# @example
|
141
|
+
# request.user_agent # => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
|
142
|
+
def user_agent
|
143
|
+
rack_request.user_agent
|
144
|
+
end
|
145
|
+
|
146
|
+
# Get the content type of the request.
|
147
|
+
# @return [String, nil] the MIME type of the request body
|
148
|
+
def format
|
149
|
+
rack_request.content_type
|
150
|
+
end
|
151
|
+
|
11
152
|
# Get the request headers.
|
12
153
|
# @example
|
13
154
|
# request.headers["Content-Type"] # => "application/json"
|
@@ -37,47 +178,26 @@ class Rage::Request
|
|
37
178
|
)
|
38
179
|
end
|
39
180
|
|
40
|
-
#
|
41
|
-
# @
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
query_string = @env["QUERY_STRING"]
|
49
|
-
|
50
|
-
port_part = (scheme == "http" && port == "80") || (scheme == "https" && port == "443") ? "" : ":#{port}"
|
51
|
-
query_part = query_string.empty? ? "" : "?#{query_string}"
|
52
|
-
|
53
|
-
"#{scheme}://#{host}#{port_part}#{path}#{query_part}"
|
181
|
+
# Get the domain part of the request.
|
182
|
+
# @param tld_length [Integer] top-level domain length
|
183
|
+
# @example Consider a URL like: `example.foo.gov`
|
184
|
+
# request.domain => "foo.gov"
|
185
|
+
# request.domain(0) => "gov"
|
186
|
+
# request.domain(2) => "example.foo.gov"
|
187
|
+
def domain(tld_length = 1)
|
188
|
+
extract_domain(host, tld_length)
|
54
189
|
end
|
55
190
|
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
def
|
60
|
-
@env["
|
191
|
+
# Get the HTTP method of the request. If the client is using the `Rack::MethodOverride`
|
192
|
+
# middleware, then the `X-HTTP-Method-Override` header is checked first.
|
193
|
+
# @return [String] The HTTP Method override header or the request method header
|
194
|
+
def method
|
195
|
+
check_method(@env["rack.methodoverride.original_method"] || @env["REQUEST_METHOD"])
|
61
196
|
end
|
62
197
|
|
63
|
-
#
|
64
|
-
# @example
|
65
|
-
# request.fullpath # => "/users?show_archived=true"
|
66
|
-
def fullpath
|
67
|
-
path = @env["PATH_INFO"]
|
68
|
-
query_string = @env["QUERY_STRING"]
|
69
|
-
query_string.empty? ? path : "#{path}?#{query_string}"
|
70
|
-
end
|
71
|
-
|
72
|
-
# Returns the user agent of the request.
|
73
|
-
# @example
|
74
|
-
# request.user_agent # => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
|
75
|
-
def user_agent
|
76
|
-
@env["HTTP_USER_AGENT"]
|
77
|
-
end
|
78
|
-
|
79
|
-
# Returns the unique request ID. By default, this ID is internally generated, and all log entries created during the request
|
198
|
+
# Get the unique request ID. By default, this ID is internally generated, and all log entries created during the request
|
80
199
|
# are tagged with it. Alternatively, you can use the {Rage::RequestId} middleware to derive the ID from the `X-Request-Id` header.
|
200
|
+
# @return [String] the request ID
|
81
201
|
def request_id
|
82
202
|
@env["rage.request_id"]
|
83
203
|
end
|
@@ -86,6 +206,32 @@ class Rage::Request
|
|
86
206
|
|
87
207
|
private
|
88
208
|
|
209
|
+
def rack_request
|
210
|
+
@rack_request ||= Rack::Request.new(@env)
|
211
|
+
end
|
212
|
+
|
213
|
+
def check_method(name)
|
214
|
+
if name
|
215
|
+
if KNOWN_HTTP_METHODS.include?(name)
|
216
|
+
name
|
217
|
+
else
|
218
|
+
raise(Rage::Errors::UnknownHTTPMethod, "#{name}, accepted HTTP methods are #{KNOWN_HTTP_METHODS.to_a}")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def extract_domain(host, tld_length)
|
224
|
+
extract_domain_from(host, tld_length) if named_host?(host)
|
225
|
+
end
|
226
|
+
|
227
|
+
def extract_domain_from(host, tld_length)
|
228
|
+
host.split(".").last(1 + tld_length).join(".")
|
229
|
+
end
|
230
|
+
|
231
|
+
def named_host?(host)
|
232
|
+
!IP_HOST_REGEXP.match?(host)
|
233
|
+
end
|
234
|
+
|
89
235
|
def if_none_match
|
90
236
|
headers["HTTP_IF_NONE_MATCH"]
|
91
237
|
end
|
data/lib/rage/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rage-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roman Samoilov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-04-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|