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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b63589b942c187505390393db5ff827ad647a8ba22b2b6d544f47a0075f73701
4
- data.tar.gz: 91cad1e502090ab52bb872ce9216f1deb2cfe150560bf328bb61267a719a5c1c
3
+ metadata.gz: 537238bd77a6af0c53b59def61a4b94616ca11b8e0bb3f31613d06198c8da2f5
4
+ data.tar.gz: 4597f16d331862d871aeacf18e701406b825e8208e8d41ec4d29f47d8ce6ce5e
5
5
  SHA512:
6
- metadata.gz: b0e44e8decbd7b68c8489e6559960585711ea1931ff5cff48622e5303b120bb5920567cf85610faaef7e25fa94d8289ceccaab82ba761ea8c28ded4b436624c6
7
- data.tar.gz: 655432e0e793cfa07b744e0adc56f6dc4becd73c9e48d3eb1c4716055849e5c03f1927211d75400a3da97838846329a9520226122635781cbd0f5f6417319687
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
@@ -387,7 +387,7 @@ class RageController::API
387
387
 
388
388
  # @private
389
389
  def __before_action_exists?(name)
390
- @__before_actions.any? { |h| h[:name] == name && !h[:around] }
390
+ @__before_actions&.any? { |h| h[:name] == name && !h[:around] }
391
391
  end
392
392
 
393
393
  # @private
data/lib/rage/errors.rb CHANGED
@@ -4,4 +4,10 @@ module Rage::Errors
4
4
 
5
5
  class RouterError < StandardError
6
6
  end
7
+
8
+ class UnknownHTTPMethod < StandardError
9
+ end
10
+
11
+ class InvalidCustomProxy < StandardError
12
+ end
7
13
  end
@@ -124,7 +124,7 @@ class Rage::Cors
124
124
  else
125
125
  origins_eval = @origins.map { |origin|
126
126
  origin.is_a?(Regexp) ?
127
- "origin =~ /#{origin.source}/.freeze" :
127
+ "origin =~ /#{origin}/.freeze" :
128
128
  "origin == '#{origin}'.freeze"
129
129
  }.join(" || ")
130
130
 
@@ -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
- path_parameters = []
28
+ path_params = []
29
29
  path = node.http_path.gsub(/:(\w+)/) do
30
- path_parameters << $1
30
+ path_params << $1
31
31
  "{#{$1}}"
32
32
  end
33
33
 
34
34
  unless memo.key?(path)
35
35
  memo[path] = {}
36
- path_parameters.each do |parameter|
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" => parameter,
41
+ "name" => param,
40
42
  "required" => true,
41
- "schema" => { "type" => parameter.end_with?("id") ? "integer" : "string" }
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
@@ -14,7 +14,7 @@ class Rage::OpenAPI::Nodes::Method
14
14
  @parents = parents
15
15
 
16
16
  @responses = {}
17
- @parameters = []
17
+ @parameters = {}
18
18
  end
19
19
 
20
20
  def root
@@ -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
- rage-rb depends on Prism to build OpenAPI specifications. Add the following line to your Gemfile:
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}"
@@ -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 !node.summary
166
- # no-op - this is likely the summary entry
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
- case type_id
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
- case type
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
- # Returns the full URL of the request.
41
- # @example
42
- # request.url # => "https://example.com/users?show_archived=true"
43
- def url
44
- scheme = @env["rack.url_scheme"]
45
- host = @env["SERVER_NAME"]
46
- port = @env["SERVER_PORT"]
47
- path = @env["PATH_INFO"]
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
- # Returns the path of the request.
57
- # @example
58
- # request.path # => "/users"
59
- def path
60
- @env["PATH_INFO"]
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
- # Returns the full path including the query string.
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rage
4
- VERSION = "1.14.0"
4
+ VERSION = "1.15.0"
5
5
  end
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.14.0
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-03-10 00:00:00.000000000 Z
11
+ date: 2025-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor