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,60 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint for validating that collection elements are in sorted order.
5
+ #
6
+ # This constraint ensures that elements in a collection are in ascending order
7
+ # by comparing the collection to its sorted version. The constraint works with
8
+ # any collection whose elements implement the Comparable module and respond to
9
+ # the <=> operator.
10
+ #
11
+ # @example Basic ordering validation
12
+ # constraint = OrderingConstraint.new(:self)
13
+ # constraint.satisfied?([1, 2, 3]) # => true
14
+ # constraint.satisfied?([1, 3, 2]) # => false
15
+ #
16
+ # @example With string elements
17
+ # constraint = OrderingConstraint.new(:self)
18
+ # constraint.satisfied?(['a', 'b', 'c']) # => true
19
+ # constraint.satisfied?(['c', 'a', 'b']) # => false
20
+ #
21
+ # @example With mixed types that can be compared
22
+ # constraint = OrderingConstraint.new(:self)
23
+ # constraint.satisfied?([1, 1.5, 2]) # => true
24
+ # constraint.satisfied?([2, 1, 1.5]) # => false
25
+ #
26
+ # @author {https://aaronmallen.me Aaron Allen}
27
+ # @since 0.1.0
28
+ class OrderingConstraint
29
+ include Behavior[nil, untyped, { }]
30
+
31
+ # Get a human-readable description of the ordering requirement.
32
+ #
33
+ # @example
34
+ # constraint = OrderingConstraint.new(:self)
35
+ # constraint.description # => "ordered"
36
+ #
37
+ # @return [String] A description of the ordering requirement
38
+ def short_description: ...
39
+
40
+ # Get a human-readable description of why ordering validation failed.
41
+ #
42
+ # @example
43
+ # constraint = OrderingConstraint.new(:self)
44
+ # constraint.satisfied?([3, 1, 2])
45
+ # constraint.short_violation_description # => "not ordered"
46
+ #
47
+ # @return [String] A description of the ordering failure
48
+ def short_violation_description: ...
49
+
50
+ # Check if the collection elements are in sorted order.
51
+ #
52
+ # Compares the collection to its sorted version to determine if the
53
+ # elements are already in ascending order.
54
+ #
55
+ # @return [Boolean] true if the elements are in sorted order
56
+ def satisfies_constraint?: ...
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,71 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint for validating numeric parity (even or odd).
5
+ #
6
+ # This constraint verifies a number's parity by calling Ruby's standard
7
+ # even? and odd? methods. It normalizes method names
8
+ # to ensure compatibility with Ruby's predicate method naming conventions.
9
+ #
10
+ # @example Basic polarity checks
11
+ # constraint = ParityConstraint.new(:self).expecting(:even)
12
+ # constraint.satisfied?(2) # => true
13
+ # constraint.satisfied?(-2) # => true
14
+ # constraint.satisfied?(1) # => false
15
+ #
16
+ # @author {https://aaronmallen.me Aaron Allen}
17
+ # @since 0.1.0
18
+ class ParityConstraint
19
+ type expected = :even | :even? | :odd | :odd?
20
+
21
+ include Behavior[expected, Numeric, { }]
22
+
23
+ # Get a human-readable description of the parity requirement.
24
+ #
25
+ # @example
26
+ # constraint = ParityConstraint.new(:self).expecting(:even)
27
+ # constraint.short_description # => "even"
28
+ #
29
+ # @return [String] Description of the parity requirement
30
+ def short_description: ...
31
+
32
+ # Get a human-readable description of why parity validation failed.
33
+ #
34
+ # @example
35
+ # constraint = ParityConstraint.new(:self).expecting(:positive)
36
+ # constraint.satisfied?(0)
37
+ # constraint.short_violation_description # => "odd"
38
+ #
39
+ # @return [String] Description of the validation failure
40
+ def short_violation_description: ...
41
+
42
+ # Coerce the expectation into the correct method name format.
43
+ #
44
+ # Ensures the expectation ends with a question mark to match Ruby's
45
+ # standard method naming for predicate methods.
46
+ #
47
+ # @example
48
+ # coerce_expectation(:even) # => :even?
49
+ # coerce_expectation(:even?) # => :eve?
50
+ #
51
+ # @param expectation [Symbol, String] The parity check to perform
52
+ #
53
+ # @return [Symbol] The coerced method name
54
+ def coerce_expectation: ...
55
+
56
+ # Check if the value satisfies the parity constraint.
57
+ #
58
+ # @return [Boolean] true if the value matches the parity requirement
59
+ def satisfies_constraint?: ...
60
+
61
+ # Validate that the expectation is a valid parity check.
62
+ #
63
+ # @param expectation [Object] The value to validate
64
+ #
65
+ # @raise [ArgumentError] if the expectation is not :even, :even?, :odd, or :odd?
66
+ # @return [void]
67
+ def validate_expectation!: ...
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,101 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint for validating numeric polarity (positive, negative, zero, nonzero).
5
+ #
6
+ # This constraint verifies a number's polarity by calling Ruby's standard
7
+ # positive?, negative?, zero?, and nonzero? methods. It normalizes method names
8
+ # to ensure compatibility with Ruby's predicate method naming conventions.
9
+ #
10
+ # @example Basic polarity checks
11
+ # constraint = PolarityConstraint.new(:self).expecting(:positive)
12
+ # constraint.satisfied?(42) # => true
13
+ # constraint.satisfied?(-42) # => false
14
+ # constraint.satisfied?(0) # => false
15
+ #
16
+ # @example Zero checks
17
+ # constraint = PolarityConstraint.new(:self).expecting(:zero)
18
+ # constraint.satisfied?(0) # => true
19
+ # constraint.satisfied?(42) # => false
20
+ #
21
+ # @example Nonzero checks
22
+ # constraint = PolarityConstraint.new(:self).expecting(:nonzero)
23
+ # constraint.satisfied?(42) # => true
24
+ # constraint.satisfied?(0) # => false
25
+ #
26
+ # @author {https://aaronmallen.me Aaron Allen}
27
+ # @since 0.1.0
28
+ class PolarityConstraint
29
+ type expected = :negative | :negative? | :nonzero | :nonzero? | :positive | :positive? | :zero | :zero?
30
+
31
+ include Behavior[expected, Numeric, { }]
32
+
33
+ # Get a human-readable description of the polarity requirement.
34
+ #
35
+ # @example
36
+ # constraint = PolarityConstraint.new(:self).expecting(:positive)
37
+ # constraint.short_description # => "positive"
38
+ #
39
+ # @return [String] Description of the polarity requirement
40
+ def short_description: ...
41
+
42
+ # Get a human-readable description of why polarity validation failed.
43
+ #
44
+ # @example
45
+ # constraint = PolarityConstraint.new(:self).expecting(:positive)
46
+ # constraint.satisfied?(0)
47
+ # constraint.short_violation_description # => "zero"
48
+ #
49
+ # @return [String] Description of the validation failure
50
+ def short_violation_description: ...
51
+
52
+ # Coerce the expectation into the correct method name format.
53
+ #
54
+ # Ensures the expectation ends with a question mark to match Ruby's
55
+ # standard method naming for predicate methods.
56
+ #
57
+ # @example
58
+ # coerce_expectation(:positive) # => :positive?
59
+ # coerce_expectation(:positive?) # => :positive?
60
+ #
61
+ # @param expectation [Symbol, String] The polarity check to perform
62
+ #
63
+ # @return [Symbol] The coerced method name
64
+ def coerce_expectation: ...
65
+
66
+ # Check if the value satisfies the polarity constraint.
67
+ #
68
+ # @return [Boolean] true if the value matches the polarity requirement
69
+ def satisfies_constraint?: ...
70
+
71
+ # Validate that the expectation is a valid polarity check.
72
+ #
73
+ # @param expectation [Object] The value to validate
74
+ #
75
+ # @raise [ArgumentError] if the expectation is not :negative, :negative?, :nonzero,
76
+ # :nonzero?, :positive, :positive?, :zero, or :zero?
77
+ # @return [void]
78
+ def validate_expectation!: ...
79
+
80
+ private
81
+
82
+ # Interpret the result from Ruby's polarity methods.
83
+ #
84
+ # Handles the different return values from Ruby's polarity checking methods:
85
+ # - positive?/negative?/zero? return true/false
86
+ # - nonzero? returns nil for zero, and self for nonzero
87
+ #
88
+ # @example
89
+ # interpret_result(true) # => true
90
+ # interpret_result(false) # => false
91
+ # interpret_result(nil) # => false
92
+ # interpret_result(42) # => true
93
+ #
94
+ # @param result [Boolean, Integer, nil] The result from the polarity check
95
+ #
96
+ # @return [Boolean] true if the result indicates the desired polarity state
97
+ def interpret_result: ((bool | Numeric)? result) -> bool
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,88 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint for validating that numeric values fall within a specified range.
5
+ #
6
+ # This constraint allows for validating numeric values against minimum and maximum
7
+ # boundaries. It supports specifying either or both boundaries, allowing for
8
+ # open-ended ranges when appropriate.
9
+ #
10
+ # @example Validating with both minimum and maximum
11
+ # constraint = RangeConstraint.new(:self, { minimum: 1, maximum: 10 })
12
+ # constraint.satisfied?(5) # => true
13
+ # constraint.satisfied?(15) # => false
14
+ #
15
+ # @example Validating with only minimum
16
+ # constraint = RangeConstraint.new(:self, { minimum: 0 })
17
+ # constraint.satisfied?(10) # => true
18
+ # constraint.satisfied?(-1) # => false
19
+ #
20
+ # @example Validating with only maximum
21
+ # constraint = RangeConstraint.new(:self, { maximum: 100 })
22
+ # constraint.satisfied?(50) # => true
23
+ # constraint.satisfied?(150) # => false
24
+ #
25
+ # @author {https://aaronmallen.me Aaron Allen}
26
+ # @since 0.1.0
27
+ class RangeConstraint
28
+ type expected = { ?minimum: Numeric, ?maximum: Numeric }
29
+
30
+ type options = { ?inclusive: bool }
31
+
32
+ include Behavior[expected, Numeric, options]
33
+
34
+ # Get a human-readable description of the range constraint.
35
+ #
36
+ # @example With both bounds
37
+ # constraint = RangeConstraint.new(:self, { minimum: 1, maximum: 10 })
38
+ # constraint.description
39
+ # # => "greater than or equal to 1 and less than or equal to 10"
40
+ #
41
+ # @example With only minimum
42
+ # constraint = RangeConstraint.new(:self, { minimum: 0 })
43
+ # constraint.description # => "greater than or equal to 0"
44
+ #
45
+ # @example With only maximum
46
+ # constraint = RangeConstraint.new(:self, { maximum: 100 })
47
+ # constraint.description # => "less than or equal to 100"
48
+ #
49
+ # @return [String] A description of the range bounds
50
+ def short_description: ...
51
+
52
+ # The description of the violations that caused the constraint to be unsatisfied.
53
+ #
54
+ # This is used to help compose a error message when the constraint is not satisfied.
55
+ # Implementing classes can override this to provide more specific failure messages.
56
+ #
57
+ # @return [String] The description of the constraint when it fails.
58
+ def short_violation_description: ...
59
+
60
+ def coerce_expectation: ...
61
+
62
+ # Check if the actual value falls within the specified range.
63
+ #
64
+ # Uses -Infinity and +Infinity as default bounds when minimum or maximum
65
+ # are not specified, respectively.
66
+ #
67
+ # @return [Boolean] true if the value is within range
68
+ def satisfies_constraint?: ...
69
+
70
+ # Validate that the expected value is a properly formatted range specification.
71
+ #
72
+ # @param expectation [Hash] The range specification to validate
73
+ #
74
+ # @raise [ArgumentError] if the specification is invalid
75
+ # @return [void]
76
+ def validate_expectation!: ...
77
+
78
+ # Validate the minimum and maximum values in a range specification.
79
+ #
80
+ # @param expectation [Hash] The range specification to validate
81
+ #
82
+ # @raise [ArgumentError] if the values are invalid
83
+ # @return [void]
84
+ def validate_minimum_and_maximum!: (untyped expectation) -> void
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,86 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint for validating that values match a specific type.
5
+ #
6
+ # This constraint provides type checking functionality through Ruby's standard
7
+ # type system, supporting class inheritance checks, module inclusion checks,
8
+ # and custom type validation through the case equality operator (===).
9
+ #
10
+ # @example Basic class type validation
11
+ # constraint = TypeConstraint.new(:self, String)
12
+ # constraint.satisfied?("hello") # => true
13
+ # constraint.satisfied?(123) # => false
14
+ #
15
+ # @example Module type validation
16
+ # constraint = TypeConstraint.new(:self, Enumerable)
17
+ # constraint.satisfied?([1, 2, 3]) # => true
18
+ # constraint.satisfied?("string") # => false
19
+ #
20
+ # @example Custom type validation
21
+ # class EvenType
22
+ # def self.===(value)
23
+ # value.is_a?(Integer) && value.even?
24
+ # end
25
+ # end
26
+ #
27
+ # constraint = TypeConstraint.new(:self, EvenType)
28
+ # constraint.satisfied?(2) # => true
29
+ # constraint.satisfied?(3) # => false
30
+ #
31
+ # @example Nil type validation
32
+ # constraint = TypeConstraint.new(:self, nil)
33
+ # constraint.satisfied?(nil) # => true
34
+ # constraint.satisfied?(false) # => false
35
+ #
36
+ # @author {https://aaronmallen.me Aaron Allen}
37
+ # @since 0.1.0
38
+ class TypeConstraint
39
+ include Behavior[Class | Module | Type::Behavior | nil, untyped, { }]
40
+
41
+ # Get a human-readable description of the expected type.
42
+ #
43
+ # @example
44
+ # constraint = TypeConstraint.new(:self, Float)
45
+ # constraint.description # => "Float"
46
+ #
47
+ # constraint = TypeConstraint.new(:self, Array)
48
+ # constraint.description # => "Array"
49
+ #
50
+ # @return [String] A description of the expected type
51
+ def short_description: ...
52
+
53
+ # The description of the violations that caused the constraint to be unsatisfied.
54
+ #
55
+ # This is used to help compose a error message when the constraint is not satisfied.
56
+ # Implementing classes can override this to provide more specific failure messages.
57
+ #
58
+ # @return [String] The description of the constraint when it fails.
59
+ def short_violation_description: ...
60
+
61
+ # Coerce the expected type, converting nil to NilClass for type checking.
62
+ #
63
+ # @param expectation [Class, Module, nil] The type to coerce
64
+ #
65
+ # @return [Class, Module] The coerced type
66
+ def coerce_expectation: ...
67
+
68
+ # Check if the actual value matches the expected type.
69
+ #
70
+ # The check is performed using both the case equality operator (===)
71
+ # and Ruby's is_a? method to provide maximum flexibility in type checking.
72
+ #
73
+ # @return [Boolean] true if the value matches the expected type
74
+ def satisfies_constraint?: ...
75
+
76
+ # Validate that the expected type is a valid Ruby type.
77
+ #
78
+ # @param expectation [Object] The type to validate
79
+ #
80
+ # @raise [ArgumentError] if the expectation is not a valid type
81
+ # @return [void]
82
+ def validate_expectation!: ...
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,54 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ # A constraint for validating that all elements in a collection are unique.
5
+ #
6
+ # This constraint ensures that an enumerable collection contains no duplicate
7
+ # elements by comparing the collection's size before and after removing duplicates.
8
+ # It works with any object that includes the Enumerable module and responds
9
+ # to #uniq and #count.
10
+ #
11
+ # @example Basic uniqueness validation
12
+ # constraint = UniquenessConstraint.new(:self)
13
+ # constraint.satisfied?([1, 2, 3]) # => true
14
+ # constraint.satisfied?([1, 2, 2, 3]) # => false
15
+ #
16
+ # @example With different collection types
17
+ # constraint = UniquenessConstraint.new(:self)
18
+ # constraint.satisfied?(Set[1, 2, 3]) # => true
19
+ # constraint.satisfied?(['a', 'b', 'b']) # => false
20
+ #
21
+ # @author {https://aaronmallen.me Aaron Allen}
22
+ # @since 0.1.0
23
+ class UniquenessConstraint
24
+ include Behavior[nil, Enumerable, { }]
25
+
26
+ # Get a human-readable description of the uniqueness requirement.
27
+ #
28
+ # @example
29
+ # constraint = UniquenessConstraint.new(:self)
30
+ # constraint.description # => "unique"
31
+ #
32
+ # @return [String] A description of the uniqueness requirement
33
+ def short_description: ...
34
+
35
+ # The description of the violations that caused the constraint to be unsatisfied.
36
+ #
37
+ # This is used to help compose a error message when the constraint is not satisfied.
38
+ # Implementing classes can override this to provide more specific failure messages.
39
+ #
40
+ # @return [String] The description of the constraint when it fails.
41
+ def short_violation_description: ...
42
+
43
+ # Check if all elements in the collection are unique.
44
+ #
45
+ # Compares the size of the collection before and after removing duplicates.
46
+ # If the sizes are equal, all elements are unique. If they differ, duplicates
47
+ # were present.
48
+ #
49
+ # @return [Boolean] true if all elements are unique
50
+ def satisfies_constraint?: ...
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,117 @@
1
+ module Domainic
2
+ module Type
3
+ module Constraint
4
+ interface _ConstraintClass
5
+ def new: (Type::accessor accessor, ?untyped expectation, **untyped options) -> Behavior
6
+ end
7
+
8
+ # A factory class responsible for dynamically loading and resolving constraint types.
9
+ #
10
+ # The Resolver handles the dynamic loading and instantiation of constraint classes based on
11
+ # their type symbols. It manages the conversion of constraint type symbols (like :string or
12
+ # :numeric) into their corresponding constraint classes (like StringConstraint or
13
+ # NumericConstraint).
14
+ #
15
+ # Key responsibilities:
16
+ # - Converting constraint type symbols into file paths
17
+ # - Loading constraint class files dynamically
18
+ # - Resolving constraint class constants
19
+ # - Providing clear error messages for unknown constraints
20
+ #
21
+ # @example Resolving a string constraint
22
+ # resolver = Resolver.new(:string)
23
+ # string_constraint_class = resolver.resolve! # => StringConstraint
24
+ #
25
+ # @example Resolving an unknown constraint
26
+ # resolver = Resolver.new(:unknown)
27
+ # resolver.resolve! # raises ArgumentError: Unknown constraint: unknown
28
+ #
29
+ # @author {https://aaronmallen.me Aaron Allen}
30
+ # @since 0.1.0
31
+ class Resolver
32
+ type registry_constraint = { constant: String, require_path: String }
33
+
34
+ @file_name: String
35
+
36
+ @constraint_type: Symbol
37
+
38
+ @constant_name: String
39
+
40
+ self.@registry: Hash[Symbol, Hash[Symbol, registry_constraint]]
41
+
42
+ # Register a new constraint with the resolver.
43
+ #
44
+ # @param lookup_key [String, Symbol] The lookup key for the constraint. This is how types should reference
45
+ # the constraint when constraining themselves.
46
+ # @param constant_name [String] The name of the constraint class constant.
47
+ # @param require_path [String] The path to the constraint class file.
48
+ #
49
+ # @raise [ArgumentError] if the constraint is already registered
50
+ # @return [void]
51
+ def self.register_constraint: (String | Symbol lookup_key, String constant_name, String require_path) -> void
52
+
53
+ # Resolve a constraint type to its corresponding class.
54
+ #
55
+ # This is a convenience method that creates a new Resolver instance and
56
+ # immediately resolves the constraint class.
57
+ #
58
+ # @param constraint_type [Symbol] The type of constraint to resolve
59
+ #
60
+ # @raise [ArgumentError] if the constraint type is unknown
61
+ # @return [Class] The resolved constraint class
62
+ def self.resolve!: (Symbol constraint_type) -> _ConstraintClass
63
+
64
+ # The registry of known constraints
65
+ #
66
+ # @return [Hash{Symbol => Hash{Symbol => String}}] The constraint registry
67
+ private def self.registry: () -> Hash[Symbol, registry_constraint]
68
+
69
+ # Initialize a new Resolver instance.
70
+ #
71
+ # @param constraint_type [Symbol] The type of constraint to resolve
72
+ #
73
+ # @return [void]
74
+ def initialize: (String | Symbol constraint_type) -> void
75
+
76
+ # Resolve the constraint type to its corresponding class.
77
+ #
78
+ # This method attempts to load and resolve the constraint class file based on
79
+ # the constraint type. The constraint class must be defined under the
80
+ # Domainic::Type::Constraint namespace and follow the naming convention:
81
+ # "{type}_constraint.rb".
82
+ #
83
+ # @example File naming convention
84
+ # :string -> string_constraint.rb -> StringConstraint
85
+ # :numeric -> numeric_constraint.rb -> NumericConstraint
86
+ #
87
+ # @raise [ArgumentError] if the constraint type is unknown
88
+ # @return [Class] The resolved constraint class
89
+ def resolve!: () -> _ConstraintClass
90
+
91
+ private
92
+
93
+ # Get the constraint class constant.
94
+ #
95
+ # Attempts to find the constraint class constant in the Domainic::Type::Constraint
96
+ # namespace.
97
+ #
98
+ # @raise [ArgumentError] if the constant cannot be found
99
+ # @return [Class] The constraint class constant
100
+ def constraint_class: () -> _ConstraintClass
101
+
102
+ # Load the constraint class file.
103
+ #
104
+ # Attempts to require the constraint class file from the constraints directory.
105
+ #
106
+ # @raise [ArgumentError] if the constraint file cannot be loaded
107
+ # @return [void]
108
+ def load_constraint!: () -> void
109
+
110
+ # Fetch the registered constraint from the registry
111
+ #
112
+ # @return [Hash{Symbol => String}] The registered constraint
113
+ def registered: () -> registry_constraint?
114
+ end
115
+ end
116
+ end
117
+ end