domainic-type 0.1.0.alpha.3.2.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/README.md +66 -10
- data/docs/USAGE.md +787 -0
- data/lib/domainic/type/accessors.rb +3 -2
- data/lib/domainic/type/behavior/date_time_behavior.rb +121 -37
- data/lib/domainic/type/behavior.rb +16 -0
- data/lib/domainic/type/config/registry.yml +24 -0
- data/lib/domainic/type/constraint/constraints/nor_constraint.rb +1 -1
- data/lib/domainic/type/constraint/constraints/predicate_constraint.rb +76 -0
- data/lib/domainic/type/definitions.rb +212 -0
- data/lib/domainic/type/types/core/complex_type.rb +122 -0
- data/lib/domainic/type/types/core/range_type.rb +47 -0
- data/lib/domainic/type/types/core/rational_type.rb +38 -0
- data/lib/domainic/type/types/core_extended/big_decimal_type.rb +34 -0
- data/lib/domainic/type/types/core_extended/set_type.rb +34 -0
- data/lib/domainic/type/types/datetime/date_time_string_type.rb +156 -0
- data/lib/domainic/type/types/datetime/timestamp_type.rb +50 -0
- data/sig/domainic/type/accessors.rbs +2 -2
- data/sig/domainic/type/behavior/date_time_behavior.rbs +35 -23
- data/sig/domainic/type/behavior.rbs +9 -0
- data/sig/domainic/type/constraint/constraints/predicate_constraint.rbs +56 -0
- data/sig/domainic/type/definitions.rbs +165 -0
- data/sig/domainic/type/types/core/complex_type.rbs +96 -0
- data/sig/domainic/type/types/core/range_type.rbs +41 -0
- data/sig/domainic/type/types/core/rational_type.rbs +32 -0
- data/sig/domainic/type/types/core_extended/big_decimal_type.rbs +27 -0
- data/sig/domainic/type/types/core_extended/set_type.rbs +27 -0
- data/sig/domainic/type/types/datetime/date_time_string_type.rbs +124 -0
- data/sig/domainic/type/types/datetime/timestamp_type.rbs +44 -0
- metadata +25 -6
data/docs/USAGE.md
ADDED
@@ -0,0 +1,787 @@
|
|
1
|
+
# Domainic::Type Usage Guide
|
2
|
+
|
3
|
+
A comprehensive guide to all features and capabilities of Domainic::Type. See the [README.md](../README.md) for a quick
|
4
|
+
introduction and installation instructions.
|
5
|
+
|
6
|
+
## Table of Contents
|
7
|
+
|
8
|
+
* [About](#about)
|
9
|
+
* [Core Concepts](#core-concepts)
|
10
|
+
* [Basic Type Validation](#basic-type-validation)
|
11
|
+
* [Type Constraints](#type-constraints)
|
12
|
+
* [Custom Constraints](#custom-constraints)
|
13
|
+
* [Error Messages](#error-messages)
|
14
|
+
* [Built-in Types](#built-in-types)
|
15
|
+
* [Simple Types](#simple-types)
|
16
|
+
* [Collection Types](#collection-types)
|
17
|
+
* [Date and Time Types](#date-and-time-types)
|
18
|
+
* [Network Types](#network-types)
|
19
|
+
* [Identifier Types](#identifier-types)
|
20
|
+
* [Specification Types](#specification-types)
|
21
|
+
* [Advanced Usage](#advanced-usage)
|
22
|
+
* [Custom Types](#custom-types)
|
23
|
+
* [Type Composition](#type-composition)
|
24
|
+
* [Integration Patterns](#integration-patterns)
|
25
|
+
|
26
|
+
## About
|
27
|
+
|
28
|
+
Stop wrestling with complex type validations and unclear error messages. Domainic::Type brings type validation to Ruby
|
29
|
+
that is both powerful and delightful to use. Build composable type constraints with crystal-clear error messages that
|
30
|
+
actually tell you what went wrong. From simple type checks to complex collection validations, make your types work for
|
31
|
+
you, not against you!
|
32
|
+
|
33
|
+
Key features include:
|
34
|
+
|
35
|
+
* Rich set of built-in types with purpose-specific validation:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
_String.being_uppercase.matching(/^[A-Z]+$/) # String validation
|
39
|
+
_Integer.being_positive.being_even # Number validation
|
40
|
+
_Array.of(_String).being_distinct # Collection validation
|
41
|
+
_EmailAddress.having_hostname("example.com") # Email validation
|
42
|
+
_UUID.being_version(4).being_standard # ID validation
|
43
|
+
```
|
44
|
+
|
45
|
+
* Composable type constraints for precise validation rules:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
username = _String
|
49
|
+
.being_lowercase # Must be lowercase
|
50
|
+
.being_alphanumeric # Letters and numbers only
|
51
|
+
.having_size_between(3, 20) # Length constraint
|
52
|
+
```
|
53
|
+
|
54
|
+
* Clear, actionable error messages that explain what failed:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
username.validate!("BAD!")
|
58
|
+
# => TypeError: Expected String(being lowercase, having size between 3 and 20),
|
59
|
+
# got String(not lowercase, having size 4)
|
60
|
+
```
|
61
|
+
|
62
|
+
* Custom type definitions for reusable validation:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
Username = _String.being_lowercase.having_size_between(3, 20)
|
66
|
+
EmailAddress = _String.matching(URI::MailTo::EMAIL_REGEXP)
|
67
|
+
```
|
68
|
+
|
69
|
+
* Full integration with Ruby's pattern matching:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
case value
|
73
|
+
when _String.being_uppercase then puts "Uppercase string"
|
74
|
+
when _Integer.being_positive then puts "Positive number"
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
* Support for optional values and complex type combinations:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
_Nilable(_String) # String or nil (also as _String?)
|
82
|
+
_Union(_String, _Integer) # String or Integer
|
83
|
+
```
|
84
|
+
|
85
|
+
## Core Concepts
|
86
|
+
|
87
|
+
### Basic Type Validation
|
88
|
+
|
89
|
+
Domainic::Type provides two main validation methods:
|
90
|
+
|
91
|
+
* `validate` - Returns true/false without raising errors
|
92
|
+
* `validate!` - Raises TypeError for invalid values
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
require 'domainic/type/definitions'
|
96
|
+
include Domainic::Type::Definitions
|
97
|
+
|
98
|
+
string_type = _String
|
99
|
+
string_type.validate("hello") # => true
|
100
|
+
string_type.validate(123) # => false
|
101
|
+
string_type.validate!("hello") # => true
|
102
|
+
string_type.validate!(123) # => TypeError: Expected String, but got Integer
|
103
|
+
```
|
104
|
+
|
105
|
+
All types also support the case equality operator (===):
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
case value
|
109
|
+
when _String then puts "It's a string!"
|
110
|
+
when _Integer then puts "It's an integer!"
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
### Type Constraints
|
115
|
+
|
116
|
+
Types can be constrained with additional validation rules. Constraints are chainable and combine logically. When
|
117
|
+
multiple constraints are applied, they form a single compound constraint that must be fully satisfied:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
# String with multiple constraints
|
121
|
+
username = _String
|
122
|
+
.being_lowercase # Must be lowercase
|
123
|
+
.being_alphanumeric # Only letters and numbers
|
124
|
+
.having_size_between(3, 20) # Length between 3-20 chars
|
125
|
+
.not_matching(/^admin/i) # Can't start with 'admin'
|
126
|
+
|
127
|
+
# Array with element and size constraints
|
128
|
+
numbers = _Array
|
129
|
+
.of(_Integer) # Elements must be integers
|
130
|
+
.being_ordered # Must be sorted
|
131
|
+
.having_minimum_size(1) # At least one element
|
132
|
+
.containing(42) # Must include 42
|
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
|
+
|
180
|
+
### Error Messages
|
181
|
+
|
182
|
+
Error messages clearly indicate what validation failed and why. For compound constraints, the error message shows all
|
183
|
+
failing constraints to help pinpoint the exact issues:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
# Type mismatch
|
187
|
+
_String.validate!(123)
|
188
|
+
# => TypeError: Expected String, but got Integer
|
189
|
+
|
190
|
+
# Constraint violation
|
191
|
+
_String.being_uppercase.validate!("hello")
|
192
|
+
# => TypeError: Expected String(being upper case), but got String(not upper case)
|
193
|
+
|
194
|
+
# Multiple constraints
|
195
|
+
_Integer.being_positive.being_even.validate!(-2)
|
196
|
+
# => TypeError: Expected Integer(being positive), got Integer(negative)
|
197
|
+
|
198
|
+
# Complex validation
|
199
|
+
_Array.of(_String.being_uppercase).validate!(["Hello", "WORLD"])
|
200
|
+
# => TypeError: Expected Array of String(being upper case), got Array containing String(not upper case)
|
201
|
+
```
|
202
|
+
|
203
|
+
## Built-in Types
|
204
|
+
|
205
|
+
### Simple Types
|
206
|
+
|
207
|
+
#### _String
|
208
|
+
|
209
|
+
String validation with comprehensive text manipulation constraints:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
_String # Basic string validation
|
213
|
+
.being_ascii # ASCII characters only
|
214
|
+
.being_alphanumeric # Letters and numbers only
|
215
|
+
.being_lowercase # Must be lowercase
|
216
|
+
.being_uppercase # Must be uppercase
|
217
|
+
.being_titlecase # Title Case Format
|
218
|
+
.being_empty # Must be empty
|
219
|
+
.being_printable # Printable characters only
|
220
|
+
.containing("text") # Must contain substring
|
221
|
+
.excluding("bad") # Must not contain substring
|
222
|
+
.matching(/pattern/) # Must match pattern
|
223
|
+
.not_matching(/admin/) # Must not match pattern
|
224
|
+
.having_size(10) # Exact length
|
225
|
+
.having_size_between(1, 100) # Length range
|
226
|
+
```
|
227
|
+
|
228
|
+
Also available as `_Text` and in nilable variants `_String?` and `_Text?`.
|
229
|
+
|
230
|
+
#### _Symbol
|
231
|
+
|
232
|
+
Symbol validation with all string constraints applied to the symbol name:
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
_Symbol # Basic symbol validation
|
236
|
+
.being_ascii # ASCII characters only
|
237
|
+
.being_alphanumeric # Letters and numbers only
|
238
|
+
.being_lowercase # Must be lowercase
|
239
|
+
.having_size_between(1, 100) # Name length range
|
240
|
+
.matching(/^[a-z_][a-z0-9_]*$/) # Pattern matching
|
241
|
+
```
|
242
|
+
|
243
|
+
Also available as `_Interned` and in nilable variants `_Symbol?` and `_Interned?`.
|
244
|
+
|
245
|
+
#### _Integer
|
246
|
+
|
247
|
+
Integer validation with numeric constraints:
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
_Integer # Basic integer validation
|
251
|
+
.being_positive # Must be positive
|
252
|
+
.being_negative # Must be negative
|
253
|
+
.being_even # Must be even
|
254
|
+
.being_odd # Must be odd
|
255
|
+
.being_zero # Must be zero
|
256
|
+
.being_divisible_by(3) # Must be divisible by 3
|
257
|
+
.being_greater_than(0) # Must be > 0
|
258
|
+
.being_less_than(100) # Must be < 100
|
259
|
+
.being_between(1, 10) # Must be between 1 and 10
|
260
|
+
```
|
261
|
+
|
262
|
+
Also available as `_Int`, `_Number` and in nilable variants `_Integer?`, `_Int?`, `_Number?`.
|
263
|
+
|
264
|
+
#### _Float
|
265
|
+
|
266
|
+
Float validation with numeric constraints:
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
_Float # Basic float validation
|
270
|
+
.being_positive # Must be positive
|
271
|
+
.being_negative # Must be negative
|
272
|
+
.being_finite # Must be finite
|
273
|
+
.being_infinite # Must be infinite
|
274
|
+
.being_divisible_by(0.5) # Must be divisible by 0.5
|
275
|
+
.being_greater_than(0.0) # Must be > 0.0
|
276
|
+
.being_less_than(1.0) # Must be < 1.0
|
277
|
+
.being_between(0.0, 1.0) # Must be between 0.0 and 1.0
|
278
|
+
```
|
279
|
+
|
280
|
+
Also available as `_Decimal`, `_Real` and in nilable variants `_Float?`, `_Decimal?`, `_Real?`.
|
281
|
+
|
282
|
+
#### _Boolean
|
283
|
+
|
284
|
+
Boolean validation accepting only true or false:
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
_Boolean === true # => true
|
288
|
+
_Boolean === false # => true
|
289
|
+
_Boolean === 1 # => false
|
290
|
+
_Boolean === 'true' # => false
|
291
|
+
```
|
292
|
+
|
293
|
+
Also available as `_Bool` and in nilable variants `_Boolean?`, `_Bool?`.
|
294
|
+
|
295
|
+
#### _BigDecimal
|
296
|
+
|
297
|
+
Validation for arbitrary-precision decimal numbers:
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
_BigDecimal # Basic BigDecimal validation
|
301
|
+
.being_positive # Must be positive
|
302
|
+
.being_negative # Must be negative
|
303
|
+
.being_finite # Must be finite
|
304
|
+
.being_infinite # Must be infinite
|
305
|
+
.being_divisible_by( # Must be divisible by 0.5
|
306
|
+
BigDecimal('0.5')
|
307
|
+
)
|
308
|
+
.being_greater_than( # Must be > 0
|
309
|
+
BigDecimal('0')
|
310
|
+
)
|
311
|
+
.being_less_than( # Must be < 1
|
312
|
+
BigDecimal('1')
|
313
|
+
)
|
314
|
+
```
|
315
|
+
|
316
|
+
Also available in nilable variant `_BigDecimal?`.
|
317
|
+
|
318
|
+
#### _Complex
|
319
|
+
|
320
|
+
Complex number validation:
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
_Complex # Basic Complex validation
|
324
|
+
.being_divisible_by(2) # Real part divisible by 2
|
325
|
+
.being_positive # Real part must be positive
|
326
|
+
.being_negative # Real part must be negative
|
327
|
+
.being_even # Real part must be even
|
328
|
+
.being_odd # Real part must be odd
|
329
|
+
```
|
330
|
+
|
331
|
+
Also available in nilable variant `_Complex?`.
|
332
|
+
|
333
|
+
#### _Rational
|
334
|
+
|
335
|
+
Rational number validation:
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
_Rational # Basic Rational validation
|
339
|
+
.being_positive # Must be positive
|
340
|
+
.being_negative # Must be negative
|
341
|
+
.being_divisible_by( # Must be divisible by 1/2
|
342
|
+
Rational(1, 2)
|
343
|
+
)
|
344
|
+
.being_greater_than( # Must be > 0
|
345
|
+
Rational(0)
|
346
|
+
)
|
347
|
+
.being_less_than( # Must be < 1
|
348
|
+
Rational(1)
|
349
|
+
)
|
350
|
+
```
|
351
|
+
|
352
|
+
Also available in nilable variant `_Rational?`.
|
353
|
+
|
354
|
+
### Collection Types
|
355
|
+
|
356
|
+
#### _Array
|
357
|
+
|
358
|
+
Array validation with element and collection constraints:
|
359
|
+
|
360
|
+
```ruby
|
361
|
+
_Array # Basic array validation
|
362
|
+
.of(_String) # Element type constraint
|
363
|
+
.being_empty # Must be empty
|
364
|
+
.being_populated # Must not be empty
|
365
|
+
.being_distinct # No duplicate elements
|
366
|
+
.being_ordered # Must be sorted
|
367
|
+
.containing(1, 2) # Must contain elements
|
368
|
+
.excluding(3, 4) # Must not contain elements
|
369
|
+
.having_size(3) # Exact size
|
370
|
+
.having_minimum_size(1) # Minimum size
|
371
|
+
.having_maximum_size(10) # Maximum size
|
372
|
+
```
|
373
|
+
|
374
|
+
Also available as `_List` and in nilable variants `_Array?`, `_List?`.
|
375
|
+
|
376
|
+
#### _Hash
|
377
|
+
|
378
|
+
Hash validation with key/value and collection constraints:
|
379
|
+
|
380
|
+
```ruby
|
381
|
+
_Hash # Basic hash validation
|
382
|
+
.of(_Symbol => _String) # Key/value type constraints
|
383
|
+
.containing_keys(:a, :b) # Required keys
|
384
|
+
.excluding_keys(:c, :d) # Forbidden keys
|
385
|
+
.containing_values(1, 2) # Required values
|
386
|
+
.excluding_values(nil) # Forbidden values
|
387
|
+
.having_size(3) # Exact size
|
388
|
+
.having_minimum_size(1) # Minimum size
|
389
|
+
```
|
390
|
+
|
391
|
+
Also available as `_Map` and in nilable variants `_Hash?`, `_Map?`.
|
392
|
+
|
393
|
+
#### _Range
|
394
|
+
|
395
|
+
Range validation with element constraints:
|
396
|
+
|
397
|
+
```ruby
|
398
|
+
_Range # Basic Range validation
|
399
|
+
.being_empty # Must be empty
|
400
|
+
.being_populated # Must not be empty
|
401
|
+
.containing(5) # Must include specific value
|
402
|
+
.excluding(0) # Must not include specific value
|
403
|
+
```
|
404
|
+
|
405
|
+
Also available in nilable variant `_Range?`.
|
406
|
+
|
407
|
+
#### _Set
|
408
|
+
|
409
|
+
Set validation with element and collection constraints:
|
410
|
+
|
411
|
+
```ruby
|
412
|
+
_Set # Basic Set validation
|
413
|
+
.of(_String) # Element type constraint
|
414
|
+
.being_empty # Must be empty
|
415
|
+
.being_populated # Must not be empty
|
416
|
+
.being_distinct # No duplicate elements
|
417
|
+
.containing(1, 2) # Must contain elements
|
418
|
+
.excluding(3, 4) # Must not contain elements
|
419
|
+
.having_size(3) # Exact size
|
420
|
+
```
|
421
|
+
|
422
|
+
Also available in nilable variant `_Set?`.
|
423
|
+
|
424
|
+
### Date and Time Types
|
425
|
+
|
426
|
+
#### _Date
|
427
|
+
|
428
|
+
Date validation with chronological constraints:
|
429
|
+
|
430
|
+
```ruby
|
431
|
+
_Date # Basic date validation
|
432
|
+
.being_after(Date.today) # Must be after date
|
433
|
+
.being_before(Date.today) # Must be before date
|
434
|
+
.being_between( # Must be in date range
|
435
|
+
Date.today,
|
436
|
+
Date.today + 30
|
437
|
+
)
|
438
|
+
```
|
439
|
+
|
440
|
+
Available in nilable variant `_Date?`.
|
441
|
+
|
442
|
+
#### _DateTime
|
443
|
+
|
444
|
+
DateTime validation with chronological constraints:
|
445
|
+
|
446
|
+
```ruby
|
447
|
+
_DateTime # Basic datetime validation
|
448
|
+
.being_after(DateTime.now) # Must be after datetime
|
449
|
+
.being_before(DateTime.now) # Must be before datetime
|
450
|
+
.being_on_or_after( # Must be >= datetime
|
451
|
+
DateTime.now
|
452
|
+
)
|
453
|
+
```
|
454
|
+
|
455
|
+
Available in nilable variant `_DateTime?`.
|
456
|
+
|
457
|
+
#### _DateTimeString
|
458
|
+
|
459
|
+
String-based datetime validation with format constraints and full datetime behavior support:
|
460
|
+
|
461
|
+
```ruby
|
462
|
+
_DateTimeString # Basic datetime string validation
|
463
|
+
# Format constraints
|
464
|
+
.having_american_format # MM/DD/YYYY format
|
465
|
+
.having_european_format # DD.MM.YYYY format
|
466
|
+
.having_iso8601_format # ISO 8601 format
|
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
|
+
)
|
476
|
+
```
|
477
|
+
|
478
|
+
Also available as `_DateString` and in nilable variants `_DateTimeString?`, `_DateString?`.
|
479
|
+
|
480
|
+
#### _Time
|
481
|
+
|
482
|
+
Time validation with chronological constraints:
|
483
|
+
|
484
|
+
```ruby
|
485
|
+
_Time # Basic time validation
|
486
|
+
.being_after(Time.now) # Must be after time
|
487
|
+
.being_before(Time.now) # Must be before time
|
488
|
+
.being_on_or_before( # Must be <= time
|
489
|
+
Time.now
|
490
|
+
)
|
491
|
+
```
|
492
|
+
|
493
|
+
Available in nilable variant `_Time?`.
|
494
|
+
|
495
|
+
#### _Timestamp
|
496
|
+
|
497
|
+
Unix timestamp validation:
|
498
|
+
|
499
|
+
```ruby
|
500
|
+
_Timestamp # Basic timestamp validation
|
501
|
+
.being_after(1640995200) # Must be after timestamp
|
502
|
+
.being_before(1672531200) # Must be before timestamp
|
503
|
+
.being_between( # Must be in range
|
504
|
+
1640995200,
|
505
|
+
1672531200
|
506
|
+
)
|
507
|
+
```
|
508
|
+
|
509
|
+
Available in nilable variant `_Timestamp?`.
|
510
|
+
|
511
|
+
### Network Types
|
512
|
+
|
513
|
+
#### _EmailAddress
|
514
|
+
|
515
|
+
Email validation with domain and format constraints:
|
516
|
+
|
517
|
+
```ruby
|
518
|
+
_EmailAddress # Basic email validation
|
519
|
+
.having_hostname("example.com") # Specific domain
|
520
|
+
.having_local_matching(/^[a-z]+$/) # Username format
|
521
|
+
.not_having_hostname("blocked.com") # Blocked domains
|
522
|
+
```
|
523
|
+
|
524
|
+
Also available as `_Email` and in nilable variants `_EmailAddress?`, `_Email?`.
|
525
|
+
|
526
|
+
#### _URI
|
527
|
+
|
528
|
+
URI validation with component and format constraints:
|
529
|
+
|
530
|
+
```ruby
|
531
|
+
_URI # Basic URI validation
|
532
|
+
.having_scheme("https") # Specific scheme
|
533
|
+
.having_hostname("api.example.com") # Specific host
|
534
|
+
.having_path("/v1/users") # Specific path
|
535
|
+
.not_having_scheme("ftp") # Forbidden scheme
|
536
|
+
```
|
537
|
+
|
538
|
+
Also available as `_URL`, `_Url`, `_Uri` and in nilable variants `_URI?`, `_URL?`, `_Url?`, `_Uri?`.
|
539
|
+
|
540
|
+
#### _Hostname
|
541
|
+
|
542
|
+
Hostname validation according to RFC standards:
|
543
|
+
|
544
|
+
```ruby
|
545
|
+
_Hostname # Basic hostname validation
|
546
|
+
.matching("example.com") # Exact hostname match
|
547
|
+
.not_matching("blocked") # Hostname exclusion
|
548
|
+
.being_ascii # ASCII characters only
|
549
|
+
.having_size_between(1, 253) # Valid length range
|
550
|
+
```
|
551
|
+
|
552
|
+
Available in nilable variant `_Hostname?`.
|
553
|
+
|
554
|
+
### Identifier Types
|
555
|
+
|
556
|
+
#### _UUID
|
557
|
+
|
558
|
+
UUID validation with version and format constraints:
|
559
|
+
|
560
|
+
```ruby
|
561
|
+
_UUID # Basic UUID validation
|
562
|
+
.being_version(4) # Specific UUID version
|
563
|
+
.being_compact # No hyphens format
|
564
|
+
.being_standard # With hyphens format
|
565
|
+
.being_version_four # Must be v4 UUID
|
566
|
+
.being_version_seven # Must be v7 UUID
|
567
|
+
```
|
568
|
+
|
569
|
+
Also available as `_Uuid` and in nilable variants `_UUID?`, `_Uuid?`.
|
570
|
+
|
571
|
+
#### _CUID
|
572
|
+
|
573
|
+
CUID validation with version and format constraints:
|
574
|
+
|
575
|
+
```ruby
|
576
|
+
_CUID # Basic CUID validation
|
577
|
+
.being_version(2) # Specific CUID version
|
578
|
+
.being_version_one # Must be v1 CUID
|
579
|
+
.being_version_two # Must be v2 CUID
|
580
|
+
```
|
581
|
+
|
582
|
+
Also available as `_Cuid` and in nilable variants `_CUID?`, `_Cuid?`.
|
583
|
+
|
584
|
+
#### _ID
|
585
|
+
|
586
|
+
A union type accepting common ID formats:
|
587
|
+
|
588
|
+
```ruby
|
589
|
+
_ID === 123 # Integer IDs
|
590
|
+
_ID === "clh3am1f30000bhqg" # CUIDs
|
591
|
+
_ID === "123e4567-e89b-12d3" # UUIDs
|
592
|
+
```
|
593
|
+
|
594
|
+
Available in nilable variant `_ID?`.
|
595
|
+
|
596
|
+
### Specification Types
|
597
|
+
|
598
|
+
#### _Anything
|
599
|
+
|
600
|
+
Accepts any value except those explicitly excluded:
|
601
|
+
|
602
|
+
```ruby
|
603
|
+
_Anything # Accepts any value
|
604
|
+
_Anything.but(_String) # Anything except strings
|
605
|
+
_Any # Alias for _Anything
|
606
|
+
```
|
607
|
+
|
608
|
+
#### _Duck
|
609
|
+
|
610
|
+
Duck type validation based on method presence:
|
611
|
+
|
612
|
+
```ruby
|
613
|
+
_Duck # Duck type validation
|
614
|
+
.responding_to(:to_s, :to_i) # Must have methods
|
615
|
+
.not_responding_to(:dangerous_op) # Must not have methods
|
616
|
+
```
|
617
|
+
|
618
|
+
Also available as `_Interface`, `_Protocol`, `_RespondingTo`.
|
619
|
+
|
620
|
+
#### _Enum
|
621
|
+
|
622
|
+
Enumerated value validation:
|
623
|
+
|
624
|
+
```ruby
|
625
|
+
_Enum(:red, :green, :blue) # Must be one of values
|
626
|
+
_Enum("draft", "published") # String enumeration
|
627
|
+
```
|
628
|
+
|
629
|
+
Also available as `_Literal` and in nilable variants `_Enum?`, `_Literal?`.
|
630
|
+
|
631
|
+
#### _Instance
|
632
|
+
|
633
|
+
Instance type validation with attribute constraints:
|
634
|
+
|
635
|
+
```ruby
|
636
|
+
_Instance # Instance validation
|
637
|
+
.of(User) # Must be User instance
|
638
|
+
.having_attributes( # With attribute types
|
639
|
+
name: _String,
|
640
|
+
age: _Integer
|
641
|
+
)
|
642
|
+
```
|
643
|
+
|
644
|
+
Also available as `_Record` and in nilable variants `_Instance?`, `_Record?`.
|
645
|
+
|
646
|
+
#### _Union
|
647
|
+
|
648
|
+
Combine multiple types with OR logic:
|
649
|
+
|
650
|
+
```ruby
|
651
|
+
_Union(_String, _Integer) # String OR Integer
|
652
|
+
_Union( # Complex union
|
653
|
+
_String.being_uppercase,
|
654
|
+
_Integer.being_positive
|
655
|
+
)
|
656
|
+
```
|
657
|
+
|
658
|
+
Also available as `_Either`.
|
659
|
+
|
660
|
+
#### _Nilable
|
661
|
+
|
662
|
+
Make any type accept nil values:
|
663
|
+
|
664
|
+
```ruby
|
665
|
+
_Nilable(_String) # String or nil
|
666
|
+
_String? # Same as above
|
667
|
+
```
|
668
|
+
|
669
|
+
Also available as `_Nullable`.
|
670
|
+
|
671
|
+
#### _Void
|
672
|
+
|
673
|
+
Accepts any value, useful for void returns:
|
674
|
+
|
675
|
+
```ruby
|
676
|
+
_Void === nil # => true
|
677
|
+
_Void === false # => true
|
678
|
+
_Void === Object.new # => true
|
679
|
+
```
|
680
|
+
|
681
|
+
## Advanced Usage
|
682
|
+
|
683
|
+
### Custom Types
|
684
|
+
|
685
|
+
Create reusable custom types:
|
686
|
+
|
687
|
+
```ruby
|
688
|
+
module Types
|
689
|
+
extend Domainic::Type::Definitions
|
690
|
+
|
691
|
+
Username = _String
|
692
|
+
.being_lowercase
|
693
|
+
.being_alphanumeric
|
694
|
+
.having_size_between(3, 20)
|
695
|
+
.not_matching(/^admin/i)
|
696
|
+
|
697
|
+
UserPassword = lambda { |username
|
698
|
+
_String
|
699
|
+
.having_size_between(8, 20)
|
700
|
+
.being_mixedcase
|
701
|
+
.not_matching(username)
|
702
|
+
}
|
703
|
+
end
|
704
|
+
|
705
|
+
username = "alice123"
|
706
|
+
Types::Username.validate!(username) # => true
|
707
|
+
Types::UserPassword.call(username).validate!("p@$Sw0rd") # => true
|
708
|
+
```
|
709
|
+
|
710
|
+
### Type Composition
|
711
|
+
|
712
|
+
Build complex type hierarchies:
|
713
|
+
|
714
|
+
```ruby
|
715
|
+
# API response validation
|
716
|
+
ApiResponse = _Hash.of(_Symbol => _Union(
|
717
|
+
# Success case
|
718
|
+
_Hash.of(
|
719
|
+
_Symbol => _Union(
|
720
|
+
_String,
|
721
|
+
_Array.of(_Integer.being_positive),
|
722
|
+
_Hash.of(_Symbol => _Boolean)
|
723
|
+
)
|
724
|
+
),
|
725
|
+
# Error case
|
726
|
+
_Hash.of(_Symbol => _String)
|
727
|
+
.containing_keys(:error, :message)
|
728
|
+
))
|
729
|
+
|
730
|
+
# Form data validation
|
731
|
+
FormData = _Hash.of(
|
732
|
+
_Symbol => _Union(
|
733
|
+
_String.being_alphanumeric.having_size_between(3, 20),
|
734
|
+
_String.matching(URI::MailTo::EMAIL_REGEXP),
|
735
|
+
_Nilable(_Integer.being_greater_than_or_equal_to(18)),
|
736
|
+
_Array.of(_String).having_maximum_size(5)
|
737
|
+
)
|
738
|
+
).containing_keys(:username, :email, :age, :interests)
|
739
|
+
```
|
740
|
+
|
741
|
+
### Integration Patterns
|
742
|
+
|
743
|
+
#### With Classes
|
744
|
+
|
745
|
+
```ruby
|
746
|
+
class User
|
747
|
+
extend Domainic::Type::Definitions
|
748
|
+
|
749
|
+
attr_reader :name, :email
|
750
|
+
|
751
|
+
def initialize(name, email)
|
752
|
+
self.name = name
|
753
|
+
self.email = email
|
754
|
+
end
|
755
|
+
|
756
|
+
def name=(value)
|
757
|
+
_String
|
758
|
+
.having_size_between(2, 50)
|
759
|
+
.validate!(value)
|
760
|
+
@name = value
|
761
|
+
end
|
762
|
+
|
763
|
+
def email=(value)
|
764
|
+
_EmailAddress.validate!(value)
|
765
|
+
@email = value
|
766
|
+
end
|
767
|
+
end
|
768
|
+
```
|
769
|
+
|
770
|
+
#### With Domainic::Attributer
|
771
|
+
|
772
|
+
```ruby
|
773
|
+
require 'domainic/attributer'
|
774
|
+
require 'domainic/type/definitions'
|
775
|
+
|
776
|
+
class Configuration
|
777
|
+
extend Domainic::Type::Definitions
|
778
|
+
include Domainic::Attributer
|
779
|
+
|
780
|
+
argument :environment, _Enum(:development, :test, :production)
|
781
|
+
argument :log_level, _Enum(:debug, :info, :warn, :error)
|
782
|
+
|
783
|
+
option :database_url, _String.matching(%r{\Apostgres://})
|
784
|
+
option :redis_url, _String.matching(%r{\Aredis://})
|
785
|
+
option :api_keys, _Array.of(_String).being_distinct
|
786
|
+
end
|
787
|
+
```
|