domainic-type 0.1.0.alpha.3.0.1 → 0.1.0.alpha.3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/domainic/type/config/registry.yml +9 -0
- data/lib/domainic/type/constraint/constraints/attribute_presence_constraint.rb +155 -0
- data/lib/domainic/type/constraint/constraints/instance_of_constraint.rb +77 -0
- data/lib/domainic/type/definitions.rb +29 -0
- data/lib/domainic/type/types/specialized/instance_type.rb +73 -0
- data/sig/domainic/type/constraint/constraints/attribute_presence_constraint.rbs +98 -0
- data/sig/domainic/type/constraint/constraints/instance_of_constraint.rbs +57 -0
- data/sig/domainic/type/definitions.rbs +24 -0
- data/sig/domainic/type/types/specialized/instance_type.rbs +63 -0
- metadata +12 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56d315e14fd05fe94174f580d09843c227cc5d763e994b47fd4094c26365dafc
|
4
|
+
data.tar.gz: cf0288834c42fdd9d985ddb889484273dbae5278e0b8f0f1bd0021317c0387ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d53f6f1acc6969c093bb3617acb04ec17d507571c514604c941e378ad80341e7145d5f15298ec4922e68fc646e5f984cfc3e49fa8a7f2ab501d30456329dc9a4
|
7
|
+
data.tar.gz: 9e5a0a9815c013086aeb5d183b66f2dfa3e0db4c31d62b8357ce0a2a12ca5bd3aafdad0a94d8c01781171a61df7a37d9dacd21b5d9d9256fe218ce62bc7b81e7
|
@@ -8,6 +8,9 @@ constraints:
|
|
8
8
|
any:
|
9
9
|
constant: Domainic::Type::Constraint::AnyConstraint
|
10
10
|
require_path: domainic/type/constraint/constraints/any_constraint
|
11
|
+
attribute_presence:
|
12
|
+
constant: Domainic::Type::Constraint::AttributePresenceConstraint
|
13
|
+
require_path: domainic/type/constraint/constraints/attribute_presence_constraint
|
11
14
|
case:
|
12
15
|
constant: Domainic::Type::Constraint::CaseConstraint
|
13
16
|
require_path: domainic/type/constraint/constraints/case_constraint
|
@@ -29,6 +32,9 @@ constraints:
|
|
29
32
|
inclusion:
|
30
33
|
constant: Domainic::Type::Constraint::InclusionConstraint
|
31
34
|
require_path: domainic/type/constraint/constraints/inclusion_constraint
|
35
|
+
instance_of:
|
36
|
+
constant: Domainic::Type::Constraint::InstanceOfConstraint
|
37
|
+
require_path: domainic/type/constraint/constraints/instance_of_constraint
|
32
38
|
match_pattern:
|
33
39
|
constant: Domainic::Type::Constraint::MatchPatternConstraint
|
34
40
|
require_path: domainic/type/constraint/constraints/match_pattern_constraint
|
@@ -84,6 +90,9 @@ types:
|
|
84
90
|
hash:
|
85
91
|
constant: Domainic::Type::HashType
|
86
92
|
require_path: domainic/type/types/core/hash_type
|
93
|
+
instance_type:
|
94
|
+
constant: Domainic::Type::InstanceType
|
95
|
+
require_path: domainic/type/types/specialized/instance_type
|
87
96
|
integer:
|
88
97
|
constant: Domainic::Type::IntegerType
|
89
98
|
require_path: domainic/type/types/core/integer_type
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/type/behavior'
|
4
|
+
require 'domainic/type/constraint/behavior'
|
5
|
+
|
6
|
+
module Domainic
|
7
|
+
module Type
|
8
|
+
module Constraint
|
9
|
+
# A constraint that validates the presence and types of object attributes
|
10
|
+
#
|
11
|
+
# This constraint verifies that objects respond to specified attributes and that
|
12
|
+
# those attributes return values matching their corresponding type constraints.
|
13
|
+
# It supports both Ruby core types (Class/Module) and custom type constraints
|
14
|
+
# that implement the Type::Behavior interface.
|
15
|
+
#
|
16
|
+
# @example Basic attribute validation
|
17
|
+
# # Validate presence and basic Ruby types
|
18
|
+
# constraint = AttributePresenceConstraint.new(:self,
|
19
|
+
# username: String,
|
20
|
+
# age: Integer
|
21
|
+
# )
|
22
|
+
#
|
23
|
+
# # Success case
|
24
|
+
# user = User.new(username: "alice", age: 30)
|
25
|
+
# constraint.satisfied?(user) # => true
|
26
|
+
#
|
27
|
+
# # Missing attribute failure
|
28
|
+
# user = User.new(username: "bob")
|
29
|
+
# constraint.satisfied?(user) # => false
|
30
|
+
# constraint.short_violation_description # => "missing attributes: age"
|
31
|
+
#
|
32
|
+
# # Wrong type failure
|
33
|
+
# user = User.new(username: 123, age: "30")
|
34
|
+
# constraint.satisfied?(user) # => false
|
35
|
+
# constraint.short_violation_description
|
36
|
+
# # => "invalid attributes: username: 123, age: \"30\""
|
37
|
+
#
|
38
|
+
# @example With custom type constraints
|
39
|
+
# # Combine with other type constraints
|
40
|
+
# constraint = AttributePresenceConstraint.new(:self,
|
41
|
+
# username: _String.having_size_between(3, 20),
|
42
|
+
# role: _Enum(:admin, :user),
|
43
|
+
# email: _String.matching(/@/)
|
44
|
+
# )
|
45
|
+
#
|
46
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
47
|
+
# @since 0.1.0
|
48
|
+
class AttributePresenceConstraint
|
49
|
+
include Behavior #[Hash[Symbol, Class | Module | Type::Behavior], untyped, {}]
|
50
|
+
|
51
|
+
# Get a description of what the constraint expects
|
52
|
+
#
|
53
|
+
# @return [String] the constraint description
|
54
|
+
# @rbs override
|
55
|
+
def short_description
|
56
|
+
"attributes #{@expected.map { |attribute, type| "#{attribute}: #{type.inspect}" }.join(', ')}".strip
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get a description of why the constraint was violated
|
60
|
+
#
|
61
|
+
# @return [String] the violation description
|
62
|
+
# @rbs override
|
63
|
+
def short_violation_description
|
64
|
+
missing, invalid = violations
|
65
|
+
return '' if missing.empty? && invalid.empty?
|
66
|
+
|
67
|
+
# @type var missing: Array[Symbol]
|
68
|
+
# @type var invalid: Array[Symbol]
|
69
|
+
|
70
|
+
[
|
71
|
+
missing.empty? ? nil : "missing attributes: #{missing.join(', ')}",
|
72
|
+
invalid.empty? ? nil : "invalid attributes: #{format_invalid_attributes(invalid)}"
|
73
|
+
].compact.join(' and ')
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
# Coerce expectation hash keys into symbols
|
79
|
+
#
|
80
|
+
# @param expectation [Hash] the raw expectation hash
|
81
|
+
#
|
82
|
+
# @return [Hash] the coerced expectation
|
83
|
+
# @rbs override
|
84
|
+
def coerce_expectation(expectation)
|
85
|
+
return expectation unless expectation.is_a?(Hash)
|
86
|
+
|
87
|
+
expectation.transform_keys(&:to_sym)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Format the list of invalid attributes for error messages
|
91
|
+
#
|
92
|
+
# @param invalid [Array<Symbol>] The list of invalid attribute names
|
93
|
+
#
|
94
|
+
# @return [String] formatted error message
|
95
|
+
# @rbs (Array[Symbol] invalid) -> String
|
96
|
+
def format_invalid_attributes(invalid)
|
97
|
+
invalid.map do |attribute| # use filter_map to exclude nil entries
|
98
|
+
"#{attribute}: #{@actual.public_send(attribute).inspect}"
|
99
|
+
end.join(', ')
|
100
|
+
end
|
101
|
+
|
102
|
+
# Check if the constraint is satisfied
|
103
|
+
#
|
104
|
+
# @return [Boolean] whether all attributes exist and have valid types
|
105
|
+
# @rbs override
|
106
|
+
def satisfies_constraint?
|
107
|
+
violations.all?(&:empty?)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Check if a value satisfies a type constraint
|
111
|
+
#
|
112
|
+
# @param value [Object] the value to check
|
113
|
+
# @param type [Class, Module, Type::Behavior] the type to check against
|
114
|
+
#
|
115
|
+
# @return [Boolean] whether the value matches the type
|
116
|
+
# @rbs (untyped value, Class | Module | Type::Behavior type) -> bool
|
117
|
+
def satisfies_type?(value, type)
|
118
|
+
type === value || value.is_a?(type) # rubocop:disable Style/CaseEquality
|
119
|
+
end
|
120
|
+
|
121
|
+
# Validate the format of the expectation hash
|
122
|
+
#
|
123
|
+
# @param expectation [Hash] the expectation to validate
|
124
|
+
#
|
125
|
+
# @raise [ArgumentError] if the expectation is invalid
|
126
|
+
# @return [void]
|
127
|
+
# @rbs override
|
128
|
+
def validate_expectation!(expectation)
|
129
|
+
raise ArgumentError, 'Expectation must be a Hash' unless expectation.is_a?(Hash)
|
130
|
+
raise ArgumentError, 'Expectation must have symbolized keys' unless expectation.keys.all?(Symbol)
|
131
|
+
|
132
|
+
expectation.each_value do |value|
|
133
|
+
next if [Class, Module, Type::Behavior].any? { |type| value.is_a?(type) }
|
134
|
+
|
135
|
+
raise ArgumentError, 'Expectation must have values of Class, Module, or Domainic::Type'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Get lists of missing and invalid attributes
|
140
|
+
#
|
141
|
+
# @return [Array(Array<Symbol>, Array<Symbol>)] missing and invalid attribute lists
|
142
|
+
# @rbs () -> Array[Array[Symbol]]
|
143
|
+
def violations
|
144
|
+
@violations ||= begin
|
145
|
+
missing = @expected.keys.reject { |attribute| @actual.respond_to?(attribute) }
|
146
|
+
invalid = @expected.reject do |attribute, type|
|
147
|
+
missing.include?(attribute) || satisfies_type?(@actual.public_send(attribute), type)
|
148
|
+
end.keys
|
149
|
+
[missing, invalid] #: Array[Array[Symbol]]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/type/constraint/behavior'
|
4
|
+
|
5
|
+
module Domainic
|
6
|
+
module Type
|
7
|
+
module Constraint
|
8
|
+
# A constraint for validating that a value is an instance of a specified class or module.
|
9
|
+
#
|
10
|
+
# This constraint checks if the value is a direct instance of the expected class or module
|
11
|
+
# (excluding subclasses or other related types).
|
12
|
+
#
|
13
|
+
# @example Basic instance validation
|
14
|
+
# constraint = InstanceOfConstraint.new(:self, String)
|
15
|
+
# constraint.satisfied?("test") # => true
|
16
|
+
# constraint.satisfied?(:symbol) # => false
|
17
|
+
#
|
18
|
+
# @example Custom class validation
|
19
|
+
# class MyClass; end
|
20
|
+
# constraint = InstanceOfConstraint.new(:self, MyClass)
|
21
|
+
# constraint.satisfied?(MyClass.new) # => true
|
22
|
+
# constraint.satisfied?("test") # => false
|
23
|
+
#
|
24
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
25
|
+
# @since 0.1.0
|
26
|
+
class InstanceOfConstraint
|
27
|
+
include Behavior #[Class | Module, untyped, {}]
|
28
|
+
|
29
|
+
# Get a human-readable description of the instance requirement.
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# constraint = InstanceOfConstraint.new(:self, String)
|
33
|
+
# constraint.description # => "instance of String"
|
34
|
+
#
|
35
|
+
# @return [String] A description of the expected instance type
|
36
|
+
# @rbs override
|
37
|
+
def short_description
|
38
|
+
"instance of #{@expected}"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get a human-readable description of why instance validation failed.
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# constraint = InstanceOfConstraint.new(:self, String)
|
45
|
+
# constraint.satisfied?(:symbol)
|
46
|
+
# constraint.short_violation_description # => "not an instance of String"
|
47
|
+
#
|
48
|
+
# @return [String] A description of the instance validation failure
|
49
|
+
# @rbs override
|
50
|
+
def short_violation_description
|
51
|
+
"not an instance of #{@expected}"
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
# Check if the actual value is an instance of the expected class or module.
|
57
|
+
#
|
58
|
+
# @return [Boolean] true if the value is an instance of the expected class/module
|
59
|
+
# @rbs override
|
60
|
+
def satisfies_constraint?
|
61
|
+
@actual.class < @expected || @actual.instance_of?(@expected)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Validate that the expected value is a Class or Module.
|
65
|
+
#
|
66
|
+
# @raise [ArgumentError] if the expected value is not a Class or Module
|
67
|
+
# @return [void]
|
68
|
+
# @rbs override
|
69
|
+
def validate_expectation!(expectation)
|
70
|
+
return if [Class, Module].any? { |mod| expectation.is_a?(mod) }
|
71
|
+
|
72
|
+
raise ArgumentError, 'Expectation must be a Class or Module'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -218,6 +218,35 @@ module Domainic
|
|
218
218
|
end
|
219
219
|
alias _Map? _Hash?
|
220
220
|
|
221
|
+
# Create an InstanceType instance.
|
222
|
+
#
|
223
|
+
# @example
|
224
|
+
# _type = _Instance.of(User)
|
225
|
+
#
|
226
|
+
# @param options [Hash] additional configuration options
|
227
|
+
#
|
228
|
+
# @return [Domainic::Type::InstanceType]
|
229
|
+
# @rbs (**__todo__ options) -> InstanceType
|
230
|
+
def _Instance(**options)
|
231
|
+
require 'domainic/type/types/specialized/instance_type'
|
232
|
+
Domainic::Type::InstanceType.new(**options)
|
233
|
+
end
|
234
|
+
alias _Record _Instance
|
235
|
+
|
236
|
+
# Create an InstanceType instance.
|
237
|
+
#
|
238
|
+
# @example
|
239
|
+
# _type = _Instance?(of: User)
|
240
|
+
#
|
241
|
+
# @param options [Hash] additional configuration options
|
242
|
+
#
|
243
|
+
# @return [Domainic::Type::UnionType] the created type (Integer or NilClass)
|
244
|
+
# @rbs (**__todo__ options) -> UnionType
|
245
|
+
def _Instance?(**options)
|
246
|
+
_Nilable(_Instance(**options))
|
247
|
+
end
|
248
|
+
alias _Record? _Instance?
|
249
|
+
|
221
250
|
# Creates an IntegerType instance.
|
222
251
|
#
|
223
252
|
# @example
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/type/behavior'
|
4
|
+
|
5
|
+
module Domainic
|
6
|
+
module Type
|
7
|
+
# A type for validation that an object is an instance of a specific class or module.
|
8
|
+
#
|
9
|
+
# This class provides flexible constraints for ensuring objects conform to
|
10
|
+
# expected types, including specific class or module checks, as well as
|
11
|
+
# constraints on attributes within those objects.
|
12
|
+
#
|
13
|
+
# Key features:
|
14
|
+
# - Instance type constraints for classes and modules
|
15
|
+
# - Attribute presence constraints
|
16
|
+
# - Extensible through the Domainic type system
|
17
|
+
#
|
18
|
+
# @example Basic usage
|
19
|
+
# type = InstanceType.new
|
20
|
+
# type.of(String) # enforce instance of String
|
21
|
+
# type.constrain("example") # => true
|
22
|
+
# type.constrain(123) # => false
|
23
|
+
#
|
24
|
+
# @example Attribute constraints
|
25
|
+
# type = InstanceType.new
|
26
|
+
# type.having_attributes(name: String, age: Integer) # enforce attribute presence and types
|
27
|
+
# type.constrain(OpenStruct.new(name: "John", age: 30)) # => true
|
28
|
+
# type.constrain(OpenStruct.new(name: "John")) # => false
|
29
|
+
#
|
30
|
+
# @example Combined constraints
|
31
|
+
# type = InstanceType.new
|
32
|
+
# type
|
33
|
+
# .of(User) # enforce instance of User
|
34
|
+
# .having_attributes(:email => String, :admin => Symbol) # attribute constraints
|
35
|
+
# type.validate(User.new(email: "admin@example.com", admin: :admin)) # => true
|
36
|
+
#
|
37
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
38
|
+
# @since 0.1.0
|
39
|
+
class InstanceType
|
40
|
+
# @rbs! extend Behavior::ClassMethods
|
41
|
+
|
42
|
+
include Behavior
|
43
|
+
|
44
|
+
# Constrain that the object has specific attributes.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# type.having_attributes(name: String, age: Integer)
|
48
|
+
# type.constrain(OpenStruct.new(name: "John", age: 30)) # => true
|
49
|
+
# type.constrain(OpenStruct.new(name: "John")) # => false
|
50
|
+
#
|
51
|
+
# @param attributes [Hash{String, Symbol => Class, Module, Behavior}] the attributes and their expected types
|
52
|
+
# @return [self] self for method chaining
|
53
|
+
# @rbs (Hash[String | Symbol, Class | Module | Behavior] attributes) -> self
|
54
|
+
def having_attributes(attributes)
|
55
|
+
constrain :self, :attribute_presence, attributes, description: 'having'
|
56
|
+
end
|
57
|
+
|
58
|
+
# Constrain that the object is an instance of the specified class or module.
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# type.of(String)
|
62
|
+
# type.constrain("example") # => true
|
63
|
+
# type.constrain(123) # => false
|
64
|
+
#
|
65
|
+
# @param type [Class, Module, Behavior] the expected class, module, or behavior
|
66
|
+
# @return [self] self for method chaining
|
67
|
+
# @rbs (Class | Module | Behavior type) -> self
|
68
|
+
def of(type)
|
69
|
+
constrain :self, :instance_of, type, description: 'of'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Domainic
|
2
|
+
module Type
|
3
|
+
module Constraint
|
4
|
+
# A constraint that validates the presence and types of object attributes
|
5
|
+
#
|
6
|
+
# This constraint verifies that objects respond to specified attributes and that
|
7
|
+
# those attributes return values matching their corresponding type constraints.
|
8
|
+
# It supports both Ruby core types (Class/Module) and custom type constraints
|
9
|
+
# that implement the Type::Behavior interface.
|
10
|
+
#
|
11
|
+
# @example Basic attribute validation
|
12
|
+
# # Validate presence and basic Ruby types
|
13
|
+
# constraint = AttributePresenceConstraint.new(:self,
|
14
|
+
# username: String,
|
15
|
+
# age: Integer
|
16
|
+
# )
|
17
|
+
#
|
18
|
+
# # Success case
|
19
|
+
# user = User.new(username: "alice", age: 30)
|
20
|
+
# constraint.satisfied?(user) # => true
|
21
|
+
#
|
22
|
+
# # Missing attribute failure
|
23
|
+
# user = User.new(username: "bob")
|
24
|
+
# constraint.satisfied?(user) # => false
|
25
|
+
# constraint.short_violation_description # => "missing attributes: age"
|
26
|
+
#
|
27
|
+
# # Wrong type failure
|
28
|
+
# user = User.new(username: 123, age: "30")
|
29
|
+
# constraint.satisfied?(user) # => false
|
30
|
+
# constraint.short_violation_description
|
31
|
+
# # => "invalid attributes: username: 123, age: \"30\""
|
32
|
+
#
|
33
|
+
# @example With custom type constraints
|
34
|
+
# # Combine with other type constraints
|
35
|
+
# constraint = AttributePresenceConstraint.new(:self,
|
36
|
+
# username: _String.having_size_between(3, 20),
|
37
|
+
# role: _Enum(:admin, :user),
|
38
|
+
# email: _String.matching(/@/)
|
39
|
+
# )
|
40
|
+
#
|
41
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
42
|
+
# @since 0.1.0
|
43
|
+
class AttributePresenceConstraint
|
44
|
+
include Behavior[Hash[Symbol, Class | Module | Type::Behavior], untyped, { }]
|
45
|
+
|
46
|
+
# Get a description of what the constraint expects
|
47
|
+
#
|
48
|
+
# @return [String] the constraint description
|
49
|
+
def short_description: ...
|
50
|
+
|
51
|
+
# Get a description of why the constraint was violated
|
52
|
+
#
|
53
|
+
# @return [String] the violation description
|
54
|
+
def short_violation_description: ...
|
55
|
+
|
56
|
+
# Coerce expectation hash keys into symbols
|
57
|
+
#
|
58
|
+
# @param expectation [Hash] the raw expectation hash
|
59
|
+
#
|
60
|
+
# @return [Hash] the coerced expectation
|
61
|
+
def coerce_expectation: ...
|
62
|
+
|
63
|
+
# Format the list of invalid attributes for error messages
|
64
|
+
#
|
65
|
+
# @param invalid [Array<Symbol>] The list of invalid attribute names
|
66
|
+
#
|
67
|
+
# @return [String] formatted error message
|
68
|
+
def format_invalid_attributes: (Array[Symbol] invalid) -> String
|
69
|
+
|
70
|
+
# Check if the constraint is satisfied
|
71
|
+
#
|
72
|
+
# @return [Boolean] whether all attributes exist and have valid types
|
73
|
+
def satisfies_constraint?: ...
|
74
|
+
|
75
|
+
# Check if a value satisfies a type constraint
|
76
|
+
#
|
77
|
+
# @param value [Object] the value to check
|
78
|
+
# @param type [Class, Module, Type::Behavior] the type to check against
|
79
|
+
#
|
80
|
+
# @return [Boolean] whether the value matches the type
|
81
|
+
def satisfies_type?: (untyped value, Class | Module | Type::Behavior type) -> bool
|
82
|
+
|
83
|
+
# Validate the format of the expectation hash
|
84
|
+
#
|
85
|
+
# @param expectation [Hash] the expectation to validate
|
86
|
+
#
|
87
|
+
# @raise [ArgumentError] if the expectation is invalid
|
88
|
+
# @return [void]
|
89
|
+
def validate_expectation!: ...
|
90
|
+
|
91
|
+
# Get lists of missing and invalid attributes
|
92
|
+
#
|
93
|
+
# @return [Array(Array<Symbol>, Array<Symbol>)] missing and invalid attribute lists
|
94
|
+
def violations: () -> Array[Array[Symbol]]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Domainic
|
2
|
+
module Type
|
3
|
+
module Constraint
|
4
|
+
# A constraint for validating that a value is an instance of a specified class or module.
|
5
|
+
#
|
6
|
+
# This constraint checks if the value is a direct instance of the expected class or module
|
7
|
+
# (excluding subclasses or other related types).
|
8
|
+
#
|
9
|
+
# @example Basic instance validation
|
10
|
+
# constraint = InstanceOfConstraint.new(:self, String)
|
11
|
+
# constraint.satisfied?("test") # => true
|
12
|
+
# constraint.satisfied?(:symbol) # => false
|
13
|
+
#
|
14
|
+
# @example Custom class validation
|
15
|
+
# class MyClass; end
|
16
|
+
# constraint = InstanceOfConstraint.new(:self, MyClass)
|
17
|
+
# constraint.satisfied?(MyClass.new) # => true
|
18
|
+
# constraint.satisfied?("test") # => false
|
19
|
+
#
|
20
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
21
|
+
# @since 0.1.0
|
22
|
+
class InstanceOfConstraint
|
23
|
+
include Behavior[Class | Module, untyped, { }]
|
24
|
+
|
25
|
+
# Get a human-readable description of the instance requirement.
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# constraint = InstanceOfConstraint.new(:self, String)
|
29
|
+
# constraint.description # => "instance of String"
|
30
|
+
#
|
31
|
+
# @return [String] A description of the expected instance type
|
32
|
+
def short_description: ...
|
33
|
+
|
34
|
+
# Get a human-readable description of why instance validation failed.
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# constraint = InstanceOfConstraint.new(:self, String)
|
38
|
+
# constraint.satisfied?(:symbol)
|
39
|
+
# constraint.short_violation_description # => "not an instance of String"
|
40
|
+
#
|
41
|
+
# @return [String] A description of the instance validation failure
|
42
|
+
def short_violation_description: ...
|
43
|
+
|
44
|
+
# Check if the actual value is an instance of the expected class or module.
|
45
|
+
#
|
46
|
+
# @return [Boolean] true if the value is an instance of the expected class/module
|
47
|
+
def satisfies_constraint?: ...
|
48
|
+
|
49
|
+
# Validate that the expected value is a Class or Module.
|
50
|
+
#
|
51
|
+
# @raise [ArgumentError] if the expected value is not a Class or Module
|
52
|
+
# @return [void]
|
53
|
+
def validate_expectation!: ...
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -184,6 +184,30 @@ module Domainic
|
|
184
184
|
|
185
185
|
alias _Map? _Hash?
|
186
186
|
|
187
|
+
# Create an InstanceType instance.
|
188
|
+
#
|
189
|
+
# @example
|
190
|
+
# _type = _Instance.of(User)
|
191
|
+
#
|
192
|
+
# @param options [Hash] additional configuration options
|
193
|
+
#
|
194
|
+
# @return [Domainic::Type::InstanceType]
|
195
|
+
def _Instance: (**__todo__ options) -> InstanceType
|
196
|
+
|
197
|
+
alias _Record _Instance
|
198
|
+
|
199
|
+
# Create an InstanceType instance.
|
200
|
+
#
|
201
|
+
# @example
|
202
|
+
# _type = _Instance?(of: User)
|
203
|
+
#
|
204
|
+
# @param options [Hash] additional configuration options
|
205
|
+
#
|
206
|
+
# @return [Domainic::Type::UnionType] the created type (Integer or NilClass)
|
207
|
+
def _Instance?: (**__todo__ options) -> UnionType
|
208
|
+
|
209
|
+
alias _Record? _Instance?
|
210
|
+
|
187
211
|
# Creates an IntegerType instance.
|
188
212
|
#
|
189
213
|
# @example
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Domainic
|
2
|
+
module Type
|
3
|
+
# A type for validation that an object is an instance of a specific class or module.
|
4
|
+
#
|
5
|
+
# This class provides flexible constraints for ensuring objects conform to
|
6
|
+
# expected types, including specific class or module checks, as well as
|
7
|
+
# constraints on attributes within those objects.
|
8
|
+
#
|
9
|
+
# Key features:
|
10
|
+
# - Instance type constraints for classes and modules
|
11
|
+
# - Attribute presence constraints
|
12
|
+
# - Extensible through the Domainic type system
|
13
|
+
#
|
14
|
+
# @example Basic usage
|
15
|
+
# type = InstanceType.new
|
16
|
+
# type.of(String) # enforce instance of String
|
17
|
+
# type.constrain("example") # => true
|
18
|
+
# type.constrain(123) # => false
|
19
|
+
#
|
20
|
+
# @example Attribute constraints
|
21
|
+
# type = InstanceType.new
|
22
|
+
# type.having_attributes(name: String, age: Integer) # enforce attribute presence and types
|
23
|
+
# type.constrain(OpenStruct.new(name: "John", age: 30)) # => true
|
24
|
+
# type.constrain(OpenStruct.new(name: "John")) # => false
|
25
|
+
#
|
26
|
+
# @example Combined constraints
|
27
|
+
# type = InstanceType.new
|
28
|
+
# type
|
29
|
+
# .of(User) # enforce instance of User
|
30
|
+
# .having_attributes(:email => String, :admin => Symbol) # attribute constraints
|
31
|
+
# type.validate(User.new(email: "admin@example.com", admin: :admin)) # => true
|
32
|
+
#
|
33
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
34
|
+
# @since 0.1.0
|
35
|
+
class InstanceType
|
36
|
+
extend Behavior::ClassMethods
|
37
|
+
|
38
|
+
include Behavior
|
39
|
+
|
40
|
+
# Constrain that the object has specific attributes.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# type.having_attributes(name: String, age: Integer)
|
44
|
+
# type.constrain(OpenStruct.new(name: "John", age: 30)) # => true
|
45
|
+
# type.constrain(OpenStruct.new(name: "John")) # => false
|
46
|
+
#
|
47
|
+
# @param attributes [Hash{String, Symbol => Class, Module, Behavior}] the attributes and their expected types
|
48
|
+
# @return [self] self for method chaining
|
49
|
+
def having_attributes: (Hash[String | Symbol, Class | Module | Behavior] attributes) -> self
|
50
|
+
|
51
|
+
# Constrain that the object is an instance of the specified class or module.
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# type.of(String)
|
55
|
+
# type.constrain("example") # => true
|
56
|
+
# type.constrain(123) # => false
|
57
|
+
#
|
58
|
+
# @param type [Class, Module, Behavior] the expected class, module, or behavior
|
59
|
+
# @return [self] self for method chaining
|
60
|
+
def of: (Class | Module | Behavior type) -> self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: domainic-type
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.alpha.3.0.
|
4
|
+
version: 0.1.0.alpha.3.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Allen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-25 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Stop wrestling with complex type validations and unclear error messages.
|
14
14
|
Domainic::Type brings type validation to Ruby that is both powerful and delightful
|
@@ -37,6 +37,7 @@ files:
|
|
37
37
|
- lib/domainic/type/constraint/constraints/all_constraint.rb
|
38
38
|
- lib/domainic/type/constraint/constraints/and_constraint.rb
|
39
39
|
- lib/domainic/type/constraint/constraints/any_constraint.rb
|
40
|
+
- lib/domainic/type/constraint/constraints/attribute_presence_constraint.rb
|
40
41
|
- lib/domainic/type/constraint/constraints/case_constraint.rb
|
41
42
|
- lib/domainic/type/constraint/constraints/character_set_constraint.rb
|
42
43
|
- lib/domainic/type/constraint/constraints/divisibility_constraint.rb
|
@@ -44,6 +45,7 @@ files:
|
|
44
45
|
- lib/domainic/type/constraint/constraints/equality_constraint.rb
|
45
46
|
- lib/domainic/type/constraint/constraints/finiteness_constraint.rb
|
46
47
|
- lib/domainic/type/constraint/constraints/inclusion_constraint.rb
|
48
|
+
- lib/domainic/type/constraint/constraints/instance_of_constraint.rb
|
47
49
|
- lib/domainic/type/constraint/constraints/match_pattern_constraint.rb
|
48
50
|
- lib/domainic/type/constraint/constraints/method_presence_constraint.rb
|
49
51
|
- lib/domainic/type/constraint/constraints/none_constraint.rb
|
@@ -65,6 +67,7 @@ files:
|
|
65
67
|
- lib/domainic/type/types/core/integer_type.rb
|
66
68
|
- lib/domainic/type/types/core/string_type.rb
|
67
69
|
- lib/domainic/type/types/core/symbol_type.rb
|
70
|
+
- lib/domainic/type/types/specialized/instance_type.rb
|
68
71
|
- lib/domainic/type/types/specification/anything_type.rb
|
69
72
|
- lib/domainic/type/types/specification/duck_type.rb
|
70
73
|
- lib/domainic/type/types/specification/enum_type.rb
|
@@ -82,6 +85,7 @@ files:
|
|
82
85
|
- sig/domainic/type/constraint/constraints/all_constraint.rbs
|
83
86
|
- sig/domainic/type/constraint/constraints/and_constraint.rbs
|
84
87
|
- sig/domainic/type/constraint/constraints/any_constraint.rbs
|
88
|
+
- sig/domainic/type/constraint/constraints/attribute_presence_constraint.rbs
|
85
89
|
- sig/domainic/type/constraint/constraints/case_constraint.rbs
|
86
90
|
- sig/domainic/type/constraint/constraints/character_set_constraint.rbs
|
87
91
|
- sig/domainic/type/constraint/constraints/divisibility_constraint.rbs
|
@@ -89,6 +93,7 @@ files:
|
|
89
93
|
- sig/domainic/type/constraint/constraints/equality_constraint.rbs
|
90
94
|
- sig/domainic/type/constraint/constraints/finiteness_constraint.rbs
|
91
95
|
- sig/domainic/type/constraint/constraints/inclusion_constraint.rbs
|
96
|
+
- sig/domainic/type/constraint/constraints/instance_of_constraint.rbs
|
92
97
|
- sig/domainic/type/constraint/constraints/match_pattern_constraint.rbs
|
93
98
|
- sig/domainic/type/constraint/constraints/method_presence_constraint.rbs
|
94
99
|
- sig/domainic/type/constraint/constraints/none_constraint.rbs
|
@@ -110,21 +115,22 @@ files:
|
|
110
115
|
- sig/domainic/type/types/core/integer_type.rbs
|
111
116
|
- sig/domainic/type/types/core/string_type.rbs
|
112
117
|
- sig/domainic/type/types/core/symbol_type.rbs
|
118
|
+
- sig/domainic/type/types/specialized/instance_type.rbs
|
113
119
|
- sig/domainic/type/types/specification/anything_type.rbs
|
114
120
|
- sig/domainic/type/types/specification/duck_type.rbs
|
115
121
|
- sig/domainic/type/types/specification/enum_type.rbs
|
116
122
|
- sig/domainic/type/types/specification/union_type.rbs
|
117
123
|
- sig/domainic/type/types/specification/void_type.rbs
|
118
124
|
- sig/manifest.yaml
|
119
|
-
homepage: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.0.
|
125
|
+
homepage: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.0.2/domainic-type
|
120
126
|
licenses:
|
121
127
|
- MIT
|
122
128
|
metadata:
|
123
129
|
bug_tracker_uri: https://github.com/domainic/domainic/issues
|
124
|
-
changelog_uri: https://github.com/domainic/domainic/releases/tag/domainic-type-v0.1.0-alpha.3.0.
|
125
|
-
homepage_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.0.
|
130
|
+
changelog_uri: https://github.com/domainic/domainic/releases/tag/domainic-type-v0.1.0-alpha.3.0.2
|
131
|
+
homepage_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.0.2/domainic-type
|
126
132
|
rubygems_mfa_required: 'true'
|
127
|
-
source_code_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.0.
|
133
|
+
source_code_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.0.2/domainic-type
|
128
134
|
post_install_message:
|
129
135
|
rdoc_options: []
|
130
136
|
require_paths:
|