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

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,340 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Domainic
4
+ module Type
5
+ module Behavior
6
+ # A module providing numeric-specific validation behaviors for types.
7
+ #
8
+ # This module extends the base Type::Behavior with methods specifically designed for validating numeric values.
9
+ # It provides a fluent interface for common numeric validations such as divisibility, equality, parity, polarity,
10
+ # and range constraints. These methods support a variety of constraints and chaining for flexibility in validation
11
+ # logic.
12
+ #
13
+ # @example Basic usage
14
+ # class NumericType
15
+ # include Domainic::Type::Behavior::NumericBehavior
16
+ #
17
+ # def initialize
18
+ # super
19
+ # being_positive # validates that the value is positive
20
+ # being_divisible_by(5) # validates that the value is divisible by 5
21
+ # end
22
+ # end
23
+ #
24
+ # @example Combining constraints
25
+ # class AdvancedNumericType
26
+ # include Domainic::Type::Behavior::NumericBehavior
27
+ #
28
+ # def initialize
29
+ # super
30
+ # being_finite
31
+ # being_greater_than_or_equal_to(0)
32
+ # being_less_than(100)
33
+ # end
34
+ # end
35
+ #
36
+ # @author {https://aaronmallen.me Aaron Allen}
37
+ # @since 0.1.0
38
+ module NumericBehavior
39
+ # Constrain the numeric value to be divisible by a given divisor.
40
+ #
41
+ # @example
42
+ # type.being_divisible_by(3)
43
+ # type.validate(9) # => true
44
+ # type.validate(10) # => false
45
+ #
46
+ # @note the divisor MUST be provided as an argument or in the options Hash.
47
+ #
48
+ # @param arguments [Array<Numeric>] a list of arguments, typically one divisor
49
+ # @param options [Hash] additional options such as tolerance for floating-point checks
50
+ # @option options [Numeric] :divisor the divisor to check for divisibility
51
+ # @option options [Numeric] :tolerance the tolerance for floating-point checks
52
+ #
53
+ # @return [self] the current instance for chaining
54
+ # @rbs (*Numeric arguments, ?divisor: Numeric, ?tolerance: Numeric) -> Behavior
55
+ def being_divisible_by(*arguments, **options)
56
+ if arguments.size > 1 || (arguments.empty? && !options.key?(:divisor))
57
+ raise ArgumentError, "wrong number of arguments (given #{arguments.size}, expected 1)"
58
+ end
59
+
60
+ # @type self: Object & Behavior
61
+ divisor = arguments.first || options[:divisor]
62
+ constrain :self, :divisibility, divisor, description: 'being', **options.except(:divisor)
63
+ end
64
+ alias being_multiple_of being_divisible_by
65
+ alias divisible_by being_divisible_by
66
+ alias multiple_of being_divisible_by
67
+
68
+ # Constrain the numeric value to equal a specific number.
69
+ #
70
+ # @example
71
+ # type.being_equal_to(42)
72
+ # type.validate(42) # => true
73
+ # type.validate(7) # => false
74
+ #
75
+ # @param numeric [Numeric] the number to constrain equality to
76
+ #
77
+ # @return [self] the current instance for chaining
78
+ # @rbs (Numeric numeric) -> Behavior
79
+ def being_equal_to(numeric)
80
+ # @type self: Object & Behavior
81
+ constrain :self, :equality, numeric, description: 'being'
82
+ end
83
+ alias eql being_equal_to
84
+ alias equal_to being_equal_to
85
+ alias equaling being_equal_to
86
+
87
+ # Constrain the numeric value to be even.
88
+ #
89
+ # @example
90
+ # type.being_even
91
+ # type.validate(4) # => true
92
+ # type.validate(3) # => false
93
+ #
94
+ # @return [self] the current instance for chaining
95
+ # @rbs () -> Behavior
96
+ def being_even
97
+ # @type self: Object & Behavior
98
+ constrain :self, :parity, :even, description: 'being'
99
+ end
100
+ alias even being_even
101
+ alias not_being_odd being_even
102
+
103
+ # Constrain the numeric value to be finite.
104
+ #
105
+ # @example
106
+ # type.being_finite
107
+ # type.validate(42) # => true
108
+ # type.validate(Float::INFINITY) # => false
109
+ #
110
+ # @return [self] the current instance for chaining
111
+ # @rbs () -> Behavior
112
+ def being_finite
113
+ # @type self: Object & Behavior
114
+ constrain :self, :finiteness, :finite, description: 'being'
115
+ end
116
+ alias finite being_finite
117
+ alias not_being_infinite being_finite
118
+
119
+ # Constrain the numeric value to be greater than a specific number.
120
+ #
121
+ # @example
122
+ # type.being_greater_than(5)
123
+ # type.validate(10) # => true
124
+ # type.validate(3) # => false
125
+ #
126
+ # @param numeric [Numeric] the minimum value (exclusive)
127
+ #
128
+ # @return [self] the current instance for chaining
129
+ # @rbs (Numeric numeric) -> Behavior
130
+ def being_greater_than(numeric)
131
+ # @type self: Object & Behavior
132
+ constrain :self, :range, { minimum: numeric }, inclusive: false, description: 'being'
133
+ end
134
+ alias gt being_greater_than
135
+ alias greater_than being_greater_than
136
+
137
+ # Constrain the numeric value to be greater than or equal to a specific number.
138
+ #
139
+ # @example
140
+ # type.being_greater_than_or_equal_to(5)
141
+ # type.validate(5) # => true
142
+ # type.validate(3) # => false
143
+ #
144
+ # @param numeric [Numeric] the minimum value (inclusive)
145
+ #
146
+ # @return [self] the current instance for chaining
147
+ # @rbs (Numeric numeric) -> Behavior
148
+ def being_greater_than_or_equal_to(numeric)
149
+ # @type self: Object & Behavior
150
+ constrain :self, :range, { minimum: numeric }, description: 'being'
151
+ end
152
+ alias gte being_greater_than_or_equal_to
153
+ alias gteq being_greater_than_or_equal_to
154
+ alias greater_than_or_equal_to being_greater_than_or_equal_to
155
+
156
+ # Constrain the numeric value to be infinite.
157
+ #
158
+ # @example
159
+ # type.being_infinite
160
+ # type.validate(Float::INFINITY) # => true
161
+ # type.validate(42) # => false
162
+ #
163
+ # @return [self] the current instance for chaining
164
+ # @rbs () -> Behavior
165
+ def being_infinite
166
+ # @type self: Object & Behavior
167
+ constrain :self, :finiteness, :infinite, description: 'being'
168
+ end
169
+ alias infinite being_infinite
170
+ alias not_being_finite being_infinite
171
+
172
+ # Constrain the numeric value to be less than a specific number.
173
+ #
174
+ # @example
175
+ # type.being_less_than(10)
176
+ # type.validate(5) # => true
177
+ # type.validate(10) # => false
178
+ #
179
+ # @param numeric [Numeric] the maximum value (exclusive)
180
+ #
181
+ # @return [self] the current instance for chaining
182
+ # @rbs (Numeric numeric) -> Behavior
183
+ def being_less_than(numeric)
184
+ # @type self: Object & Behavior
185
+ constrain :self, :range, { maximum: numeric }, inclusive: false, description: 'being'
186
+ end
187
+ alias lt being_less_than
188
+ alias less_than being_less_than
189
+
190
+ # Constrain the numeric value to be less than or equal to a specific number.
191
+ #
192
+ # @example
193
+ # type.being_less_than_or_equal_to(10)
194
+ # type.validate(5) # => true
195
+ # type.validate(15) # => false
196
+ #
197
+ # @param numeric [Numeric] the maximum value (inclusive)
198
+ #
199
+ # @return [self] the current instance for chaining
200
+ # @rbs (Numeric numeric) -> Behavior
201
+ def being_less_than_or_equal_to(numeric)
202
+ # @type self: Object & Behavior
203
+ constrain :self, :range, { maximum: numeric }, description: 'being'
204
+ end
205
+ alias lte being_less_than_or_equal_to
206
+ alias lteq being_less_than_or_equal_to
207
+ alias less_than_or_equal_to being_less_than_or_equal_to
208
+
209
+ # Constrain the numeric value to be negative.
210
+ #
211
+ # @example
212
+ # type.being_negative
213
+ # type.validate(-5) # => true
214
+ # type.validate(5) # => false
215
+ #
216
+ # @return [self] the current instance for chaining
217
+ # @rbs () -> Behavior
218
+ def being_negative
219
+ # @type self: Object & Behavior
220
+ constrain :self, :polarity, :negative, description: 'being'
221
+ end
222
+ alias negative being_negative
223
+ alias not_being_positive being_negative
224
+
225
+ # Constrain the numeric value to be odd.
226
+ #
227
+ # @example
228
+ # type.being_odd
229
+ # type.validate(3) # => true
230
+ # type.validate(4) # => false
231
+ #
232
+ # @return [self] the current instance for chaining
233
+ # @rbs () -> Behavior
234
+ def being_odd
235
+ # @type self: Object & Behavior
236
+ constrain :self, :parity, :odd, description: 'being'
237
+ end
238
+ alias odd being_odd
239
+ alias not_being_even being_odd
240
+
241
+ # Constrain the numeric value to be positive.
242
+ #
243
+ # @example
244
+ # type.being_positive
245
+ # type.validate(5) # => true
246
+ # type.validate(-5) # => false
247
+ #
248
+ # @return [self] the current instance for chaining
249
+ # @rbs () -> Behavior
250
+ def being_positive
251
+ # @type self: Object & Behavior
252
+ constrain :self, :polarity, :positive, description: 'being'
253
+ end
254
+ alias positive being_positive
255
+ alias not_being_negative being_positive
256
+
257
+ # Constrain the numeric value to be zero.
258
+ #
259
+ # @example
260
+ # type.being_zero
261
+ # type.validate(0) # => true
262
+ # type.validate(5) # => false
263
+ #
264
+ # @return [self] the current instance for chaining
265
+ # @rbs () -> Behavior
266
+ def being_zero
267
+ # @type self: Object & Behavior
268
+ constrain :self, :polarity, :zero, description: 'being'
269
+ end
270
+ alias zero being_zero
271
+
272
+ # Constrain the numeric value to not be divisible by a specific divisor.
273
+ #
274
+ # @example
275
+ # type.not_being_divisible_by(3)
276
+ # type.validate(5) # => true
277
+ # type.validate(9) # => false
278
+ #
279
+ # @note the divisor MUST be provided as an argument or in the options Hash.
280
+ #
281
+ # @param arguments [Array<Numeric>] a list of arguments, typically one divisor
282
+ # @param options [Hash] additional options such as tolerance for floating-point checks
283
+ # @option options [Numeric] :divisor the divisor to check for divisibility
284
+ # @option options [Numeric] :tolerance the tolerance for floating-point checks
285
+ #
286
+ # @return [self] the current instance for chaining
287
+ # @rbs (*Numeric arguments, ?divisor: Numeric, ?tolerance: Numeric) -> Behavior
288
+ def not_being_divisible_by(*arguments, **options)
289
+ if arguments.size > 1 || (arguments.empty? && !options.key?(:divisor))
290
+ raise ArgumentError, "wrong number of arguments (given #{arguments.size}, expected 1)"
291
+ end
292
+
293
+ # @type self: Object & Behavior
294
+ divisor = arguments.first || options[:divisor]
295
+ divisible_by = @constraints.prepare :self, :divisibility, divisor, **options.except(:divisor)
296
+ constrain :self, :not, divisible_by, concerning: :divisibility, description: 'being'
297
+ end
298
+ alias not_being_multiple_of not_being_divisible_by
299
+ alias not_divisible_by not_being_divisible_by
300
+ alias not_multiple_of not_being_divisible_by
301
+
302
+ # Constrain the numeric value to not equal a specific number.
303
+ #
304
+ # @example
305
+ # type.not_being_equal_to(42)
306
+ # type.validate(7) # => true
307
+ # type.validate(42) # => false
308
+ #
309
+ # @param numeric [Numeric] the number to constrain inequality to
310
+ #
311
+ # @return [self] the current instance for chaining
312
+ # @rbs (Numeric numeric) -> Behavior
313
+ def not_being_equal_to(numeric)
314
+ # @type self: Object & Behavior
315
+ equal_to = @constraints.prepare :self, :equality, numeric
316
+ constrain :self, :not, equal_to, concerning: :inequality, description: 'being'
317
+ end
318
+ alias not_eql not_being_equal_to
319
+ alias not_equal_to not_being_equal_to
320
+ alias not_equaling not_being_equal_to
321
+
322
+ # Constrain the numeric value to not be zero.
323
+ #
324
+ # @example
325
+ # type.not_being_zero
326
+ # type.validate(5) # => true
327
+ # type.validate(0) # => false
328
+ #
329
+ # @return [self] the current instance for chaining
330
+ # @rbs () -> Behavior
331
+ def not_being_zero
332
+ # @type self: Object & Behavior
333
+ constrain :self, :polarity, :nonzero, description: 'being'
334
+ end
335
+ alias nonzero not_being_zero
336
+ alias not_zero not_being_zero
337
+ end
338
+ end
339
+ end
340
+ end
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+
5
+ module Domainic
6
+ module Type
7
+ module Behavior
8
+ # A module providing size-based validation behaviors for types.
9
+ #
10
+ # This module extends the base Type::Behavior with methods specifically designed for validating
11
+ # the size or length of objects. It provides a consistent interface for size-based validations
12
+ # across different types (collections, strings, etc) with support for exact, minimum, maximum,
13
+ # and range-based size constraints.
14
+ #
15
+ # Key features:
16
+ # - Exact size validation
17
+ # - Minimum/maximum bounds
18
+ # - Range-based size constraints
19
+ # - Consistent interface across different types
20
+ # - Multiple method aliases for intuitive use
21
+ #
22
+ # @example Basic usage
23
+ # class MyType
24
+ # include Domainic::Type::Behavior::SizableBehavior
25
+ #
26
+ # def initialize
27
+ # super
28
+ # having_size(5) # exactly 5 elements
29
+ # having_minimum_size(3) # at least 3 elements
30
+ # having_maximum_size(10) # at most 10 elements
31
+ # end
32
+ # end
33
+ #
34
+ # @example Using size ranges
35
+ # class MyType
36
+ # include Domainic::Type::Behavior::SizableBehavior
37
+ #
38
+ # def initialize
39
+ # super
40
+ # having_size_between(5, 10) # between 5 and 10 elements
41
+ # # Or with keywords
42
+ # having_size_between(minimum: 5, maximum: 10)
43
+ # end
44
+ # end
45
+ #
46
+ # @example Method aliases
47
+ # type.having_size(5) # exact size
48
+ # type.size(5) # same as above
49
+ # type.length(5) # same as above
50
+ # type.count(5) # same as above
51
+ #
52
+ # type.having_min_size(3) # minimum size
53
+ # type.minimum_size(3) # same as above
54
+ # type.min_length(3) # same as above
55
+ #
56
+ # type.having_max_size(10) # maximum size
57
+ # type.maximum_size(10) # same as above
58
+ # type.max_length(10) # same as above
59
+ #
60
+ # @author {https://aaronmallen.me Aaron Allen}
61
+ # @since 0.1.0
62
+ module SizableBehavior
63
+ class << self
64
+ private
65
+
66
+ # Parse arguments for having_size_between
67
+ #
68
+ # @note this in my opinion is better than polluting the namespace of the including class even with a private
69
+ # method. This way, the method is only available within the module itself. See {#having_size_between}.
70
+ #
71
+ # @param minimum [Integer, nil] minimum size value from positional args
72
+ # @param maximum [Integer, nil] maximum size value from positional args
73
+ # @param options [Hash] keyword arguments containing min/max values
74
+ #
75
+ # @raise [ArgumentError] if minimum or maximum value can't be determined
76
+ # @return [Array<Integer>] parsed [minimum, maximum] values
77
+ # @rbs (Integer? minimum, Integer? maximum, Hash[Symbol, Integer?] options) -> Array[Integer]
78
+ def parse_having_between_arguments(minimum, maximum, options)
79
+ min = minimum || options[:min] || options[:minimum]
80
+ max = maximum || options[:max] || options[:maximum]
81
+ raise_having_between_argument_error!(caller, min, max, options) if min.nil? || max.nil?
82
+
83
+ [min, max] #: Array[Integer]
84
+ end
85
+
86
+ # Raise appropriate ArgumentError for having_size_between
87
+ #
88
+ # @param original_caller [Array<String>] caller stack for error
89
+ # @param minimum [Integer, nil] attempted minimum value
90
+ # @param maximum [Integer, nil] attempted maximum value
91
+ # @param options [Hash] keyword arguments that were provided
92
+ #
93
+ # @raise [ArgumentError] with appropriate message
94
+ # @return [void]
95
+ # @rbs (
96
+ # Array[String] original_caller,
97
+ # Integer? minimum,
98
+ # Integer? maximum,
99
+ # Hash[Symbol, Integer?] options
100
+ # ) -> void
101
+ def raise_having_between_argument_error!(original_caller, minimum, maximum, options)
102
+ message = if options.empty?
103
+ "wrong number of arguments (given #{[minimum, maximum].compact.count}, expected 2)"
104
+ else
105
+ "missing keyword: :#{%i[minimum maximum].find { |key| !options.key?(key) }}"
106
+ end
107
+
108
+ error = ArgumentError.new(message)
109
+ error.set_backtrace(original_caller)
110
+ raise error
111
+ end
112
+ end
113
+
114
+ # Set a maximum size constraint
115
+ #
116
+ # Creates a constraint ensuring the size of the validated object does not exceed
117
+ # the specified value.
118
+ #
119
+ # @example
120
+ # type.having_maximum_size(10) # size must be <= 10
121
+ # type.max_size(10) # same as above
122
+ # type.maximum_count(10) # same as above
123
+ #
124
+ # @param size [Integer] the maximum allowed size
125
+ # @return [self] for method chaining
126
+ # @rbs (Integer size) -> Behavior
127
+ def having_maximum_size(size)
128
+ # @type self: Object & Behavior
129
+ constrain :size, :range, { maximum: size },
130
+ concerning: :size, description: "having #{__callee__.to_s.split('_').last}"
131
+ end
132
+ alias having_max_count having_maximum_size
133
+ alias having_max_length having_maximum_size
134
+ alias having_max_size having_maximum_size
135
+ alias having_maximum_count having_maximum_size
136
+ alias having_maximum_length having_maximum_size
137
+ alias max_count having_maximum_size
138
+ alias max_length having_maximum_size
139
+ alias max_size having_maximum_size
140
+ alias maximum_count having_maximum_size
141
+ alias maximum_length having_maximum_size
142
+ alias maximum_size having_maximum_size
143
+
144
+ # Set a minimum size constraint
145
+ #
146
+ # Creates a constraint ensuring the size of the validated object is at least
147
+ # the specified value.
148
+ #
149
+ # @example
150
+ # type.having_minimum_size(5) # size must be >= 5
151
+ # type.min_size(5) # same as above
152
+ # type.minimum_count(5) # same as above
153
+ #
154
+ # @param size [Integer] the minimum required size
155
+ # @return [self] for method chaining
156
+ # @rbs (Integer size) -> Behavior
157
+ def having_minimum_size(size)
158
+ # @type self: Object & Behavior
159
+ constrain :size, :range, { minimum: size },
160
+ concerning: :size, description: "having #{__callee__.to_s.split('_').last}"
161
+ end
162
+ alias having_min_count having_minimum_size
163
+ alias having_min_length having_minimum_size
164
+ alias having_min_size having_minimum_size
165
+ alias having_minimum_count having_minimum_size
166
+ alias having_minimum_length having_minimum_size
167
+ alias min_count having_minimum_size
168
+ alias min_length having_minimum_size
169
+ alias min_size having_minimum_size
170
+ alias minimum_count having_minimum_size
171
+ alias minimum_length having_minimum_size
172
+ alias minimum_size having_minimum_size
173
+
174
+ # Set an exact size constraint
175
+ #
176
+ # Creates a constraint ensuring the size of the validated object equals
177
+ # the specified value.
178
+ #
179
+ # @example
180
+ # type.having_size(7) # size must be exactly 7
181
+ # type.size(7) # same as above
182
+ # type.count(7) # same as above
183
+ #
184
+ # @param size [Integer] the required size
185
+ # @return [self] for method chaining
186
+ # @rbs (Integer size) -> Behavior
187
+ def having_size(size)
188
+ # @type self: Object & Behavior
189
+ constrain :size, :range, { minimum: size, maximum: size },
190
+ concerning: :size, description: "having #{__callee__.to_s.split('_').last}"
191
+ end
192
+ alias count having_size
193
+ alias having_count having_size
194
+ alias having_length having_size
195
+ alias length having_size
196
+ alias size having_size
197
+
198
+ # Set a size range constraint
199
+ #
200
+ # Creates a constraint ensuring the size of the validated object falls within
201
+ # the specified range. The range is exclusive of the minimum and maximum values.
202
+ #
203
+ # @example With positional arguments
204
+ # type.having_size_between(5, 10) # 5 < size < 10
205
+ #
206
+ # @example With keyword arguments
207
+ # type.having_size_between(minimum: 5, maximum: 10)
208
+ # type.having_size_between(min: 5, max: 10)
209
+ #
210
+ # @example With mixed arguments
211
+ # type.having_size_between(5, max: 10)
212
+ #
213
+ # @param minimum [Integer, nil] minimum size value (exclusive)
214
+ # @param maximum [Integer, nil] maximum size value (exclusive)
215
+ # @param options [Hash] alternate way to specify min/max
216
+ # @option options [Integer] :min minimum size (exclusive)
217
+ # @option options [Integer] :minimum same as :min
218
+ # @option options [Integer] :max maximum size (exclusive)
219
+ # @option options [Integer] :maximum same as :max
220
+ #
221
+ # @raise [ArgumentError] if minimum or maximum value can't be determined
222
+ # @return [self] for method chaining
223
+ # @rbs (
224
+ # ?Integer? minimum,
225
+ # ?Integer? maximum,
226
+ # ?max: Integer?,
227
+ # ?maximum: Integer?,
228
+ # ?min: Integer?,
229
+ # ?minimum: Integer?
230
+ # ) -> Behavior
231
+ def having_size_between(minimum = nil, maximum = nil, **options)
232
+ # @type self: Object & Behavior
233
+ min, max =
234
+ SizableBehavior.send(:parse_having_between_arguments, minimum, maximum, options.transform_keys(&:to_sym))
235
+ constrain :size, :range, { minimum: min, maximum: max },
236
+ concerning: :size, description: "having #{__callee__.to_s.split('_').[](1)}", inclusive: false
237
+ end
238
+ alias count_between having_size_between
239
+ alias having_count_between having_size_between
240
+ alias having_length_between having_size_between
241
+ alias length_between having_size_between
242
+ alias size_between having_size_between
243
+ end
244
+ end
245
+ end
246
+ end