ruby_routes 2.0.0 → 2.1.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: 4017abab11dd0945bcfe1659b2ed1456c2cf82a8993db55d094d283c48c8ebd0
4
- data.tar.gz: 07f0aba728bf81cdfdc245e20a212f1775b2314a826c88f0e25b8ff2b608c195
3
+ metadata.gz: fb6b2a38cb8f31bb3e79fa66dbbb1a0620c6eec1f668680e152efe38e7926b42
4
+ data.tar.gz: db5d62093e15a422d85e6c7ec373327ca1f37fd24f7b851c12bdb2f70cf81622
5
5
  SHA512:
6
- metadata.gz: a4aa77ba441b9e87cdcb31cae5b5f12babaf93396e400723d5f845392ac132520c4398e19d49d0755e05fc305791b54a3676a460ba8c73cd4b5ee55647fb2589
7
- data.tar.gz: a3b883c253731dfad5eaf34a28fb89684d0f9fa921127f6e428225743a93d87934508e49f59c3710964cc8a444b729f9278872aa8d991c4302abbb7ff8645b66
6
+ metadata.gz: 7e84488c6309cea2dab99be9a350139f2042f3d9601e4a0d1d20eb3d66c5530934f2c4f0c5b384dec7fa3095802f4d710ab2cc4f1a0158486cd5bba618995801
7
+ data.tar.gz: 0fda0c79e0a935ba9227faba495030c9ff2144ebcc289969eed0a0eb95596d0d69289d1a12b160ef37c18d4021fd5ae172c12b7db48b3beeddb32435e6a2d7ad
@@ -1,3 +1,4 @@
1
+ require_relative 'segments/base_segment'
1
2
  require_relative 'segments/dynamic_segment'
2
3
  require_relative 'segments/static_segment'
3
4
  require_relative 'segments/wildcard_segment'
@@ -36,8 +36,13 @@ module RubyRoutes
36
36
  end
37
37
 
38
38
  def find(path, method, params_out = nil)
39
+ # Handle nil path and method cases
40
+ path ||= ''
41
+ method = method.to_s.upcase if method
42
+ # Strip query string before matching
43
+ clean_path = path.split('?', 2).first || ''
39
44
  # Fast path: root route
40
- if path == '/' || path.empty?
45
+ if clean_path == '/' || clean_path.empty?
41
46
  handler = @root.get_handler(method)
42
47
  if @root.is_endpoint && handler
43
48
  return [handler, params_out || {}]
@@ -46,7 +51,7 @@ module RubyRoutes
46
51
  end
47
52
  end
48
53
 
49
- segments = split_path_cached(path)
54
+ segments = split_path_cached(clean_path)
50
55
  current = @root
51
56
  params = params_out || {}
52
57
  params.clear if params_out
@@ -1,6 +1,7 @@
1
1
  require 'uri'
2
2
  require 'timeout'
3
3
  require 'set'
4
+ require 'rack'
4
5
  require_relative 'route/small_lru'
5
6
 
6
7
  module RubyRoutes
@@ -33,6 +34,7 @@ module RubyRoutes
33
34
 
34
35
  def extract_params(request_path, parsed_qp = nil)
35
36
  path_params = extract_path_params_fast(request_path)
37
+
36
38
  return EMPTY_HASH unless path_params
37
39
 
38
40
  # Use optimized param building
@@ -103,7 +105,7 @@ module RubyRoutes
103
105
  ROOT_PATH = '/'.freeze
104
106
  UNRESERVED_RE = /\A[a-zA-Z0-9\-._~]+\z/.freeze
105
107
  QUERY_CACHE_SIZE = 128
106
-
108
+
107
109
  # Common HTTP methods - interned for performance
108
110
  HTTP_GET = 'GET'.freeze
109
111
  HTTP_POST = 'POST'.freeze
@@ -220,17 +222,14 @@ module RubyRoutes
220
222
  return EMPTY_HASH if @compiled_segments.empty? && request_path == ROOT_PATH
221
223
  return nil if @compiled_segments.empty?
222
224
 
223
- # Fast path normalization
224
225
  path_parts = split_path_fast(request_path)
