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