domainic-type 0.1.0.alpha.3.2.0 → 0.1.0.alpha.3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ ```