domainic-type 0.1.0.alpha.3.0.2 → 0.1.0.alpha.3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|