ruby_routes 2.1.0 → 2.3.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 +232 -162
- data/lib/ruby_routes/constant.rb +137 -18
- data/lib/ruby_routes/lru_strategies/hit_strategy.rb +31 -4
- data/lib/ruby_routes/lru_strategies/miss_strategy.rb +21 -0
- data/lib/ruby_routes/node.rb +82 -41
- data/lib/ruby_routes/radix_tree/finder.rb +164 -0
- data/lib/ruby_routes/radix_tree/inserter.rb +98 -0
- data/lib/ruby_routes/radix_tree.rb +83 -142
- data/lib/ruby_routes/route/check_helpers.rb +109 -0
- data/lib/ruby_routes/route/constraint_validator.rb +159 -0
- data/lib/ruby_routes/route/param_support.rb +202 -0
- data/lib/ruby_routes/route/path_builder.rb +86 -0
- data/lib/ruby_routes/route/path_generation.rb +102 -0
- data/lib/ruby_routes/route/query_helpers.rb +56 -0
- data/lib/ruby_routes/route/segment_compiler.rb +163 -0
- data/lib/ruby_routes/route/small_lru.rb +96 -17
- data/lib/ruby_routes/route/validation_helpers.rb +151 -0
- data/lib/ruby_routes/route/warning_helpers.rb +54 -0
- data/lib/ruby_routes/route.rb +121 -451
- data/lib/ruby_routes/route_set/cache_helpers.rb +174 -0
- data/lib/ruby_routes/route_set/collection_helpers.rb +127 -0
- data/lib/ruby_routes/route_set.rb +126 -148
- data/lib/ruby_routes/router/build_helpers.rb +100 -0
- data/lib/ruby_routes/router/builder.rb +96 -0
- data/lib/ruby_routes/router/http_helpers.rb +135 -0
- data/lib/ruby_routes/router/resource_helpers.rb +137 -0
- data/lib/ruby_routes/router/scope_helpers.rb +109 -0
- data/lib/ruby_routes/router.rb +196 -179
- data/lib/ruby_routes/segment.rb +28 -8
- data/lib/ruby_routes/segments/base_segment.rb +40 -4
- data/lib/ruby_routes/segments/dynamic_segment.rb +48 -12
- data/lib/ruby_routes/segments/static_segment.rb +43 -7
- data/lib/ruby_routes/segments/wildcard_segment.rb +56 -12
- data/lib/ruby_routes/string_extensions.rb +52 -15
- data/lib/ruby_routes/url_helpers.rb +106 -24
- data/lib/ruby_routes/utility/inflector_utility.rb +35 -0
- data/lib/ruby_routes/utility/key_builder_utility.rb +179 -0
- data/lib/ruby_routes/utility/method_utility.rb +137 -0
- data/lib/ruby_routes/utility/path_utility.rb +89 -0
- data/lib/ruby_routes/utility/route_utility.rb +49 -0
- data/lib/ruby_routes/version.rb +3 -1
- data/lib/ruby_routes.rb +68 -11
- metadata +30 -7
@@ -1,9 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'route/small_lru'
|
1
4
|
require_relative 'segment'
|
5
|
+
require_relative 'utility/path_utility'
|
6
|
+
require_relative 'utility/method_utility'
|
7
|
+
require_relative 'node'
|
8
|
+
require_relative 'radix_tree/inserter'
|
9
|
+
require_relative 'radix_tree/finder'
|
2
10
|
|
3
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
|
4
36
|
class RadixTree
|
37
|
+
include RubyRoutes::Utility::PathUtility
|
38
|
+
include RubyRoutes::Utility::MethodUtility
|
39
|
+
include Inserter
|
40
|
+
include Finder
|
41
|
+
|
5
42
|
class << self
|
6
|
-
#
|
43
|
+
# Backwards DSL convenience: RadixTree.new(args) → Route
|
7
44
|
def new(*args, &block)
|
8
45
|
if args.any?
|
9
46
|
RubyRoutes::Route.new(*args, &block)
|
@@ -13,161 +50,65 @@ module RubyRoutes
|
|
13
50
|
end
|
14
51
|
end
|
15
52
|
|
53
|
+
# Initialize empty tree and split cache.
|
16
54
|
def initialize
|
17
|
-
@
|
18
|
-
@split_cache
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
55
|
+
@root_node = Node.new
|
56
|
+
@split_cache = RubyRoutes::Route::SmallLru.new(2048)
|
57
|
+
@split_cache_max = 2048
|
58
|
+
@split_cache_order = []
|
59
|
+
@empty_segment_list = [].freeze
|
22
60
|
end
|
23
61
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
Array(methods).each { |method| current.add_handler(method.to_s.upcase, handler) }
|
62
|
+
# Add a route to the tree (delegates insertion logic).
|
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
|
36
73
|
end
|
37
74
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
method = method.to_s.upcase if method
|
42
|
-
# Strip query string before matching
|
43
|
-
clean_path = path.split('?', 2).first || ''
|
44
|
-
# Fast path: root route
|
45
|
-
if clean_path == '/' || clean_path.empty?
|
46
|
-
handler = @root.get_handler(method)
|
47
|
-
if @root.is_endpoint && handler
|
48
|
-
return [handler, params_out || {}]
|
49
|
-
else
|
50
|
-
return [nil, {}]
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
segments = split_path_cached(clean_path)
|
55
|
-
current = @root
|
56
|
-
params = params_out || {}
|
57
|
-
params.clear if params_out
|
58
|
-
|
59
|
-
# Unrolled traversal for common case (1-3 segments)
|
60
|
-
case segments.size
|
61
|
-
when 1
|
62
|
-
next_node, _ = current.traverse_for(segments[0], 0, segments, params)
|
63
|
-
current = next_node
|
64
|
-
when 2
|
65
|
-
next_node, should_break = current.traverse_for(segments[0], 0, segments, params)
|
66
|
-
return [nil, {}] unless next_node
|
67
|
-
current = next_node
|
68
|
-
unless should_break
|
69
|
-
next_node, _ = current.traverse_for(segments[1], 1, segments, params)
|
70
|
-
current = next_node
|
71
|
-
end
|
72
|
-
when 3
|
73
|
-
next_node, should_break = current.traverse_for(segments[0], 0, segments, params)
|
74
|
-
return [nil, {}] unless next_node
|
75
|
-
current = next_node
|
76
|
-
unless should_break
|
77
|
-
next_node, should_break = current.traverse_for(segments[1], 1, segments, params)
|
78
|
-
return [nil, {}] unless next_node
|
79
|
-
current = next_node
|
80
|
-
unless should_break
|
81
|
-
next_node, _ = current.traverse_for(segments[2], 2, segments, params)
|
82
|
-
current = next_node
|
83
|
-
end
|
84
|
-
end
|
85
|
-
else
|
86
|
-
# General case for longer paths
|
87
|
-
segments.each_with_index do |text, idx|
|
88
|
-
next_node, should_break = current.traverse_for(text, idx, segments, params)
|
89
|
-
return [nil, {}] unless next_node
|
90
|
-
current = next_node
|
91
|
-
break if should_break
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
return [nil, {}] unless current
|
96
|
-
handler = current.get_handler(method)
|
97
|
-
return [nil, {}] unless current.is_endpoint && handler
|
98
|
-
|
99
|
-
# Fast constraint check
|
100
|
-
if handler.respond_to?(:constraints) && !handler.constraints.empty?
|
101
|
-
return [nil, {}] unless constraints_match_fast(handler.constraints, params)
|
102
|
-
end
|
103
|
-
|
104
|
-
[handler, params]
|
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
|
105
78
|
end
|
106
79
|
|
107
80
|
private
|
108
81
|
|
109
|
-
#
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
end
|
116
|
-
|
117
|
-
result = split_path_raw(path)
|
118
|
-
|
119
|
-
# Cache with simple LRU eviction
|
120
|
-
@split_cache[path] = result
|
121
|
-
@split_cache_order << path
|
122
|
-
if @split_cache_order.size > @split_cache_max
|
123
|
-
oldest = @split_cache_order.shift
|
124
|
-
@split_cache.delete(oldest)
|
125
|
-
end
|
126
|
-
|
127
|
-
result
|
128
|
-
end
|
129
|
-
|
130
|
-
# Raw path splitting without caching (for registration)
|
131
|
-
def split_path_raw(path)
|
132
|
-
return [] if path == '/' || path.empty?
|
82
|
+
# Split path with small manual LRU cache.
|
83
|
+
#
|
84
|
+
# @param raw_path [String]
|
85
|
+
# @return [Array<String>]
|
86
|
+
def split_path_cached(raw_path)
|
87
|
+
return @empty_segment_list if raw_path == '/'
|
133
88
|
|
134
|
-
|
135
|
-
|
136
|
-
end_idx = path.end_with?('/') ? -2 : -1
|
89
|
+
cached = @split_cache.get(raw_path)
|
90
|
+
return cached if cached
|
137
91
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
path[start_idx..end_idx].split('/')
|
142
|
-
end
|
92
|
+
segments = split_path(raw_path)
|
93
|
+
@split_cache.set(raw_path, segments)
|
94
|
+
segments
|
143
95
|
end
|
144
96
|
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
# Fast integer check without regex
|
160
|
-
return false unless value.is_a?(String) && value.match?(/\A\d+\z/)
|
161
|
-
when :uuid
|
162
|
-
# Fast UUID check
|
163
|
-
return false unless value.is_a?(String) && value.length == 36 &&
|
164
|
-
value.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)
|
165
|
-
when Symbol
|
166
|
-
# Handle other symbolic constraints
|
167
|
-
next # unknown symbol constraint — allow
|
168
|
-
end
|
97
|
+
# Evaluate constraint rules for a candidate route.
|
98
|
+
#
|
99
|
+
# @param route_handler [Object]
|
100
|
+
# @param captured_params [Hash]
|
101
|
+
# @return [Boolean]
|
102
|
+
def check_constraints(route_handler, captured_params)
|
103
|
+
return true unless route_handler.respond_to?(:validate_constraints_fast!)
|
104
|
+
|
105
|
+
begin
|
106
|
+
# Use a duplicate to avoid unintended mutation by validators.
|
107
|
+
route_handler.validate_constraints_fast!(captured_params)
|
108
|
+
true
|
109
|
+
rescue RubyRoutes::ConstraintViolation
|
110
|
+
false
|
169
111
|
end
|
170
|
-
true
|
171
112
|
end
|
172
113
|
end
|
173
114
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyRoutes
|
4
|
+
class Route
|
5
|
+
# CheckHelpers: small helpers for validating hash-form constraints.
|
6
|
+
#
|
7
|
+
# This module provides methods for validating various hash-form constraints,
|
8
|
+
# such as minimum length, maximum length, format, inclusion, exclusion, and range.
|
9
|
+
# Each method raises a `RubyRoutes::ConstraintViolation` exception if the
|
10
|
+
# validation fails.
|
11
|
+
module CheckHelpers
|
12
|
+
# Check minimum length constraint.
|
13
|
+
#
|
14
|
+
# This method validates that the value meets the minimum length specified
|
15
|
+
# in the constraint. If the value is shorter than the minimum length, a
|
16
|
+
# `ConstraintViolation` is raised.
|
17
|
+
#
|
18
|
+
# @param constraint [Hash] The constraint hash containing `:min_length`.
|
19
|
+
# @param value [String] The value to validate.
|
20
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is shorter than the minimum length.
|
21
|
+
# @return [void]
|
22
|
+
def check_min_length(constraint, value)
|
23
|
+
return unless constraint[:min_length] && value.length < constraint[:min_length]
|
24
|
+
|
25
|
+
raise RubyRoutes::ConstraintViolation, "Value too short (minimum #{constraint[:min_length]} characters)"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Check maximum length constraint.
|
29
|
+
#
|
30
|
+
# This method validates that the value does not exceed the maximum length
|
31
|
+
# specified in the constraint. If the value is longer than the maximum length,
|
32
|
+
# a `ConstraintViolation` is raised.
|
33
|
+
#
|
34
|
+
# @param constraint [Hash] The constraint hash containing `:max_length`.
|
35
|
+
# @param value [String] The value to validate.
|
36
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value exceeds the maximum length.
|
37
|
+
# @return [void]
|
38
|
+
def check_max_length(constraint, value)
|
39
|
+
return unless constraint[:max_length] && value.length > constraint[:max_length]
|
40
|
+
|
41
|
+
raise RubyRoutes::ConstraintViolation, "Value too long (maximum #{constraint[:max_length]} characters)"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Check format constraint.
|
45
|
+
#
|
46
|
+
# This method validates that the value matches the format specified in the
|
47
|
+
# constraint. If the value does not match the format, a `ConstraintViolation`
|
48
|
+
# is raised.
|
49
|
+
#
|
50
|
+
# @param constraint [Hash] The constraint hash containing `:format` (a Regexp).
|
51
|
+
# @param value [String] The value to validate.
|
52
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value does not match the required format.
|
53
|
+
# @return [void]
|
54
|
+
def check_format(constraint, value)
|
55
|
+
return unless constraint[:format] && !value.match?(constraint[:format])
|
56
|
+
|
57
|
+
raise RubyRoutes::ConstraintViolation, 'Value does not match required format'
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check in list constraint.
|
61
|
+
#
|
62
|
+
# This method validates that the value is included in the list specified
|
63
|
+
# in the constraint. If the value is not in the list, a `ConstraintViolation`
|
64
|
+
# is raised.
|
65
|
+
#
|
66
|
+
# @param constraint [Hash] The constraint hash containing `:in` (an Array).
|
67
|
+
# @param value [String] The value to validate.
|
68
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is not in the allowed list.
|
69
|
+
# @return [void]
|
70
|
+
def check_in_list(constraint, value)
|
71
|
+
return unless constraint[:in] && !constraint[:in].include?(value)
|
72
|
+
|
73
|
+
raise RubyRoutes::ConstraintViolation, 'Value not in allowed list'
|
74
|
+
end
|
75
|
+
|
76
|
+
# Check not in list constraint.
|
77
|
+
#
|
78
|
+
# This method validates that the value is not included in the list specified
|
79
|
+
# in the constraint. If the value is in the forbidden list, a `ConstraintViolation`
|
80
|
+
# is raised.
|
81
|
+
#
|
82
|
+
# @param constraint [Hash] The constraint hash containing `:not_in` (an Array).
|
83
|
+
# @param value [String] The value to validate.
|
84
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is in the forbidden list.
|
85
|
+
# @return [void]
|
86
|
+
def check_not_in_list(constraint, value)
|
87
|
+
return unless constraint[:not_in]&.include?(value)
|
88
|
+
|
89
|
+
raise RubyRoutes::ConstraintViolation, 'Value in forbidden list'
|
90
|
+
end
|
91
|
+
|
92
|
+
# Check range constraint.
|
93
|
+
#
|
94
|
+
# This method validates that the value falls within the range specified
|
95
|
+
# in the constraint. If the value is outside the range, a `ConstraintViolation`
|
96
|
+
# is raised.
|
97
|
+
#
|
98
|
+
# @param constraint [Hash] The constraint hash containing `:range` (a Range).
|
99
|
+
# @param value [String] The value to validate.
|
100
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is not in the allowed range.
|
101
|
+
# @return [void]
|
102
|
+
def check_range(constraint, value)
|
103
|
+
return unless constraint[:range] && !constraint[:range].cover?(value.to_i)
|
104
|
+
|
105
|
+
raise RubyRoutes::ConstraintViolation, 'Value not in allowed range'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../constant'
|
4
|
+
|
5
|
+
module RubyRoutes
|
6
|
+
class Route
|
7
|
+
# ConstraintValidator: extracted constraint logic.
|
8
|
+
#
|
9
|
+
# This module provides methods for validating route constraints, including
|
10
|
+
# support for regular expressions, procs, hash-based constraints, and built-in
|
11
|
+
# validation rules. It also handles timeouts and raises appropriate exceptions
|
12
|
+
# for constraint violations.
|
13
|
+
module ConstraintValidator
|
14
|
+
# Validate all constraints for the given parameters.
|
15
|
+
#
|
16
|
+
# This method iterates through all constraints and validates each parameter
|
17
|
+
# against its corresponding rule.
|
18
|
+
#
|
19
|
+
# @param params [Hash] The parameters to validate.
|
20
|
+
# @return [void]
|
21
|
+
def validate_constraints_fast!(params)
|
22
|
+
@constraints.each do |key, rule|
|
23
|
+
param_key = key.to_s
|
24
|
+
next unless params.key?(param_key)
|
25
|
+
|
26
|
+
validate_constraint_for(rule, key, params[param_key])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Dispatch a single constraint check.
|
31
|
+
#
|
32
|
+
# This method validates a single parameter against its constraint rule.
|
33
|
+
#
|
34
|
+
# @param rule [Object] The constraint rule (Regexp, Proc, Symbol, Hash).
|
35
|
+
# @param key [String, Symbol] The parameter key.
|
36
|
+
# @param value [Object] The value to validate.
|
37
|
+
# @return [void]
|
38
|
+
def validate_constraint_for(rule, key, value)
|
39
|
+
case rule
|
40
|
+
when Regexp then validate_regexp_constraint(rule, value)
|
41
|
+
when Proc then validate_proc_constraint(key, rule, value)
|
42
|
+
when Hash then validate_hash_constraint!(rule, value.to_s)
|
43
|
+
else
|
44
|
+
validate_builtin_constraint(rule, value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Handle built-in symbol/string rules via a simple lookup.
|
49
|
+
#
|
50
|
+
# @param rule [Symbol, String] The built-in constraint rule.
|
51
|
+
# @param value [Object] The value to validate.
|
52
|
+
# @return [void]
|
53
|
+
def validate_builtin_constraint(rule, value)
|
54
|
+
method_sym = RubyRoutes::Constant::BUILTIN_VALIDATORS[rule.to_sym] if rule
|
55
|
+
send(method_sym, value) if method_sym
|
56
|
+
end
|
57
|
+
|
58
|
+
# Validate a regexp constraint.
|
59
|
+
#
|
60
|
+
# This method validates a value against a regular expression constraint.
|
61
|
+
# It raises a timeout error if the validation takes too long.
|
62
|
+
#
|
63
|
+
# @param regexp [Regexp] The regular expression to match.
|
64
|
+
# @param value [Object] The value to validate.
|
65
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value does not match the regexp.
|
66
|
+
def validate_regexp_constraint(regexp, value)
|
67
|
+
Timeout.timeout(0.1) { invalid! unless regexp.match?(value.to_s) }
|
68
|
+
rescue Timeout::Error
|
69
|
+
raise RubyRoutes::ConstraintViolation, 'Regex constraint timed out'
|
70
|
+
end
|
71
|
+
|
72
|
+
# Validate a proc constraint.
|
73
|
+
#
|
74
|
+
# This method validates a value using a proc constraint. It emits a
|
75
|
+
# deprecation warning for proc constraints and handles timeouts and errors.
|
76
|
+
#
|
77
|
+
# @param key [String, Symbol] The parameter key.
|
78
|
+
# @param proc [Proc] The proc to call.
|
79
|
+
# @param value [Object] The value to validate.
|
80
|
+
# @raise [RubyRoutes::ConstraintViolation] If the proc constraint fails or times out.
|
81
|
+
def validate_proc_constraint(key, proc, value)
|
82
|
+
warn_proc_constraint_deprecation(key)
|
83
|
+
Timeout.timeout(0.05) { invalid! unless proc.call(value.to_s) }
|
84
|
+
rescue Timeout::Error
|
85
|
+
raise RubyRoutes::ConstraintViolation, 'Proc constraint timed out'
|
86
|
+
rescue StandardError => e
|
87
|
+
raise RubyRoutes::ConstraintViolation, "Proc constraint failed: #{e.message}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Validate an integer constraint.
|
91
|
+
#
|
92
|
+
# @param value [Object] The value to validate.
|
93
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is not an integer.
|
94
|
+
def validate_int_constraint(value)
|
95
|
+
invalid! unless value.to_s.match?(/\A\d+\z/)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Validate a UUID constraint.
|
99
|
+
#
|
100
|
+
# @param value [Object] The value to validate.
|
101
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is not a valid UUID.
|
102
|
+
def validate_uuid_constraint(value)
|
103
|
+
validate_uuid!(value)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Validate an email constraint.
|
107
|
+
#
|
108
|
+
# @param value [Object] The value to validate.
|
109
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is not a valid email.
|
110
|
+
def validate_email_constraint(value)
|
111
|
+
invalid! unless value.to_s.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Validate a slug constraint.
|
115
|
+
#
|
116
|
+
# @param value [Object] The value to validate.
|
117
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is not a valid slug.
|
118
|
+
def validate_slug_constraint(value)
|
119
|
+
invalid! unless value.to_s.match?(/\A[a-z0-9]+(?:-[a-z0-9]+)*\z/)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Validate an alpha constraint.
|
123
|
+
#
|
124
|
+
# @param value [Object] The value to validate.
|
125
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is not alphabetic.
|
126
|
+
def validate_alpha_constraint(value)
|
127
|
+
invalid! unless value.to_s.match?(/\A[a-zA-Z]+\z/)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Validate an alphanumeric constraint.
|
131
|
+
#
|
132
|
+
# @param value [Object] The value to validate.
|
133
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is not alphanumeric.
|
134
|
+
def validate_alphanumeric_constraint(value)
|
135
|
+
invalid! unless value.to_s.match?(/\A[a-zA-Z0-9]+\z/)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Validate a UUID.
|
139
|
+
#
|
140
|
+
# This method validates that a value is a properly formatted UUID.
|
141
|
+
#
|
142
|
+
# @param value [Object] The value to validate.
|
143
|
+
# @raise [RubyRoutes::ConstraintViolation] If the value is not a valid UUID.
|
144
|
+
def validate_uuid!(value)
|
145
|
+
string = value.to_s
|
146
|
+
unless string.length == 36 && string.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)
|
147
|
+
invalid!
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Raise a constraint violation.
|
152
|
+
#
|
153
|
+
# @raise [RubyRoutes::ConstraintViolation] Always raises this exception.
|
154
|
+
def invalid!
|
155
|
+
raise RubyRoutes::ConstraintViolation
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|