domainic-type 0.1.0.alpha.2.1.0 → 0.1.0.alpha.3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/LICENSE +1 -1
  4. data/README.md +28 -4
  5. data/lib/domainic/type/accessors.rb +41 -0
  6. data/lib/domainic/type/behavior/enumerable_behavior.rb +262 -0
  7. data/lib/domainic/type/behavior/numeric_behavior.rb +340 -0
  8. data/lib/domainic/type/behavior/sizable_behavior.rb +246 -0
  9. data/lib/domainic/type/behavior/string_behavior.rb +379 -0
  10. data/lib/domainic/type/behavior.rb +239 -0
  11. data/lib/domainic/type/config/registry.yml +101 -0
  12. data/lib/domainic/type/constraint/behavior.rb +342 -0
  13. data/lib/domainic/type/constraint/constraints/all_constraint.rb +81 -0
  14. data/lib/domainic/type/constraint/constraints/and_constraint.rb +105 -0
  15. data/lib/domainic/type/constraint/constraints/any_constraint.rb +83 -0
  16. data/lib/domainic/type/constraint/constraints/case_constraint.rb +104 -0
  17. data/lib/domainic/type/constraint/constraints/character_set_constraint.rb +111 -0
  18. data/lib/domainic/type/constraint/constraints/divisibility_constraint.rb +126 -0
  19. data/lib/domainic/type/constraint/constraints/emptiness_constraint.rb +69 -0
  20. data/lib/domainic/type/constraint/constraints/equality_constraint.rb +75 -0
  21. data/lib/domainic/type/constraint/constraints/finiteness_constraint.rb +123 -0
  22. data/lib/domainic/type/constraint/constraints/inclusion_constraint.rb +74 -0
  23. data/lib/domainic/type/constraint/constraints/match_pattern_constraint.rb +87 -0
  24. data/lib/domainic/type/constraint/constraints/method_presence_constraint.rb +72 -0
  25. data/lib/domainic/type/constraint/constraints/none_constraint.rb +83 -0
  26. data/lib/domainic/type/constraint/constraints/nor_constraint.rb +105 -0
  27. data/lib/domainic/type/constraint/constraints/not_constraint.rb +76 -0
  28. data/lib/domainic/type/constraint/constraints/or_constraint.rb +106 -0
  29. data/lib/domainic/type/constraint/constraints/ordering_constraint.rb +75 -0
  30. data/lib/domainic/type/constraint/constraints/parity_constraint.rb +102 -0
  31. data/lib/domainic/type/constraint/constraints/polarity_constraint.rb +147 -0
  32. data/lib/domainic/type/constraint/constraints/range_constraint.rb +135 -0
  33. data/lib/domainic/type/constraint/constraints/type_constraint.rb +110 -0
  34. data/lib/domainic/type/constraint/constraints/uniqueness_constraint.rb +69 -0
  35. data/lib/domainic/type/constraint/resolver.rb +172 -0
  36. data/lib/domainic/type/constraint/set.rb +266 -0
  37. data/lib/domainic/type/definitions.rb +364 -0
  38. data/lib/domainic/type/types/core/array_type.rb +48 -0
  39. data/lib/domainic/type/types/core/float_type.rb +39 -0
  40. data/lib/domainic/type/types/core/hash_type.rb +143 -0
  41. data/lib/domainic/type/types/core/integer_type.rb +38 -0
  42. data/lib/domainic/type/types/core/string_type.rb +51 -0
  43. data/lib/domainic/type/types/core/symbol_type.rb +51 -0
  44. data/lib/domainic/type/types/specification/anything_type.rb +22 -0
  45. data/lib/domainic/type/types/specification/duck_type.rb +55 -0
  46. data/lib/domainic/type/types/specification/enum_type.rb +26 -0
  47. data/lib/domainic/type/types/specification/union_type.rb +26 -0
  48. data/lib/domainic/type/types/specification/void_type.rb +12 -0
  49. data/lib/domainic/type.rb +7 -0
  50. data/lib/domainic-type.rb +3 -0
  51. data/sig/domainic/type/accessors.rbs +22 -0
  52. data/sig/domainic/type/behavior/enumerable_behavior.rbs +238 -0
  53. data/sig/domainic/type/behavior/numeric_behavior.rbs +299 -0
  54. data/sig/domainic/type/behavior/sizable_behavior.rbs +218 -0
  55. data/sig/domainic/type/behavior/string_behavior.rbs +315 -0
  56. data/sig/domainic/type/behavior.rbs +153 -0
  57. data/sig/domainic/type/constraint/behavior.rbs +258 -0
  58. data/sig/domainic/type/constraint/constraints/all_constraint.rbs +55 -0
  59. data/sig/domainic/type/constraint/constraints/and_constraint.rbs +72 -0
  60. data/sig/domainic/type/constraint/constraints/any_constraint.rbs +57 -0
  61. data/sig/domainic/type/constraint/constraints/case_constraint.rbs +73 -0
  62. data/sig/domainic/type/constraint/constraints/character_set_constraint.rbs +82 -0
  63. data/sig/domainic/type/constraint/constraints/divisibility_constraint.rbs +91 -0
  64. data/sig/domainic/type/constraint/constraints/emptiness_constraint.rbs +54 -0
  65. data/sig/domainic/type/constraint/constraints/equality_constraint.rbs +60 -0
  66. data/sig/domainic/type/constraint/constraints/finiteness_constraint.rbs +82 -0
  67. data/sig/domainic/type/constraint/constraints/inclusion_constraint.rbs +59 -0
  68. data/sig/domainic/type/constraint/constraints/match_pattern_constraint.rbs +66 -0
  69. data/sig/domainic/type/constraint/constraints/method_presence_constraint.rbs +51 -0
  70. data/sig/domainic/type/constraint/constraints/none_constraint.rbs +57 -0
  71. data/sig/domainic/type/constraint/constraints/nor_constraint.rbs +72 -0
  72. data/sig/domainic/type/constraint/constraints/not_constraint.rbs +56 -0
  73. data/sig/domainic/type/constraint/constraints/or_constraint.rbs +74 -0
  74. data/sig/domainic/type/constraint/constraints/ordering_constraint.rbs +60 -0
  75. data/sig/domainic/type/constraint/constraints/parity_constraint.rbs +71 -0
  76. data/sig/domainic/type/constraint/constraints/polarity_constraint.rbs +101 -0
  77. data/sig/domainic/type/constraint/constraints/range_constraint.rbs +88 -0
  78. data/sig/domainic/type/constraint/constraints/type_constraint.rbs +86 -0
  79. data/sig/domainic/type/constraint/constraints/uniqueness_constraint.rbs +54 -0
  80. data/sig/domainic/type/constraint/resolver.rbs +117 -0
  81. data/sig/domainic/type/constraint/set.rbs +159 -0
  82. data/sig/domainic/type/definitions.rbs +304 -0
  83. data/sig/domainic/type/types/core/array_type.rbs +42 -0
  84. data/sig/domainic/type/types/core/float_type.rbs +33 -0
  85. data/sig/domainic/type/types/core/hash_type.rbs +107 -0
  86. data/sig/domainic/type/types/core/integer_type.rbs +32 -0
  87. data/sig/domainic/type/types/core/string_type.rbs +45 -0
  88. data/sig/domainic/type/types/core/symbol_type.rbs +45 -0
  89. data/sig/domainic/type/types/specification/anything_type.rbs +14 -0
  90. data/sig/domainic/type/types/specification/duck_type.rbs +41 -0
  91. data/sig/domainic/type/types/specification/enum_type.rbs +14 -0
  92. data/sig/domainic/type/types/specification/union_type.rbs +14 -0
  93. data/sig/domainic/type/types/specification/void_type.rbs +8 -0
  94. data/sig/domainic/type.rbs +5 -0
  95. data/sig/domainic-type.rbs +1 -0
  96. data/sig/manifest.yaml +2 -0
  97. metadata +108 -71
