domainic-type 0.1.0.alpha.3.3.0 → 0.1.0.alpha.3.4.0
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/.yardopts +11 -0
- data/docs/USAGE.md +56 -1
- data/lib/domainic/type/behavior/date_time_behavior.rb +5 -1
- data/lib/domainic/type/behavior.rb +16 -0
- data/lib/domainic/type/config/registry.yml +3 -0
- data/lib/domainic/type/constraint/constraints/nor_constraint.rb +1 -1
- data/lib/domainic/type/constraint/constraints/predicate_constraint.rb +76 -0
- data/sig/domainic/type/behavior.rbs +9 -0
- data/sig/domainic/type/constraint/constraints/predicate_constraint.rbs +56 -0
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e864e12054b6928f12f7465eace3657aaa736e264a1655e7838ec4f2e658ebed
|
4
|
+
data.tar.gz: ac7c71c1bd2c2f992bb6aca1306291838a4b7eabc77326130db553de8a9dc18f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6029d7d12cb562dd7f2688472f0da97bcb548c544f163cf5215736d82ee0927e6f2c4ff87cc0d1818eefe722d10ac0ee2dd4ebccabff45412793340928e63cb8
|
7
|
+
data.tar.gz: 7ba6d7e41e431f2dd401bffecd60701a9dfeab35eeea3bef9085e97941d1b8327ddfd7cbe5febc577a0e1b3cce49147f6b686b56f1f6251257836d58327d198d
|
data/.yardopts
ADDED
data/docs/USAGE.md
CHANGED
@@ -9,6 +9,7 @@ introduction and installation instructions.
|
|
9
9
|
* [Core Concepts](#core-concepts)
|
10
10
|
* [Basic Type Validation](#basic-type-validation)
|
11
11
|
* [Type Constraints](#type-constraints)
|
12
|
+
* [Custom Constraints](#custom-constraints)
|
12
13
|
* [Error Messages](#error-messages)
|
13
14
|
* [Built-in Types](#built-in-types)
|
14
15
|
* [Simple Types](#simple-types)
|
@@ -131,6 +132,51 @@ numbers = _Array
|
|
131
132
|
.containing(42) # Must include 42
|
132
133
|
```
|
133
134
|
|
135
|
+
### Custom Constraints
|
136
|
+
|
137
|
+
All types in Domainic::Type support adding custom constraints through the `satisfies` method. This is useful when you
|
138
|
+
need validation logic that goes beyond what the built-in constraints provide:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
# Basic value range validation that can't be expressed with built-in constraints
|
142
|
+
kelvin = _Float.satisfies(
|
143
|
+
->(value) { value >= 0.0 },
|
144
|
+
description: 'being a valid Kelvin temperature',
|
145
|
+
violation_description: 'temperature below absolute zero'
|
146
|
+
)
|
147
|
+
|
148
|
+
kelvin.validate!(0.0) # => true
|
149
|
+
kelvin.validate!(-1.0)
|
150
|
+
# => TypeError: Expected Float(being a valid Kelvin temperature), got Float(temperature below absolute zero)
|
151
|
+
|
152
|
+
# Validating relationships between values in complex objects
|
153
|
+
booking = _Hash
|
154
|
+
.of(_Symbol => _DateTime)
|
155
|
+
.containing_keys(:check_in, :check_out)
|
156
|
+
.satisfies(
|
157
|
+
->(dates) { dates[:check_out] > dates[:check_in] },
|
158
|
+
description: 'having valid stay duration',
|
159
|
+
violation_description: 'check-out not after check-in'
|
160
|
+
)
|
161
|
+
|
162
|
+
# Access different parts of objects with accessors
|
163
|
+
balanced_ledger = _Array
|
164
|
+
.of(_Hash)
|
165
|
+
.satisfies(
|
166
|
+
->(entries) { entries.sum { |e| e[:amount] }.zero? },
|
167
|
+
accessor: :entries,
|
168
|
+
description: 'having balanced transactions',
|
169
|
+
violation_description: 'transactions do not sum to zero'
|
170
|
+
)
|
171
|
+
```
|
172
|
+
|
173
|
+
The `satisfies` method accepts:
|
174
|
+
|
175
|
+
* A predicate function that returns true/false for validating values
|
176
|
+
* Optional description that explains what makes a value valid
|
177
|
+
* Optional violation_description that explains why validation failed
|
178
|
+
* Optional accessor to validate specific parts of complex objects
|
179
|
+
|
134
180
|
### Error Messages
|
135
181
|
|
136
182
|
Error messages clearly indicate what validation failed and why. For compound constraints, the error message shows all
|
@@ -410,14 +456,23 @@ Available in nilable variant `_DateTime?`.
|
|
410
456
|
|
411
457
|
#### _DateTimeString
|
412
458
|
|
413
|
-
String-based datetime validation with format constraints:
|
459
|
+
String-based datetime validation with format constraints and full datetime behavior support:
|
414
460
|
|
415
461
|
```ruby
|
416
462
|
_DateTimeString # Basic datetime string validation
|
463
|
+
# Format constraints
|
417
464
|
.having_american_format # MM/DD/YYYY format
|
418
465
|
.having_european_format # DD.MM.YYYY format
|
419
466
|
.having_iso8601_format # ISO 8601 format
|
420
467
|
.having_rfc2822_format # RFC 2822 format
|
468
|
+
|
469
|
+
# Inherits all datetime constraints
|
470
|
+
.being_after('2024-01-01') # Must be after date
|
471
|
+
.being_before('2024-12-31') # Must be before date
|
472
|
+
.being_between( # Must be in range
|
473
|
+
'2024-01-01',
|
474
|
+
'2024-12-31'
|
475
|
+
)
|
421
476
|
```
|
422
477
|
|
423
478
|
Also available as `_DateString` and in nilable variants `_DateTimeString?`, `_DateString?`.
|
@@ -103,10 +103,14 @@ module Domainic
|
|
103
103
|
Time.at(value).to_datetime
|
104
104
|
when String
|
105
105
|
DATETIME_PATTERNS.each do |pattern|
|
106
|
-
|
106
|
+
result = DateTime.strptime(value, pattern)
|
107
|
+
# Validate the parsing preserved the original values by reformatting
|
108
|
+
# the result with the same pattern and comparing
|
109
|
+
return result if value == result.strftime(pattern)
|
107
110
|
rescue ArgumentError
|
108
111
|
next
|
109
112
|
end
|
113
|
+
DateTime.parse(value) # Fallback to Ruby's built-in parser and allow it to raise.
|
110
114
|
else
|
111
115
|
DateTime.parse(value) # Fallback to Ruby's built-in parser and allow it to raise.
|
112
116
|
end
|
@@ -186,6 +186,22 @@ module Domainic
|
|
186
186
|
end
|
187
187
|
end
|
188
188
|
|
189
|
+
# Add a custom constraint to this type.
|
190
|
+
#
|
191
|
+
# @param proc [Proc] the constraint to add
|
192
|
+
# @param accessor [Type::Accessor] the accessor to constrain
|
193
|
+
# @param options [Hash{Symbol => Object}] additional constraint options
|
194
|
+
#
|
195
|
+
# @return [self] for chaining constraints
|
196
|
+
# @rbs (
|
197
|
+
# Proc proc,
|
198
|
+
# ?accessor: Type::accessor,
|
199
|
+
# **untyped options
|
200
|
+
# ) -> Behavior
|
201
|
+
def satisfies(proc, accessor: :self, **options)
|
202
|
+
constrain accessor, :predicate, proc, **options
|
203
|
+
end
|
204
|
+
|
189
205
|
# Convert the type to a String representation.
|
190
206
|
#
|
191
207
|
# @return [String] The type as a String
|
@@ -62,6 +62,9 @@ constraints:
|
|
62
62
|
polarity:
|
63
63
|
constant: Domainic::Type::Constraint::PolarityConstraint
|
64
64
|
require_path: domainic/type/constraint/constraints/polarity_constraint
|
65
|
+
predicate:
|
66
|
+
constant: Domainic::Type::Constraint::PredicateConstraint
|
67
|
+
require_path: domainic/type/constraint/constraints/predicate_constraint
|
65
68
|
range:
|
66
69
|
constant: Domainic::Type::Constraint::RangeConstraint
|
67
70
|
require_path: domainic/type/constraint/constraints/range_constraint
|
@@ -39,7 +39,7 @@ module Domainic
|
|
39
39
|
# @rbs override
|
40
40
|
def short_description
|
41
41
|
descriptions = @expected.map(&:short_description)
|
42
|
-
return descriptions.first if descriptions.size == 1
|
42
|
+
return "not #{descriptions.first}" if descriptions.size == 1
|
43
43
|
|
44
44
|
*first, last = descriptions
|
45
45
|
"#{first.join(', ')} nor #{last}"
|
@@ -0,0 +1,76 @@
|
|
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 values using a custom predicate function.
|
9
|
+
#
|
10
|
+
# This constraint allows for custom validation logic through a Proc that returns
|
11
|
+
# a boolean value. It enables users to create arbitrary validation rules when
|
12
|
+
# the built-in constraints don't cover their specific needs.
|
13
|
+
#
|
14
|
+
# @example Basic usage
|
15
|
+
# constraint = PredicateConstraint.new(:self, ->(x) { x > 0 })
|
16
|
+
# constraint.satisfied?(1) # => true
|
17
|
+
# constraint.satisfied?(-1) # => false
|
18
|
+
#
|
19
|
+
# @example With custom violation description
|
20
|
+
# constraint = PredicateConstraint.new(:self, ->(x) { x > 0 }, violation_description: 'not greater than zero')
|
21
|
+
# constraint.satisfied?(-1) # => false
|
22
|
+
# constraint.short_violation_description # => "not greater than zero"
|
23
|
+
#
|
24
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
25
|
+
# @since 0.1.0
|
26
|
+
class PredicateConstraint
|
27
|
+
# @rbs!
|
28
|
+
# type expected = ^(untyped value) -> bool
|
29
|
+
#
|
30
|
+
# type options = { ?violation_description: String}
|
31
|
+
|
32
|
+
include Behavior #[expected, untyped, options]
|
33
|
+
|
34
|
+
# Get a description of what the constraint expects.
|
35
|
+
#
|
36
|
+
# @note This constraint type does not provide a description as predicates are arbitrary.
|
37
|
+
#
|
38
|
+
# @return [String] an empty string
|
39
|
+
# @rbs override
|
40
|
+
def short_description = ''
|
41
|
+
|
42
|
+
# Get a description of why the predicate validation failed.
|
43
|
+
#
|
44
|
+
# @return [String] the custom violation description if provided
|
45
|
+
# @rbs override
|
46
|
+
def short_violation_description
|
47
|
+
# @type ivar @options: { ?violation_description: String }
|
48
|
+
@options.fetch(:violation_description, '')
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
# Check if the value satisfies the predicate function.
|
54
|
+
#
|
55
|
+
# @return [Boolean] true if the predicate returns true
|
56
|
+
# @rbs override
|
57
|
+
def satisfies_constraint?
|
58
|
+
@expected.call(@actual)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Validate that the expectation is a Proc.
|
62
|
+
#
|
63
|
+
# @param expectation [Object] the expectation to validate
|
64
|
+
#
|
65
|
+
# @raise [ArgumentError] if the expectation is not a Proc
|
66
|
+
# @return [void]
|
67
|
+
# @rbs override
|
68
|
+
def validate_expectation!(expectation)
|
69
|
+
return if expectation.is_a?(Proc)
|
70
|
+
|
71
|
+
raise ArgumentError, 'Expectation must be a Proc'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -125,6 +125,15 @@ module Domainic
|
|
125
125
|
# @return [void]
|
126
126
|
def initialize: (**untyped) -> void
|
127
127
|
|
128
|
+
# Add a custom constraint to this type.
|
129
|
+
#
|
130
|
+
# @param proc [Proc] the constraint to add
|
131
|
+
# @param accessor [Type::Accessor] the accessor to constrain
|
132
|
+
# @param options [Hash{Symbol => Object}] additional constraint options
|
133
|
+
#
|
134
|
+
# @return [self] for chaining constraints
|
135
|
+
def satisfies: (Proc proc, ?accessor: Type::accessor, **untyped options) -> Behavior
|
136
|
+
|
128
137
|
# Convert the type to a String representation.
|
129
138
|
#
|
130
139
|
# @return [String] The type as a String
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Domainic
|
2
|
+
module Type
|
3
|
+
module Constraint
|
4
|
+
# A constraint for validating values using a custom predicate function.
|
5
|
+
#
|
6
|
+
# This constraint allows for custom validation logic through a Proc that returns
|
7
|
+
# a boolean value. It enables users to create arbitrary validation rules when
|
8
|
+
# the built-in constraints don't cover their specific needs.
|
9
|
+
#
|
10
|
+
# @example Basic usage
|
11
|
+
# constraint = PredicateConstraint.new(:self, ->(x) { x > 0 })
|
12
|
+
# constraint.satisfied?(1) # => true
|
13
|
+
# constraint.satisfied?(-1) # => false
|
14
|
+
#
|
15
|
+
# @example With custom violation description
|
16
|
+
# constraint = PredicateConstraint.new(:self, ->(x) { x > 0 }, violation_description: 'not greater than zero')
|
17
|
+
# constraint.satisfied?(-1) # => false
|
18
|
+
# constraint.short_violation_description # => "not greater than zero"
|
19
|
+
#
|
20
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
21
|
+
# @since 0.1.0
|
22
|
+
class PredicateConstraint
|
23
|
+
type expected = ^(untyped value) -> bool
|
24
|
+
|
25
|
+
type options = { ?violation_description: String }
|
26
|
+
|
27
|
+
include Behavior[expected, untyped, options]
|
28
|
+
|
29
|
+
# Get a description of what the constraint expects.
|
30
|
+
#
|
31
|
+
# @note This constraint type does not provide a description as predicates are arbitrary.
|
32
|
+
#
|
33
|
+
# @return [String] an empty string
|
34
|
+
def short_description: ...
|
35
|
+
|
36
|
+
# Get a description of why the predicate validation failed.
|
37
|
+
#
|
38
|
+
# @return [String] the custom violation description if provided
|
39
|
+
def short_violation_description: ...
|
40
|
+
|
41
|
+
# Check if the value satisfies the predicate function.
|
42
|
+
#
|
43
|
+
# @return [Boolean] true if the predicate returns true
|
44
|
+
def satisfies_constraint?: ...
|
45
|
+
|
46
|
+
# Validate that the expectation is a Proc.
|
47
|
+
#
|
48
|
+
# @param expectation [Object] the expectation to validate
|
49
|
+
#
|
50
|
+
# @raise [ArgumentError] if the expectation is not a Proc
|
51
|
+
# @return [void]
|
52
|
+
def validate_expectation!: ...
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
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.
|
4
|
+
version: 0.1.0.alpha.3.4.0
|
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-30 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
|
@@ -21,6 +21,7 @@ executables: []
|
|
21
21
|
extensions: []
|
22
22
|
extra_rdoc_files: []
|
23
23
|
files:
|
24
|
+
- ".yardopts"
|
24
25
|
- CHANGELOG.md
|
25
26
|
- LICENSE
|
26
27
|
- README.md
|
@@ -59,6 +60,7 @@ files:
|
|
59
60
|
- lib/domainic/type/constraint/constraints/ordering_constraint.rb
|
60
61
|
- lib/domainic/type/constraint/constraints/parity_constraint.rb
|
61
62
|
- lib/domainic/type/constraint/constraints/polarity_constraint.rb
|
63
|
+
- lib/domainic/type/constraint/constraints/predicate_constraint.rb
|
62
64
|
- lib/domainic/type/constraint/constraints/range_constraint.rb
|
63
65
|
- lib/domainic/type/constraint/constraints/type_constraint.rb
|
64
66
|
- lib/domainic/type/constraint/constraints/uniqueness_constraint.rb
|
@@ -125,6 +127,7 @@ files:
|
|
125
127
|
- sig/domainic/type/constraint/constraints/ordering_constraint.rbs
|
126
128
|
- sig/domainic/type/constraint/constraints/parity_constraint.rbs
|
127
129
|
- sig/domainic/type/constraint/constraints/polarity_constraint.rbs
|
130
|
+
- sig/domainic/type/constraint/constraints/predicate_constraint.rbs
|
128
131
|
- sig/domainic/type/constraint/constraints/range_constraint.rbs
|
129
132
|
- sig/domainic/type/constraint/constraints/type_constraint.rbs
|
130
133
|
- sig/domainic/type/constraint/constraints/uniqueness_constraint.rbs
|
@@ -159,15 +162,16 @@ files:
|
|
159
162
|
- sig/domainic/type/types/specification/union_type.rbs
|
160
163
|
- sig/domainic/type/types/specification/void_type.rbs
|
161
164
|
- sig/manifest.yaml
|
162
|
-
homepage: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.
|
165
|
+
homepage: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.4.0/domainic-type
|
163
166
|
licenses:
|
164
167
|
- MIT
|
165
168
|
metadata:
|
166
169
|
bug_tracker_uri: https://github.com/domainic/domainic/issues
|
167
|
-
changelog_uri: https://github.com/domainic/domainic/releases/tag/domainic-type-v0.1.0-alpha.3.
|
168
|
-
|
170
|
+
changelog_uri: https://github.com/domainic/domainic/releases/tag/domainic-type-v0.1.0-alpha.3.4.0
|
171
|
+
documentation_uri: https://rubydoc.info/gems/domainic-type/0.1.0.alpha.3.4.0
|
172
|
+
homepage_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.4.0/domainic-type
|
169
173
|
rubygems_mfa_required: 'true'
|
170
|
-
source_code_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.
|
174
|
+
source_code_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.4.0/domainic-type
|
171
175
|
post_install_message:
|
172
176
|
rdoc_options: []
|
173
177
|
require_paths:
|