domainic-type 0.1.0.alpha.3.1.0 → 0.1.0.alpha.3.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba2ef6a6a8d06966d736c794476322b088878573154468dc55de61a85704f459
4
- data.tar.gz: 8f7fcb77831968e9f6f4f42c58bc2f63892b56bae35902aec892932dc5425acc
3
+ metadata.gz: 24abfbef9f478a43dce3382a737ceec2ea4310f69454d5d3e8903699f97454a0
4
+ data.tar.gz: aaede220a86b78362f4dd2a8e51ffdb320ba7352a88b4980dec244e7452662fc
5
5
  SHA512:
6
- metadata.gz: 8fbe93a49fd3d0063c7d9e392cc1e99180cbdfacb3a1a355c813eaa3df5f476be03e6c44b265871bdea82c646c250b5f486b4446f8c0ce2efbc4481c778d5553
7
- data.tar.gz: 2509ae814a1dbb07a2100c24fe0aff90b8c59123f41d8b37a90bce60c9ad33fe1ecd9d57562533e3f7c52f5c051c27120136d1d7f1efe1df4714212281663827
6
+ metadata.gz: 22de008f6805303b6540c879a35a11366c41e49be7dc7ca76347436afaedf256e5b19d8de9242e0351ab0ba78bb6eebf6ad3a202c105b1523ba414836e3a5c64
7
+ data.tar.gz: 1eb03da0104dcd61ca5149471ae71f774206f342e01d23e73e71917db7189610346d7432d6f9ef9c0ef5a3f92767ec048905adb2004578d99fc7be82faf6c2b6
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Domainic
6
+ module Type
7
+ module Behavior
8
+ # A module providing date/time validation constraint methods
9
+ #
10
+ # This module extends types with methods for constraining date/time values based on
11
+ # chronological relationships. It provides a fluent interface for building complex
12
+ # date/time validation constraints using natural language methods.
13
+ #
14
+ # @example Basic date comparisons
15
+ # type = DateType.new
16
+ # .being_after(Date.new(2024, 1, 1))
17
+ # .being_before(Date.new(2024, 12, 31))
18
+ #
19
+ # @example Range-based validation
20
+ # type = DateType.new
21
+ # .being_between(Date.new(2024, 1, 1), Date.new(2024, 12, 31))
22
+ #
23
+ # @example Inclusive/exclusive bounds
24
+ # type = DateType.new
25
+ # .being_on_or_after(Date.new(2024, 1, 1))
26
+ # .being_on_or_before(Date.new(2024, 12, 31))
27
+ #
28
+ # @author {https://aaronmallen.me Aaron Allen}
29
+ # @since 0.1.0
30
+ module DateTimeBehavior
31
+ TO_DATETIME_COERCER = lambda { |value|
32
+ if [Date, DateTime, Time].any? { |type| value.is_a?(type) }
33
+ value
34
+ else
35
+ DateTime.parse(value.to_s)
36
+ end
37
+ } #: Proc
38
+
39
+ class << self
40
+ private
41
+
42
+ # Parse arguments for being_between
43
+ #
44
+ # @note this in my opinion is better than polluting the namespace of the including class even with a private
45
+ # method. This way, the method is only available within the module itself. See {#being_between}.
46
+ #
47
+ # @param after [Date, DateTime, String, Time, nil] minimum size value from positional args
48
+ # @param before [Date, DateTime, String, Time, nil] maximum size value from positional args
49
+ # @param options [Hash] keyword arguments containing after/before values
50
+ #
51
+ # @raise [ArgumentError] if minimum or maximum value can't be determined
52
+ # @return [Array<Date, DateTime, String, Time, nil>] parsed [after, before] values
53
+ # @rbs (
54
+ # (Date | DateTime | String | Time)? after,
55
+ # (Date | DateTime | String | Time)? before,
56
+ # Hash[Symbol, (Date | DateTime | String | Time)?] options
57
+ # ) -> Array[(Date | DateTime | String | Time)?]
58
+ def parse_being_between_arguments!(after, before, options)
59
+ after ||= options[:after]
60
+ before ||= options[:before]
61
+ raise_being_between_argument_error!(caller, after, before, options) if after.nil? || before.nil?
62
+
63
+ [after, before] #: Array[(Date | DateTime | String | Time)?]
64
+ end
65
+
66
+ # Raise appropriate ArgumentError for being_between
67
+ #
68
+ # @param original_caller [Array<String>] caller stack for error
69
+ # @param after [Date, DateTime, String, Time, nil] after value from positional args
70
+ # @param before [Date, DateTime, String, Time, nil] before value from positional args
71
+ # @param options [Hash] keyword arguments containing after/before values
72
+ #
73
+ # @raise [ArgumentError] with appropriate message
74
+ # @return [void]
75
+ # @rbs (
76
+ # Array[String] original_caller,
77
+ # (Date | DateTime | String | Time)? after,
78
+ # (Date | DateTime | String | Time)? before,
79
+ # Hash[Symbol, (Date | DateTime | String | Time)?] options
80
+ # ) -> void
81
+ def raise_being_between_argument_error!(original_caller, after, before, options)
82
+ message = if options.empty?
83
+ "wrong number of arguments (given #{[after, before].compact.count}, expected 2)"
84
+ else
85
+ "missing keyword: :#{%i[after before].find { |key| !options.key?(key) }}"
86
+ end
87
+
88
+ error = ArgumentError.new(message)
89
+ error.set_backtrace(original_caller)
90
+ raise error
91
+ end
92
+ end
93
+
94
+ # Constrain the value to be chronologically after a given date/time
95
+ #
96
+ # @param other [Date, DateTime, String, Time] the date/time to compare against
97
+ # @return [self] self for method chaining
98
+ # @rbs (Date | DateTime | String | Time other) -> Behavior
99
+ def being_after(other)
100
+ # @type self: Object & Behavior
101
+ constrain :self, :range, { minimum: other },
102
+ coerce_with: TO_DATETIME_COERCER, description: 'being', inclusive: false
103
+ end
104
+ alias after being_after
105
+
106
+ # Constrain the value to be chronologically before a given date/time
107
+ #
108
+ # @param other [Date, DateTime, String, Time] the date/time to compare against
109
+ # @return [self] self for method chaining
110
+ # @rbs (Date | DateTime | String | Time other) -> Behavior
111
+ def being_before(other)
112
+ # @type self: Object & Behavior
113
+ constrain :self, :range, { maximum: other },
114
+ coerce_with: TO_DATETIME_COERCER, description: 'being', inclusive: false
115
+ end
116
+ alias before being_before
117
+
118
+ # Constrain the value to be chronologically between two date/times
119
+ #
120
+ # @param after [Date, DateTime, String, Time] the earliest allowed date/time
121
+ # @param before [Date, DateTime, String, Time] the latest allowed date/time
122
+ # @param options [Hash] alternative way to specify after/before via keywords
123
+ # @option options [Date, DateTime, String, Time] :after earliest allowed date/time
124
+ # @option options [Date, DateTime, String, Time] :before latest allowed date/time
125
+ # @return [self] self for method chaining
126
+ # @rbs (Date | DateTime | String | Time after, Date | DateTime | String | Time before) -> Behavior
127
+ def being_between(after = nil, before = nil, **options)
128
+ # @type self: Object & Behavior
129
+ after, before =
130
+ DateTimeBehavior.send(:parse_being_between_arguments!, after, before, options.transform_keys(&:to_sym))
131
+ constrain :self, :range, { minimum: after, maximum: before },
132
+ coerce_with: TO_DATETIME_COERCER, description: 'being', inclusive: false
133
+ end
134
+ alias between being_between
135
+
136
+ # Constrain the value to be exactly equal to a given date/time
137
+ #
138
+ # @param other [Date, DateTime, String, Time] the date/time to compare against
139
+ # @return [self] self for method chaining
140
+ # @rbs (Date | DateTime | String | Time other) -> Behavior
141
+ def being_equal_to(other)
142
+ # @type self: Object & Behavior
143
+ constrain :self, :equality, other,
144
+ coerce_with: TO_DATETIME_COERCER, description: 'being'
145
+ end
146
+ alias at being_equal_to
147
+
148
+ # Constrain the value to be chronologically on or after a given date/time
149
+ #
150
+ # @param other [Date, DateTime, String, Time] the date/time to compare against
151
+ # @return [self] self for method chaining
152
+ # @rbs (Date | DateTime | String | Time other) -> Behavior
153
+ def being_on_or_after(other)
154
+ # @type self: Object & Behavior
155
+ constrain :self, :range, { minimum: other },
156
+ coerce_with: TO_DATETIME_COERCER, description: 'being'
157
+ end
158
+ alias at_or_after being_on_or_after
159
+ alias being_at_or_after being_on_or_after
160
+ alias on_or_after being_on_or_after
161
+
162
+ # Constrain the value to be chronologically on or before a given date/time
163
+ #
164
+ # @param other [Date, DateTime, String, Time] the date/time to compare against
165
+ # @return [self] self for method chaining
166
+ # @rbs (Date | DateTime | String | Time other) -> Behavior
167
+ def being_on_or_before(other)
168
+ # @type self: Object & Behavior
169
+ constrain :self, :range, { maximum: other },
170
+ coerce_with: TO_DATETIME_COERCER, description: 'being'
171
+ end
172
+ alias at_or_before being_on_or_before
173
+ alias being_at_or_before being_on_or_before
174
+ alias on_or_before being_on_or_before
175
+ end
176
+ end
177
+ end
178
+ end
@@ -75,7 +75,7 @@ module Domainic
75
75
  # @raise [ArgumentError] if minimum or maximum value can't be determined
