domainic-type 0.1.0.alpha.3.2.0 → 0.1.0.alpha.3.4.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.
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
+ ```