@@ -0,0 +1,379 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior/sizable_behavior'
4
+
5
+ module Domainic
6
+ module Type
7
+ module Behavior
8
+ # A module providing string-specific validation behaviors for types.
9
+ #
10
+ # This module extends the base Type::Behavior with methods specifically designed for
11
+ # validating string values. It provides a fluent interface for common string validations
12
+ # such as case checking, character set validation, equality, and pattern matching.
13
+ #
14
+ # Key capabilities:
15
+ # - Case validation (upper, lower, mixed, title case)
16
+ # - Character set checking (ASCII, alphanumeric, letters, numbers)
17
+ # - Pattern matching with regular expressions
18
+ # - Substring inclusion/exclusion
19
+ # - Character ordering checks
20
+ # - Length constraints (via SizableBehavior)
21
+ #
22
+ # @example Basic string validation
23
+ # class StringType
24
+ # include Domainic::Type::Behavior::StringBehavior
25
+ #
26
+ # def initialize
27
+ # super
28
+ # being_ascii # validates ASCII characters only
29
+ # being_alphanumeric # validates letters and numbers only
30
+ # having_minimum_size(5) # validates minimum length
31
+ # end
32
+ # end
33
+ #
34
+ # @example Complex string validation
35
+ # class UsernameType
36
+ # include Domainic::Type::Behavior::StringBehavior
37
+ #
38
+ # def initialize
39
+ # super
40
+ # being_lowercase
41
+ # being_alphanumeric
42
+ # having_size_between(3, 20)
43
+ # not_matching(/^admin/i) # prevent admin-like usernames
44
+ # end
45
+ # end
46
+ #
47
+ # @example Building constraints dynamically
48
+ # type = StringType.new
49
+ # .being_ascii
50
+ # .being_titlecase
51
+ # .having_size_between(5, 50)
52
+ # .matching(/^[A-Z]/)
53
+ #
54
+ # @author {https://aaronmallen.me Aaron Allen}
55
+ # @since 0.1.0
56
+ module StringBehavior
57
+ include SizableBehavior
58
+
59
+ # Validate string contains only alphanumeric characters.
60
+ #
61
+ # @example
62
+ # type.being_alphanumeric
63
+ # type.validate("abc123") # => true
64
+ # type.validate("abc-123") # => false
65
+ #
66
+ # @return [Behavior] self for method chaining
67
+ # @rbs () -> Behavior
68
+ def being_alphanumeric
69
+ # @type self: Object & Behavior
70
+ constrain :self, :character_set, :alphanumeric, description: 'being'
71
+ end
72
+ alias alphanumeric being_alphanumeric
73
+
74
+ # Validate string contains only ASCII characters.
75
+ #
76
+ # @example
77
+ # type.being_ascii
78
+ # type.validate("hello") # => true
79
+ # type.validate("héllo") # => false
80
+ #
81
+ # @return [Behavior] self for method chaining
82
+ # @rbs () -> Behavior
83
+ def being_ascii
84
+ # @type self: Object & Behavior
85
+ constrain :self, :character_set, :ascii, description: 'being'
86
+ end
87
+ alias ascii being_ascii
88
+ alias ascii_only being_ascii
89
+
90
+ # Validate string is empty.
91
+ #
92
+ # @example
93
+ # type.being_empty
94
+ # type.validate("") # => true
95
+ # type.validate("a") # => false
96
+ #
97
+ # @return [Behavior] self for method chaining
98
+ # @rbs () -> Behavior
99
+ def being_empty
100
+ # @type self: Object & Behavior
101
+ constrain :self, :emptiness, description: 'being'
102
+ end
103
+ alias empty being_empty
104
+
105
+ # Validate string equals a specific value.
106
+ #
107
+ # @example
108
+ # type.being_equal_to("hello")
109
+ # type.validate("hello") # => true
110
+ # type.validate("world") # => false
111
+ #
112
+ # @param literal [String, Symbol] the value to compare against
113
+ # @return [Behavior] self for method chaining
114
+ # @rbs (String | Symbol literal) -> Behavior
115
+ def being_equal_to(literal)
116
+ # @type self: Object & Behavior
117
+ constrain :self, :equality, literal, description: 'equaling'
118
+ end
119
+ alias eql being_equal_to
120
+ alias equal_to being_equal_to
121
+ alias equaling being_equal_to
122
+
123
+ # Validate string is lowercase.
124
+ #
125
+ # @example
126
+ # type.being_lowercase
127
+ # type.validate("hello") # => true
128
+ # type.validate("Hello") # => false
129
+ #
130
+ # @return [Behavior] self for method chaining
131
+ # @rbs () -> Behavior
132
+ def being_lowercase
133
+ # @type self: Object & Behavior
134
+ constrain :self, :case, :lower, description: 'being'
135
+ end
136
+ alias lowercase being_lowercase
137
+ alias not_being_uppercase being_lowercase
138
+
139
+ # Validate string contains both uppercase and lowercase characters.
140
+ #
141
+ # @example
142
+ # type.being_mixedcase
143
+ # type.validate("helloWORLD") # => true
144
+ # type.validate("hello") # => false
145
+ #
146
+ # @return [Behavior] self for method chaining
147
+ # @rbs () -> Behavior
148
+ def being_mixedcase
149
+ # @type self: Object & Behavior
150
+ constrain :self, :case, :mixed, description: 'being'
151
+ end
152
+ alias mixedcase being_mixedcase
153
+
154
+ # Validate string contains only letters.
155
+ #
156
+ # @example
157
+ # type.being_only_letters
158
+ # type.validate("hello") # => true
159
+ # type.validate("hello123") # => false
160
+ #
161
+ # @return [Behavior] self for method chaining
162
+ # @rbs () -> Behavior
163
+ def being_only_letters
164
+ # @type self: Object & Behavior
165
+ constrain :self, :character_set, :alpha, description: 'being'
166
+ end
167
+ alias alpha being_only_letters
168
+ alias letters_only being_only_letters
169
+ alias only_letters being_only_letters
170
+
171
+ # Validate string contains only numbers.
172
+ #
173
+ # @example
174
+ # type.being_only_numbers
175
+ # type.validate("123") # => true
176
+ # type.validate("abc123") # => false
177
+ #
178
+ # @return [Behavior] self for method chaining
179
+ # @rbs () -> Behavior
180
+ def being_only_numbers
181
+ # @type self: Object & Behavior
182
+ constrain :self, :character_set, :numeric, description: 'being'
183
+ end
184
+ alias digits_only being_only_numbers
185
+ alias numbers_only being_only_numbers
186
+ alias numeric being_only_numbers
187
+ alias only_digits being_only_numbers
188
+ alias only_numbers being_only_numbers
189
+
190
+ # Validate string characters are in sorted order.
191
+ #
192
+ # @example
193
+ # type.being_ordered
194
+ # type.validate("abcd") # => true
195
+ # type.validate("dcba") # => false
196
+ #
197
+ # @return [Behavior] self for method chaining
198
+ # @rbs () -> Behavior
199
+ def being_ordered
200
+ # @type self: Object & Behavior
201
+ constrain :chars, :ordering, description: 'being'
202
+ end
203
+ alias not_being_unordered being_ordered
204
+ alias ordered being_ordered
205
+
206
+ # Validate string contains only printable characters.
207
+ #
208
+ # @example
209
+ # type.being_printable
210
+ # type.validate("Hello!") # => true
211
+ # type.validate("Hello\x00World") # => false
212
+ #
213
+ # @return [Behavior] self for method chaining
214
+ # @rbs () -> Behavior
215
+ def being_printable
216
+ # @type self: Object & Behavior
217
+ constrain :self, :character_set, :printable, description: 'being'
218
+ end
219
+ alias printable being_printable
220
+
221
+ # Validate string is in title case (first letter of each word capitalized).
222
+ #
223
+ # @example
224
+ # type.being_titlecase
225
+ # type.validate("Hello World") # => true
226
+ # type.validate("hello world") # => false
227
+ #
228
+ # @return [Behavior] self for method chaining
229
+ # @rbs () -> Behavior
230
+ def being_titlecase
231
+ # @type self: Object & Behavior
232
+ constrain :self, :case, :title, description: 'being', coerce_with: lambda(&:to_s)
233
+ end
234
+ alias titlecase being_titlecase
235
+
236
+ # Validate string characters are not in sorted order.
237
+ #
238
+ # @example
239
+ # type.being_unordered
240
+ # type.validate("dcba") # => true
241
+ # type.validate("abcd") # => false
242
+ #
243
+ # @return [Behavior] self for method chaining
244
+ # @rbs () -> Behavior
245
+ def being_unordered
246
+ # @type self: Object & Behavior
247
+ ordered = @constraints.prepare :chars, :ordering, coerce_with: lambda(&:to_s)
248
+ constrain :self, :not, ordered, concerning: :ordering, description: 'being'
249
+ end
250
+ alias not_being_ordered being_unordered
251
+ alias unordered being_unordered
252
+
253
+ # Validate string is uppercase.
254
+ #
255
+ # @example
256
+ # type.being_uppercase
257
+ # type.validate("HELLO") # => true
258
+ # type.validate("Hello") # => false
259
+ #
260
+ # @return [Behavior] self for method chaining
261
+ # @rbs () -> Behavior
262
+ def being_uppercase
263
+ # @type self: Object & Behavior
264
+ constrain :self, :case, :upper, description: 'being'
265
+ end
266
+ alias not_being_lowercase being_uppercase
267
+ alias uppercase being_uppercase
268
+
269
+ # Validate string contains all specified substrings.
270
+ #
271
+ # @example
272
+ # type.containing("hello", "world")
273
+ # type.validate("hello world") # => true
274
+ # type.validate("hello") # => false
275
+ #
276
+ # @param literals [Array<String, Symbol>] the substrings to look for
277
+ # @return [Behavior] self for method chaining
278
+ # @rbs (*String | Symbol literals) -> Behavior
279
+ def containing(*literals)
280
+ # @type self: Object & Behavior
281
+ including = literals.map do |literal|
282
+ @constraints.prepare :self, :inclusion, literal, coerce_with: lambda(&:to_s)
283
+ end
284
+ constrain :self, :and, including, concerning: :inclusion
285
+ end
286
+ alias including containing
287
+
288
+ # Validate string does not contain any specified substrings.
289
+ #
290
+ # @example
291
+ # type.excluding("foo", "bar")
292
+ # type.validate("hello world") # => true
293
+ # type.validate("foo bar") # => false
294
+ #
295
+ # @param literals [Array<String, Symbol>] the substrings to exclude
296
+ # @return [Behavior] self for method chaining
297
+ # @rbs (*String | Symbol literals) -> Behavior
298
+ def excluding(*literals)
299
+ # @type self: Object & Behavior
300
+ including = literals.map do |literal|
301
+ @constraints.prepare :self, :inclusion, literal, coerce_with: lambda(&:to_s)
302
+ end
303
+ constrain :self, :nor, including, concerning: :exclusion
304
+ end
305
+ alias omitting excluding
306
+
307
+ # Validate string matches all specified patterns.
308
+ #
309
+ # @example
310
+ # type.matching(/^\w+$/, /\d/)
311
+ # type.validate("hello123") # => true
312
+ # type.validate("hello") # => false
313
+ #
314
+ # @param patterns [Array<String, Regexp>] the patterns to match against
315
+ # @return [Behavior] self for method chaining
316
+ # @rbs (*String | Regexp patterns) -> Behavior
317
+ def matching(*patterns)
318
+ # @type self: Object & Behavior
319
+ matching_patterns = patterns.map do |pattern|
320
+ @constraints.prepare :self, :match_pattern, pattern
321
+ end
322
+ constrain :self, :and, matching_patterns, concerning: :pattern_inclusion
323
+ end
324
+
325
+ # Validate string is not empty.
326
+ #
327
+ # @example
328
+ # type.not_being_empty
329
+ # type.validate("a") # => true
330
+ # type.validate("") # => false
331
+ #
332
+ # @return [Behavior] self for method chaining
333
+ # @rbs () -> Behavior
334
+ def not_being_empty
335
+ # @type self: Object & Behavior
336
+ empty = @constraints.prepare :self, :emptiness
337
+ constrain :self, :not, empty, concerning: :emptiness, description: 'being'
338
+ end
339
+
340
+ # Validate string does not equal a specific value.
341
+ #
342
+ # @example
343
+ # type.not_being_equal_to("admin")
344
+ # type.validate("user") # => true
345
+ # type.validate("admin") # => false
346
+ #
347
+ # @param literal [String, Symbol] the value to compare against
348
+ # @return [Behavior] self for method chaining
349
+ # @rbs (String | Symbol literal) -> Behavior
350
+ def not_being_equal_to(literal)
351
+ # @type self: Object & Behavior
352
+ equal_to = @constraints.prepare :self, :equality, literal
353
+ constrain :self, :not, equal_to, concerning: :equality, description: 'being'
354
+ end
355
+ alias not_eql not_being_equal_to
356
+ alias not_equal_to not_being_equal_to
357
+ alias not_equaling not_being_equal_to
358
+
359
+ # Validate string does not match any specified patterns.
360
+ #
361
+ # @example
362
+ # type.not_matching(/\d/, /[A-Z]/)
363
+ # type.validate("hello") # => true
364
+ # type.validate("Hello123") # => false
365
+ #
366
+ # @param patterns [Array<String, Regexp>] the patterns to avoid matching
367
+ # @return [Behavior] self for method chaining
368
+ # @rbs (*String | Regexp patterns) -> Behavior
369
+ def not_matching(*patterns)
370
+ # @type self: Object & Behavior
371
+ matching_patterns = patterns.map do |pattern|
372
+ @constraints.prepare :self, :match_pattern, pattern
373
+ end
374
+ constrain :self, :nor, matching_patterns, concerning: :pattern_exclusion
375
+ end
376
+ end
377
+ end
378
+ end
379
+ end
@@ -0,0 +1,239 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/constraint/set'
4
+
5
+ module Domainic
6
+ module Type
7
+ # A module providing core type validation behavior.
8
+ #
9
+ # The Behavior module provides the foundation for type validation in the Domainic
10
+ # system. It manages a set of constraints that define what makes a value valid
11
+ # for a particular type and provides methods to validate values against these
12
+ # constraints.
13
+ #
14
+ # Key features:
15
+ # - Intrinsic constraints defined at the class level
16
+ # - Instance-level constraint customization
17
+ # - Support for both silent and exception-raising validation
18
+ # - Case equality operator integration (===)
19
+ #
20
+ # @example Defining a custom type
21
+ # class MyType
22
+ # include Domainic::Type::Behavior
23
+ #
24
+ # private
25
+ #
26
+ # def initialize(**options)
27
+ # super
28
+ # constrain(:self, :string_check, :string)
29
+ # constrain(:length, :minimum, :range, minimum: 5)
30
+ # end
31
+ # end
32
+ #
33
+ # @example Using a custom type
34
+ # type = MyType.new
35
+ # type.validate("hello") # => true
36
+ # type.validate("hi") # => false
37
+ # type.validate!(123) # raises TypeError
38
+ #
39
+ # case "hello"
40
+ # when MyType then puts "Valid!"
41
+ # end
42
+ #
43
+ # @author {https://aaronmallen.me Aaron Allen}
44
+ # @since 0.1.0
45
+ module Behavior
46
+ # @rbs @constraint_set: Constraint::Set
47
+
48
+ # Configure class methods when module is included.
49
+ #
50
+ # @param base [Class] The class including this module
51
+ #
52
+ # @return [void]
53
+ # @rbs (Class | Module base) -> void
54
+ def self.included(base)
55
+ super
56
+ base.extend(ClassMethods)
57
+ end
58
+
59
+ # Class methods for types including the Behavior module.
60
+ #
61
+ # These methods provide class-level validation capabilities and constraint
62
+ # management.
63
+ #
64
+ # @since 0.1.0
65
+ module ClassMethods
66
+ # @rbs @intrinsic_constraints: Constraint::Set
67
+
68
+ # Convert the type to a String representation.
69
+ #
70
+ # @return [String] The type as a String
71
+ # @rbs () -> String
72
+ def to_s
73
+ # @type self: Class & Behavior
74
+ (name || '').split('::').last&.delete_suffix('Type') #: String
75
+ end
76
+ alias inspect to_s
77
+
78
+ # Validate a value against this type.
79
+ #
80
+ # @param value [Object] The value to validate
81
+ #
82
+ # @return [Boolean] true if the value is valid
83
+ # @rbs (untyped value) -> bool
84
+ def validate(value)
85
+ # @type self: Class & Behavior
86
+ new.validate(value)
87
+ end
88
+ alias === validate
89
+
90
+ # Validate a value against this type, raising an error on failure.
91
+ #
92
+ # @param value [Object] The value to validate
93
+ #
94
+ # @raise [TypeError] if the value is invalid
95
+ # @return [Boolean] true if the value is valid
96
+ # @rbs (untyped value) -> bool
97
+ def validate!(value)
98
+ # @type self: Class & Behavior
99
+ new.validate!(value)
100
+ end
101
+
102
+ private
103
+
104
+ # Add an intrinsic constraint to this type.
105
+ #
106
+ # @see Constraint::Set#add
107
+ #
108
+ # @return [void]
109
+ # @rbs (
110
+ # Type::accessor accessor,
111
+ # String | Symbol constraint_type,
112
+ # ?untyped expectation,
113
+ # **untyped options
114
+ # ) -> void
115
+ def intrinsic(...)
116
+ intrinsic_constraints.add(...)
117
+ end
118
+
119
+ # Get the set of intrinsic constraints for this type.
120
+ #
121
+ # @return [Constraint::Set] The constraint set
122
+ # @rbs () -> Constraint::Set
123
+ def intrinsic_constraints
124
+ @intrinsic_constraints ||= Constraint::Set.new
125
+ end
126
+
127
+ # Delegate unknown methods to a new instance.
128
+ #
129
+ # @return [Object] The result of calling the method on a new instance
130
+ # @rbs (Symbol method_name, *untyped arguments, **untyped keyword_arguments) -> Behavior
131
+ def method_missing(method_name, *arguments, **keyword_arguments)
132
+ return super unless respond_to_missing?(method_name, false)
133
+
134
+ # @type self: Class & Behavior
135
+ new.public_send(method_name, *arguments, **keyword_arguments)
136
+ end
137
+
138
+ # Check if an unknown method can be delegated.
139
+ #
140
+ # @param method_name [Symbol] The name of the method
141
+ # @param _include_private [Boolean] Whether to include private methods
142
+ #
143
+ # @return [Boolean] true if the method can be delegated
144
+ # @rbs (Symbol method_name, ?bool _include_private) -> bool
145
+ def respond_to_missing?(method_name, _include_private = false)
146
+ # @type self: Class & Behavior
147
+ instance_methods.include?(method_name) || super
148
+ end
149
+ end
150
+
151
+ # Initialize a new type instance.
152
+ #
153
+ # @param options [Hash] Configuration options for constraints
154
+ #
155
+ # @return [void]
156
+ # @rbs (**untyped) -> void
157
+ def initialize(**options)
158
+ @constraints = self.class.send(:intrinsic_constraints).dup
159
+
160
+ options.each_pair do |method_name, arguments|
161
+ if arguments.is_a?(Hash)
162
+ public_send(method_name, **arguments)
163
+ else
164
+ public_send(method_name, *arguments)
165
+ end
166
+ end
167
+ end
168
+
169
+ # Convert the type to a String representation.
170
+ #
171
+ # @return [String] The type as a String
172
+ # @rbs () -> String
173
+ def to_s
174
+ if @constraints.description.empty?
175
+ self.class.to_s
176
+ else
177
+ "#{self.class}(#{@constraints.description})"
178
+ end
179
+ end
180
+ alias inspect to_s
181
+
182
+ # Validate a value against this type's constraints.
183
+ #
184
+ # @param value [Object] The value to validate
185
+ #
186
+ # @return [Boolean] true if the value satisfies all constraints
187
+ # @rbs (untyped value) -> bool
188
+ def validate(value)
189
+ @constraints.all? do |constraint|
190
+ break false unless constraint.satisfied?(value) # fail fast because we don't care WHY we failed.
191
+
192
+ true
193
+ end
194
+ end
195
+ alias === validate
196
+
197
+ # Validate a value against this type's constraints, raising an error on failure.
198
+ #
199
+ # @param value [Object] The value to validate
200
+ #
201
+ # @raise [TypeError] if the value fails any constraints
202
+ # @return [Boolean] true if the value satisfies all constraints
203
+ # @rbs (untyped value) -> bool
204
+ def validate!(value)
205
+ @constraints.each do |constraint|
206
+ break if !constraint.satisfied?(value) && constraint.abort_on_failure?
207
+ end
208
+
209
+ return true unless @constraints.failures?
210
+
211
+ message = if @constraints.violation_description.empty?
212
+ "Expected #{self}, but got #{value.class}"
213
+ else
214
+ "Expected #{self}, but got #{value.class}(#{@constraints.violation_description})"
215
+ end
216
+
217
+ raise TypeError, message
218
+ end
219
+
220
+ private
221
+
222
+ # Add a constraint to this type instance.
223
+ #
224
+ # @see Constraint::Set#add
225
+ #
226
+ # @return [self]
227
+ # @rbs (
228
+ # Type::accessor accessor,
229
+ # String | Symbol constraint_type,
230
+ # ?untyped expectation,
231
+ # **untyped options
232
+ # ) -> self
233
+ def constrain(...)
234
+ @constraints.add(...)
235
+ self
236
+ end
237
+ end
238
+ end
239
+ end