ruby_routes 2.4.0 → 2.6.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/README.md +0 -23
- data/lib/ruby_routes/constant.rb +20 -3
- data/lib/ruby_routes/lru_strategies/hit_strategy.rb +1 -1
- data/lib/ruby_routes/node.rb +15 -87
- data/lib/ruby_routes/radix_tree/finder.rb +79 -52
- data/lib/ruby_routes/radix_tree/inserter.rb +2 -54
- data/lib/ruby_routes/radix_tree/traversal_strategy/base.rb +18 -0
- data/lib/ruby_routes/radix_tree/traversal_strategy/generic_loop.rb +25 -0
- data/lib/ruby_routes/radix_tree/traversal_strategy/unrolled.rb +45 -0
- data/lib/ruby_routes/radix_tree/traversal_strategy.rb +26 -0
- data/lib/ruby_routes/radix_tree.rb +12 -62
- data/lib/ruby_routes/route/check_helpers.rb +3 -3
- data/lib/ruby_routes/route/constraint_validator.rb +24 -1
- data/lib/ruby_routes/route/matcher.rb +11 -0
- data/lib/ruby_routes/route/param_support.rb +9 -7
- data/lib/ruby_routes/route/path_builder.rb +11 -6
- data/lib/ruby_routes/route/path_generation.rb +5 -1
- data/lib/ruby_routes/route/small_lru.rb +43 -2
- data/lib/ruby_routes/route/validation_helpers.rb +6 -36
- data/lib/ruby_routes/route/warning_helpers.rb +7 -5
- data/lib/ruby_routes/route.rb +35 -55
- data/lib/ruby_routes/route_set/cache_helpers.rb +32 -13
- data/lib/ruby_routes/route_set/collection_helpers.rb +15 -14
- data/lib/ruby_routes/route_set.rb +32 -69
- data/lib/ruby_routes/router/build_helpers.rb +1 -1
- data/lib/ruby_routes/router/builder.rb +16 -33
- data/lib/ruby_routes/router/http_helpers.rb +13 -49
- data/lib/ruby_routes/router/resource_helpers.rb +26 -39
- data/lib/ruby_routes/router/scope_helpers.rb +26 -14
- data/lib/ruby_routes/router.rb +41 -24
- data/lib/ruby_routes/segment.rb +3 -3
- data/lib/ruby_routes/segments/base_segment.rb +8 -0
- data/lib/ruby_routes/segments/static_segment.rb +3 -1
- data/lib/ruby_routes/strategies/base.rb +18 -0
- data/lib/ruby_routes/strategies/hash_based_strategy.rb +33 -0
- data/lib/ruby_routes/strategies/hybrid_strategy.rb +70 -0
- data/lib/ruby_routes/strategies/radix_tree_strategy.rb +24 -0
- data/lib/ruby_routes/strategies.rb +5 -0
- data/lib/ruby_routes/utility/key_builder_utility.rb +4 -26
- data/lib/ruby_routes/utility/method_utility.rb +18 -17
- data/lib/ruby_routes/utility/path_utility.rb +18 -7
- data/lib/ruby_routes/version.rb +1 -1
- data/lib/ruby_routes.rb +3 -1
- metadata +11 -2
- data/lib/ruby_routes/string_extensions.rb +0 -65
@@ -9,38 +9,14 @@ require_relative 'radix_tree/inserter'
|
|
9
9
|
require_relative 'radix_tree/finder'
|
10
10
|
|
11
11
|
module RubyRoutes
|
12
|
-
# RadixTree
|
13
|
-
#
|
14
|
-
# Compact routing trie supporting:
|
15
|
-
# - Static segments (/users)
|
16
|
-
# - Dynamic segments (/users/:id)
|
17
|
-
# - Wildcard splat (/assets/*path)
|
18
|
-
#
|
19
|
-
# Design / Behavior:
|
20
|
-
# - Traversal keeps track of the deepest valid endpoint encountered
|
21
|
-
# (best_match_node) so that if a later branch fails, we can still
|
22
|
-
# return a shorter matching route.
|
23
|
-
# - Dynamic and wildcard captures are written directly into the caller
|
24
|
-
# supplied params Hash (or a fresh one) to avoid intermediate objects.
|
25
|
-
# - A very small manual LRU (Hash + order Array) caches the result of
|
26
|
-
# splitting raw paths into their segment arrays.
|
27
|
-
#
|
28
|
-
# Matching Precedence:
|
29
|
-
# static > dynamic > wildcard
|
30
|
-
#
|
31
|
-
# Thread Safety:
|
32
|
-
# - Not thread‑safe for mutation (intended for boot‑time construction).
|
33
|
-
# Safe for concurrent reads after routes are added.
|
34
|
-
#
|
35
|
-
# @api internal
|
36
12
|
class RadixTree
|
37
13
|
include RubyRoutes::Utility::PathUtility
|
38
14
|
include RubyRoutes::Utility::MethodUtility
|
39
|
-
include Inserter
|
40
|
-
include Finder
|
15
|
+
include RubyRoutes::RadixTree::Inserter
|
16
|
+
include RubyRoutes::RadixTree::Finder
|
41
17
|
|
42
18
|
class << self
|
43
|
-
#
|
19
|
+
# Allow RadixTree.new(path, options...) to act as a convenience factory
|
44
20
|
def new(*args, &block)
|
45
21
|
if args.any?
|
46
22
|
RubyRoutes::Route.new(*args, &block)
|
@@ -50,48 +26,22 @@ module RubyRoutes
|
|
50
26
|
end
|
51
27
|
end
|
52
28
|
|
53
|
-
# Initialize empty tree and split cache.
|
54
29
|
def initialize
|
55
|
-
@root
|
56
|
-
@split_cache
|
57
|
-
@split_cache_max
|
58
|
-
@
|
59
|
-
@empty_segment_list = [].freeze
|
30
|
+
@root = Node.new
|
31
|
+
@split_cache = RubyRoutes::Route::SmallLru.new(RubyRoutes::Constant::CACHE_SIZE)
|
32
|
+
@split_cache_max = RubyRoutes::Constant::CACHE_SIZE # larger cache for better hit rates
|
33
|
+
@empty_segments = [].freeze # reuse for root path
|
60
34
|
end
|
61
35
|
|
62
|
-
|
63
|
-
|
64
|
-
# @param raw_path [String]
|
65
|
-
# @param http_methods [Array<String,Symbol>]
|
66
|
-
# @param route_handler [Object]
|
67
|
-
# @return [Object] route_handler
|
68
|
-
def add(raw_path, http_methods, route_handler)
|
69
|
-
normalized_path = normalize_path(raw_path)
|
70
|
-
normalized_methods = http_methods.map { |m| normalize_http_method(m) }
|
71
|
-
insert_route(normalized_path, normalized_methods, route_handler)
|
72
|
-
route_handler
|
73
|
-
end
|
74
|
-
|
75
|
-
# Public find delegates to Finder#find (now simplified on this class).
|
76
|
-
def find(request_path_input, request_method_input, params_out = {})
|
77
|
-
super
|
36
|
+
def add(path, methods, handler)
|
37
|
+
insert_route(path, methods, handler)
|
78
38
|
end
|
79
39
|
|
80
40
|
private
|
81
41
|
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
# @return [Array<String>]
|
86
|
-
def split_path_cached(raw_path)
|
87
|
-
return @empty_segment_list if raw_path == '/'
|
88
|
-
|
89
|
-
cached = @split_cache.get(raw_path)
|
90
|
-
return cached if cached
|
91
|
-
|
92
|
-
segments = split_path(raw_path)
|
93
|
-
@split_cache.set(raw_path, segments)
|
94
|
-
segments
|
42
|
+
# Cached path splitting with optimized common cases
|
43
|
+
def split_path_cached(path)
|
44
|
+
@split_cache.get(path) || @split_cache.set(path, split_path(path))
|
95
45
|
end
|
96
46
|
end
|
97
47
|
end
|
@@ -20,7 +20,7 @@ module RubyRoutes
|
|
20
20
|
# @raise [RubyRoutes::ConstraintViolation] If the value is shorter than the minimum length.
|
21
21
|
# @return [void]
|
22
22
|
def check_min_length(constraint, value)
|
23
|
-
return unless (min = constraint[:min_length]) && value
|
23
|
+
return unless (min = constraint[:min_length]) && value&.length < min
|
24
24
|
|
25
25
|
raise RubyRoutes::ConstraintViolation, "Value too short (minimum #{constraint[:min_length]} characters)"
|
26
26
|
end
|
@@ -36,7 +36,7 @@ module RubyRoutes
|
|
36
36
|
# @raise [RubyRoutes::ConstraintViolation] If the value exceeds the maximum length.
|
37
37
|
# @return [void]
|
38
38
|
def check_max_length(constraint, value)
|
39
|
-
return unless (max = constraint[:max_length]) && value
|
39
|
+
return unless (max = constraint[:max_length]) && value&.length > max
|
40
40
|
|
41
41
|
raise RubyRoutes::ConstraintViolation, "Value too long (maximum #{constraint[:max_length]} characters)"
|
42
42
|
end
|
@@ -52,7 +52,7 @@ module RubyRoutes
|
|
52
52
|
# @raise [RubyRoutes::ConstraintViolation] If the value does not match the required format.
|
53
53
|
# @return [void]
|
54
54
|
def check_format(constraint, value)
|
55
|
-
return unless (format = constraint[:format]) &&
|
55
|
+
return unless (format = constraint[:format]) && !value&.match?(format)
|
56
56
|
|
57
57
|
raise RubyRoutes::ConstraintViolation, 'Value does not match required format'
|
58
58
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'timeout'
|
4
4
|
require_relative '../constant'
|
5
|
+
require_relative 'warning_helpers'
|
5
6
|
|
6
7
|
module RubyRoutes
|
7
8
|
class Route
|
@@ -12,6 +13,7 @@ module RubyRoutes
|
|
12
13
|
# validation rules. It also handles timeouts and raises appropriate exceptions
|
13
14
|
# for constraint violations.
|
14
15
|
module ConstraintValidator
|
16
|
+
include RubyRoutes::Route::WarningHelpers
|
15
17
|
# Validate all constraints for the given parameters.
|
16
18
|
#
|
17
19
|
# This method iterates through all constraints and validates each parameter
|
@@ -47,6 +49,24 @@ module RubyRoutes
|
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
52
|
+
# Validate hash-form constraint rules.
|
53
|
+
#
|
54
|
+
# This method validates a value against a set of hash-form constraints,
|
55
|
+
# such as minimum length, maximum length, format, inclusion, exclusion,
|
56
|
+
# and range.
|
57
|
+
#
|
58
|
+
# @param constraint [Hash] The constraint rules.
|
59
|
+
# @param value [String] The value to validate.
|
60
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value violates any constraint.
|
61
|
+
# @return [void]
|
62
|
+
def validate_hash_constraint!(constraint, value)
|
63
|
+
check_min_length(constraint, value)
|
64
|
+
check_max_length(constraint, value)
|
65
|
+
check_format(constraint, value)
|
66
|
+
check_in_list(constraint, value)
|
67
|
+
check_not_in_list(constraint, value)
|
68
|
+
check_range(constraint, value)
|
69
|
+
end
|
50
70
|
# Handle built-in symbol/string rules via a simple lookup.
|
51
71
|
#
|
52
72
|
# @param rule [Symbol, String] The built-in constraint rule.
|
@@ -66,6 +86,8 @@ module RubyRoutes
|
|
66
86
|
validate_alpha_constraint(value)
|
67
87
|
when 'alphanumeric'
|
68
88
|
validate_alphanumeric_constraint(value)
|
89
|
+
else
|
90
|
+
invalid!
|
69
91
|
end
|
70
92
|
end
|
71
93
|
|
@@ -122,7 +144,8 @@ module RubyRoutes
|
|
122
144
|
# @param value [Object] The value to validate.
|
123
145
|
# @raise [RubyRoutes::ConstraintViolation] If the value is not a valid email.
|
124
146
|
def validate_email_constraint(value)
|
125
|
-
|
147
|
+
email_regex = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+\z/
|
148
|
+
invalid! unless value.to_s.match?(email_regex)
|
126
149
|
end
|
127
150
|
|
128
151
|
# Validate a slug constraint.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'path_generation'
|
4
3
|
require_relative 'warning_helpers'
|
4
|
+
require_relative 'query_helpers'
|
5
5
|
|
6
6
|
module RubyRoutes
|
7
7
|
class Route
|
@@ -14,6 +14,7 @@ module RubyRoutes
|
|
14
14
|
# Thread-safety: Thread-local storage is used to avoid allocation and cross-thread mutation.
|
15
15
|
module ParamSupport
|
16
16
|
include RubyRoutes::Route::WarningHelpers
|
17
|
+
include RubyRoutes::Route::QueryHelpers
|
17
18
|
|
18
19
|
private
|
19
20
|
|
@@ -25,11 +26,9 @@ module RubyRoutes
|
|
25
26
|
# @param params [Hash] The user-provided parameters.
|
26
27
|
# @return [Hash] The merged parameters.
|
27
28
|
def build_merged_params(params)
|
28
|
-
return @defaults if params.nil? || params.empty?
|
29
|
-
|
30
29
|
merged_hash = acquire_merge_hash
|
31
30
|
merge_defaults_into(merged_hash)
|
32
|
-
merge_user_params_into(merged_hash, params)
|
31
|
+
merge_user_params_into(merged_hash, params) unless params.nil? || params.empty?
|
33
32
|
merged_hash
|
34
33
|
end
|
35
34
|
|
@@ -59,9 +58,12 @@ module RubyRoutes
|
|
59
58
|
# @return [void]
|
60
59
|
def merge_user_params_into(merged_hash, params)
|
61
60
|
params.each do |key, value|
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
string_key = if key.is_a?(String)
|
62
|
+
key
|
63
|
+
else
|
64
|
+
(Thread.current[:ruby_routes_symbol_keys] ||= {})[key] ||= key.to_s.freeze
|
65
|
+
end
|
66
|
+
merged_hash[string_key] = value
|
65
67
|
end
|
66
68
|
end
|
67
69
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'small_lru'
|
4
|
+
|
3
5
|
module RubyRoutes
|
4
6
|
class Route
|
5
7
|
# PathBuilder: generation + segment encoding
|
@@ -25,7 +27,7 @@ module RubyRoutes
|
|
25
27
|
# Append a segment to the buffer.
|
26
28
|
#
|
27
29
|
# @param buffer [String] the buffer to append to
|
28
|
-
# @param segment [
|
30
|
+
# @param segment [RubyRoutes::Segments::BaseSegment] the segment to append
|
29
31
|
# @param merged [Hash] the merged parameters
|
30
32
|
# @param index [Integer] the current index
|
31
33
|
# @param last_index [Integer] the last index
|
@@ -48,8 +50,8 @@ module RubyRoutes
|
|
48
50
|
# Rough heuristic (static sizes + average dynamic)
|
49
51
|
base = 1
|
50
52
|
@compiled_segments.each do |segment|
|
51
|
-
base += case segment
|
52
|
-
when
|
53
|
+
base += case segment
|
54
|
+
when RubyRoutes::Segments::StaticSegment then segment.literal_text.length + 1
|
53
55
|
else 20
|
54
56
|
end
|
55
57
|
end
|
@@ -75,9 +77,12 @@ module RubyRoutes
|
|
75
77
|
def encode_segment_fast(string)
|
76
78
|
return string if RubyRoutes::Constant::UNRESERVED_RE.match?(string)
|
77
79
|
|
78
|
-
@encoding_cache ||=
|
79
|
-
|
80
|
-
|
80
|
+
@encoding_cache ||= RubyRoutes::Route::SmallLru.new(256)
|
81
|
+
cached = @encoding_cache.get(string)
|
82
|
+
return cached if cached
|
83
|
+
|
84
|
+
encoded = URI.encode_www_form_component(string).gsub('+', '%20')
|
85
|
+
@encoding_cache.set(string, encoded)
|
81
86
|
end
|
82
87
|
end
|
83
88
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'warning_helpers'
|
4
|
+
require_relative 'validation_helpers'
|
5
|
+
require_relative 'param_support'
|
4
6
|
|
5
7
|
module RubyRoutes
|
6
8
|
class Route
|
@@ -9,6 +11,8 @@ module RubyRoutes
|
|
9
11
|
# route-related warnings (kept separate to reduce parent module size).
|
10
12
|
module PathGeneration
|
11
13
|
include RubyRoutes::Route::WarningHelpers
|
14
|
+
include RubyRoutes::Route::ValidationHelpers
|
15
|
+
include RubyRoutes::Route::ParamSupport
|
12
16
|
|
13
17
|
private
|
14
18
|
|
@@ -25,8 +29,8 @@ module RubyRoutes
|
|
25
29
|
return @static_path if static_short_circuit?(params)
|
26
30
|
return @static_path || RubyRoutes::Constant::ROOT_PATH if trivial_route?
|
27
31
|
|
28
|
-
validate_required_once(params)
|
29
32
|
merged_params = build_merged_params(params)
|
33
|
+
validate_required_once(merged_params.dup)
|
30
34
|
|
31
35
|
build_or_fetch_generated_path(merged_params)
|
32
36
|
end
|
@@ -27,7 +27,7 @@ module RubyRoutes
|
|
27
27
|
# @return [Integer] number of cache hits
|
28
28
|
# @return [Integer] number of cache misses
|
29
29
|
# @return [Integer] number of evictions (when capacity exceeded)
|
30
|
-
attr_reader :max_size, :hits, :misses, :evictions
|
30
|
+
attr_reader :max_size, :hits, :misses, :evictions, :hash
|
31
31
|
|
32
32
|
# @param max_size [Integer] positive maximum size
|
33
33
|
# @raise [ArgumentError] if max_size < 1
|
@@ -91,6 +91,42 @@ module RubyRoutes
|
|
91
91
|
@hash.keys
|
92
92
|
end
|
93
93
|
|
94
|
+
# Check if a key exists in the cache.
|
95
|
+
#
|
96
|
+
# @param key [Object] The key to check.
|
97
|
+
# @return [Boolean] True if the key exists, false otherwise.
|
98
|
+
def has_key?(key)
|
99
|
+
@hash.key?(key)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Hash-like access for reading (delegates to get).
|
103
|
+
#
|
104
|
+
# @param key [Object] The key to retrieve.
|
105
|
+
# @return [Object, nil] The cached value or nil.
|
106
|
+
def [](key)
|
107
|
+
get(key)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Include matcher (checks for key-value pair).
|
111
|
+
#
|
112
|
+
# @param pair [Hash] A hash with a single key-value pair (e.g., { 'key' => 'value' }).
|
113
|
+
# @return [Boolean] True if the pair exists in the cache, false otherwise.
|
114
|
+
def include?(pair)
|
115
|
+
return false unless pair.is_a?(Hash) && pair.size == 1
|
116
|
+
|
117
|
+
key, value = pair.first
|
118
|
+
@hash[key] == value
|
119
|
+
end
|
120
|
+
|
121
|
+
# Hash-like access for writing (delegates to set).
|
122
|
+
#
|
123
|
+
# @param key [Object] The key to set.
|
124
|
+
# @param value [Object] The value to cache.
|
125
|
+
# @return [Object] The value.
|
126
|
+
def []=(key, value)
|
127
|
+
set(key, value)
|
128
|
+
end
|
129
|
+
|
94
130
|
# Debug / spec helper (avoid exposing internal Hash directly).
|
95
131
|
# @return [Hash] shallow copy of internal store
|
96
132
|
def inspect_hash
|
@@ -109,6 +145,12 @@ module RubyRoutes
|
|
109
145
|
@misses += 1
|
110
146
|
end
|
111
147
|
|
148
|
+
def clear_counters!
|
149
|
+
@hits = 0
|
150
|
+
@misses = 0
|
151
|
+
@evictions = 0
|
152
|
+
end
|
153
|
+
|
112
154
|
# Internal helper used by hit strategy to promote key.
|
113
155
|
# @param key [Object]
|
114
156
|
# @return [void]
|
@@ -116,7 +158,6 @@ module RubyRoutes
|
|
116
158
|
val = @hash.delete(key)
|
117
159
|
@hash[key] = val if val
|
118
160
|
end
|
119
|
-
private :promote
|
120
161
|
end
|
121
162
|
end
|
122
163
|
end
|
@@ -48,20 +48,14 @@ module RubyRoutes
|
|
48
48
|
def validate_required_once(params)
|
49
49
|
return if @required_params.empty?
|
50
50
|
|
51
|
-
# Check cache for existing validation result
|
52
51
|
cached_result = get_cached_validation(params)
|
53
52
|
if cached_result
|
54
53
|
missing, nils = cached_result
|
55
54
|
else
|
56
|
-
# Perform validation
|
57
55
|
missing, nils = validate_required_params(params)
|
58
|
-
|
59
|
-
if params.frozen?
|
60
|
-
cache_validation_result(params, [missing, nils])
|
61
|
-
end
|
56
|
+
cache_validation_result(params.freeze, [missing, nils])
|
62
57
|
end
|
63
58
|
|
64
|
-
# Raise if invalid
|
65
59
|
raise RouteNotFound, "Missing params: #{missing.join(', ')}" unless missing.empty?
|
66
60
|
raise RouteNotFound, "Missing or nil params: #{nils.join(', ')}" unless nils.empty?
|
67
61
|
end
|
@@ -78,10 +72,6 @@ module RubyRoutes
|
|
78
72
|
return RubyRoutes::Constant::EMPTY_PAIR if @required_params.empty?
|
79
73
|
params ||= {}
|
80
74
|
|
81
|
-
if (cached = get_cached_validation(params))
|
82
|
-
return cached
|
83
|
-
end
|
84
|
-
|
85
75
|
missing = []
|
86
76
|
nils = []
|
87
77
|
@required_params.each do |required_key|
|
@@ -124,14 +114,13 @@ module RubyRoutes
|
|
124
114
|
# @return [void]
|
125
115
|
def cache_validation_result(params, result)
|
126
116
|
return unless params.frozen?
|
127
|
-
return unless @validation_cache
|
117
|
+
return unless @validation_cache
|
128
118
|
|
129
|
-
@cache_mutex.synchronize
|
119
|
+
@cache_mutex.synchronize do
|
120
|
+
return if @validation_cache.size >= 64
|
121
|
+
@validation_cache.set(params.hash, result)
|
122
|
+
end
|
130
123
|
end
|
131
|
-
|
132
|
-
# Fetch cached validation result.
|
133
|
-
#
|
134
|
-
# This method retrieves a cached validation result for the given parameters.
|
135
124
|
#
|
136
125
|
# @param params [Hash] The parameters used for validation.
|
137
126
|
# @return [Object, nil] The cached validation result, or `nil` if not found.
|
@@ -150,25 +139,6 @@ module RubyRoutes
|
|
150
139
|
pool = Thread.current[:ruby_routes_hash_pool] ||= []
|
151
140
|
pool.push(hash) if pool.size < 5
|
152
141
|
end
|
153
|
-
|
154
|
-
# Validate hash-form constraint rules.
|
155
|
-
#
|
156
|
-
# This method validates a value against a set of hash-form constraints,
|
157
|
-
# such as minimum length, maximum length, format, inclusion, exclusion,
|
158
|
-
# and range.
|
159
|
-
#
|
160
|
-
# @param constraint [Hash] The constraint rules.
|
161
|
-
# @param value [String] The value to validate.
|
162
|
-
# @raise [RubyRoutes::ConstraintViolation] If the value violates any constraint.
|
163
|
-
# @return [void]
|
164
|
-
def validate_hash_constraint!(constraint, value)
|
165
|
-
check_min_length(constraint, value)
|
166
|
-
check_max_length(constraint, value)
|
167
|
-
check_format(constraint, value)
|
168
|
-
check_in_list(constraint, value)
|
169
|
-
check_not_in_list(constraint, value)
|
170
|
-
check_range(constraint, value)
|
171
|
-
end
|
172
142
|
end
|
173
143
|
end
|
174
144
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'set'
|
4
|
+
require 'thread'
|
4
5
|
|
5
6
|
module RubyRoutes
|
6
7
|
class Route
|
@@ -19,13 +20,14 @@ module RubyRoutes
|
|
19
20
|
# @param param [String, Symbol] The parameter name for which the warning
|
20
21
|
# is being emitted.
|
21
22
|
# @return [void]
|
23
|
+
|
22
24
|
def warn_proc_constraint_deprecation(param)
|
23
25
|
key = param.to_sym
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
@warnings_mutex ||= Mutex.new
|
27
|
+
@warnings_mutex.synchronize do
|
28
|
+
@proc_warnings_shown ||= Set.new
|
29
|
+
warn_proc_warning(key) if @proc_warnings_shown.add?(key)
|
30
|
+
end
|
29
31
|
end
|
30
32
|
|
31
33
|
# Warn about `Proc` constraint deprecation.
|
data/lib/ruby_routes/route.rb
CHANGED
@@ -12,13 +12,15 @@ require_relative 'utility/method_utility'
|
|
12
12
|
require_relative 'utility/path_utility'
|
13
13
|
require_relative 'utility/route_utility'
|
14
14
|
require_relative 'route/param_support'
|
15
|
-
require_relative '
|
15
|
+
require_relative 'segment'
|
16
16
|
require_relative 'route/path_builder'
|
17
17
|
require_relative 'route/constraint_validator'
|
18
18
|
require_relative 'route/check_helpers'
|
19
19
|
require_relative 'route/query_helpers'
|
20
20
|
require_relative 'route/validation_helpers'
|
21
|
+
require_relative 'route/segment_compiler'
|
21
22
|
require_relative 'route/path_generation'
|
23
|
+
require_relative 'route_set/cache_helpers'
|
22
24
|
|
23
25
|
module RubyRoutes
|
24
26
|
# Route
|
@@ -51,43 +53,43 @@ module RubyRoutes
|
|
51
53
|
# @api public
|
52
54
|
class Route
|
53
55
|
include ParamSupport
|
54
|
-
include SegmentCompiler
|
55
56
|
include PathBuilder
|
56
57
|
include RubyRoutes::Route::ConstraintValidator
|
57
58
|
include RubyRoutes::Route::ValidationHelpers
|
58
59
|
include RubyRoutes::Route::QueryHelpers
|
59
60
|
include RubyRoutes::Route::PathGeneration
|
61
|
+
include RubyRoutes::Route::SegmentCompiler
|
60
62
|
include RubyRoutes::Utility::MethodUtility
|
61
63
|
include RubyRoutes::Utility::PathUtility
|
62
64
|
include RubyRoutes::Utility::KeyBuilderUtility
|
65
|
+
include RubyRoutes::RouteSet::CacheHelpers
|
63
66
|
|
64
67
|
attr_reader :path, :methods, :controller, :action, :name, :constraints, :defaults
|
65
68
|
|
66
|
-
public :extract_params, :parse_query_params, :query_params, :generate_path
|
69
|
+
public :extract_params, :parse_query_params, :query_params, :generate_path, :merge_query_params_into_hash
|
67
70
|
|
68
71
|
# Create a new Route.
|
69
72
|
#
|
70
73
|
# @param path [String] The raw route path (may include `:params` or `*splat`).
|
71
74
|
# @param options [Hash] The options for the route.
|
72
75
|
# @option options [Symbol, String, Array<Symbol, String>] :via (:get) HTTP method(s).
|
73
|
-
# @option options [String] :to
|
74
|
-
# @option options [String] :controller
|
75
|
-
# @option options [String
|
76
|
-
# @option options [
|
77
|
-
# @option options [Hash] :
|
78
|
-
# @option options [
|
76
|
+
# @option options [String] :to The controller#action string.
|
77
|
+
# @option options [String] :controller The controller name.
|
78
|
+
# @option options [String] :action The action name.
|
79
|
+
# @option options [String] :as The route name.
|
80
|
+
# @option options [Hash] :constraints The route constraints.
|
81
|
+
# @option options [Hash] :defaults The route defaults.
|
79
82
|
def initialize(path, options = {})
|
83
|
+
@options = options
|
80
84
|
@path = normalize_path(path)
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
@name = options[:as]
|
86
|
-
@constraints = options[:constraints] || {}
|
87
|
-
@defaults = (options[:defaults] || {}).transform_keys(&:to_s).freeze
|
85
|
+
@name = @options[:as]
|
86
|
+
@constraints = (@options[:constraints] || {}).freeze
|
87
|
+
@defaults = (@options[:defaults] || {}).transform_keys(&:to_s).freeze
|
88
88
|
@param_key_slots = [[nil, nil], [nil, nil]]
|
89
89
|
@required_validated_once = false
|
90
90
|
|
91
|
+
setup_caches
|
92
|
+
|
91
93
|
precompile_route_data
|
92
94
|
validate_route!
|
93
95
|
end
|
@@ -121,54 +123,32 @@ module RubyRoutes
|
|
121
123
|
|
122
124
|
private
|
123
125
|
|
124
|
-
#
|
126
|
+
# Split path into parts.
|
125
127
|
#
|
126
|
-
# @param
|
127
|
-
# @return [
|
128
|
-
def
|
129
|
-
|
130
|
-
@methods = raw_http_methods.map { |method| normalize_http_method(method) }.freeze
|
131
|
-
@methods_set = @methods.to_set.freeze
|
128
|
+
# @param path [String] The path to split.
|
129
|
+
# @return [Array<String>]
|
130
|
+
def split_path(path)
|
131
|
+
path.split('/').reject(&:empty?)
|
132
132
|
end
|
133
133
|
|
134
|
-
#
|
135
|
-
|
136
|
-
# @param options [Hash] The options for the route.
|
137
|
-
# @return [void]
|
138
|
-
def setup_controller_and_action(options)
|
139
|
-
@controller = extract_controller(options)
|
140
|
-
@action = options[:action] || extract_action(options[:to])
|
141
|
-
end
|
142
|
-
|
143
|
-
# Infer controller name from options or `:to`.
|
144
|
-
#
|
145
|
-
# @param options [Hash] The options for the route.
|
146
|
-
# @return [String, nil] The inferred controller name.
|
147
|
-
def extract_controller(options)
|
148
|
-
to = options[:to]
|
149
|
-
return options[:controller] unless to
|
150
|
-
|
151
|
-
to.to_s.split('#', 2).first
|
152
|
-
end
|
153
|
-
|
154
|
-
# Infer action from `:to` string.
|
155
|
-
#
|
156
|
-
# @param to [String, nil] The `:to` string.
|
157
|
-
# @return [String, nil] The inferred action name.
|
158
|
-
def extract_action(to)
|
159
|
-
return nil unless to
|
160
|
-
|
161
|
-
to.to_s.split('#', 2).last
|
162
|
-
end
|
134
|
+
# Expose for testing / external callers.
|
135
|
+
public :extract_path_params_fast
|
163
136
|
|
164
137
|
# Precompile route data for performance.
|
165
138
|
#
|
166
139
|
# @return [void]
|
167
140
|
def precompile_route_data
|
141
|
+
raw_http_methods = Array(@options[:via] || :get)
|
142
|
+
@methods = raw_http_methods.map { |method| normalize_http_method(method) }.freeze
|
143
|
+
@methods_set = @methods.to_set.freeze
|
144
|
+
|
145
|
+
to_str = @options[:to].to_s
|
146
|
+
to_controller, to_action = to_str.split('#', 2)
|
147
|
+
@controller = @options[:controller] || to_controller
|
148
|
+
@action = @options[:action] || to_action
|
149
|
+
|
168
150
|
@is_resource = @path.match?(%r{/:id(?:$|\.)})
|
169
|
-
|
170
|
-
@query_cache = SmallLru.new(RubyRoutes::Constant::QUERY_CACHE_SIZE)
|
171
|
-
@cache_mutex = Mutex.new # Thread-safe access to caches
|
151
|
+
|
172
152
|
initialize_validation_cache
|
173
153
|
compile_segments
|
174
154
|
compile_required_params
|