76
76
  # @return [Array<Integer>] parsed [minimum, maximum] values
77
77
  # @rbs (Integer? minimum, Integer? maximum, Hash[Symbol, Integer?] options) -> Array[Integer]
78
- def parse_having_between_arguments(minimum, maximum, options)
78
+ def parse_having_between_arguments!(minimum, maximum, options)
79
79
  min = minimum || options[:min] || options[:minimum]
80
80
  max = maximum || options[:max] || options[:maximum]
81
81
  raise_having_between_argument_error!(caller, min, max, options) if min.nil? || max.nil?
@@ -231,7 +231,7 @@ module Domainic
231
231
  def having_size_between(minimum = nil, maximum = nil, **options)
232
232
  # @type self: Object & Behavior
233
233
  min, max =
234
- SizableBehavior.send(:parse_having_between_arguments, minimum, maximum, options.transform_keys(&:to_sym))
234
+ SizableBehavior.send(:parse_having_between_arguments!, minimum, maximum, options.transform_keys(&:to_sym))
235
235
  constrain :size, :range, { minimum: min, maximum: max },
236
236
  concerning: :size, description: "having #{__callee__.to_s.split('_').[](1)}", inclusive: false
237
237
  end
@@ -81,6 +81,12 @@ types:
81
81
  cuid:
82
82
  constant: Domainic::Type::CUIDType
83
83
  require_path: domainic/type/types/identifier/cuid_type
84
+ date:
85
+ constant: Domainic::Type::DateType
86
+ require_path: domainic/type/types/datetime/date_type
87
+ date_time:
88
+ constant: Domainic::Type::DateTimeType
89
+ require_path: domainic/type/types/datetime/date_time_type
84
90
  duck:
85
91
  constant: Domainic::Type::DuckType
86
92
  require_path: domainic/type/types/specification/duck_type
@@ -111,6 +117,9 @@ types:
111
117
  symbol:
112
118
  constant: Domainic::Type::SymbolType
113
119
  require_path: domainic/type/types/core/symbol_type
120
+ time:
121
+ constant: Domainic::Type::TimeType
122
+ require_path: domainic/type/types/datetime/time_type
114
123
  union:
115
124
  constant: Domainic::Type::UnionType
116
125
  require_path: domainic/type/types/specification/union_type
@@ -5,36 +5,56 @@ require 'domainic/type/constraint/behavior'
5
5
  module Domainic
6
6
  module Type
7
7
  module Constraint
8
- # A constraint for validating that numeric values fall within a specified range.
8
+ # A constraint for validating that comparable values fall within a specified range.
9
9
  #
10
- # This constraint allows for validating numeric values against minimum and maximum
11
- # boundaries. It supports specifying either or both boundaries, allowing for
12
- # open-ended ranges when appropriate.
10
+ # This constraint allows for validating any values that implement comparison operators
11
+ # (<, <=, >, >=) against minimum and maximum boundaries. It supports specifying either
12
+ # or both boundaries, allowing for open-ended ranges when appropriate. This makes it
13
+ # suitable for numeric ranges, date/time ranges, or any other comparable types.
13
14
  #
14
- # @example Validating with both minimum and maximum
15
+ # @example Validating numeric ranges
15
16
  # constraint = RangeConstraint.new(:self, { minimum: 1, maximum: 10 })
16
17
  # constraint.satisfied?(5) # => true
17
18
  # constraint.satisfied?(15) # => false
18
19
  #
19
- # @example Validating with only minimum
20
- # constraint = RangeConstraint.new(:self, { minimum: 0 })
21
- # constraint.satisfied?(10) # => true
22
- # constraint.satisfied?(-1) # => false
20
+ # @example Validating date ranges
21
+ # constraint = RangeConstraint.new(:self, {
22
+ # minimum: Date.new(2024, 1, 1),
23
+ # maximum: Date.new(2024, 12, 31)
24
+ # })
25
+ # constraint.satisfied?(Date.new(2024, 6, 15)) # => true
26
+ # constraint.satisfied?(Date.new(2023, 12, 31)) # => false
23
27
  #
24
- # @example Validating with only maximum
25
- # constraint = RangeConstraint.new(:self, { maximum: 100 })
26
- # constraint.satisfied?(50) # => true
27
- # constraint.satisfied?(150) # => false
28
+ # @example Validating with only minimum
29
+ # constraint = RangeConstraint.new(:self, { minimum: Time.now })
30
+ # constraint.satisfied?(Time.now + 3600) # => true
31
+ # constraint.satisfied?(Time.now - 3600) # => false
28
32
  #
29
33
  # @author {https://aaronmallen.me Aaron Allen}
30
34
  # @since 0.1.0
31
35
  class RangeConstraint
32
36
  # @rbs!
33
- # type expected = { ?minimum: Numeric, ?maximum: Numeric }
37
+ # interface _Compatible
38
+ # def <: (untyped other) -> bool
39
+ #
40
+ # def <=: (untyped other) -> bool
41
+ #
42
+ # def >: (untyped other) -> bool
43
+ #
44
+ # def >=: (untyped other) -> bool
45
+ #
46
+ # def inspect: () -> untyped
47
+ #
48
+ # def nil?: () -> bool
49
+ #
50
+ # def send: (Symbol method_name, *untyped arguments, **untyped keyword_arguments) -> untyped
51
+ # end
52
+ #
53
+ # type expected = { ?minimum: _Compatible, ?maximum: _Compatible}
34
54
  #
35
55
  # type options = { ?inclusive: bool }
36
56
 
37
- include Behavior #[expected, Numeric, options]
57
+ include Behavior #[expected, _Compatible, options]
38
58
 
39
59
  # Get a human-readable description of the range constraint.
40
60
  #
@@ -55,8 +75,8 @@ module Domainic
55
75
  # @rbs override
56
76
  def short_description
57
77
  min, max = @expected.values_at(:minimum, :maximum)
58
- min_description = "greater than or equal to #{min}"
59
- max_description = "less than or equal to #{max}"
78
+ min_description = "greater than#{inclusive? ? ' or equal to' : ''} #{min}"
79
+ max_description = "less than#{inclusive? ? ' or equal to' : ''} #{max}"
60
80
 
61
81
  return "#{min_description} and #{max_description}" unless min.nil? || max.nil?
62
82
  return min_description unless min.nil?
@@ -91,10 +111,10 @@ module Domainic
91
111
  # @rbs override
92
112
  def satisfies_constraint?
93
113
  min, max = @expected.values_at(:minimum, :maximum)
94
- min_comparison, max_comparison = @options.fetch(:inclusive, true) ? %i[>= <=] : %i[> <]
114
+ min_comparison, max_comparison = inclusive? ? %i[>= <=] : %i[> <]
95
115
 
96
- @actual.send(min_comparison, (min || -Float::INFINITY)) &&
97
- @actual.send(max_comparison, (max || Float::INFINITY))
116
+ (min.nil? ? true : @actual.send(min_comparison, min)) &&
117
+ (max.nil? ? true : @actual.send(max_comparison, max))
98
118
  end
99
119
 
100
120
  # Validate that the expected value is a properly formatted range specification.
@@ -112,6 +132,16 @@ module Domainic
112
132
  validate_minimum_and_maximum!(expectation)
113
133
  end
114
134
 
135
+ private
136
+
137
+ # Check if the range constraint is inclusive.
138
+ #
139
+ # @return [Boolean] `true` if the range is inclusive, `false` otherwise
140
+ # @rbs () -> bool
141
+ def inclusive?
142
+ @options.fetch(:inclusive, true) #: bool
143
+ end
144
+
115
145
  # Validate the minimum and maximum values in a range specification.
116
146
  #
117
147
  # @param expectation [Hash] The range specification to validate
@@ -120,15 +150,28 @@ module Domainic
120
150
  # @return [void]
121
151
  # @rbs (untyped expectation) -> void
122
152
  def validate_minimum_and_maximum!(expectation)
123
- expectation.each_pair do |property, value|
124
- raise ArgumentError, ":#{property} must be a Numeric" unless value.nil? || value.is_a?(Numeric)
125
- end
153
+ validate_minimum_and_maximum_types!(expectation)
126
154
 
127
155
  min, max = expectation.values_at(:minimum, :maximum)
128
156
  return if min.nil? || max.nil? || min <= max
129
157
 
130
158
  raise ArgumentError, ':minimum must be less than or equal to :maximum'
131
159
  end
160
+
161
+ # Validate the minimum and maximum value types are compatible with the constraint.
162
+ #
163
+ # @param expectation [Hash] The range specification to validate
164
+ #
165
+ # @raise [ArgumentError] if the values are invalid
166
+ # @return [void]
167
+ # @rbs (untyped expectation) -> void
168
+ def validate_minimum_and_maximum_types!(expectation)
169
+ expectation.each_pair do |property, value|
170
+ unless value.nil? || (%i[< <= > >=].all? { |m| value.respond_to?(m) })
171
+ raise ArgumentError, ":#{property}: #{value} is not compatible with RangeConstraint"
172
+ end
173
+ end
174
+ end
132
175
  end
133
176
  end
134
177
  end
@@ -133,6 +133,68 @@ module Domainic
133
133
  end
134
134
  alias _Cuid? _CUID?
135
135
 
136
+ # Creates a DateType instance.
137
+ #
138
+ # DateType restricts values to valid `Date` objects.
139
+ #
140
+ # @example
141
+ # type = _Date
142
+ # type.validate(Date.new(2024, 1, 1)) # => true
143
+ #
144
+ # @param options [Hash] additional configuration options
145
+ #
146
+ # @return [Domainic::Type::DateType] the created type
147
+ # @rbs (**__todo__ options) -> DateType
148
+ def _Date(**options)
149
+ require 'domainic/type/types/datetime/date_type'
150
+ Domainic::Type::DateType.new(**options)
151
+ end
152
+
153
+ # Creates a nilable DateType instance.
154
+ #
155
+ # @example
156
+ # _Date? === Date.new(2024, 1, 1) # => true
157
+ # _Date? === nil # => true
158
+ #
159
+ # @param options [Hash] additional configuration options
160
+ #
161
+ # @return [Domainic::Type::UnionType] the created type (DateType or NilClass)
162
+ # @rbs (**__todo__ options) -> UnionType
163
+ def _Date?(**options)
164
+ _Nilable(_Date(**options))
165
+ end
166
+
167
+ # Creates a DateTimeType instance.
168
+ #
169
+ # DateTimeType restricts values to valid `DateTime` objects.
170
+ #
171
+ # @example
172
+ # type = _DateTime
173
+ # type.validate(DateTime.new(2024, 1, 1, 12, 0, 0)) # => true
174
+ #
175
+ # @param options [Hash] additional configuration options
176
+ #
177
+ # @return [Domainic::Type::DateTimeType] the created type
178
+ # @rbs (**__todo__ options) -> DateTimeType
179
+ def _DateTime(**options)
180
+ require 'domainic/type/types/datetime/date_time_type'
181
+ Domainic::Type::DateTimeType.new(**options)
182
+ end
183
+
184
+ # Creates a nilable DateTimeType instance.
185
+ #
186
+ # @example
187
+ # _DateTime? === DateTime.new(2024, 1, 1, 12, 0, 0) # => true
188
+ # _DateTime? === nil # => true
189
+ #
190
+ # @param options [Hash] additional configuration options
191
+ #
192
+ # @return [Domainic::Type::UnionType] the created type (DateTimeType or NilClass)
193
+ # @rbs (**__todo__ options) -> UnionType
194
+ def _DateTime?(**options)
195
+ _Nilable(_DateTime(**options))
196
+ end
197
+
136
198
  # Creates a DuckType instance.