225
-
226
- # Check if we have a wildcard/splat segment
226
+
227
+ # Check for wildcard/splat segment
227
228
  has_splat = @compiled_segments.any? { |seg| seg[:type] == :splat }
228
-
229
+
229
230
  if has_splat
230
- # For wildcard routes, path can have more parts than segments
231
231
  return nil if path_parts.size < @compiled_segments.size - 1
232
232
  else
233
- # For non-wildcard routes, size must match exactly
234
233
  return nil if @compiled_segments.size != path_parts.size
235
234
  end
236
235
 
@@ -238,8 +237,8 @@ module RubyRoutes
238
237
  end
239
238
 
240
239
  def split_path_fast(request_path)
241
- # Optimized path splitting
242
- path = request_path
240
+ # Remove query string before splitting
241
+ path = request_path.split('?', 2).first
243
242
  path = path[1..-1] if path.start_with?('/')
244
243
  path = path[0...-1] if path.end_with?('/') && path != ROOT_PATH
245
244
  path.empty? ? [] : path.split('/')
@@ -268,7 +267,7 @@ module RubyRoutes
268
267
  return @defaults if params.empty?
269
268
 
270
269
  merged = get_thread_local_merged_hash
271
-
270
+
272
271
  # Merge defaults first if they exist
273
272
  merged.update(@defaults) unless @defaults.empty?
274
273
 
@@ -279,7 +278,7 @@ module RubyRoutes
279
278
  # Fallback for older Ruby versions
280
279
  params.each { |k, v| merged[k.to_s] = v }
281
280
  end
282
-
281
+
283
282
  merged
284
283
  end
285
284
 
@@ -307,7 +306,7 @@ module RubyRoutes
307
306
 
308
307
  # Pre-allocate array for parts to avoid string buffer operations
309
308
  parts = []
310
-
309
+
311
310
  @compiled_segments.each do |seg|
312
311
  case seg[:type]
313
312
  when :static
@@ -340,10 +339,13 @@ module RubyRoutes
340
339
  # Fast segment encoding with caching for common values
341
340
  def encode_segment_fast(str)
342
341
  return str if UNRESERVED_RE.match?(str)
343
-
342
+
344
343
  # Cache encoded segments to avoid repeated encoding
345
344
  @encoding_cache ||= {}
346
- @encoding_cache[str] ||= URI.encode_www_form_component(str)
345
+ @encoding_cache[str] ||= begin
346
+ # Use URI.encode_www_form_component but replace + with %20 for path segments
347
+ URI.encode_www_form_component(str).gsub('+', '%20')
348
+ end
347
349
  end
348
350
 
349
351
  # Optimized query params with caching
@@ -352,7 +354,7 @@ module RubyRoutes
352
354
  return EMPTY_HASH unless query_start
353
355
 
354
356
  query_string = path[(query_start + 1)..-1]
355
- return EMPTY_HASH if query_string.empty?
357
+ return EMPTY_HASH if query_string.empty? || query_string.match?(/^\?+$/)
356
358
 
357
359
  # Cache query param parsing
358
360
  if (cached = @query_cache.get(query_string))
@@ -403,7 +405,7 @@ module RubyRoutes
403
405
  when Proc
404
406
  # DEPRECATED: Proc constraints are deprecated due to security risks
405
407
  warn_proc_constraint_deprecation(param)
406
-
408
+
407
409
  # For backward compatibility, still execute but with strict timeout
408
410
  begin
409
411
  Timeout.timeout(0.05) do # Reduced timeout for security
@@ -442,23 +444,23 @@ module RubyRoutes
442
444
 
443
445
  def warn_proc_constraint_deprecation(param)
444
446
  return if @proc_warnings_shown&.include?(param)
445
-
447
+
446
448
  @proc_warnings_shown ||= Set.new
447
449
  @proc_warnings_shown << param
448
-
450
+
449
451
  warn <<~WARNING
450
452
  [DEPRECATION] Proc constraints are deprecated due to security risks.
451
-
453
+
452
454
  Parameter: #{param}
453
455
  Route: #{@path}
454
-
456
+
455
457
  Secure alternatives:
456
458
  - Use regex: constraints: { #{param}: /\\A\\d+\\z/ }
457
459
  - Use built-in types: constraints: { #{param}: :int }
458
460
  - Use hash constraints: constraints: { #{param}: { min_length: 3, format: /\\A[a-z]+\\z/ } }
459
-
461
+
460
462
  Available built-in types: :int, :uuid, :email, :slug, :alpha, :alphanumeric
461
-
463
+
462
464
  This warning will become an error in a future version.
463
465
  WARNING
464
466
  end
@@ -468,23 +470,23 @@ module RubyRoutes
468
470
  if constraint[:min_length] && value.length < constraint[:min_length]
469
471
  raise RubyRoutes::ConstraintViolation, "Value too short (minimum #{constraint[:min_length]} characters)"
470
472
  end
471
-
473
+
472
474
  if constraint[:max_length] && value.length > constraint[:max_length]
473
475
  raise RubyRoutes::ConstraintViolation, "Value too long (maximum #{constraint[:max_length]} characters)"
474
476
  end
475
-
477
+
476
478
  if constraint[:format] && !value.match?(constraint[:format])
477
479
  raise RubyRoutes::ConstraintViolation, "Value does not match required format"
478
480
  end
479
-
481
+
480
482
  if constraint[:in] && !constraint[:in].include?(value)
481
483
  raise RubyRoutes::ConstraintViolation, "Value not in allowed list"
482
484
  end
483
-
485
+
484
486
  if constraint[:not_in] && constraint[:not_in].include?(value)
485
487
  raise RubyRoutes::ConstraintViolation, "Value in forbidden list"
486
488
  end
487
-
489
+
488
490
  if constraint[:range] && !constraint[:range].cover?(value.to_i)
489
491
  raise RubyRoutes::ConstraintViolation, "Value not in allowed range"
490
492
  end
@@ -73,7 +73,7 @@ module RubyRoutes
73
73
  controller: route.controller,
74
74
  action: route.action
75
75
  }.freeze
76
-
76
+
77
77
  insert_cache_entry(cache_key, result)
78
78
  result
79
79
  end
@@ -143,9 +143,8 @@ module RubyRoutes
143
143
 
144
144
  # Optimized cache key building - avoid string interpolation
145
145
  def build_cache_key(method, path)
146
- # Use string interpolation which is faster than buffer + dup + freeze
147
146
  # String interpolation creates a new string directly without intermediate allocations
148
- "#{method}:#{path}".freeze
147
+ "#{method}:#{path}"
149
148
  end
150
149
 
151
150
  # Get thread-local params hash, reusing when possible
@@ -14,8 +14,10 @@ class String
14
14
  case self
15
15
  when /y$/
16
16
  self.sub(/y$/, 'ies')
17
- when /sh$/, /ch$/, /x$/, /z$/
17
+ when /sh$/, /ch$/, /x$/
18
18
  self + 'es'
19
+ when /z$/
20
+ self + 'zes'
19
21
  when /s$/
20
22
  # Words ending in 's' are already plural
21
23
  self
@@ -4,6 +4,7 @@ module RubyRoutes
4
4
  module UrlHelpers
5
5
  def self.included(base)
6
6
  base.extend(ClassMethods)
7
+ base.include(base.url_helpers)
7
8
  end
8
9
 
9
10
  module ClassMethods
@@ -49,11 +50,11 @@ module RubyRoutes
49
50
  # HTML forms only support GET and POST
50
51
  # For other methods, use POST with _method hidden field
51
52
  form_method = (method == 'get') ? 'get' : 'post'
52
-
53
+
53
54
  safe_path = CGI.escapeHTML(path.to_s)
54
55
  safe_form_method = CGI.escapeHTML(form_method)
55
56
  html = "<form action=\"#{safe_path}\" method=\"#{safe_form_method}\">"
56
-
57
+
57
58
  # Add _method hidden field for non-GET/POST methods
58
59
  if method != 'get' && method != 'post'
59
60
  safe_method = CGI.escapeHTML(method)
@@ -1,3 +1,3 @@
1
1
  module RubyRoutes
2
- VERSION = "2.0.0"
2
+ VERSION = "2.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_routes
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yosef Benny Widyokarsono
@@ -37,6 +37,20 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '13.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rack
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.2'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.2'
40
54
  - !ruby/object:Gem::Dependency
41
55
  name: simplecov
42
56
  requirement: !ruby/object:Gem::Requirement