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,258 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A module providing core functionality for implementing type constraints.
5
+ #
6
+ # The Behavior module serves as the foundation for all type constraints in the Domainic::Type system.
7
+ # It provides a flexible interface for defining how values should be constrained, supporting both
8
+ # simple type checking and complex validation rules.
9
+ #
10
+ # Key features include:
11
+ # - Flexible value access through configurable accessors
12
+ # - Support for custom validation logic
13
+ # - Coercion hooks for both actual and expected values
14
+ # - Detailed failure reporting
15
+ # - Type-aware error messages
16
+ #
17
+ # @abstract Implementing classes must override {#satisfies_constraint?} to define their specific
18
+ # constraint logic.
19
+ #
20
+ # @example Implementing a basic numeric constraint
21
+ # class GreaterThanConstraint
22
+ # include Domainic::Type::Constraint::Behavior
23
+ #
24
+ # def short_description
25
+ # "greater than #{@expected}"
26
+ # end
27
+ #
28
+ # def short_violation_description
29
+ # @actual.to_s
30
+ # end
31
+ #
32
+ # protected
33
+ #
34
+ # def satisfies_constraint?
35
+ # @actual > @expected
36
+ # end
37
+ #
38
+ # def validate_expectation!(expectation)
39
+ # raise ArgumentError, 'Expected value must be numeric' unless expectation.is_a?(Numeric)
40
+ # end
41
+ # end
42
+ #
43
+ # @author {https://aaronmallen.me Aaron Allen}
44
+ # @since 0.1.0
45
+ module Behavior[Expected < Object, Actual < Object, Options < Hash[Symbol, untyped]]
46
+ type options = { ?abort_on_failure: bool, ?coerce_with: Array[Proc] | Proc }
47
+
48
+ @result: bool?
49
+
50
+ @quantifier_description: (String | Symbol)?
51
+
52
+ @options: options
53
+
54
+ @expected: Expected
55
+
56
+ @actual: Actual
57
+
58
+ @accessor: Type::accessor
59
+
60
+ attr_reader quantifier_description: (String | Symbol)?
61
+
62
+ # Initialize a new constraint instance.
63
+ #
64
+ # @param accessor [Symbol] The accessor to use to retrieve the value being constrained
65
+ # @param quantifier_description [String, Symbol, nil] The description of how the constraint applies
66
+ # to elements, such as "all", "any", or "none" for collection constraints, or a specific type name
67
+ # for type constraints. Used to form natural language descriptions like "having elements of String"
68
+ # or "containing any of [1, 2, 3]"
69
+ #
70
+ # @raise [ArgumentError] if the accessor is not included in {VALID_ACCESSORS}
71
+ # @return [Behavior] A new instance of the constraint.
72
+ def initialize: (Type::accessor accessor, ?(String | Symbol)? quantifier_description) -> void
73
+
74
+ # Whether to abort further validation on an unsatisfied constraint.
75
+ #
76
+ # When this is true it tells the type to stop validating the value against the remaining constraints.
77
+ # This is particularly useful for fundamental type constraints where subsequent validations would
78
+ # be meaningless if the basic type check fails.
79
+ #
80
+ # @return [Boolean] Whether to abort on failure.
81
+ def abort_on_failure?: () -> bool
82
+
83
+ # Set the expected value to compare against.
84
+ #
85
+ # @param expectation [Object] The expected value to compare against.
86
+ #
87
+ # @raise [ArgumentError] if the expectation is invalid according to {#validate_expectation!}
88
+ # @return [self] The constraint instance.
89
+ def expecting: (untyped expectation) -> self
90
+
91
+ # Whether the constraint is a failure.
92
+ #
93
+ # @return [Boolean] `true` if the constraint is a failure, `false` otherwise.
94
+ def failure?: () -> bool
95
+
96
+ alias failed? failure?
97
+
98
+ # The full description of the constraint.
99
+ #
100
+ # @return [String, nil] The full description of the constraint.
101
+ def full_description: () -> String?
102
+
103
+ # The full description of the violations that caused the constraint to be unsatisfied.
104
+ #
105
+ # @return [String, nil] The full description of the constraint when it fails.
106
+ def full_violation_description: () -> String?
107
+
108
+ # Whether the constraint is satisfied.
109
+ #
110
+ # This method orchestrates the constraint validation process by:
111
+ # 1. Accessing the value using the configured accessor
112
+ # 2. Coercing the actual value if needed
113
+ # 3. Checking if the constraint is satisfied
114
+ # 4. Handling any errors that occur during validation
115
+ #
116
+ # @param value [Object] The value to validate against the constraint.
117
+ #
118
+ # @return [Boolean] Whether the constraint is satisfied.
119
+ def satisfied?: (Actual value) -> bool
120
+
121
+ # The short description of the constraint.
122
+ #
123
+ # This is used to help compose a error message when the constraint is not satisfied.
124
+ # Implementing classes should override this to provide meaningful descriptions of their
125
+ # constraint behavior.
126
+ #
127
+ # @return [String] The description of the constraint.
128
+ def short_description: () -> String
129
+
130
+ # The short description of the violations that caused the constraint to be unsatisfied.
131
+ #
132
+ # This is used to help compose a error message when the constraint is not satisfied.
133
+ # Implementing classes can override this to provide more specific failure messages.
134
+ #
135
+ # @return [String] The description of the constraint when it fails.
136
+ def short_violation_description: () -> String
137
+
138
+ # Whether the constraint is a success.
139
+ #
140
+ # @return [Boolean] `true` if the constraint is a success, `false` otherwise.
141
+ def successful?: () -> bool
142
+
143
+ alias success? successful?
144
+
145
+ # Merge additional options into the constraint.
146
+ #
147
+ # @param options [Hash{String, Symbol => Object}] Additional options
148
+ # @option options [Boolean] :abort_on_failure (false) Whether to {#abort_on_failure?}
149
+ # @option options [Array<Proc>, Proc] :coerce_with Coercers to run on the value before validating the
150
+ # constraint.
151
+ #
152
+ # @return [self] The constraint instance.
153
+ def with_options: (?options & Options options) -> self
154
+
155
+ # Coerce the value being validated into the expected type.
156
+ #
157
+ # This hook allows implementing classes to transform the actual value before validation.
158
+ # This is particularly useful when the constraint needs to handle multiple input formats
159
+ # or needs to normalize values before comparison.
160
+ #
161
+ # @example Coerce input into an array
162
+ # def coerce_actual(actual)
163
+ # Array(actual)
164
+ # end
165
+ #
166
+ # @param actual [Object] The actual value to coerce.
167
+ #
168
+ # @return [Object] The coerced value.
169
+ def coerce_actual: (untyped actual) -> Actual
170
+
171
+ # Coerce actual values using type-provided coercion procs
172
+ #
173
+ # This method processes the actual value through any type-level coercion procs
174
+ # that were provided via options. This runs after the constraint's own coercion
175
+ # but before validation.
176
+ #
177
+ # @param actual [Object] The actual value to coerce
178
+ #
179
+ # @return [Object] The coerced value
180
+ def coerce_actual_for_type: (untyped actual) -> untyped
181
+
182
+ # Coerce the expected value into the expected type.
183
+ #
184
+ # This hook allows implementing classes to transform or normalize the expected value
185
+ # when it's set. This is useful for handling different formats of expected values
186
+ # or combining multiple expectations.
187
+ #
188
+ # @example Coerce a range specification
189
+ # def coerce_expectation(expectation)
190
+ # case expectation
191
+ # when Range then { minimum: expectation.begin, maximum: expectation.end }
192
+ # when Hash then @expected.merge(expectation)
193
+ # else expectation
194
+ # end
195
+ # end
196
+ #
197
+ # @param expectation [Object] The expected value to coerce.
198
+ #
199
+ # @return [Object] The coerced value.
200
+ def coerce_expectation: (untyped expectation) -> Expected
201
+
202
+ # The primary implementation of the constraint.
203
+ #
204
+ # This is the core method that all constraints must implement to define their specific
205
+ # validation logic. It is called by {#satisfied?} after the value has been accessed
206
+ # and coerced.
207
+ #
208
+ # The implementing class has access to two instance variables:
209
+ # - @actual: The actual value being validated (after coercion)
210
+ # - @expected: The expected value to validate against (after coercion)
211
+ #
212
+ # @example Implementing a greater than constraint
213
+ # def satisfies_constraint?
214
+ # @actual > @expected
215
+ # end
216
+ #
217
+ # @raise [NotImplementedError] if the including class doesn't implement this method
218
+ # @return [Boolean] Whether the constraint is satisfied.
219
+ def satisfies_constraint?: () -> bool
220
+
221
+ # Validate the expected value.
222
+ #
223
+ # This hook allows implementing classes to validate the expected value when it's set.
224
+ # Override this method when the constraint requires specific types or formats for
225
+ # the expected value.
226
+ #
227
+ # @example Validate numeric expectation
228
+ # def validate_expectation!(expectation)
229
+ # return if expectation.is_a?(Numeric)
230
+ #
231
+ # raise ArgumentError, "Expected value must be numeric, got #{expectation.class}"
232
+ # end
233
+ #
234
+ # @param expectation [Object] The expected value to validate.
235
+ #
236
+ # @return [void]
237
+ def validate_expectation!: (untyped expectation) -> void
238
+
239
+ private
240
+
241
+ # Generate the full description for the corresponding short description.
242
+ #
243
+ # @param description [String] The short description to expand.
244
+ #
245
+ # @return [String] The full description.
246
+ def full_description_for: (String description) -> String?
247
+
248
+ # Validate the accessor.
249
+ #
250
+ # @param accessor [Symbol] The accessor to validate.
251
+ #
252
+ # @raise [ArgumentError] if the accessor is not included in {VALID_ACCESSORS}.
253
+ # @return [void]
254
+ def validate_accessor!: (Type::accessor accessor) -> void
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,55 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint that ensures all elements in an enumerable satisfy a given constraint.
5
+ #
6
+ # The AllConstraint allows applying a constraint to every element within an enumerable value,
7
+ # making it possible to validate collections where each element must meet certain criteria.
8
+ #
9
+ # Key features:
10
+ # - Validates each element against the expected constraint
11
+ # - Short-circuits on first failing element
12
+ # - Provides clear error messages about failing elements
13
+ # - Handles empty collections appropriately
14
+ #
15
+ # @example Validating array of strings
16
+ # string_constraint = StringConstraint.new(:self)
17
+ # all_strings = AllConstraint.new(:self, string_constraint)
18
+ #
19
+ # all_strings.satisfied?(['a', 'b', 'c']) # => true
20
+ # all_strings.satisfied?(['a', 1, 'c']) # => false
21
+ #
22
+ # @author {https://aaronmallen.me Aaron Allen}
23
+ # @since 0.1.0
24
+ class AllConstraint
25
+ include Behavior[Behavior[untyped, untyped, untyped], Enumerable, { }]
26
+
27
+ # Get a description of what the constraint expects.
28
+ #
29
+ # @return [String] the constraint description
30
+ def short_description: ...
31
+
32
+ # The description of the violations that caused the constraint to be unsatisfied.
33
+ #
34
+ # This is used to help compose a error message when the constraint is not satisfied.
35
+ # Implementing classes can override this to provide more specific failure messages.
36
+ #
37
+ # @return [String] The description of the constraint when it fails.
38
+ def short_violation_description: ...
39
+
40
+ # Check if all elements satisfy the expected constraint.
41
+ #
42
+ # @return [Boolean] whether the constraint is satisfied
43
+ def satisfies_constraint?: ...
44
+
45
+ # Validate that the expectation is a valid constraint.
46
+ #
47
+ # @param expectation [Object] the expectation to validate
48
+ #
49
+ # @raise [ArgumentError] if the expectation is not a valid constraint
50
+ # @return [void]
51
+ def validate_expectation!: ...
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,72 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint that combines multiple constraints with logical AND behavior.
5
+ #
6
+ # The AndConstraint validates that a value satisfies all of its provided constraints,
7
+ # implementing logical AND behavior. This enables validation rules like "must be both
8
+ # a string and non-empty" or "must be numeric and positive".
9
+ #
10
+ # Key features:
11
+ # - Combines multiple constraints with AND logic
12
+ # - Short-circuits on first failing constraint
13
+ # - Provides clear error messages for failing validations
14
+ # - Supports incremental constraint addition
15
+ #
16
+ # @example Validating a value is both a String and non-empty
17
+ # string_constraint = StringConstraint.new(:self)
18
+ # non_empty = LengthConstraint.new(:length, minimum: 1)
19
+ # string_and_non_empty = AndConstraint.new(:self, [string_constraint, non_empty])
20
+ #
21
+ # string_and_non_empty.satisfied?("test") # => true
22
+ # string_and_non_empty.satisfied?("") # => false
23
+ # string_and_non_empty.satisfied?(123) # => false
24
+ #
25
+ # @author {https://aaronmallen.me Aaron Allen}
26
+ # @since 0.1.0
27
+ class AndConstraint
28
+ include Behavior[Array[Behavior[untyped, untyped, untyped]], untyped, { }]
29
+
30
+ # Get a description of what the constraint expects.
31
+ #
32
+ # @return [String] a description combining all constraint descriptions with 'and'
33
+ def short_description: ...
34
+
35
+ def expecting: (Behavior[untyped, untyped, untyped]) -> self
36
+
37
+ # The description of the violations that caused the constraint to be unsatisfied.
38
+ #
39
+ # This method provides detailed feedback about which constraints failed,
40
+ # listing all violations that prevented validation from succeeding.
41
+ #
42
+ # @return [String] The combined violation descriptions from all constraints
43
+ def short_violation_description: ...
44
+
45
+ # Coerce the expectation into an array and append new constraints.
46
+ #
47
+ # This enables both initializing with an array of constraints and adding
48
+ # new constraints incrementally via expecting().
49
+ #
50
+ # @param expectation [Behavior] the constraint to add
51
+ #
52
+ # @return [Array<Behavior>] the updated array of constraints
53
+ def coerce_expectation: (untyped expectation) -> Array[Behavior[untyped, untyped, untyped]]
54
+
55
+ # Check if the value satisfies all expected constraints.
56
+ #
57
+ # Short-circuits on the first failing constraint for efficiency.
58
+ #
59
+ # @return [Boolean] whether all constraints are satisfied
60
+ def satisfies_constraint?: ...
61
+
62
+ # Validate that the expectation is an array of valid constraints.
63
+ #
64
+ # @param expectation [Object] the expectation to validate
65
+ #
66
+ # @raise [ArgumentError] if the expectation is not valid
67
+ # @return [void]
68
+ def validate_expectation!: ...
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,57 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint that ensures any element in an enumerable satisfies a given constraint.
5
+ #
6
+ # The AnyConstraint validates that at least one element in an enumerable value meets
7
+ # the provided constraint. This enables validation rules like "must contain at least
8
+ # one string" or "must have any positive number".
9
+ #
10
+ # Key features:
11
+ # - Validates enumerable elements against a constraint
12
+ # - Short-circuits on first satisfying element
13
+ # - Provides clear error messages for failing elements
14
+ # - Handles non-enumerable values gracefully
15
+ #
16
+ # @example Validating an array contains any string
17
+ # string_constraint = StringConstraint.new(:self)
18
+ # any_string = AnyConstraint.new(:self, string_constraint)
19
+ #
20
+ # any_string.satisfied?(['a', 1, 'c']) # => true
21
+ # any_string.satisfied?([1, 2, 3]) # => false
22
+ # any_string.satisfied?(nil) # => false (not enumerable)
23
+ #
24
+ # @author {https://aaronmallen.me Aaron Allen}
25
+ # @since 0.1.0
26
+ class AnyConstraint
27
+ include Behavior[Behavior[untyped, untyped, untyped], Enumerable, { }]
28
+
29
+ # Get a description of what the constraint expects.
30
+ #
31
+ # @return [String] a description combining all constraint descriptions
32
+ def short_description: ...
33
+
34
+ # The description of the violations that caused the constraint to be unsatisfied.
35
+ #
36
+ # This method provides detailed feedback when no constraints are satisfied,
37
+ # listing all the ways in which the value failed validation.
38
+ #
39
+ # @return [String] The combined violation descriptions from all constraints
40
+ def short_violation_description: ...
41
+
42
+ # Check if the value satisfies any of the expected constraints.
43
+ #
44
+ # @return [Boolean] whether any constraint is satisfied
45
+ def satisfies_constraint?: ...
46
+
47
+ # Validate that the expectation is an array of valid constraints.
48
+ #
49
+ # @param expectation [Object] the expectation to validate
50
+ #
51
+ # @raise [ArgumentError] if the expectation is not valid
52
+ # @return [void]
53
+ def validate_expectation!: ...
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,73 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint for validating string case formatting.
5
+ #
6
+ # This constraint verifies that string values conform to specific case formats:
7
+ # - upper: all characters are uppercase
8
+ # - lower: all characters are lowercase
9
+ # - mixed: contains both uppercase and lowercase characters
10
+ # - title: words are capitalized (first letter uppercase, rest lowercase)
11
+ #
12
+ # @example Uppercase validation
13
+ # constraint = CaseConstraint.new(:self, :upper)
14
+ # constraint.satisfied?("HELLO") # => true
15
+ # constraint.satisfied?("Hello") # => false
16
+ #
17
+ # @example Title case validation
18
+ # constraint = CaseConstraint.new(:self, :title)
19
+ # constraint.satisfied?("Hello World") # => true
20
+ # constraint.satisfied?("hello world") # => false
21
+ #
22
+ # @example Mixed case validation
23
+ # constraint = CaseConstraint.new(:self, :mixed)
24
+ # constraint.satisfied?("helloWORLD") # => true
25
+ # constraint.satisfied?("HELLO") # => false
26
+ #
27
+ # @author {https://aaronmallen.me Aaron Allen}
28
+ # @since 0.1.0
29
+ class CaseConstraint
30
+ type expected = :upper | :lower | :mixed | :title
31
+
32
+ include Behavior[expected, untyped, { }]
33
+
34
+ # Valid case format options
35
+ #
36
+ # @return [Array<Symbol>] List of valid case formats
37
+ VALID_CASES: Array[expected]
38
+
39
+ # Get a human-readable description of the case requirement.
40
+ #
41
+ # @example
42
+ # constraint = CaseConstraint.new(:self, :upper)
43
+ # constraint.short_description # => "upper case"
44
+ #
45
+ # @return [String] A description of the case requirement
46
+ def short_description: ...
47
+
48
+ # Get a human-readable description of why case validation failed.
49
+ #
50
+ # @example
51
+ # constraint = CaseConstraint.new(:self, :upper)
52
+ # constraint.satisfied?("Hello")
53
+ # constraint.short_violation_description # => "not upper case"
54
+ #
55
+ # @return [String] A description of the case mismatch
56
+ def short_violation_description: ...
57
+
58
+ # Check if the string matches the expected case format.
59
+ #
60
+ # @return [Boolean] true if the string matches the expected case
61
+ def satisfies_constraint?: ...
62
+
63
+ # Validate that the expectation is a valid case format.
64
+ #
65
+ # @param expectation [Object] The case format to validate
66
+ #
67
+ # @raise [ArgumentError] if the expectation is not a valid case format
68
+ # @return [void]
69
+ def validate_expectation!: ...
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,82 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint for validating string character sets.
5
+ #
6
+ # This constraint verifies that string values contain only characters from
7
+ # specific character sets:
8
+ # - ascii: ASCII characters (0x00-0x7F)
9
+ # - alphanumeric: Letters and numbers
10
+ # - alpha: Letters only
11
+ # - numeric: Numbers only
12
+ # - printable: Visible characters and spaces
13
+ #
14
+ # @example ASCII validation
15
+ # constraint = CharacterSetConstraint.new(:self, :ascii)
16
+ # constraint.satisfied?("hello") # => true
17
+ # constraint.satisfied?("héllo") # => false
18
+ #
19
+ # @example Alphanumeric validation
20
+ # constraint = CharacterSetConstraint.new(:self, :alphanumeric)
21
+ # constraint.satisfied?("abc123") # => true
22
+ # constraint.satisfied?("abc-123") # => false
23
+ #
24
+ # @example Numeric validation
25
+ # constraint = CharacterSetConstraint.new(:self, :numeric)
26
+ # constraint.satisfied?("12345") # => true
27
+ # constraint.satisfied?("123.45") # => false
28
+ #
29
+ # @author {https://aaronmallen.me Aaron Allen}
30
+ # @since 0.1.0
31
+ class CharacterSetConstraint
32
+ type expected = :ascii | :alphanumeric | :alpha | :numeric | :printable
33
+
34
+ include Behavior[expected, untyped, { }]
35
+
36
+ # Valid character set patterns
37
+ #
38
+ # @return [Hash{Symbol => Regexp}] Map of set names to validation patterns
39
+ VALID_SETS: Hash[expected, Regexp]
40
+
41
+ # Get a human-readable description of the character set requirement.
42
+ #
43
+ # @example
44
+ # constraint = CharacterSetConstraint.new(:self, :numeric)
45
+ # constraint.short_description # => "only numeric characters"
46
+ #
47
+ # @return [String] A description of the character set requirement
48
+ def short_description: ...
49
+
50
+ # Get a human-readable description of why character validation failed.
51
+ #
52
+ # @example
53
+ # constraint = CharacterSetConstraint.new(:self, :numeric)
54
+ # constraint.satisfied?("abc123")
55
+ # constraint.short_violation_description # => "non-numeric characters"
56
+ #
57
+ # @return [String] A description of the character set violation
58
+ def short_violation_description: ...
59
+
60
+ # Convert character set name to symbol.
61
+ #
62
+ # @param expectation [String, Symbol] The character set name
63
+ #
64
+ # @return [Symbol] The character set name as a symbol
65
+ def coerce_expectation: ...
66
+
67
+ # Check if the string contains only characters from the expected set.
68
+ #
69
+ # @return [Boolean] true if all characters match the expected set
70
+ def satisfies_constraint?: ...
71
+
72
+ # Validate that the expectation is a valid character set.
73
+ #
74
+ # @param expectation [Object] The character set to validate
75
+ #
76
+ # @raise [ArgumentError] if the expectation is not a valid character set
77
+ # @return [void]
78
+ def validate_expectation!: ...
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,91 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint for validating that numeric values are divisible by a specified value.
5
+ #
6
+ # This constraint checks if one number is evenly divisible by another, allowing for
7
+ # a configurable tolerance to handle floating-point arithmetic imprecision. It can
8
+ # be used to validate properties like:
9
+ # - Even/odd numbers (divisible by 2)
10
+ # - Factors and multiples
11
+ # - Decimal place alignment (divisible by 0.1, 0.01, etc)
12
+ #
13
+ # @example Basic divisibility check
14
+ # constraint = DivisibilityConstraint.new(:self, 5)
15
+ # constraint.satisfied?(10) # => true
16
+ # constraint.satisfied?(7) # => false
17
+ #
18
+ # @example With floating point values
19
+ # constraint = DivisibilityConstraint.new(:self, 0.1)
20
+ # constraint.satisfied?(0.3) # => true
21
+ # constraint.satisfied?(0.35) # => false
22
+ #
23
+ # @example Custom tolerance
24
+ # constraint = DivisibilityConstraint.new(:self)
25
+ # constraint.expecting(3)
26
+ # constraint.with_options(tolerance: 1e-5)
27
+ #
28
+ # @author {https://aaronmallen.me Aaron Allen}
29
+ # @since 0.1.0
30
+ class DivisibilityConstraint
31
+ type options = { ?tolerance: Numeric }
32
+
33
+ include Behavior[Numeric, Numeric, options]
34
+
35
+ # Default tolerance for floating-point arithmetic comparisons.
36
+ #
37
+ # @return [Float] The default tolerance value
38
+ DEFAULT_TOLERANCE: Float
39
+
40
+ # Get a human-readable description of the divisibility requirement.
41
+ #
42
+ # @example
43
+ # constraint = DivisibilityConstraint.new(:self, 5)
44
+ # constraint.short_description # => "divisible by 5"
45
+ #
46
+ # @return [String] Description of the divisibility requirement
47
+ def short_description: ...
48
+
49
+ # Get a human-readable description of why divisibility validation failed.
50
+ #
51
+ # @example With non-numeric value
52
+ # constraint = DivisibilityConstraint.new(:self, 5)
53
+ # constraint.satisfied?("not a number")
54
+ # constraint.short_violation_description # => "not Numeric"
55
+ #
56
+ # @example With non-divisible value
57
+ # constraint = DivisibilityConstraint.new(:self, 5)
58
+ # constraint.satisfied?(7)
59
+ # constraint.short_violation_description # => "not divisible by 5"
60
+ #
61
+ # @return [String] Description of the validation failure
62
+ def short_violation_description: ...
63
+
64
+ # Check if the actual value is evenly divisible by the expected value.
65
+ #
66
+ # This method handles both integer and floating-point values, using the
67
+ # configured tolerance to account for floating-point arithmetic imprecision.
68
+ #
69
+ # @return [Boolean] true if the value is evenly divisible
70
+ def satisfies_constraint?: ...
71
+
72
+ # Validate that the expected value is a non-zero number.
73
+ #
74
+ # @param expectation [Object] The value to validate
75
+ #
76
+ # @raise [ArgumentError] if the value is not a non-zero number
77
+ # @return [void]
78
+ def validate_expectation!: ...
79
+
80
+ private
81
+
82
+ # Parse a value into a float, returning nil if parsing fails.
83
+ #
84
+ # @param value [Numeric] The value to parse
85
+ #
86
+ # @return [Float, nil] The parsed float value or nil if parsing failed
87
+ def parse_value: (Numeric value) -> Float?
88
+ end
89
+ end
90
+ end
91
+ end