137
199
  #
138
200
  # DuckType allows specifying behavior based on method availability.
@@ -475,6 +537,73 @@ module Domainic
475
537
  end
476
538
  alias _Interned? _Symbol?
477
539
 
540
+ # Creates a TimeType instance.
541
+ #
542
+ # TimeType restricts values to valid `Time` objects.
543
+ #
544
+ # @example
545
+ # type = _Time
546
+ # type.validate(Time.new(2024, 1, 1, 12, 0, 0)) # => true
547
+ #
548
+ # @param options [Hash] additional configuration options
549
+ #
550
+ # @return [Domainic::Type::TimeType] the created type
551
+ # @rbs (**__todo__ options) -> TimeType
552
+ def _Time(**options)
553
+ require 'domainic/type/types/datetime/time_type'
554
+ Domainic::Type::TimeType.new(**options)
555
+ end
556
+
557
+ # Creates a nilable TimeType instance.
558
+ #
559
+ # @example
560
+ # _Time? === Time.new(2024, 1, 1, 12, 0, 0) # => true
561
+ # _Time? === nil # => true
562
+ #
563
+ # @param options [Hash] additional configuration options
564
+ #
565
+ # @return [Domainic::Type::UnionType] the created type (TimeType or NilClass)
566
+ # @rbs (**__todo__ options) -> UnionType
567
+ def _Time?(**options)
568
+ _Nilable(_Time(**options))
569
+ end
570
+
571
+ # Creates a URIType instance.
572
+ #
573
+ # URIType restricts values to valid URIs.
574
+ #
575
+ # @example
576
+ # type = _URI.having_scheme('https')
577
+ #
578
+ # @param options [Hash] additional configuration options
579
+ #
580
+ # @return [Domainic::Type::URIType] the created type
581
+ # @rbs (**__todo__ options) -> URIType
582
+ def _URI(**options)
583
+ require 'domainic/type/types/network/uri_type'
584
+ Domainic::Type::URIType.new(**options)
585
+ end
586
+ alias _URL _URI
587
+ alias _Url _URI
588
+ alias _Uri _URI
589
+
590
+ # Creates a nilable URIType instance.
591
+ #
592
+ # @example
593
+ # _Uri?.validate("https://example.com") # => true
594
+ # _Uri?.validate(nil) # => true
595
+ #
596
+ # @param options [Hash] additional configuration options
597
+ #
598
+ # @return [Domainic::Type::UnionType] the created type (URIType or NilClass)
599
+ # @rbs (**__todo__ options) -> UnionType
600
+ def _URI?(**options)
601
+ _Nilable(_Uri(**options))
602
+ end
603
+ alias _URL? _URI?
604
+ alias _Url? _URI?
605
+ alias _Uri? _URI?
606
+
478
607
  # Creates a UUIDType instance
479
608
  #
480
609
  # @example
@@ -523,38 +652,6 @@ module Domainic
523
652
  end
524
653
  alias _Either _Union
525
654
 
526
- # Creates a URIType instance.
527
- #
528
- # URIType restricts values to valid URIs.
529
- #
530
- # @example
531
- # type = _URI.having_scheme('https')
532
- #
533
- # @param options [Hash] additional configuration options
534
- #
535
- # @return [Domainic::Type::URIType] the created type
536
- # @rbs (**__todo__ options) -> URIType
537
- def _Uri(**options)
538
- require 'domainic/type/types/network/uri_type'
539
- Domainic::Type::URIType.new(**options)
540
- end
541
- alias _Url _Uri
542
-
543
- # Creates a nilable URIType instance.
544
- #
545
- # @example
546
- # _Uri?.validate("https://example.com") # => true
547
- # _Uri?.validate(nil) # => true
548
- #
549
- # @param options [Hash] additional configuration options
550
- #
551
- # @return [Domainic::Type::UnionType] the created type (URIType or NilClass)
552
- # @rbs (**__todo__ options) -> UnionType
553
- def _Uri?(**options)
554
- _Nilable(_Uri(**options))
555
- end
556
- alias _Url? _Uri?
557
-
558
655
  # Creates a VoidType instance.
559
656
  #
560
657
  # Represents an operation that returns no value.
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'domainic/type/behavior'
5
+ require 'domainic/type/behavior/date_time_behavior'
6
+
7
+ module Domainic
8
+ module Type
9
+ # A type for validating DateTime objects
10
+ #
11
+ # This type ensures the value is a `DateTime` object and supports comprehensive
12
+ # date-time validation through `DateTimeBehavior`. It is ideal for scenarios requiring
13
+ # precise date-time constraints.
14
+ #
15
+ # Key features:
16
+ # - Ensures the value is a `DateTime` object
17
+ # - Supports chronological relationship validation (e.g., before, after)
18
+ # - Provides range and equality checks
19
+ #
20
+ # @example Basic usage
21
+ # type = DateTimeType.new
22
+ # type.validate(DateTime.now) # => true
23
+ # type.validate(Date.today) # => false
24
+ #
25
+ # @example Range validation
26
+ # type = DateTimeType.new
27
+ # .being_between(DateTime.new(2024, 1, 1, 0, 0, 0), DateTime.new(2024, 12, 31, 23, 59, 59))
28
+ # type.validate(DateTime.new(2024, 6, 15, 12, 0, 0)) # => true
29
+ #
30
+ # @author {https://aaronmallen.me Aaron Allen}
31
+ # @since 0.1.0
32
+ class DateTimeType
33
+ # @rbs! extend Behavior::ClassMethods
34
+
35
+ include Behavior
36
+ include Behavior::DateTimeBehavior
37
+
38
+ intrinsically_constrain :self, :type, DateTime, abort_on_failure: true, description: :not_described
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'domainic/type/behavior'
5
+ require 'domainic/type/behavior/date_time_behavior'
6
+
7
+ module Domainic
8
+ module Type
9
+ # A type for validating Date objects
10
+ #
11
+ # This type provides robust validation for `Date` objects, ensuring values conform
12
+ # to specified chronological constraints. It supports the full range of date-based
13
+ # constraints provided by `DateTimeBehavior`.
14
+ #
15
+ # Key features:
16
+ # - Ensures the value is a `Date` object
17
+ # - Supports validation for chronological relationships (e.g., before, after)
18
+ # - Full integration with `DateTimeBehavior` for range and equality checks
19
+ #
20
+ # @example Basic usage
21
+ # type = DateType.new
22
+ # type.validate(Date.today) # => true
23
+ # type.validate(DateTime.now) # => false
24
+ #
25
+ # @example With range constraints
26
+ # type = DateType.new
27
+ # .being_between(Date.new(2024, 1, 1), Date.new(2024, 12, 31))
28
+ # type.validate(Date.new(2024, 6, 15)) # => true
29
+ # type.validate(Date.new(2023, 12, 31)) # => false
30
+ #
31
+ # @author {https://aaronmallen.me Aaron Allen}
32
+ # @since 0.1.0
33
+ class DateType
34
+ # @rbs! extend Behavior::ClassMethods
35
+
36
+ include Behavior
37
+ include Behavior::DateTimeBehavior
38
+
39
+ intrinsically_constrain :self, :type, Date, abort_on_failure: true, description: :not_described
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'domainic/type/behavior'
5
+ require 'domainic/type/behavior/date_time_behavior'
6
+
7
+ module Domainic
8
+ module Type
9
+ # A type for validating Time objects
10
+ #
11
+ # This type ensures the value is a `Time` object and integrates with
12
+ # `DateTimeBehavior` to provide a rich set of time-based validation capabilities.
13
+ #
14
+ # Key features:
15
+ # - Ensures the value is a `Time` object
16
+ # - Supports chronological relationship constraints (e.g., before, after)
17
+ # - Provides range and equality checks
18
+ #
19
+ # @example Basic usage
20
+ # type = TimeType.new
21
+ # type.validate(Time.now) # => true
22
+ # type.validate(Date.today) # => false
23
+ #
24
+ # @example Range validation
25
+ # type = TimeType.new
26
+ # .being_between(Time.now, Time.now + 3600)
27
+ # type.validate(Time.now + 1800) # => true
28
+ # type.validate(Time.now + 7200) # => false
29
+ #
30
+ # @author {https://aaronmallen.me Aaron Allen}
31
+ # @since 0.1.0
32
+ class TimeType
33
+ # @rbs! extend Behavior::ClassMethods
34
+
35
+ include Behavior
36
+ include Behavior::DateTimeBehavior
37
+
38
+ intrinsically_constrain :self, :type, Time, abort_on_failure: true, description: :not_described
39
+ end
40
+ end
41
+ end
@@ -60,7 +60,7 @@ module Domainic
60
60
  end
61
61
 
62
62
  # Core CUID constraints
63
- intrinsically_constrain :self, :type, String, description: :not_described
63
+ intrinsically_constrain :self, :type, String, abort_on_failure: true, description: :not_described
64
64
 
65
65
  # Accept either v1 or v2 format by default
66
66
  intrinsically_constrain :self, :match_pattern, CUID::ALL_REGEXP,
@@ -176,7 +176,7 @@ module Domainic
176
176
  end
177
177
 
178
178
  # Core UUID constraints based on RFC 4122
179
- intrinsically_constrain :self, :type, String, description: :not_described
179
+ intrinsically_constrain :self, :type, String, abort_on_failure: true, description: :not_described
180
180
 
181
181
  # The base UUID type should accept either standard or compact format
182
182
  intrinsically_constrain :self, :match_pattern,
@@ -47,7 +47,7 @@ module Domainic
47
47
  include Behavior::URIBehavior
48
48
 
49
49
  # Core email constraints based on RFCs 5321 and 5322
50
- intrinsically_constrain :self, :type, String, description: :not_described
50
+ intrinsically_constrain :self, :type, String, abort_on_failure: true, description: :not_described
51
51
  intrinsically_constrain :self, :match_pattern, URI::MailTo::EMAIL_REGEXP, description: :not_described
52
52
  intrinsically_constrain :length, :range, { maximum: 254 }, description: :not_described, concerning: :size
53
53
  intrinsically_constrain :self, :character_set, :ascii, description: :not_described
@@ -60,7 +60,7 @@ module Domainic
60
60
  \z/x #: Regexp
61
61
 
62
62
  # Core hostname constraints based on RFC standards
63
- intrinsically_constrain :self, :type, String, description: :not_described
63
+ intrinsically_constrain :self, :type, String, abort_on_failure: true, description: :not_described
64
64
  intrinsically_constrain :self, :match_pattern, RFC_HOSTNAME_REGEXP, description: :not_described
65
65
  intrinsically_constrain :length, :range, { maximum: 253 }, description: :not_described, concerning: :size
66
66
  intrinsically_constrain :self, :character_set, :ascii, description: :not_described
@@ -52,7 +52,7 @@ module Domainic
52
52
  URI_REGEXP = /\A#{URI::DEFAULT_PARSER.make_regexp}\z/ #: Regexp
53
53
 
54
54
  # Core URI constraints based on RFC standards
55
- intrinsically_constrain :self, :type, String, description: :not_described
55
+ intrinsically_constrain :self, :type, String, abort_on_failure: true, description: :not_described
56
56
  intrinsically_constrain :self, :match_pattern, URI_REGEXP, description: :not_described
57
57
  intrinsically_constrain :self, :character_set, :ascii, description: :not_described
58
58
 
@@ -66,7 +66,7 @@ module Domainic
66
66
  # @return [self] self for method chaining
67
67
  # @rbs (Class | Module | Behavior type) -> self
68
68
  def of(type)
69
- constrain :self, :instance_of, type, description: 'of'
69
+ constrain :self, :instance_of, type, abort_on_failure: true, description: 'of'
70
70
  end
71
71
  end
72
72
  end
