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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/LICENSE +1 -1
- data/README.md +28 -4
- data/lib/domainic/type/accessors.rb +41 -0
- data/lib/domainic/type/behavior/enumerable_behavior.rb +262 -0
- data/lib/domainic/type/behavior/numeric_behavior.rb +340 -0
- data/lib/domainic/type/behavior/sizable_behavior.rb +246 -0
- data/lib/domainic/type/behavior/string_behavior.rb +379 -0
- data/lib/domainic/type/behavior.rb +239 -0
- data/lib/domainic/type/config/registry.yml +101 -0
- data/lib/domainic/type/constraint/behavior.rb +342 -0
- data/lib/domainic/type/constraint/constraints/all_constraint.rb +81 -0
- data/lib/domainic/type/constraint/constraints/and_constraint.rb +105 -0
- data/lib/domainic/type/constraint/constraints/any_constraint.rb +83 -0
- data/lib/domainic/type/constraint/constraints/case_constraint.rb +104 -0
- data/lib/domainic/type/constraint/constraints/character_set_constraint.rb +111 -0
- data/lib/domainic/type/constraint/constraints/divisibility_constraint.rb +126 -0
- data/lib/domainic/type/constraint/constraints/emptiness_constraint.rb +69 -0
- data/lib/domainic/type/constraint/constraints/equality_constraint.rb +75 -0
- data/lib/domainic/type/constraint/constraints/finiteness_constraint.rb +123 -0
- data/lib/domainic/type/constraint/constraints/inclusion_constraint.rb +74 -0
- data/lib/domainic/type/constraint/constraints/match_pattern_constraint.rb +87 -0
- data/lib/domainic/type/constraint/constraints/method_presence_constraint.rb +72 -0
- data/lib/domainic/type/constraint/constraints/none_constraint.rb +83 -0
- data/lib/domainic/type/constraint/constraints/nor_constraint.rb +105 -0
- data/lib/domainic/type/constraint/constraints/not_constraint.rb +76 -0
- data/lib/domainic/type/constraint/constraints/or_constraint.rb +106 -0
- data/lib/domainic/type/constraint/constraints/ordering_constraint.rb +75 -0
- data/lib/domainic/type/constraint/constraints/parity_constraint.rb +102 -0
- data/lib/domainic/type/constraint/constraints/polarity_constraint.rb +147 -0
- data/lib/domainic/type/constraint/constraints/range_constraint.rb +135 -0
- data/lib/domainic/type/constraint/constraints/type_constraint.rb +110 -0
- data/lib/domainic/type/constraint/constraints/uniqueness_constraint.rb +69 -0
- data/lib/domainic/type/constraint/resolver.rb +172 -0
- data/lib/domainic/type/constraint/set.rb +266 -0
- data/lib/domainic/type/definitions.rb +364 -0
- data/lib/domainic/type/types/core/array_type.rb +48 -0
- data/lib/domainic/type/types/core/float_type.rb +39 -0
- data/lib/domainic/type/types/core/hash_type.rb +143 -0
- data/lib/domainic/type/types/core/integer_type.rb +38 -0
- data/lib/domainic/type/types/core/string_type.rb +51 -0
- data/lib/domainic/type/types/core/symbol_type.rb +51 -0
- data/lib/domainic/type/types/specification/anything_type.rb +22 -0
- data/lib/domainic/type/types/specification/duck_type.rb +55 -0
- data/lib/domainic/type/types/specification/enum_type.rb +26 -0
- data/lib/domainic/type/types/specification/union_type.rb +26 -0
- data/lib/domainic/type/types/specification/void_type.rb +12 -0
- data/lib/domainic/type.rb +7 -0
- data/lib/domainic-type.rb +3 -0
- data/sig/domainic/type/accessors.rbs +22 -0
- data/sig/domainic/type/behavior/enumerable_behavior.rbs +238 -0
- data/sig/domainic/type/behavior/numeric_behavior.rbs +299 -0
- data/sig/domainic/type/behavior/sizable_behavior.rbs +218 -0
- data/sig/domainic/type/behavior/string_behavior.rbs +315 -0
- data/sig/domainic/type/behavior.rbs +153 -0
- data/sig/domainic/type/constraint/behavior.rbs +258 -0
- data/sig/domainic/type/constraint/constraints/all_constraint.rbs +55 -0
- data/sig/domainic/type/constraint/constraints/and_constraint.rbs +72 -0
- data/sig/domainic/type/constraint/constraints/any_constraint.rbs +57 -0
- data/sig/domainic/type/constraint/constraints/case_constraint.rbs +73 -0
- data/sig/domainic/type/constraint/constraints/character_set_constraint.rbs +82 -0
- data/sig/domainic/type/constraint/constraints/divisibility_constraint.rbs +91 -0
- data/sig/domainic/type/constraint/constraints/emptiness_constraint.rbs +54 -0
- data/sig/domainic/type/constraint/constraints/equality_constraint.rbs +60 -0
- data/sig/domainic/type/constraint/constraints/finiteness_constraint.rbs +82 -0
- data/sig/domainic/type/constraint/constraints/inclusion_constraint.rbs +59 -0
- data/sig/domainic/type/constraint/constraints/match_pattern_constraint.rbs +66 -0
- data/sig/domainic/type/constraint/constraints/method_presence_constraint.rbs +51 -0
- data/sig/domainic/type/constraint/constraints/none_constraint.rbs +57 -0
- data/sig/domainic/type/constraint/constraints/nor_constraint.rbs +72 -0
- data/sig/domainic/type/constraint/constraints/not_constraint.rbs +56 -0
- data/sig/domainic/type/constraint/constraints/or_constraint.rbs +74 -0
- data/sig/domainic/type/constraint/constraints/ordering_constraint.rbs +60 -0
- data/sig/domainic/type/constraint/constraints/parity_constraint.rbs +71 -0
- data/sig/domainic/type/constraint/constraints/polarity_constraint.rbs +101 -0
- data/sig/domainic/type/constraint/constraints/range_constraint.rbs +88 -0
- data/sig/domainic/type/constraint/constraints/type_constraint.rbs +86 -0
- data/sig/domainic/type/constraint/constraints/uniqueness_constraint.rbs +54 -0
- data/sig/domainic/type/constraint/resolver.rbs +117 -0
- data/sig/domainic/type/constraint/set.rbs +159 -0
- data/sig/domainic/type/definitions.rbs +304 -0
- data/sig/domainic/type/types/core/array_type.rbs +42 -0
- data/sig/domainic/type/types/core/float_type.rbs +33 -0
- data/sig/domainic/type/types/core/hash_type.rbs +107 -0
- data/sig/domainic/type/types/core/integer_type.rbs +32 -0
- data/sig/domainic/type/types/core/string_type.rbs +45 -0
- data/sig/domainic/type/types/core/symbol_type.rbs +45 -0
- data/sig/domainic/type/types/specification/anything_type.rbs +14 -0
- data/sig/domainic/type/types/specification/duck_type.rbs +41 -0
- data/sig/domainic/type/types/specification/enum_type.rbs +14 -0
- data/sig/domainic/type/types/specification/union_type.rbs +14 -0
- data/sig/domainic/type/types/specification/void_type.rbs +8 -0
- data/sig/domainic/type.rbs +5 -0
- data/sig/domainic-type.rbs +1 -0
- data/sig/manifest.yaml +2 -0
- 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
|