domainic-type 0.1.0.alpha.3.0.2 → 0.1.0.alpha.3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/domainic/type/behavior/string_behavior/matching_behavior.rb +115 -0
- data/lib/domainic/type/behavior/string_behavior.rb +2 -111
- data/lib/domainic/type/behavior/uri_behavior.rb +97 -0
- data/lib/domainic/type/behavior.rb +21 -1
- data/lib/domainic/type/config/registry.yml +15 -0
- data/lib/domainic/type/definitions.rb +182 -1
- data/lib/domainic/type/types/core/array_type.rb +1 -1
- data/lib/domainic/type/types/core/float_type.rb +1 -1
- data/lib/domainic/type/types/core/hash_type.rb +1 -1
- data/lib/domainic/type/types/core/integer_type.rb +1 -1
- data/lib/domainic/type/types/core/string_type.rb +1 -1
- data/lib/domainic/type/types/core/symbol_type.rb +1 -1
- data/lib/domainic/type/types/identifier/cuid_type.rb +140 -0
- data/lib/domainic/type/types/identifier/uuid_type.rb +513 -0
- data/lib/domainic/type/types/network/email_address_type.rb +149 -0
- data/lib/domainic/type/types/network/hostname_type.rb +107 -0
- data/lib/domainic/type/types/network/uri_type.rb +224 -0
- data/sig/domainic/type/behavior/string_behavior/matching_behavior.rbs +84 -0
- data/sig/domainic/type/behavior/string_behavior.rbs +2 -82
- data/sig/domainic/type/behavior/uri_behavior.rbs +95 -0
- data/sig/domainic/type/behavior.rbs +9 -0
- data/sig/domainic/type/definitions.rbs +149 -1
- data/sig/domainic/type/types/identifier/cuid_type.rbs +110 -0
- data/sig/domainic/type/types/identifier/uuid_type.rbs +469 -0
- data/sig/domainic/type/types/network/email_address_type.rbs +127 -0
- data/sig/domainic/type/types/network/hostname_type.rbs +76 -0
- data/sig/domainic/type/types/network/uri_type.rbs +159 -0
- metadata +20 -6
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/type/behavior'
|
4
|
+
require 'domainic/type/behavior/string_behavior/matching_behavior'
|
5
|
+
require 'domainic/type/behavior/sizable_behavior'
|
6
|
+
require 'domainic/type/behavior/uri_behavior'
|
7
|
+
|
8
|
+
module Domainic
|
9
|
+
module Type
|
10
|
+
# A type for validating hostnames according to RFC 1034 and RFC 1123 standards
|
11
|
+
#
|
12
|
+
# This type provides comprehensive hostname validation, ensuring that values conform to
|
13
|
+
# DNS standards for hostnames. It supports constraints on the domain structure, such as
|
14
|
+
# top-level domain validation and hostname pattern matching.
|
15
|
+
#
|
16
|
+
# Key features:
|
17
|
+
# - RFC-compliant hostname validation
|
18
|
+
# - Maximum length enforcement (253 characters)
|
19
|
+
# - ASCII character set requirement
|
20
|
+
# - Validation of allowed or disallowed hostnames and TLDs
|
21
|
+
#
|
22
|
+
# @example Basic usage
|
23
|
+
# type = HostNameType.new
|
24
|
+
# type.validate("example.com") # => true
|
25
|
+
# type.validate("invalid_host") # => false
|
26
|
+
#
|
27
|
+
# @example With domain constraints
|
28
|
+
# type = HostNameType.new
|
29
|
+
# .having_top_level_domain("com", "org")
|
30
|
+
#
|
31
|
+
# @example With hostname inclusion/exclusion
|
32
|
+
# type = HostNameType.new
|
33
|
+
# .having_hostname("example.com")
|
34
|
+
# .not_having_hostname("forbidden.com")
|
35
|
+
#
|
36
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
37
|
+
# @since 0.1.0
|
38
|
+
class HostnameType
|
39
|
+
# @rbs! extend Behavior::ClassMethods
|
40
|
+
|
41
|
+
include Behavior
|
42
|
+
include Behavior::StringBehavior::MatchingBehavior
|
43
|
+
include Behavior::SizableBehavior
|
44
|
+
include Behavior::URIBehavior
|
45
|
+
|
46
|
+
RFC_HOSTNAME_REGEXP = /\A
|
47
|
+
[a-zA-Z0-9] # Start with an alphanumeric character
|
48
|
+
(?: # Begin optional group for the rest of the segment
|
49
|
+
[a-zA-Z0-9-]{0,61} # Allow up to 61 characters (letters, digits, hyphens)
|
50
|
+
[a-zA-Z0-9] # End segment with an alphanumeric character
|
51
|
+
)? # The group is optional
|
52
|
+
(?: # Begin optional group for additional domain segments
|
53
|
+
\. # Segment must start with a dot
|
54
|
+
[a-zA-Z0-9] # Alphanumeric character at the start of the new segment
|
55
|
+
(?: # Optional group for segment body
|
56
|
+
[a-zA-Z0-9-]{0,61} # Allow up to 61 characters (letters, digits, hyphens)
|
57
|
+
[a-zA-Z0-9] # End segment with an alphanumeric character
|
58
|
+
)? # End optional segment body
|
59
|
+
)* # Allow zero or more additional segments
|
60
|
+
\z/x #: Regexp
|
61
|
+
|
62
|
+
# Core hostname constraints based on RFC standards
|
63
|
+
intrinsically_constrain :self, :type, String, description: :not_described
|
64
|
+
intrinsically_constrain :self, :match_pattern, RFC_HOSTNAME_REGEXP, description: :not_described
|
65
|
+
intrinsically_constrain :length, :range, { maximum: 253 }, description: :not_described, concerning: :size
|
66
|
+
intrinsically_constrain :self, :character_set, :ascii, description: :not_described
|
67
|
+
|
68
|
+
# Constrain hostname to allowed hostnames
|
69
|
+
#
|
70
|
+
# Creates a constraint ensuring the hostname matches one of the specified hostnames.
|
71
|
+
# This is useful for restricting to specific domains.
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# type.having_hostname("example.com", "company.com")
|
75
|
+
# type.validate("example.com") # => true
|
76
|
+
# type.validate("other.com") # => false
|
77
|
+
#
|
78
|
+
# @param hostnames [Array<String>] List of allowed hostnames
|
79
|
+
# @return [self] self for method chaining
|
80
|
+
# @rbs (*String hostnames) -> self
|
81
|
+
def matching(*hostnames)
|
82
|
+
pattern = /\A(?:#{hostnames.map { |h| Regexp.escape(h) }.join('|')})\z/i
|
83
|
+
constrain :self, :match_pattern, pattern, concerning: :inclusion
|
84
|
+
end
|
85
|
+
alias allowing matching
|
86
|
+
|
87
|
+
# Constrain hostname to exclude specific hostnames
|
88
|
+
#
|
89
|
+
# Ensures the hostname does not match any of the specified hostnames. Useful for blacklisting.
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# type.not_having_hostname("forbidden.com")
|
93
|
+
# type.validate("allowed.com") # => true
|
94
|
+
# type.validate("forbidden.com") # => false
|
95
|
+
#
|
96
|
+
# @param hostnames [Array<String>] List of forbidden hostnames
|
97
|
+
# @return [self] self for method chaining
|
98
|
+
# @rbs (*String hostnames) -> self
|
99
|
+
def not_matching(*hostnames)
|
100
|
+
pattern = /\A(?:#{hostnames.map { |h| Regexp.escape(h) }.join('|')})\z/i
|
101
|
+
hostname_pattern = @constraints.prepare :self, :match_pattern, pattern
|
102
|
+
constrain :self, :not, hostname_pattern, concerning: :hostname_exclusion
|
103
|
+
end
|
104
|
+
alias forbidding not_matching
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/type/behavior'
|
4
|
+
require 'domainic/type/behavior/string_behavior/matching_behavior'
|
5
|
+
require 'domainic/type/behavior/sizable_behavior'
|
6
|
+
require 'domainic/type/behavior/uri_behavior'
|
7
|
+
require 'domainic/type/types/network/hostname_type'
|
8
|
+
require 'uri'
|
9
|
+
|
10
|
+
module Domainic
|
11
|
+
module Type
|
12
|
+
# A type for validating URIs according to RFC 3986 standards
|
13
|
+
#
|
14
|
+
# This type provides comprehensive URI validation, ensuring that values conform to
|
15
|
+
# standard URI syntax. It supports constraints on components like the scheme, hostname,
|
16
|
+
# path, and query string.
|
17
|
+
#
|
18
|
+
# Key features:
|
19
|
+
# - RFC-compliant URI validation
|
20
|
+
# - Scheme, hostname, path, and query validation
|
21
|
+
# - Maximum length enforcement
|
22
|
+
# - ASCII character set requirement
|
23
|
+
#
|
24
|
+
# @example Basic usage
|
25
|
+
# type = UriType.new
|
26
|
+
# type.validate("https://example.com/path?query=1") # => true
|
27
|
+
# type.validate("not-a-uri") # => false
|
28
|
+
#
|
29
|
+
# @example With scheme constraints
|
30
|
+
# type = UriType.new
|
31
|
+
# .having_scheme("http", "https")
|
32
|
+
#
|
33
|
+
# @example With hostname and path constraints
|
34
|
+
# type = UriType.new
|
35
|
+
# .having_hostname("example.com")
|
36
|
+
# .matching_path(/^\/[a-z]+$/)
|
37
|
+
#
|
38
|
+
# @example With maximum length
|
39
|
+
# type = UriType.new
|
40
|
+
# .having_maximum_size(2000)
|
41
|
+
#
|
42
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
43
|
+
# @since 0.1.0
|
44
|
+
class URIType
|
45
|
+
# @rbs! extend Behavior::ClassMethods
|
46
|
+
|
47
|
+
include Behavior
|
48
|
+
include Behavior::StringBehavior::MatchingBehavior
|
49
|
+
include Behavior::SizableBehavior
|
50
|
+
include Behavior::URIBehavior
|
51
|
+
|
52
|
+
URI_REGEXP = /\A#{URI::DEFAULT_PARSER.make_regexp}\z/ #: Regexp
|
53
|
+
|
54
|
+
# Core URI constraints based on RFC standards
|
55
|
+
intrinsically_constrain :self, :type, String, description: :not_described
|
56
|
+
intrinsically_constrain :self, :match_pattern, URI_REGEXP, description: :not_described
|
57
|
+
intrinsically_constrain :self, :character_set, :ascii, description: :not_described
|
58
|
+
|
59
|
+
# Constrain URI hostname to allowed values
|
60
|
+
#
|
61
|
+
# Uses `HostnameType` to validate the hostname part of the URI.
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# type.having_hostname("example.com")
|
65
|
+
# type.validate("https://example.com") # => true
|
66
|
+
# type.validate("https://other.com") # => false
|
67
|
+
#
|
68
|
+
# @param hostnames [Array<String>] List of allowed hostnames
|
69
|
+
# @return [self] self for method chaining
|
70
|
+
# @rbs (*String hostnames) -> self
|
71
|
+
def having_hostname(*hostnames)
|
72
|
+
hostname_type = HostnameType.new(matching: hostnames)
|
73
|
+
constrain :self, :type, hostname_type,
|
74
|
+
coerce_with: lambda { |value|
|
75
|
+
begin
|
76
|
+
URI.parse(value).host
|
77
|
+
rescue StandardError
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
}, concerning: :hostname_inclusion
|
81
|
+
end
|
82
|
+
alias allowing_hostname having_hostname
|
83
|
+
alias host having_hostname
|
84
|
+
alias hostname having_hostname
|
85
|
+
alias with_hostname having_hostname
|
86
|
+
|
87
|
+
# Constrain URI path to match any of the specified patterns
|
88
|
+
#
|
89
|
+
# Ensures that the path part of the URI matches at least one of the given patterns.
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# type.having_path(/^\/[a-z]+$/, /^\/api\/v\d+/)
|
93
|
+
# type.validate("https://example.com/path") # => true
|
94
|
+
# type.validate("https://example.com/api/v1") # => true
|
95
|
+
# type.validate("https://example.com/123") # => false
|
96
|
+
#
|
97
|
+
# @param patterns [Array<Regexp>] Patterns the path must match
|
98
|
+
# @return [self] self for method chaining
|
99
|
+
# @rbs (*String | Regexp patterns) -> self
|
100
|
+
def having_path(*patterns)
|
101
|
+
patterns = patterns.map { |p| p.is_a?(String) ? Regexp.new(p) : p }
|
102
|
+
combined_pattern = Regexp.union(patterns)
|
103
|
+
constrain :self, :match_pattern, combined_pattern,
|
104
|
+
coerce_with: lambda { |value|
|
105
|
+
begin
|
106
|
+
URI.parse(value).path
|
107
|
+
rescue StandardError
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
}, concerning: :path_inclusion
|
111
|
+
end
|
112
|
+
alias allowing_path having_path
|
113
|
+
alias with_path having_path
|
114
|
+
|
115
|
+
# Constrain URI scheme to specific values
|
116
|
+
#
|
117
|
+
# Ensures that the scheme part of the URI matches one of the specified schemes.
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
# type.having_scheme("http", "https")
|
121
|
+
# type.validate("https://example.com") # => true
|
122
|
+
# type.validate("ftp://example.com") # => false
|
123
|
+
#
|
124
|
+
# @param schemes [Array<String>] List of allowed schemes
|
125
|
+
# @return [self] self for method chaining
|
126
|
+
# @rbs (*String schemes) -> self
|
127
|
+
def having_scheme(*schemes)
|
128
|
+
included_schemes = schemes.map { |scheme| @constraints.prepare :self, :inclusion, scheme.downcase }
|
129
|
+
constrain :self, :or, included_schemes,
|
130
|
+
coerce_with: lambda { |value|
|
131
|
+
begin
|
132
|
+
URI.parse(value).scheme&.downcase
|
133
|
+
rescue URI::InvalidURIError
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
}, concerning: :scheme_inclusion
|
137
|
+
end
|
138
|
+
alias allowing_scheme having_scheme
|
139
|
+
alias scheme having_scheme
|
140
|
+
|
141
|
+
# Constrain URI hostname to exclude specific values
|
142
|
+
#
|
143
|
+
# Uses `HostnameType` to blacklist hostnames.
|
144
|
+
#
|
145
|
+
# @example
|
146
|
+
# type.not_having_hostname("forbidden.com")
|
147
|
+
# type.validate("https://allowed.com") # => true
|
148
|
+
# type.validate("https://forbidden.com") # => false
|
149
|
+
#
|
150
|
+
# @param hostnames [Array<String>] List of forbidden hostnames
|
151
|
+
# @return [self] self for method chaining
|
152
|
+
# @rbs (*String hostnames) -> self
|
153
|
+
def not_having_hostname(*hostnames)
|
154
|
+
hostname_type = HostnameType.new(not_matching: hostnames)
|
155
|
+
constrain :self, :type, hostname_type,
|
156
|
+
coerce_with: lambda { |value|
|
157
|
+
begin
|
158
|
+
URI.parse(value).host
|
159
|
+
rescue StandardError
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
}, concerning: :hostname_exclusion
|
163
|
+
end
|
164
|
+
alias forbidding_hostname not_having_hostname
|
165
|
+
alias not_host not_having_hostname
|
166
|
+
alias not_hostname not_having_hostname
|
167
|
+
|
168
|
+
# Constrain URI path to exclude any of the specified patterns
|
169
|
+
#
|
170
|
+
# Ensures that the path part of the URI does not match any of the given patterns.
|
171
|
+
#
|
172
|
+
# @example
|
173
|
+
# type.not_having_path(/^\/admin/, /^\/private/)
|
174
|
+
# type.validate("https://example.com/user") # => true
|
175
|
+
# type.validate("https://example.com/admin") # => false
|
176
|
+
# type.validate("https://example.com/private") # => false
|
177
|
+
#
|
178
|
+
# @param patterns [Array<Regexp>] Patterns the path must not match
|
179
|
+
# @return [self] self for method chaining
|
180
|
+
# @rbs (*String | Regexp patterns) -> self
|
181
|
+
def not_having_path(*patterns)
|
182
|
+
patterns = patterns.map { |p| p.is_a?(String) ? Regexp.new(p) : p }
|
183
|
+
combined_pattern = Regexp.union(patterns)
|
184
|
+
path_pattern = @constraints.prepare :self, :match_pattern, combined_pattern,
|
185
|
+
coerce_with: lambda { |value|
|
186
|
+
begin
|
187
|
+
URI.parse(value).path
|
188
|
+
rescue StandardError
|
189
|
+
nil
|
190
|
+
end
|
191
|
+
}
|
192
|
+
constrain :self, :not, path_pattern, concerning: :path_exclusion
|
193
|
+
end
|
194
|
+
alias forbidding_path not_having_path
|
195
|
+
alias not_allowing_path not_having_path
|
196
|
+
|
197
|
+
# Constrain URI scheme to exclude specific values
|
198
|
+
#
|
199
|
+
# Ensures that the scheme part of the URI does not match the specified schemes.
|
200
|
+
#
|
201
|
+
# @example
|
202
|
+
# type.not_having_scheme("ftp")
|
203
|
+
# type.validate("https://example.com") # => true
|
204
|
+
# type.validate("ftp://example.com") # => false
|
205
|
+
#
|
206
|
+
# @param schemes [Array<String>] List of forbidden schemes
|
207
|
+
# @return [self] self for method chaining
|
208
|
+
# @rbs (*String schemes) -> self
|
209
|
+
def not_having_scheme(*schemes)
|
210
|
+
included_schemes = schemes.map { |scheme| @constraints.prepare :self, :inclusion, scheme.downcase }
|
211
|
+
constrain :self, :nor, included_schemes,
|
212
|
+
coerce_with: lambda { |value|
|
213
|
+
begin
|
214
|
+
URI.parse(value).scheme&.downcase
|
215
|
+
rescue URI::InvalidURIError
|
216
|
+
nil
|
217
|
+
end
|
218
|
+
}, concerning: :scheme_inclusion
|
219
|
+
end
|
220
|
+
alias forbidding_scheme not_having_scheme
|
221
|
+
alias not_allowing_scheme not_having_scheme
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Domainic
|
2
|
+
module Type
|
3
|
+
module Behavior
|
4
|
+
module StringBehavior
|
5
|
+
# A module providing string matching constraint methods
|
6
|
+
#
|
7
|
+
# This module extends types with methods for constraining string values based on
|
8
|
+
# equality, pattern matching, and substring inclusion/exclusion. It provides a
|
9
|
+
# fluent interface for building complex string matching constraints.
|
10
|
+
#
|
11
|
+
# @example Basic equality constraints
|
12
|
+
# type = StringType.new
|
13
|
+
# .being_equal_to("expected")
|
14
|
+
# .not_being_equal_to("forbidden")
|
15
|
+
#
|
16
|
+
# @example Pattern matching constraints
|
17
|
+
# type = StringType.new
|
18
|
+
# .matching(/^\w+$/, /[0-9]/)
|
19
|
+
# .not_matching(/admin/i)
|
20
|
+
#
|
21
|
+
# @example Substring constraints
|
22
|
+
# type = StringType.new
|
23
|
+
# .containing("allowed", "required")
|
24
|
+
# .excluding("forbidden", "blocked")
|
25
|
+
#
|
26
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
27
|
+
# @since 0.1.0
|
28
|
+
module MatchingBehavior
|
29
|
+
# Constrain string to equal a specific value
|
30
|
+
#
|
31
|
+
# @param literal [String, Symbol] the value to match against
|
32
|
+
# @return [Behavior] self for method chaining
|
33
|
+
def being_equal_to: (String | Symbol literal) -> Behavior
|
34
|
+
|
35
|
+
alias eql being_equal_to
|
36
|
+
|
37
|
+
alias equal_to being_equal_to
|
38
|
+
|
39
|
+
alias equaling being_equal_to
|
40
|
+
|
41
|
+
# Constrain string to contain specific substrings
|
42
|
+
#
|
43
|
+
# @param literals [Array<String, Symbol>] the required substrings
|
44
|
+
# @return [Behavior] self for method chaining
|
45
|
+
def containing: (*String | Symbol literals) -> Behavior
|
46
|
+
|
47
|
+
alias including containing
|
48
|
+
|
49
|
+
# Constrain string to exclude specific substrings
|
50
|
+
#
|
51
|
+
# @param literals [Array<String, Symbol>] the forbidden substrings
|
52
|
+
# @return [Behavior] self for method chaining
|
53
|
+
def excluding: (*String | Symbol literals) -> Behavior
|
54
|
+
|
55
|
+
alias omitting excluding
|
56
|
+
|
57
|
+
# Constrain string to match specific patterns
|
58
|
+
#
|
59
|
+
# @param patterns [Array<String, Regexp>] the required patterns
|
60
|
+
# @return [Behavior] self for method chaining
|
61
|
+
def matching: (*String | Regexp patterns) -> Behavior
|
62
|
+
|
63
|
+
# Constrain string to not equal a specific value
|
64
|
+
#
|
65
|
+
# @param literal [String, Symbol] the forbidden value
|
66
|
+
# @return [Behavior] self for method chaining
|
67
|
+
def not_being_equal_to: (String | Symbol literal) -> Behavior
|
68
|
+
|
69
|
+
alias not_eql not_being_equal_to
|
70
|
+
|
71
|
+
alias not_equal_to not_being_equal_to
|
72
|
+
|
73
|
+
alias not_equaling not_being_equal_to
|
74
|
+
|
75
|
+
# Constrain string to not match specific patterns
|
76
|
+
#
|
77
|
+
# @param patterns [Array<String, Regexp>] the forbidden patterns
|
78
|
+
# @return [Behavior] self for method chaining
|
79
|
+
def not_matching: (*String | Regexp patterns) -> Behavior
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -50,6 +50,8 @@ module Domainic
|
|
50
50
|
# @author {https://aaronmallen.me Aaron Allen}
|
51
51
|
# @since 0.1.0
|
52
52
|
module StringBehavior
|
53
|
+
include MatchingBehavior
|
54
|
+
|
53
55
|
include SizableBehavior
|
54
56
|
|
55
57
|
# Validate string contains only alphanumeric characters.
|
@@ -90,23 +92,6 @@ module Domainic
|
|
90
92
|
|
91
93
|
alias empty being_empty
|
92
94
|
|
93
|
-
# Validate string equals a specific value.
|
94
|
-
#
|
95
|
-
# @example
|
96
|
-
# type.being_equal_to("hello")
|
97
|
-
# type.validate("hello") # => true
|
98
|
-
# type.validate("world") # => false
|
99
|
-
#
|
100
|
-
# @param literal [String, Symbol] the value to compare against
|
101
|
-
# @return [Behavior] self for method chaining
|
102
|
-
def being_equal_to: (String | Symbol literal) -> Behavior
|
103
|
-
|
104
|
-
alias eql being_equal_to
|
105
|
-
|
106
|
-
alias equal_to being_equal_to
|
107
|
-
|
108
|
-
alias equaling being_equal_to
|
109
|
-
|
110
95
|
# Validate string is lowercase.
|
111
96
|
#
|
112
97
|
# @example
|
@@ -235,43 +220,6 @@ module Domainic
|
|
235
220
|
|
236
221
|
alias uppercase being_uppercase
|
237
222
|
|
238
|
-
# Validate string contains all specified substrings.
|
239
|
-
#
|
240
|
-
# @example
|
241
|
-
# type.containing("hello", "world")
|
242
|
-
# type.validate("hello world") # => true
|
243
|
-
# type.validate("hello") # => false
|
244
|
-
#
|
245
|
-
# @param literals [Array<String, Symbol>] the substrings to look for
|
246
|
-
# @return [Behavior] self for method chaining
|
247
|
-
def containing: (*String | Symbol literals) -> Behavior
|
248
|
-
|
249
|
-
alias including containing
|
250
|
-
|
251
|
-
# Validate string does not contain any specified substrings.
|
252
|
-
#
|
253
|
-
# @example
|
254
|
-
# type.excluding("foo", "bar")
|
255
|
-
# type.validate("hello world") # => true
|
256
|
-
# type.validate("foo bar") # => false
|
257
|
-
#
|
258
|
-
# @param literals [Array<String, Symbol>] the substrings to exclude
|
259
|
-
# @return [Behavior] self for method chaining
|
260
|
-
def excluding: (*String | Symbol literals) -> Behavior
|
261
|
-
|
262
|
-
alias omitting excluding
|
263
|
-
|
264
|
-
# Validate string matches all specified patterns.
|
265
|
-
#
|
266
|
-
# @example
|
267
|
-
# type.matching(/^\w+$/, /\d/)
|
268
|
-
# type.validate("hello123") # => true
|
269
|
-
# type.validate("hello") # => false
|
270
|
-
#
|
271
|
-
# @param patterns [Array<String, Regexp>] the patterns to match against
|
272
|
-
# @return [Behavior] self for method chaining
|
273
|
-
def matching: (*String | Regexp patterns) -> Behavior
|
274
|
-
|
275
223
|
# Validate string is not empty.
|
276
224
|
#
|
277
225
|
# @example
|
@@ -281,34 +229,6 @@ module Domainic
|
|
281
229
|
#
|
282
230
|
# @return [Behavior] self for method chaining
|
283
231
|
def not_being_empty: () -> Behavior
|
284
|
-
|
285
|
-
# Validate string does not equal a specific value.
|
286
|
-
#
|
287
|
-
# @example
|
288
|
-
# type.not_being_equal_to("admin")
|
289
|
-
# type.validate("user") # => true
|
290
|
-
# type.validate("admin") # => false
|
291
|
-
#
|
292
|
-
# @param literal [String, Symbol] the value to compare against
|
293
|
-
# @return [Behavior] self for method chaining
|
294
|
-
def not_being_equal_to: (String | Symbol literal) -> Behavior
|
295
|
-
|
296
|
-
alias not_eql not_being_equal_to
|
297
|
-
|
298
|
-
alias not_equal_to not_being_equal_to
|
299
|
-
|
300
|
-
alias not_equaling not_being_equal_to
|
301
|
-
|
302
|
-
# Validate string does not match any specified patterns.
|
303
|
-
#
|
304
|
-
# @example
|
305
|
-
# type.not_matching(/\d/, /[A-Z]/)
|
306
|
-
# type.validate("hello") # => true
|
307
|
-
# type.validate("Hello123") # => false
|
308
|
-
#
|
309
|
-
# @param patterns [Array<String, Regexp>] the patterns to avoid matching
|
310
|
-
# @return [Behavior] self for method chaining
|
311
|
-
def not_matching: (*String | Regexp patterns) -> Behavior
|
312
232
|
end
|
313
233
|
end
|
314
234
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Domainic
|
2
|
+
module Type
|
3
|
+
module Behavior
|
4
|
+
# A module providing URI-based validation behaviors for types.
|
5
|
+
#
|
6
|
+
# This module extends the base Type::Behavior with methods designed for validating URI-related constraints.
|
7
|
+
# It focuses on ensuring URIs conform to specific rules, such as restricting or excluding particular
|
8
|
+
# top-level domains (TLDs). These features are useful for scenarios like domain-specific validations
|
9
|
+
# or blocking specific domain extensions.
|
10
|
+
#
|
11
|
+
# Key features:
|
12
|
+
# - Top-level domain inclusion constraints
|
13
|
+
# - Top-level domain exclusion constraints
|
14
|
+
# - Flexible and customizable pattern matching
|
15
|
+
# - Consistent interface for defining URI-related constraints
|
16
|
+
#
|
17
|
+
# @example Basic usage
|
18
|
+
# class MyType
|
19
|
+
# include Domainic::Type::Behavior::URIBehavior
|
20
|
+
#
|
21
|
+
# def initialize
|
22
|
+
# super
|
23
|
+
# having_top_level_domain("com", "org") # Allow only .com and .org
|
24
|
+
# not_having_top_level_domain("test", "dev") # Exclude .test and .dev
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# @example Method aliases
|
29
|
+
# type.having_top_level_domain("com") # Allow only .com
|
30
|
+
# type.tld("com") # Same as above
|
31
|
+
# type.with_tld("com") # Same as above
|
32
|
+
#
|
33
|
+
# type.not_having_top_level_domain("test") # Exclude .test
|
34
|
+
# type.not_tld("test") # Same as above
|
35
|
+
# type.not_top_level_domain("test") # Same as above
|
36
|
+
#
|
37
|
+
# This module provides a consistent interface for working with URIs, ensuring flexibility and
|
38
|
+
# extensibility for domain and TLD validation tasks.
|
39
|
+
#
|
40
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
41
|
+
# @since 0.1.0
|
42
|
+
module URIBehavior
|
43
|
+
# Constrain URIBehavior to allowed top-level domains
|
44
|
+
#
|
45
|
+
# Creates a constraint ensuring the URIBehavior uses one of the specified top-level
|
46
|
+
# domains (TLDs). This allows restricting emails to specific TLDs like .com or .org.
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# type.having_top_level_domain("com", "org")
|
50
|
+
# type.validate("example.com") # => true
|
51
|
+
# type.validate("example.net") # => false
|
52
|
+
#
|
53
|
+
# @param top_level_domains [Array<String>] List of allowed TLDs
|
54
|
+
# @return [self] self for method chaining
|
55
|
+
def having_top_level_domain: (*String top_level_domains) -> Behavior
|
56
|
+
|
57
|
+
alias allowing_tld having_top_level_domain
|
58
|
+
|
59
|
+
alias allowing_top_level_domain having_top_level_domain
|
60
|
+
|
61
|
+
alias having_tld having_top_level_domain
|
62
|
+
|
63
|
+
alias tld having_top_level_domain
|
64
|
+
|
65
|
+
alias with_tld having_top_level_domain
|
66
|
+
|
67
|
+
alias with_top_level_domain having_top_level_domain
|
68
|
+
|
69
|
+
# Constrain URIBehavior to exclude specific top-level domains
|
70
|
+
#
|
71
|
+
# Creates a constraint ensuring the email does not use any of the specified
|
72
|
+
# top-level domains (TLDs). Useful for blocking certain TLDs.
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# type.not_having_top_level_domain("test")
|
76
|
+
# type.validate("example.com") # => true
|
77
|
+
# type.validate("example.test") # => false
|
78
|
+
#
|
79
|
+
# @param top_level_domains [Array<String>] List of forbidden TLDs
|
80
|
+
# @return [self] self for method chaining
|
81
|
+
def not_having_top_level_domain: (*String top_level_domains) -> Behavior
|
82
|
+
|
83
|
+
alias forbidding_tld not_having_top_level_domain
|
84
|
+
|
85
|
+
alias forbidding_top_level_domain not_having_top_level_domain
|
86
|
+
|
87
|
+
alias not_having_tld not_having_top_level_domain
|
88
|
+
|
89
|
+
alias not_tld not_having_top_level_domain
|
90
|
+
|
91
|
+
alias not_top_level_domain not_having_top_level_domain
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -87,6 +87,8 @@ module Domainic
|
|
87
87
|
#
|
88
88
|
# @see Constraint::Set#add
|
89
89
|
#
|
90
|
+
# @deprecated Use {#intrinsically_constrain} instead
|
91
|
+
#
|
90
92
|
# @return [void]
|
91
93
|
def intrinsic: (Type::accessor accessor, String | Symbol constraint_type, ?untyped expectation, **untyped options) -> void
|
92
94
|
|
@@ -95,6 +97,13 @@ module Domainic
|
|
95
97
|
# @return [Constraint::Set] The constraint set
|
96
98
|
def intrinsic_constraints: () -> Constraint::Set
|
97
99
|
|
100
|
+
# Add an intrinsic constraint to this type.
|
101
|
+
#
|
102
|
+
# @see Constraint::Set#add
|
103
|
+
#
|
104
|
+
# @return [void]
|
105
|
+
def intrinsically_constrain: (Type::accessor accessor, String | Symbol constraint_type, ?untyped expectation, **untyped options) -> void
|
106
|
+
|
98
107
|
# Delegate unknown methods to a new instance.
|
99
108
|
#
|
100
109
|
# @return [Object] The result of calling the method on a new instance
|