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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/lib/domainic/type/behavior/string_behavior/matching_behavior.rb +115 -0
  3. data/lib/domainic/type/behavior/string_behavior.rb +2 -111
  4. data/lib/domainic/type/behavior/uri_behavior.rb +97 -0
  5. data/lib/domainic/type/behavior.rb +21 -1
  6. data/lib/domainic/type/config/registry.yml +15 -0
  7. data/lib/domainic/type/definitions.rb +182 -1
  8. data/lib/domainic/type/types/core/array_type.rb +1 -1
  9. data/lib/domainic/type/types/core/float_type.rb +1 -1
  10. data/lib/domainic/type/types/core/hash_type.rb +1 -1
  11. data/lib/domainic/type/types/core/integer_type.rb +1 -1
  12. data/lib/domainic/type/types/core/string_type.rb +1 -1
  13. data/lib/domainic/type/types/core/symbol_type.rb +1 -1
  14. data/lib/domainic/type/types/identifier/cuid_type.rb +140 -0
  15. data/lib/domainic/type/types/identifier/uuid_type.rb +513 -0
  16. data/lib/domainic/type/types/network/email_address_type.rb +149 -0
  17. data/lib/domainic/type/types/network/hostname_type.rb +107 -0
  18. data/lib/domainic/type/types/network/uri_type.rb +224 -0
  19. data/sig/domainic/type/behavior/string_behavior/matching_behavior.rbs +84 -0
  20. data/sig/domainic/type/behavior/string_behavior.rbs +2 -82
  21. data/sig/domainic/type/behavior/uri_behavior.rbs +95 -0
  22. data/sig/domainic/type/behavior.rbs +9 -0
  23. data/sig/domainic/type/definitions.rbs +149 -1
  24. data/sig/domainic/type/types/identifier/cuid_type.rbs +110 -0
  25. data/sig/domainic/type/types/identifier/uuid_type.rbs +469 -0
  26. data/sig/domainic/type/types/network/email_address_type.rbs +127 -0
  27. data/sig/domainic/type/types/network/hostname_type.rbs +76 -0
  28. data/sig/domainic/type/types/network/uri_type.rbs +159 -0
  29. 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