@@ -0,0 +1,115 @@
1
+ module Domainic
2
+ module Type
3
+ module Behavior
4
+ # A module providing date/time validation constraint methods
5
+ #
6
+ # This module extends types with methods for constraining date/time values based on
7
+ # chronological relationships. It provides a fluent interface for building complex
8
+ # date/time validation constraints using natural language methods.
9
+ #
10
+ # @example Basic date comparisons
11
+ # type = DateType.new
12
+ # .being_after(Date.new(2024, 1, 1))
13
+ # .being_before(Date.new(2024, 12, 31))
14
+ #
15
+ # @example Range-based validation
16
+ # type = DateType.new
17
+ # .being_between(Date.new(2024, 1, 1), Date.new(2024, 12, 31))
18
+ #
19
+ # @example Inclusive/exclusive bounds
20
+ # type = DateType.new
21
+ # .being_on_or_after(Date.new(2024, 1, 1))
22
+ # .being_on_or_before(Date.new(2024, 12, 31))
23
+ #
24
+ # @author {https://aaronmallen.me Aaron Allen}
25
+ # @since 0.1.0
26
+ module DateTimeBehavior
27
+ TO_DATETIME_COERCER: Proc
28
+
29
+ # Parse arguments for being_between
30
+ #
31
+ # @note this in my opinion is better than polluting the namespace of the including class even with a private
32
+ # method. This way, the method is only available within the module itself. See {#being_between}.
33
+ #
34
+ # @param after [Date, DateTime, String, Time, nil] minimum size value from positional args
35
+ # @param before [Date, DateTime, String, Time, nil] maximum size value from positional args
36
+ # @param options [Hash] keyword arguments containing after/before values
37
+ #
38
+ # @raise [ArgumentError] if minimum or maximum value can't be determined
39
+ # @return [Array<Date, DateTime, String, Time, nil>] parsed [after, before] values
40
+ private def self.parse_being_between_arguments!: ((Date | DateTime | String | Time)? after, (Date | DateTime | String | Time)? before, Hash[Symbol, (Date | DateTime | String | Time)?] options) -> Array[(Date | DateTime | String | Time)?]
41
+
42
+ # Raise appropriate ArgumentError for being_between
43
+ #
44
+ # @param original_caller [Array<String>] caller stack for error
45
+ # @param after [Date, DateTime, String, Time, nil] after value from positional args
46
+ # @param before [Date, DateTime, String, Time, nil] before value from positional args
47
+ # @param options [Hash] keyword arguments containing after/before values
48
+ #
49
+ # @raise [ArgumentError] with appropriate message
50
+ # @return [void]
51
+ private def self.raise_being_between_argument_error!: (Array[String] original_caller, (Date | DateTime | String | Time)? after, (Date | DateTime | String | Time)? before, Hash[Symbol, (Date | DateTime | String | Time)?] options) -> void
52
+
53
+ # Constrain the value to be chronologically after a given date/time
54
+ #
55
+ # @param other [Date, DateTime, String, Time] the date/time to compare against
56
+ # @return [self] self for method chaining
57
+ def being_after: (Date | DateTime | String | Time other) -> Behavior
58
+
59
+ alias after being_after
60
+
61
+ # Constrain the value to be chronologically before a given date/time
62
+ #
63
+ # @param other [Date, DateTime, String, Time] the date/time to compare against
64
+ # @return [self] self for method chaining
65
+ def being_before: (Date | DateTime | String | Time other) -> Behavior
66
+
67
+ alias before being_before
68
+
69
+ # Constrain the value to be chronologically between two date/times
70
+ #
71
+ # @param after [Date, DateTime, String, Time] the earliest allowed date/time
72
+ # @param before [Date, DateTime, String, Time] the latest allowed date/time
73
+ # @param options [Hash] alternative way to specify after/before via keywords
74
+ # @option options [Date, DateTime, String, Time] :after earliest allowed date/time
75
+ # @option options [Date, DateTime, String, Time] :before latest allowed date/time
76
+ # @return [self] self for method chaining
77
+ def being_between: (Date | DateTime | String | Time after, Date | DateTime | String | Time before) -> Behavior
78
+
79
+ alias between being_between
80
+
81
+ # Constrain the value to be exactly equal to a given date/time
82
+ #
83
+ # @param other [Date, DateTime, String, Time] the date/time to compare against
84
+ # @return [self] self for method chaining
85
+ def being_equal_to: (Date | DateTime | String | Time other) -> Behavior
86
+
87
+ alias at being_equal_to
88
+
89
+ # Constrain the value to be chronologically on or after a given date/time
90
+ #
91
+ # @param other [Date, DateTime, String, Time] the date/time to compare against
92
+ # @return [self] self for method chaining
93
+ def being_on_or_after: (Date | DateTime | String | Time other) -> Behavior
94
+
95
+ alias at_or_after being_on_or_after
96
+
97
+ alias being_at_or_after being_on_or_after
98
+
99
+ alias on_or_after being_on_or_after
100
+
101
+ # Constrain the value to be chronologically on or before a given date/time
102
+ #
103
+ # @param other [Date, DateTime, String, Time] the date/time to compare against
104
+ # @return [self] self for method chaining
105
+ def being_on_or_before: (Date | DateTime | String | Time other) -> Behavior
106
+
107
+ alias at_or_before being_on_or_before
108
+
109
+ alias being_at_or_before being_on_or_before
110
+
111
+ alias on_or_before being_on_or_before
112
+ end
113
+ end
114
+ end
115
+ end
@@ -67,7 +67,7 @@ module Domainic
67
67
  #
68
68
  # @raise [ArgumentError] if minimum or maximum value can't be determined
69
69
  # @return [Array<Integer>] parsed [minimum, maximum] values
70
- private def self.parse_having_between_arguments: (Integer? minimum, Integer? maximum, Hash[Symbol, Integer?] options) -> Array[Integer]
70
+ private def self.parse_having_between_arguments!: (Integer? minimum, Integer? maximum, Hash[Symbol, Integer?] options) -> Array[Integer]
71
71
 
72
72
  # Raise appropriate ArgumentError for having_size_between
73
73
  #
@@ -1,35 +1,55 @@
1
1
  module Domainic
2
2
  module Type
3
3
  module Constraint
4
- # A constraint for validating that numeric values fall within a specified range.
4
+ # A constraint for validating that comparable values fall within a specified range.
5
5
  #
6
- # This constraint allows for validating numeric values against minimum and maximum
7
- # boundaries. It supports specifying either or both boundaries, allowing for
8
- # open-ended ranges when appropriate.
6
+ # This constraint allows for validating any values that implement comparison operators
7
+ # (<, <=, >, >=) against minimum and maximum boundaries. It supports specifying either
8
+ # or both boundaries, allowing for open-ended ranges when appropriate. This makes it
9
+ # suitable for numeric ranges, date/time ranges, or any other comparable types.
9
10
  #
10
- # @example Validating with both minimum and maximum
11
+ # @example Validating numeric ranges
11
12
  # constraint = RangeConstraint.new(:self, { minimum: 1, maximum: 10 })
12
13
  # constraint.satisfied?(5) # => true
13
14
  # constraint.satisfied?(15) # => false
14
15
  #
15
- # @example Validating with only minimum
16
- # constraint = RangeConstraint.new(:self, { minimum: 0 })
17
- # constraint.satisfied?(10) # => true
18
- # constraint.satisfied?(-1) # => false
16
+ # @example Validating date ranges
17
+ # constraint = RangeConstraint.new(:self, {
18
+ # minimum: Date.new(2024, 1, 1),
19
+ # maximum: Date.new(2024, 12, 31)
20
+ # })
21
+ # constraint.satisfied?(Date.new(2024, 6, 15)) # => true
22
+ # constraint.satisfied?(Date.new(2023, 12, 31)) # => false
19
23
  #
20
- # @example Validating with only maximum
21
- # constraint = RangeConstraint.new(:self, { maximum: 100 })
22
- # constraint.satisfied?(50) # => true
23
- # constraint.satisfied?(150) # => false
24
+ # @example Validating with only minimum
25
+ # constraint = RangeConstraint.new(:self, { minimum: Time.now })
26
+ # constraint.satisfied?(Time.now + 3600) # => true
27
+ # constraint.satisfied?(Time.now - 3600) # => false
24
28
  #
25
29
  # @author {https://aaronmallen.me Aaron Allen}
26
30
  # @since 0.1.0
27
31
  class RangeConstraint
28
- type expected = { ?minimum: Numeric, ?maximum: Numeric }
32
+ interface _Compatible
33
+ def <: (untyped other) -> bool
34
+
35
+ def <=: (untyped other) -> bool
36
+
37
+ def >: (untyped other) -> bool
38
+
39
+ def >=: (untyped other) -> bool
40
+
41
+ def inspect: () -> untyped
42
+
43
+ def nil?: () -> bool
44
+
45
+ def send: (Symbol method_name, *untyped arguments, **untyped keyword_arguments) -> untyped
46
+ end
47
+
48
+ type expected = { ?minimum: _Compatible, ?maximum: _Compatible }
29
49
 
30
50
  type options = { ?inclusive: bool }
31
51
 
32
- include Behavior[expected, Numeric, options]
52
+ include Behavior[expected, _Compatible, options]
33
53
 
34
54
  # Get a human-readable description of the range constraint.
35
55
  #
@@ -75,6 +95,13 @@ module Domainic
75
95
  # @return [void]
76
96
  def validate_expectation!: ...
77
97
 
