ruby_routes 2.5.0 → 2.7.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 +1 -1
- data/lib/ruby_routes/cache_setup.rb +32 -0
- data/lib/ruby_routes/constant.rb +18 -3
- data/lib/ruby_routes/lru_strategies/hit_strategy.rb +1 -6
- data/lib/ruby_routes/node.rb +14 -87
- data/lib/ruby_routes/radix_tree/finder.rb +88 -46
- data/lib/ruby_routes/radix_tree/inserter.rb +2 -55
- 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 +54 -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 +2 -2
- 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 -8
- 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 +51 -5
- data/lib/ruby_routes/route/validation_helpers.rb +6 -46
- data/lib/ruby_routes/route.rb +32 -59
- data/lib/ruby_routes/route_set/cache_helpers.rb +24 -25
- data/lib/ruby_routes/route_set/collection_helpers.rb +7 -16
- data/lib/ruby_routes/route_set.rb +36 -59
- data/lib/ruby_routes/router/build_helpers.rb +1 -7
- data/lib/ruby_routes/router/builder.rb +12 -12
- data/lib/ruby_routes/router/http_helpers.rb +7 -48
- data/lib/ruby_routes/router/resource_helpers.rb +23 -37
- data/lib/ruby_routes/router/scope_helpers.rb +26 -14
- data/lib/ruby_routes/router.rb +28 -29
- data/lib/ruby_routes/segment.rb +3 -3
- data/lib/ruby_routes/segments/base_segment.rb +8 -0
- data/lib/ruby_routes/segments/dynamic_segment.rb +1 -1
- 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 +11 -11
- data/lib/ruby_routes/utility/path_utility.rb +19 -7
- data/lib/ruby_routes/version.rb +1 -1
- data/lib/ruby_routes.rb +3 -1
- metadata +12 -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
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'timeout'
|
4
4
|
require_relative '../constant'
|
5
|
+
require_relative 'warning_helpers'
|
6
|
+
require_relative 'check_helpers'
|
5
7
|
|
6
8
|
module RubyRoutes
|
7
9
|
class Route
|
@@ -12,6 +14,8 @@ module RubyRoutes
|
|
12
14
|
# validation rules. It also handles timeouts and raises appropriate exceptions
|
13
15
|
# for constraint violations.
|
14
16
|
module ConstraintValidator
|
17
|
+
include RubyRoutes::Route::WarningHelpers
|
18
|
+
include RubyRoutes::Route::CheckHelpers
|
15
19
|
# Validate all constraints for the given parameters.
|
16
20
|
#
|
17
21
|
# This method iterates through all constraints and validates each parameter
|
@@ -47,6 +51,24 @@ module RubyRoutes
|
|
47
51
|
end
|
48
52
|
end
|
49
53
|
|
54
|
+
# Validate hash-form constraint rules.
|
55
|
+
#
|
56
|
+
# This method validates a value against a set of hash-form constraints,
|
57
|
+
# such as minimum length, maximum length, format, inclusion, exclusion,
|
58
|
+
# and range.
|
59
|
+
#
|
60
|
+
# @param constraint [Hash] The constraint rules.
|
61
|
+
# @param value [String] The value to validate.
|
62
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value violates any constraint.
|
63
|
+
# @return [void]
|
64
|
+
def validate_hash_constraint!(constraint, value)
|
65
|
+
check_min_length(constraint, value)
|
66
|
+
check_max_length(constraint, value)
|
67
|
+
check_format(constraint, value)
|
68
|
+
check_in_list(constraint, value)
|
69
|
+
check_not_in_list(constraint, value)
|
70
|
+
check_range(constraint, value)
|
71
|
+
end
|
50
72
|
# Handle built-in symbol/string rules via a simple lookup.
|
51
73
|
#
|
52
74
|
# @param rule [Symbol, String] The built-in constraint rule.
|
@@ -124,7 +146,8 @@ module RubyRoutes
|
|
124
146
|
# @param value [Object] The value to validate.
|
125
147
|
# @raise [RubyRoutes::ConstraintViolation] If the value is not a valid email.
|
126
148
|
def validate_email_constraint(value)
|
127
|
-
|
149
|
+
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/
|
150
|
+
invalid! unless value.to_s.match?(email_regex)
|
128
151
|
end
|
129
152
|
|
130
153
|
# Validate a slug constraint.
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'path_generation'
|
4
3
|
require_relative 'warning_helpers'
|
5
|
-
require_relative '
|
4
|
+
require_relative 'query_helpers'
|
6
5
|
|
7
6
|
module RubyRoutes
|
8
7
|
class Route
|
@@ -15,6 +14,7 @@ module RubyRoutes
|
|
15
14
|
# Thread-safety: Thread-local storage is used to avoid allocation and cross-thread mutation.
|
16
15
|
module ParamSupport
|
17
16
|
include RubyRoutes::Route::WarningHelpers
|
17
|
+
include RubyRoutes::Route::QueryHelpers
|
18
18
|
|
19
19
|
private
|
20
20
|
|
@@ -26,11 +26,9 @@ module RubyRoutes
|
|
26
26
|
# @param params [Hash] The user-provided parameters.
|
27
27
|
# @return [Hash] The merged parameters.
|
28
28
|
def build_merged_params(params)
|
29
|
-
return @defaults if params.nil? || params.empty?
|
30
|
-
|
31
29
|
merged_hash = acquire_merge_hash
|
32
30
|
merge_defaults_into(merged_hash)
|
33
|
-
merge_user_params_into(merged_hash, params)
|
31
|
+
merge_user_params_into(merged_hash, params) unless params.nil? || params.empty?
|
34
32
|
merged_hash
|
35
33
|
end
|
36
34
|
|
@@ -60,9 +58,12 @@ module RubyRoutes
|
|
60
58
|
# @return [void]
|
61
59
|
def merge_user_params_into(merged_hash, params)
|
62
60
|
params.each do |key, value|
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
66
67
|
end
|
67
68
|
end
|
68
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,14 +145,24 @@ 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
|
-
#
|
114
|
-
#
|
155
|
+
# Moves the key-value pair to the end of the hash (most recently used position).
|
156
|
+
#
|
157
|
+
# @param key [Object] The key to promote
|
158
|
+
# @return [Object, nil] The value associated with the key, or nil if not found
|
115
159
|
def promote(key)
|
160
|
+
return nil unless @hash.key?(key)
|
161
|
+
|
116
162
|
val = @hash.delete(key)
|
117
|
-
@hash[key] = val
|
163
|
+
@hash[key] = val
|
164
|
+
val
|
118
165
|
end
|
119
|
-
private :promote
|
120
166
|
end
|
121
167
|
end
|
122
168
|
end
|
@@ -12,16 +12,6 @@ module RubyRoutes
|
|
12
12
|
module ValidationHelpers
|
13
13
|
include RubyRoutes::Route::CheckHelpers
|
14
14
|
|
15
|
-
# Initialize validation result cache.
|
16
|
-
#
|
17
|
-
# This method initializes an LRU (Least Recently Used) cache for storing
|
18
|
-
# validation results, with a maximum size of 64 entries.
|
19
|
-
#
|
20
|
-
# @return [void]
|
21
|
-
def initialize_validation_cache
|
22
|
-
@validation_cache = SmallLru.new(64)
|
23
|
-
end
|
24
|
-
|
25
15
|
# Validate fundamental route shape.
|
26
16
|
#
|
27
17
|
# This method ensures that the route has a valid controller, action, and
|
@@ -48,20 +38,14 @@ module RubyRoutes
|
|
48
38
|
def validate_required_once(params)
|
49
39
|
return if @required_params.empty?
|
50
40
|
|
51
|
-
# Check cache for existing validation result
|
52
41
|
cached_result = get_cached_validation(params)
|
53
42
|
if cached_result
|
54
43
|
missing, nils = cached_result
|
55
44
|
else
|
56
|
-
# Perform validation
|
57
45
|
missing, nils = validate_required_params(params)
|
58
|
-
|
59
|
-
if params.frozen?
|
60
|
-
cache_validation_result(params, [missing, nils])
|
61
|
-
end
|
46
|
+
cache_validation_result(params.freeze, [missing, nils])
|
62
47
|
end
|
63
48
|
|
64
|
-
# Raise if invalid
|
65
49
|
raise RouteNotFound, "Missing params: #{missing.join(', ')}" unless missing.empty?
|
66
50
|
raise RouteNotFound, "Missing or nil params: #{nils.join(', ')}" unless nils.empty?
|
67
51
|
end
|
@@ -78,10 +62,6 @@ module RubyRoutes
|
|
78
62
|
return RubyRoutes::Constant::EMPTY_PAIR if @required_params.empty?
|
79
63
|
params ||= {}
|
80
64
|
|
81
|
-
if (cached = get_cached_validation(params))
|
82
|
-
return cached
|
83
|
-
end
|
84
|
-
|
85
65
|
missing = []
|
86
66
|
nils = []
|
87
67
|
@required_params.each do |required_key|
|
@@ -124,14 +104,13 @@ module RubyRoutes
|
|
124
104
|
# @return [void]
|
125
105
|
def cache_validation_result(params, result)
|
126
106
|
return unless params.frozen?
|
127
|
-
return unless @validation_cache
|
107
|
+
return unless @validation_cache
|
128
108
|
|
129
|
-
@cache_mutex.synchronize
|
109
|
+
@cache_mutex.synchronize do
|
110
|
+
return if @validation_cache.size >= 64
|
111
|
+
@validation_cache.set(params.hash, result)
|
112
|
+
end
|
130
113
|
end
|
131
|
-
|
132
|
-
# Fetch cached validation result.
|
133
|
-
#
|
134
|
-
# This method retrieves a cached validation result for the given parameters.
|
135
114
|
#
|
136
115
|
# @param params [Hash] The parameters used for validation.
|
137
116
|
# @return [Object, nil] The cached validation result, or `nil` if not found.
|
@@ -150,25 +129,6 @@ module RubyRoutes
|
|
150
129
|
pool = Thread.current[:ruby_routes_hash_pool] ||= []
|
151
130
|
pool.push(hash) if pool.size < 5
|
152
131
|
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
132
|
end
|
173
133
|
end
|
174
134
|
end
|
data/lib/ruby_routes/route.rb
CHANGED
@@ -6,18 +6,21 @@ require 'rack'
|
|
6
6
|
require 'set'
|
7
7
|
require_relative 'constant'
|
8
8
|
require_relative 'node'
|
9
|
+
require_relative 'cache_setup'
|
10
|
+
require_relative 'route_set/cache_helpers'
|
9
11
|
require_relative 'route/small_lru'
|
10
12
|
require_relative 'utility/key_builder_utility'
|
11
13
|
require_relative 'utility/method_utility'
|
12
14
|
require_relative 'utility/path_utility'
|
13
15
|
require_relative 'utility/route_utility'
|
14
16
|
require_relative 'route/param_support'
|
15
|
-
require_relative '
|
17
|
+
require_relative 'segment'
|
16
18
|
require_relative 'route/path_builder'
|
17
19
|
require_relative 'route/constraint_validator'
|
18
20
|
require_relative 'route/check_helpers'
|
19
21
|
require_relative 'route/query_helpers'
|
20
22
|
require_relative 'route/validation_helpers'
|
23
|
+
require_relative 'route/segment_compiler'
|
21
24
|
require_relative 'route/path_generation'
|
22
25
|
|
23
26
|
module RubyRoutes
|
@@ -51,43 +54,44 @@ module RubyRoutes
|
|
51
54
|
# @api public
|
52
55
|
class Route
|
53
56
|
include ParamSupport
|
54
|
-
include SegmentCompiler
|
55
57
|
include PathBuilder
|
56
58
|
include RubyRoutes::Route::ConstraintValidator
|
57
59
|
include RubyRoutes::Route::ValidationHelpers
|
58
60
|
include RubyRoutes::Route::QueryHelpers
|
59
61
|
include RubyRoutes::Route::PathGeneration
|
62
|
+
include RubyRoutes::Route::SegmentCompiler
|
60
63
|
include RubyRoutes::Utility::MethodUtility
|
61
64
|
include RubyRoutes::Utility::PathUtility
|
62
65
|
include RubyRoutes::Utility::KeyBuilderUtility
|
66
|
+
include RubyRoutes::RouteSet::CacheHelpers
|
67
|
+
include RubyRoutes::CacheSetup
|
63
68
|
|
64
69
|
attr_reader :path, :methods, :controller, :action, :name, :constraints, :defaults
|
65
70
|
|
66
|
-
public :extract_params, :parse_query_params, :query_params, :generate_path
|
71
|
+
public :extract_params, :parse_query_params, :query_params, :generate_path, :merge_query_params_into_hash
|
67
72
|
|
68
73
|
# Create a new Route.
|
69
74
|
#
|
70
75
|
# @param path [String] The raw route path (may include `:params` or `*splat`).
|
71
76
|
# @param options [Hash] The options for the route.
|
72
77
|
# @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 [
|
78
|
+
# @option options [String] :to The controller#action string.
|
79
|
+
# @option options [String] :controller The controller name.
|
80
|
+
# @option options [String] :action The action name.
|
81
|
+
# @option options [String] :as The route name.
|
82
|
+
# @option options [Hash] :constraints The route constraints.
|
83
|
+
# @option options [Hash] :defaults The route defaults.
|
79
84
|
def initialize(path, options = {})
|
85
|
+
@options = options
|
80
86
|
@path = normalize_path(path)
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
@name = options[:as]
|
86
|
-
@constraints = (options[:constraints] || {}).freeze
|
87
|
-
@defaults = (options[:defaults] || {}).transform_keys(&:to_s).freeze
|
87
|
+
@name = @options[:as]
|
88
|
+
@constraints = (@options[:constraints] || {}).freeze
|
89
|
+
@defaults = (@options[:defaults] || {}).transform_keys(&:to_s).freeze
|
88
90
|
@param_key_slots = [[nil, nil], [nil, nil]]
|
89
91
|
@required_validated_once = false
|
90
92
|
|
93
|
+
setup_caches
|
94
|
+
|
91
95
|
precompile_route_data
|
92
96
|
validate_route!
|
93
97
|
end
|
@@ -121,56 +125,25 @@ module RubyRoutes
|
|
121
125
|
|
122
126
|
private
|
123
127
|
|
124
|
-
#
|
128
|
+
# Expose for testing / external callers.
|
129
|
+
public :extract_path_params_fast
|
130
|
+
|
131
|
+
# Precompile route data for performance.
|
125
132
|
#
|
126
|
-
# @param options [Hash] The options for the route.
|
127
133
|
# @return [void]
|
128
|
-
def
|
129
|
-
raw_http_methods = Array(options[:via] || :get)
|
134
|
+
def precompile_route_data
|
135
|
+
raw_http_methods = Array(@options[:via] || :get)
|
130
136
|
@methods = raw_http_methods.map { |method| normalize_http_method(method) }.freeze
|
131
137
|
@methods_set = @methods.to_set.freeze
|
132
|
-
end
|
133
|
-
|
134
|
-
# Set up controller and action from options.
|
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
|
-
return options[:controller] if options[:controller]
|
149
|
-
to = options[:to]
|
150
|
-
return nil unless to
|
151
|
-
|
152
|
-
to.to_s.split('#', 2).first
|
153
|
-
end
|
154
|
-
|
155
|
-
# Infer action from `:to` string.
|
156
|
-
#
|
157
|
-
# @param to [String, nil] The `:to` string.
|
158
|
-
# @return [String, nil] The inferred action name.
|
159
|
-
def extract_action(to)
|
160
|
-
return nil unless to
|
161
138
|
|
162
|
-
to.to_s
|
163
|
-
|
139
|
+
to_str = @options[:to].to_s
|
140
|
+
to_controller, to_action = to_str.split('#', 2)
|
141
|
+
@controller = @options[:controller] || to_controller
|
142
|
+
@action = @options[:action] || to_action
|
164
143
|
|
165
|
-
# Precompile route data for performance.
|
166
|
-
#
|
167
|
-
# @return [void]
|
168
|
-
def precompile_route_data
|
169
144
|
@is_resource = @path.match?(%r{/:id(?:$|\.)})
|
170
|
-
|
171
|
-
|
172
|
-
@cache_mutex = Mutex.new # Thread-safe access to caches
|
173
|
-
initialize_validation_cache
|
145
|
+
|
146
|
+
setup_caches
|
174
147
|
compile_segments
|
175
148
|
compile_required_params
|
176
149
|
check_static_path
|