domainic-type 0.1.0.alpha.3.0.1 → 0.1.0.alpha.3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|