98
+ private
99
+
100
+ # Check if the range constraint is inclusive.
101
+ #
102
+ # @return [Boolean] `true` if the range is inclusive, `false` otherwise
103
+ def inclusive?: () -> bool
104
+
78
105
  # Validate the minimum and maximum values in a range specification.
79
106
  #
80
107
  # @param expectation [Hash] The range specification to validate
@@ -82,6 +109,14 @@ module Domainic
82
109
  # @raise [ArgumentError] if the values are invalid
83
110
  # @return [void]
84
111
  def validate_minimum_and_maximum!: (untyped expectation) -> void
112
+
113
+ # Validate the minimum and maximum value types are compatible with the constraint.
114
+ #
115
+ # @param expectation [Hash] The range specification to validate
116
+ #
117
+ # @raise [ArgumentError] if the values are invalid
118
+ # @return [void]
119
+ def validate_minimum_and_maximum_types!: (untyped expectation) -> void
85
120
  end
86
121
  end
87
122
  end
@@ -111,6 +111,54 @@ module Domainic
111
111
 
112
112
  alias _Cuid? _CUID?
113
113
 
114
+ # Creates a DateType instance.
115
+ #
116
+ # DateType restricts values to valid `Date` objects.
117
+ #
118
+ # @example
119
+ # type = _Date
120
+ # type.validate(Date.new(2024, 1, 1)) # => true
121
+ #
122
+ # @param options [Hash] additional configuration options
123
+ #
124
+ # @return [Domainic::Type::DateType] the created type
125
+ def _Date: (**__todo__ options) -> DateType
126
+
127
+ # Creates a nilable DateType instance.
128
+ #
129
+ # @example
130
+ # _Date? === Date.new(2024, 1, 1) # => true
131
+ # _Date? === nil # => true
132
+ #
133
+ # @param options [Hash] additional configuration options
134
+ #
135
+ # @return [Domainic::Type::UnionType] the created type (DateType or NilClass)
136
+ def _Date?: (**__todo__ options) -> UnionType
137
+
138
+ # Creates a DateTimeType instance.
139
+ #
140
+ # DateTimeType restricts values to valid `DateTime` objects.
141
+ #
142
+ # @example
143
+ # type = _DateTime
144
+ # type.validate(DateTime.new(2024, 1, 1, 12, 0, 0)) # => true
145
+ #
146
+ # @param options [Hash] additional configuration options
147
+ #
148
+ # @return [Domainic::Type::DateTimeType] the created type
149
+ def _DateTime: (**__todo__ options) -> DateTimeType
150
+
151
+ # Creates a nilable DateTimeType instance.
152
+ #
153
+ # @example
154
+ # _DateTime? === DateTime.new(2024, 1, 1, 12, 0, 0) # => true
155
+ # _DateTime? === nil # => true
156
+ #
157
+ # @param options [Hash] additional configuration options
158
+ #
159
+ # @return [Domainic::Type::UnionType] the created type (DateTimeType or NilClass)
160
+ def _DateTime?: (**__todo__ options) -> UnionType
161
+
114
162
  # Creates a DuckType instance.
115
163
  #
116
164
  # DuckType allows specifying behavior based on method availability.
@@ -395,6 +443,65 @@ module Domainic
395
443
 
396
444
  alias _Interned? _Symbol?
397
445
 
446
+ # Creates a TimeType instance.
447
+ #
448
+ # TimeType restricts values to valid `Time` objects.
449
+ #
450
+ # @example
451
+ # type = _Time
452
+ # type.validate(Time.new(2024, 1, 1, 12, 0, 0)) # => true
453
+ #
454
+ # @param options [Hash] additional configuration options
455
+ #
456
+ # @return [Domainic::Type::TimeType] the created type
457
+ def _Time: (**__todo__ options) -> TimeType
458
+
459
+ # Creates a nilable TimeType instance.
460
+ #
461
+ # @example
462
+ # _Time? === Time.new(2024, 1, 1, 12, 0, 0) # => true
463
+ # _Time? === nil # => true
464
+ #
465
+ # @param options [Hash] additional configuration options
466
+ #
467
+ # @return [Domainic::Type::UnionType] the created type (TimeType or NilClass)
468
+ def _Time?: (**__todo__ options) -> UnionType
469
+
470
+ # Creates a URIType instance.
471
+ #
472
+ # URIType restricts values to valid URIs.
473
+ #
474
+ # @example
475
+ # type = _URI.having_scheme('https')
476
+ #
477
+ # @param options [Hash] additional configuration options
478
+ #
479
+ # @return [Domainic::Type::URIType] the created type
480
+ def _URI: (**__todo__ options) -> URIType
481
+
482
+ alias _URL _URI
483
+
484
+ alias _Url _URI
485
+
486
+ alias _Uri _URI
487
+
488
+ # Creates a nilable URIType instance.
489
+ #
490
+ # @example
491
+ # _Uri?.validate("https://example.com") # => true
492
+ # _Uri?.validate(nil) # => true
493
+ #
494
+ # @param options [Hash] additional configuration options
495
+ #
496
+ # @return [Domainic::Type::UnionType] the created type (URIType or NilClass)
497
+ def _URI?: (**__todo__ options) -> UnionType
498
+
499
+ alias _URL? _URI?
500
+
501
+ alias _Url? _URI?
502
+
503
+ alias _Uri? _URI?
504
+
398
505
  # Creates a UUIDType instance
399
506
  #
400
507
  # @example
@@ -435,33 +542,6 @@ module Domainic
435
542
 
436
543
  alias _Either _Union
437
544
 
438
- # Creates a URIType instance.
439
- #
440
- # URIType restricts values to valid URIs.
441
- #
442
- # @example
443
- # type = _URI.having_scheme('https')
444
- #
445
- # @param options [Hash] additional configuration options
446
- #
447
- # @return [Domainic::Type::URIType] the created type
448
- def _Uri: (**__todo__ options) -> URIType
449
-
450
- alias _Url _Uri
451
-
452
- # Creates a nilable URIType instance.
453
- #
454
- # @example
455
- # _Uri?.validate("https://example.com") # => true
456
- # _Uri?.validate(nil) # => true
457
- #
458
- # @param options [Hash] additional configuration options
459
- #
460
- # @return [Domainic::Type::UnionType] the created type (URIType or NilClass)
461
- def _Uri?: (**__todo__ options) -> UnionType
462
-
463
- alias _Url? _Uri?
464
-
465
545
  # Creates a VoidType instance.
466
546
  #
467
547
  # Represents an operation that returns no value.
@@ -0,0 +1,34 @@
1
+ module Domainic
2
+ module Type
3
+ # A type for validating DateTime objects
4
+ #
5
+ # This type ensures the value is a `DateTime` object and supports comprehensive
6
+ # date-time validation through `DateTimeBehavior`. It is ideal for scenarios requiring
7
+ # precise date-time constraints.
8
+ #
9
+ # Key features:
10
+ # - Ensures the value is a `DateTime` object
11
+ # - Supports chronological relationship validation (e.g., before, after)
12
+ # - Provides range and equality checks
13
+ #
14
+ # @example Basic usage
15
+ # type = DateTimeType.new
16
+ # type.validate(DateTime.now) # => true
17
+ # type.validate(Date.today) # => false
18
+ #
19
+ # @example Range validation
20
+ # type = DateTimeType.new
21
+ # .being_between(DateTime.new(2024, 1, 1, 0, 0, 0), DateTime.new(2024, 12, 31, 23, 59, 59))
22
+ # type.validate(DateTime.new(2024, 6, 15, 12, 0, 0)) # => true
23
+ #
24
+ # @author {https://aaronmallen.me Aaron Allen}
25
+ # @since 0.1.0
26
+ class DateTimeType
27
+ extend Behavior::ClassMethods
28
+
29
+ include Behavior
30
+
31
+ include Behavior::DateTimeBehavior
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ module Domainic
2
+ module Type
3
+ # A type for validating Date objects
4
+ #
5
+ # This type provides robust validation for `Date` objects, ensuring values conform
6
+ # to specified chronological constraints. It supports the full range of date-based
7
+ # constraints provided by `DateTimeBehavior`.
8
+ #
9
+ # Key features:
10
+ # - Ensures the value is a `Date` object
11
+ # - Supports validation for chronological relationships (e.g., before, after)
12
+ # - Full integration with `DateTimeBehavior` for range and equality checks
13
+ #
14
+ # @example Basic usage
15
+ # type = DateType.new
16
+ # type.validate(Date.today) # => true
17
+ # type.validate(DateTime.now) # => false
18
+ #
19
+ # @example With range constraints
20
+ # type = DateType.new
21
+ # .being_between(Date.new(2024, 1, 1), Date.new(2024, 12, 31))
22
+ # type.validate(Date.new(2024, 6, 15)) # => true
23
+ # type.validate(Date.new(2023, 12, 31)) # => false
24
+ #
25
+ # @author {https://aaronmallen.me Aaron Allen}
26
+ # @since 0.1.0
27
+ class DateType
28
+ extend Behavior::ClassMethods
29
+
30
+ include Behavior
31
+
32
+ include Behavior::DateTimeBehavior
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ module Domainic
2
+ module Type
3
+ # A type for validating Time objects
4
+ #
5
+ # This type ensures the value is a `Time` object and integrates with
6
+ # `DateTimeBehavior` to provide a rich set of time-based validation capabilities.
7
+ #
8
+ # Key features:
9
+ # - Ensures the value is a `Time` object
10
+ # - Supports chronological relationship constraints (e.g., before, after)
11
+ # - Provides range and equality checks
12
+ #
13
+ # @example Basic usage
14
+ # type = TimeType.new
15
+ # type.validate(Time.now) # => true
16
+ # type.validate(Date.today) # => false
17
+ #
18
+ # @example Range validation
19
+ # type = TimeType.new
20
+ # .being_between(Time.now, Time.now + 3600)
21
+ # type.validate(Time.now + 1800) # => true
22
+ # type.validate(Time.now + 7200) # => false
23
+ #
24
+ # @author {https://aaronmallen.me Aaron Allen}
25
+ # @since 0.1.0
26
+ class TimeType
27
+ extend Behavior::ClassMethods
28
+
29
+ include Behavior
30
+
31
+ include Behavior::DateTimeBehavior
32
+ end
33
+ end
34
+ end
data/sig/manifest.yaml CHANGED
@@ -1,2 +1,5 @@
1
1
  dependencies:
2
+ - name: forwardable
3
+ - name: date
2
4
  - name: yaml
5
+ - name: uri
metadata CHANGED
@@ -1,7 +1,7 @@
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.1.0
4
+ version: 0.1.0.alpha.3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Allen
@@ -28,6 +28,7 @@ files:
28
28
  - lib/domainic/type.rb
29
29
  - lib/domainic/type/accessors.rb
30
30
  - lib/domainic/type/behavior.rb
31
+ - lib/domainic/type/behavior/date_time_behavior.rb
31
32
  - lib/domainic/type/behavior/enumerable_behavior.rb
32
33
  - lib/domainic/type/behavior/numeric_behavior.rb
33
34
  - lib/domainic/type/behavior/sizable_behavior.rb
@@ -69,6 +70,9 @@ files:
69
70
  - lib/domainic/type/types/core/integer_type.rb
70
71
  - lib/domainic/type/types/core/string_type.rb
71
72
  - lib/domainic/type/types/core/symbol_type.rb
73
+ - lib/domainic/type/types/datetime/date_time_type.rb
74
+ - lib/domainic/type/types/datetime/date_type.rb
75
+ - lib/domainic/type/types/datetime/time_type.rb
72
76
  - lib/domainic/type/types/identifier/cuid_type.rb
73
77
  - lib/domainic/type/types/identifier/uuid_type.rb
74
78
  - lib/domainic/type/types/network/email_address_type.rb
@@ -84,6 +88,7 @@ files:
84
88
  - sig/domainic/type.rbs
85
89
  - sig/domainic/type/accessors.rbs
86
90
  - sig/domainic/type/behavior.rbs
91
+ - sig/domainic/type/behavior/date_time_behavior.rbs
87
92
  - sig/domainic/type/behavior/enumerable_behavior.rbs
88
93
  - sig/domainic/type/behavior/numeric_behavior.rbs
89
94
  - sig/domainic/type/behavior/sizable_behavior.rbs
@@ -124,6 +129,9 @@ files:
124
129
  - sig/domainic/type/types/core/integer_type.rbs
125
130
  - sig/domainic/type/types/core/string_type.rbs
126
131
  - sig/domainic/type/types/core/symbol_type.rbs
132
+ - sig/domainic/type/types/datetime/date_time_type.rbs
133
+ - sig/domainic/type/types/datetime/date_type.rbs
134
+ - sig/domainic/type/types/datetime/time_type.rbs
127
135
  - sig/domainic/type/types/identifier/cuid_type.rbs
128
136
  - sig/domainic/type/types/identifier/uuid_type.rbs
129
137
  - sig/domainic/type/types/network/email_address_type.rbs
@@ -136,15 +144,15 @@ files:
136
144
  - sig/domainic/type/types/specification/union_type.rbs
137
145
  - sig/domainic/type/types/specification/void_type.rbs
138
146
  - sig/manifest.yaml
139
- homepage: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.1.0/domainic-type
147
+ homepage: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.2.0/domainic-type
140
148
  licenses:
141
149
  - MIT
142
150
  metadata:
143
151
  bug_tracker_uri: https://github.com/domainic/domainic/issues
144
- changelog_uri: https://github.com/domainic/domainic/releases/tag/domainic-type-v0.1.0-alpha.3.1.0
145
- homepage_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.1.0/domainic-type
152
+ changelog_uri: https://github.com/domainic/domainic/releases/tag/domainic-type-v0.1.0-alpha.3.2.0
153
+ homepage_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.2.0/domainic-type
146
154
  rubygems_mfa_required: 'true'
147
- source_code_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.1.0/domainic-type
155
+ source_code_uri: https://github.com/domainic/domainic/tree/domainic-type-v0.1.0-alpha.3.2.0/domainic-type
148
156
  post_install_message:
149
157
  rdoc_options: []
150
158
  require_paths: