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