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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5342f3d3bb47776a4cb27404a0a244d8e0d2761f14de0fc1efe0638ac354c387
4
- data.tar.gz: e5c55975ec267fba4623239df150111b6bb13eda48448ff4e026007e50342578
3
+ metadata.gz: e864e12054b6928f12f7465eace3657aaa736e264a1655e7838ec4f2e658ebed
4
+ data.tar.gz: ac7c71c1bd2c2f992bb6aca1306291838a4b7eabc77326130db553de8a9dc18f
5
5
  SHA512:
6
- metadata.gz: c1563338aa961165140ef8d72d22c04d9f46e5c08fd95c02b037d32b74f21570b136c7b07094093b4dd47c59b5be6b7cfaa003114ced0e4aad963a15305088e5
7
- data.tar.gz: 01751a3ccf57ddea40d185aa6d04e12ea7a78bd94c0770aeb4af8dbd449d43ac0eec5eda64f91fe1828ff05ab0e2a53d8b24b78f4c13347c9fbaeae343be928b
6
+ metadata.gz: 6029d7d12cb562dd7f2688472f0da97bcb548c544f163cf5215736d82ee0927e6f2c4ff87cc0d1818eefe722d10ac0ee2dd4ebccabff45412793340928e63cb8
7
+ data.tar.gz: 7ba6d7e41e431f2dd401bffecd60701a9dfeab35eeea3bef9085e97941d1b8327ddfd7cbe5febc577a0e1b3cce49147f6b686b56f1f6251257836d58327d198d
data/.yardopts ADDED
@@ -0,0 +1,11 @@
1
+ --title Domainic::Type
2
+ --readme README.md
3
+ --no-private
4
+ --protected
5
+ --markup markdown
6
+ --markup-provider redcarpet
7
+ --embed-mixins
8
+ --tag rbs
9
+ --hide-tag rbs
10
+ --files CHANGELOG.md,LICENSE,docs/USAGE.md
11
+ 'lib/**/*.rb'
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
- return DateTime.strptime(value, pattern)
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.3.0
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-27 00:00:00.000000000 Z
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.3.0/domainic-type
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.3.0
168
- homepage_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.3.0/domainic-type
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.3.0/domainic-type
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: