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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/README.md +66 -10
  4. data/docs/USAGE.md +787 -0
  5. data/lib/domainic/type/accessors.rb +3 -2
  6. data/lib/domainic/type/behavior/date_time_behavior.rb +121 -37
  7. data/lib/domainic/type/behavior.rb +16 -0
  8. data/lib/domainic/type/config/registry.yml +24 -0
  9. data/lib/domainic/type/constraint/constraints/nor_constraint.rb +1 -1
  10. data/lib/domainic/type/constraint/constraints/predicate_constraint.rb +76 -0
  11. data/lib/domainic/type/definitions.rb +212 -0
  12. data/lib/domainic/type/types/core/complex_type.rb +122 -0
  13. data/lib/domainic/type/types/core/range_type.rb +47 -0
  14. data/lib/domainic/type/types/core/rational_type.rb +38 -0
  15. data/lib/domainic/type/types/core_extended/big_decimal_type.rb +34 -0
  16. data/lib/domainic/type/types/core_extended/set_type.rb +34 -0
  17. data/lib/domainic/type/types/datetime/date_time_string_type.rb +156 -0
  18. data/lib/domainic/type/types/datetime/timestamp_type.rb +50 -0
  19. data/sig/domainic/type/accessors.rbs +2 -2
  20. data/sig/domainic/type/behavior/date_time_behavior.rbs +35 -23
  21. data/sig/domainic/type/behavior.rbs +9 -0
  22. data/sig/domainic/type/constraint/constraints/predicate_constraint.rbs +56 -0
  23. data/sig/domainic/type/definitions.rbs +165 -0
  24. data/sig/domainic/type/types/core/complex_type.rbs +96 -0
  25. data/sig/domainic/type/types/core/range_type.rbs +41 -0
  26. data/sig/domainic/type/types/core/rational_type.rbs +32 -0
  27. data/sig/domainic/type/types/core_extended/big_decimal_type.rbs +27 -0
  28. data/sig/domainic/type/types/core_extended/set_type.rbs +27 -0
  29. data/sig/domainic/type/types/datetime/date_time_string_type.rbs +124 -0
  30. data/sig/domainic/type/types/datetime/timestamp_type.rbs +44 -0
  31. 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
+ ```