bcdd-result 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  <p align="center">
2
2
  <h1 align="center" id="-bcddresult">🔀 BCDD::Result</h1>
3
3
  <p align="center"><i>Empower Ruby apps with a pragmatic use of Railway Oriented Programming.</i></p>
4
- <br>
4
+ <p align="center">
5
+ <img src="https://img.shields.io/badge/ruby->%3D%202.7.0-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
6
+ <img src="https://badge.fury.io/rb/bcdd-result.svg" alt="bcdd-result gem version" height="18">
7
+ </p>
5
8
  </p>
6
9
 
7
- A general-purpose result monad that allows you to create objects that represent a success (`BCDD::Result::Success`) or failure (`BCDD::Result::Failure`).
10
+ It's a general-purpose result monad that allows you to create objects representing a success (`BCDD::Result::Success`) or failure (`BCDD::Result::Failure`).
8
11
 
9
12
  **What problem does it solve?**
10
13
 
@@ -17,6 +20,7 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
17
20
  - [Ruby Version](#ruby-version)
18
21
  - [Installation](#installation)
19
22
  - [Usage](#usage)
23
+ - [`BCDD::Result` *versus* `Result`](#bcddresult-versus-result)
20
24
  - [Reference](#reference)
21
25
  - [Result Attributes](#result-attributes)
22
26
  - [Receiving types in `result.success?` or `result.failure?`](#receiving-types-in-resultsuccess-or-resultfailure)
@@ -25,16 +29,35 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
25
29
  - [`result.on_type`](#resulton_type)
26
30
  - [`result.on_success`](#resulton_success)
27
31
  - [`result.on_failure`](#resulton_failure)
32
+ - [`result.on_unknown`](#resulton_unknown)
28
33
  - [`result.handle`](#resulthandle)
29
34
  - [Result Value](#result-value)
30
35
  - [`result.value_or`](#resultvalue_or)
31
- - [`result.data_or`](#resultdata_or)
36
+ - [Result Data](#result-data)
37
+ - [`result.data`](#resultdata)
32
38
  - [Railway Oriented Programming](#railway-oriented-programming)
33
39
  - [`result.and_then`](#resultand_then)
34
- - [`BCDD::Resultable`](#bcddresultable)
35
- - [Class example (instance methods)](#class-example-instance-methods)
36
- - [Module example (singleton methods)](#module-example-singleton-methods)
40
+ - [`BCDD::Result::Mixin`](#bcddresultmixin)
41
+ - [Class example (Instance Methods)](#class-example-instance-methods)
42
+ - [Module example (Singleton Methods)](#module-example-singleton-methods)
37
43
  - [Restrictions](#restrictions)
44
+ - [Pattern Matching](#pattern-matching)
45
+ - [`Array`/`Find` patterns](#arrayfind-patterns)
46
+ - [`Hash` patterns](#hash-patterns)
47
+ - [BCDD::Result::Expectations](#bcddresultexpectations)
48
+ - [`BCDD::Result::Expectations`](#bcddresultexpectations-1)
49
+ - [Standalone *versus* Mixin mode](#standalone-versus-mixin-mode)
50
+ - [Type checking - Result Hooks](#type-checking---result-hooks)
51
+ - [`#success?` and `#failure?`](#success-and-failure)
52
+ - [`#on` and `#on_type`](#on-and-on_type)
53
+ - [`#on_success` and `#on_failure`](#on_success-and-on_failure)
54
+ - [`#handle`](#handle)
55
+ - [Type checking - Result Creation](#type-checking---result-creation)
56
+ - [Mixin mode](#mixin-mode)
57
+ - [Standalone mode](#standalone-mode)
58
+ - [Value checking - Result Creation](#value-checking---result-creation)
59
+ - [Success()](#success)
60
+ - [Failure()](#failure)
38
61
  - [About](#about)
39
62
  - [Development](#development)
40
63
  - [Contributing](#contributing)
@@ -61,7 +84,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
61
84
 
62
85
  $ gem install bcdd-result
63
86
 
64
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
87
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
65
88
 
66
89
  ## Usage
67
90
 
@@ -81,7 +104,23 @@ BCDD::Result::Success(:ok) #
81
104
  BCDD::Result::Failure(:err) #
82
105
  ```
83
106
 
84
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
107
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
108
+
109
+ #### `BCDD::Result` *versus* `Result`
110
+
111
+ The `BCDD::Result` is the main module of this gem. It contains all the features, constants, and methods you will use to create and manipulate results.
112
+
113
+ The `Result` is an alias of `BCDD::Result`. It was created to facilitate the use of this gem in the code. So, instead of requiring `BCDD::Result` everywhere, you can require `Result` and use it as an alias.
114
+
115
+ ```ruby
116
+ require 'result'
117
+
118
+ Result::Success(:ok) # <BCDD::Result::Success type=:ok value=nil>
119
+ ```
120
+
121
+ All the examples in this README that use `BCDD::Result` can also be used with `Result`.
122
+
123
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
85
124
 
86
125
  ## Reference
87
126
 
@@ -137,7 +176,7 @@ result.type # :no
137
176
  result.value # nil
138
177
  ```
139
178
 
140
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
179
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
141
180
 
142
181
  #### Receiving types in `result.success?` or `result.failure?`
143
182
 
@@ -163,7 +202,7 @@ result.failure?(:err) # true
163
202
  result.failure?(:error) # false
164
203
  ```
165
204
 
166
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
205
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
167
206
 
168
207
  ### Result Hooks
169
208
 
@@ -182,7 +221,7 @@ def divide(arg1, arg2)
182
221
  end
183
222
  ```
184
223
 
185
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
224
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
186
225
 
187
226
  #### `result.on`
188
227
 
@@ -220,7 +259,7 @@ result.object_id == output.object_id # true
220
259
 
221
260
  *PS: The `divide()` implementation is [here](#result-hooks).*
222
261
 
223
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
262
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
224
263
 
225
264
  #### `result.on_type`
226
265
 
@@ -249,7 +288,7 @@ divide(4, 4).on_failure { |error| puts error }
249
288
 
250
289
  *PS: The `divide()` implementation is [here](#result-hooks).*
251
290
 
252
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
291
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
253
292
 
254
293
  #### `result.on_failure`
255
294
 
@@ -271,7 +310,28 @@ divide(4, 0).on_failure(:invalid_arg) { |error| puts error }
271
310
 
272
311
  *PS: The `divide()` implementation is [here](#result-hooks).*
273
312
 
274
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
313
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
314
+
315
+ #### `result.on_unknown`
316
+
317
+ `BCDD::Result#on_unknown` will perform the block when no other hook (`#on`, `#on_type`, `#on_failure`, `#on_success`) has been executed.
318
+
319
+ Regardless of the block being executed, the return of the method will always be the result itself.
320
+
321
+ The result value will be exposed as the first argument of the block.
322
+
323
+ ```ruby
324
+ divide(4, 2)
325
+ .on(:invalid_arg) { |msg| puts msg }
326
+ .on(:division_by_zero) { |msg| puts msg }
327
+ .on_unknown { |value, type| puts [type, value].inspect }
328
+
329
+ # The code above will print '[:division_completed, 2]' and return the result itself.
330
+ ```
331
+
332
+ *PS: The `divide()` implementation is [here](#result-hooks).*
333
+
334
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
275
335
 
276
336
  #### `result.handle`
277
337
 
@@ -282,6 +342,7 @@ divide(4, 2).handle do |result|
282
342
  result.success { |number| number }
283
343
  result.failure(:invalid_arg) { |err| puts err }
284
344
  result.type(:division_by_zero) { raise ZeroDivisionError }
345
+ result.unknown { raise NotImplementedError }
285
346
  end
286
347
 
287
348
  #or
@@ -289,6 +350,7 @@ end
289
350
  divide(4, 2).handle do |on|
290
351
  on.success { |number| number }
291
352
  on.failure { |err| puts err }
353
+ on.unknown { raise NotImplementedError }
292
354
  end
293
355
 
294
356
  #or
@@ -297,6 +359,7 @@ divide(4, 2).handle do |on|
297
359
  on.type(:invalid_arg) { |err| puts err }
298
360
  on.type(:division_by_zero) { raise ZeroDivisionError }
299
361
  on.type(:division_completed) { |number| number }
362
+ on.unknown { raise NotImplementedError }
300
363
  end
301
364
 
302
365
  # or
@@ -305,6 +368,7 @@ divide(4, 2).handle do |on|
305
368
  on[:invalid_arg] { |err| puts err }
306
369
  on[:division_by_zero] { raise ZeroDivisionError }
307
370
  on[:division_completed] { |number| number }
371
+ on.unknown { raise NotImplementedError }
308
372
  end
309
373
 
310
374
  # The [] syntax 👆 is an alias of #type.
@@ -317,13 +381,13 @@ end
317
381
 
318
382
  *PS: The `divide()` implementation is [here](#result-hooks).*
319
383
 
320
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
384
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
321
385
 
322
386
  ### Result Value
323
387
 
324
- The most simple way to get the result value is by calling `BCDD::Result#value` or `BCDD::Result#data`.
388
+ The most simple way to get the result value is by calling `BCDD::Result#value`.
325
389
 
326
- But sometimes you need to get the value of a successful result or a default value if it is a failure. In this case, you can use `BCDD::Result#value_or` or `BCDD::Result#data_or`.
390
+ But sometimes you need to get the value of a successful result or a default value if it is a failure. In this case, you can use `BCDD::Result#value_or`.
327
391
 
328
392
  #### `result.value_or`
329
393
 
@@ -350,11 +414,50 @@ divide(100, 0).value_or { 0 } # 0
350
414
 
351
415
  *PS: The `divide()` implementation is [here](#result-hooks).*
352
416
 
353
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
417
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
418
+
419
+ ### Result Data
420
+
421
+ #### `result.data`
422
+
423
+ The `BCDD::Result#data` exposes the result attributes (name, type, value) directly and as a hash (`to_h`/`to_hash`) and array (`to_a`/`to_ary`).
424
+
425
+ This is helpful if you need to access the result attributes generically or want to use Ruby features like splat (`*`) and double splat (`**`) operators.
426
+
427
+ See the examples below to understand how to use it.
428
+
429
+ ```ruby
430
+ result = BCDD::Result::Success(:ok, 1)
431
+
432
+ success_data = result.data # #<BCDD::Result::Data name=:success type=:ok value=1>
433
+
434
+ success_data.name # :success
435
+ success_data.type # :ok
436
+ success_data.value # 1
437
+
438
+ success_data.to_h # {:name=>:success, :type=>:ok, :value=>1}
439
+ success_data.to_a # [:success, :ok, 1]
354
440
 
355
- #### `result.data_or`
441
+ name, type, value = success_data
356
442
 
357
- `BCDD::Result#data_or` is an alias of `BCDD::Result#value_or`.
443
+ [name, type, value] # [:success, :ok, 1]
444
+
445
+ def print_to_ary(name, type, value)
446
+ puts [name, type, value].inspect
447
+ end
448
+
449
+ def print_to_hash(name:, type:, value:)
450
+ puts [name, type, value].inspect
451
+ end
452
+
453
+ print_to_ary(*success_data) # [:success, :ok, 1]
454
+
455
+ print_to_hash(**success_data) # [:success, :ok, 1]
456
+ ```
457
+
458
+ > **NOTE:** The example above uses a success result, but the same is valid for a failure result.
459
+
460
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
358
461
 
359
462
  ### Railway Oriented Programming
360
463
 
@@ -416,21 +519,19 @@ Divide.call(2, 2)
416
519
  #<BCDD::Result::Success type=:division_completed data=1>
417
520
  ```
418
521
 
419
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
420
-
421
- #### `BCDD::Resultable`
522
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
422
523
 
423
- It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`.
524
+ #### `BCDD::Result::Mixin`
424
525
 
425
- The main difference between these methods and `BCDD::Result::Success()`/`BCDD::Result::Failure()` is that the first ones will use the target object as the result's subject.
526
+ It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`. The main difference between these methods and `BCDD::Result::Success()`/`BCDD::Result::Failure()` is that the first ones will use the target object (who received the include/extend) as the result's subject.
426
527
 
427
- And because of this, you can use the `#and_then` method to call methods from the target object (result's subject).
528
+ And because of this, you can use the `#and_then` method to call methods from the result's subject.
428
529
 
429
- ##### Class example (instance methods)
530
+ ##### Class example (Instance Methods)
430
531
 
431
532
  ```ruby
432
533
  class Divide
433
- include BCDD::Resultable
534
+ include BCDD::Result::Mixin
434
535
 
435
536
  attr_reader :arg1, :arg2
436
537
 
@@ -474,11 +575,11 @@ Divide.new('4', 2).call #<BCDD::Result::Failure type=:invalid_arg value="arg1 mu
474
575
  Divide.new(4, '2').call #<BCDD::Result::Failure type=:invalid_arg value="arg2 must be numeric">
475
576
  ```
476
577
 
477
- ##### Module example (singleton methods)
578
+ ##### Module example (Singleton Methods)
478
579
 
479
580
  ```ruby
480
581
  module Divide
481
- extend BCDD::Resultable
582
+ extend BCDD::Result::Mixin
482
583
  extend self
483
584
 
484
585
  def call(arg1, arg2)
@@ -518,17 +619,463 @@ Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must b
518
619
 
519
620
  The unique condition for using the `#and_then` to call methods is that they must use the `Success()` and `Failure()` to produce their results.
520
621
 
521
- If you use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or call another `BCDD::Resultable` object, the `#and_then` will raise an error because the subjects will be different.
622
+ If you use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or use result from another `BCDD::Result::Mixin` instance, the `#and_then` will raise an error because the subjects will be different.
522
623
 
523
624
  > **Note**: You still can use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
524
625
 
525
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
626
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
627
+
628
+ ### Pattern Matching
629
+
630
+ The `BCDD::Result` also provides support to pattern matching.
631
+
632
+ In the further examples, I will use the `Divide` lambda to exemplify its usage.
633
+
634
+ ```ruby
635
+ Divide = lambda do |arg1, arg2|
636
+ arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
637
+ arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
638
+
639
+ return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
640
+
641
+ BCDD::Result::Success(:division_completed, arg1 / arg2)
642
+ end
643
+ ```
644
+
645
+ #### `Array`/`Find` patterns
646
+
647
+ ```ruby
648
+ case Divide.call(4, 2)
649
+ in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
650
+ in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
651
+ in BCDD::Result::Success[:division_completed, value] then puts value
652
+ end
653
+
654
+ # The code above will print: 2
655
+
656
+ case Divide.call(4, 0)
657
+ in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
658
+ in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
659
+ in BCDD::Result::Success[:division_completed, value] then puts value
660
+ end
661
+
662
+ # The code above will print: arg2 must not be zero
663
+ ```
664
+
665
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
666
+
667
+ #### `Hash` patterns
668
+
669
+ ```ruby
670
+ case Divide.call(10, 2)
671
+ in { failure: { invalid_arg: msg } } then puts msg
672
+ in { failure: { division_by_zero: msg } } then puts msg
673
+ in { success: { division_completed: value } } then puts value
674
+ end
675
+
676
+ # The code above will print: 5
677
+
678
+ case Divide.call('10', 2)
679
+ in { failure: { invalid_arg: msg } } then puts msg
680
+ in { failure: { division_by_zero: msg } } then puts msg
681
+ in { success: { division_completed: value } } then puts value
682
+ end
683
+
684
+ # The code above will print: arg1 must be numeric
685
+ ```
686
+
687
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
688
+
689
+ #### BCDD::Result::Expectations
690
+
691
+ I'd like you to please read the following section to understand how to use this feature.
692
+
693
+ But if you are using Ruby >= 3.0, you can use the `in` operator to use the pattern matching to validate the result's value.
694
+
695
+ ```ruby
696
+ module Divide
697
+ extend BCDD::Result::Expectations.mixin(
698
+ success: {
699
+ numbers: ->(value) { value in [Numeric, Numeric] },
700
+ division_completed: ->(value) { value in (Integer | Float) }
701
+ },
702
+ failure: { invalid_arg: String, division_by_zero: String }
703
+ )
704
+
705
+ def self.call(arg1, arg2)
706
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
707
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
708
+
709
+ arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
710
+
711
+ Success(:division_completed, arg1 / arg2)
712
+ end
713
+ end
714
+ ```
715
+
716
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
717
+
718
+ ### `BCDD::Result::Expectations`
719
+
720
+ This feature lets you define contracts for your results' types and values. There are two ways to use it: the standalone (`BCDD::Result::Expectations.new`) and the mixin (`BCDD::Result::Expectations.mixin`) mode.
721
+
722
+ It was designed to ensure all the aspects of the result's type and value. So, an error will be raised if you try to create or handle a result with an unexpected type or value.
723
+
724
+ #### Standalone *versus* Mixin mode
725
+
726
+ The _**standalone mode**_ creates an object that knows how to produce and validate results based on the defined expectations. Look at the example below:
727
+
728
+ ```ruby
729
+ module Divide
730
+ Expected = BCDD::Result::Expectations.new(
731
+ success: %i[numbers division_completed],
732
+ failure: %i[invalid_arg division_by_zero]
733
+ )
734
+
735
+ def self.call(arg1, arg2)
736
+ arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
737
+ arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
738
+
739
+ arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
740
+
741
+ Expected::Success(:division_completed, arg1 / arg2)
742
+ end
743
+ end
744
+ ```
745
+
746
+ In the code above, we define a constant `Divide::Expected`. And because of this (it is a constant), we can use it inside and outside the module.
747
+
748
+ Look what happens if you try to create a result without one of the expected types.
749
+
750
+ ```ruby
751
+ Divide::Expected::Success(:ok)
752
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed
753
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
754
+
755
+ Divide::Expected::Failure(:err)
756
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
757
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
758
+ ```
759
+
760
+ The _**mixin mode**_ is similar to `BCDD::Result::Mixin`, but it also defines the expectations for the result's types and values.
761
+
762
+ ```ruby
763
+ class Divide
764
+ include BCDD::Result::Expectations.mixin(
765
+ success: %i[numbers division_completed],
766
+ failure: %i[invalid_arg division_by_zero]
767
+ )
768
+
769
+ def call(arg1, arg2)
770
+ validate_numbers(arg1, arg2)
771
+ .and_then(:validate_non_zero)
772
+ .and_then(:divide)
773
+ end
774
+
775
+ private
776
+
777
+ def validate_numbers(arg1, arg2)
778
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
779
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
780
+
781
+ Success(:numbers, [arg1, arg2])
782
+ end
783
+
784
+ def validate_non_zero(numbers)
785
+ return Success(:numbers, numbers) unless numbers.last.zero?
786
+
787
+ Failure(:division_by_zero, 'arg2 must not be zero')
788
+ end
789
+
790
+ def divide((number1, number2))
791
+ Success(:division_completed, number1 / number2)
792
+ end
793
+ end
794
+ ```
795
+
796
+ This mode also defines an `Expected` constant to be used inside and outside the module.
797
+
798
+ > **PROTIP:**
799
+ > You can use the `Expected` constant to mock the result's type and value in your tests. As they will have the exact expectations, your tests will check if the result clients are handling the result correctly.
800
+
801
+ Now that you know the two modes, let's understand how expectations can be beneficial and powerful for defining contracts.
802
+
803
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
804
+
805
+ #### Type checking - Result Hooks
806
+
807
+ The `BCDD::Result::Expectations` will check if the result's type is valid. This checking will be performed in all the methods that rely on the result's type, like `#success?`, `#failure?`, `#on`, `#on_type`, `#on_success`, `#on_failure`, `#handle`.
808
+
809
+ ##### `#success?` and `#failure?`
810
+
811
+ When you check whether a result is a success or failure, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
812
+
813
+ **Success example:**
814
+
815
+ ```ruby
816
+ result = Divide.new.call(10, 2)
817
+
818
+ result.success? # true
819
+ result.success?(:numbers) # false
820
+ result.success?(:division_completed) # true
821
+
822
+ result.success?(:ok)
823
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed
824
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
825
+ ```
826
+
827
+ **Failure example:**
828
+
829
+ ```ruby
830
+ result = Divide.new.call(10, '2')
831
+
832
+ result.failure? # true
833
+ result.failure?(:invalid_arg) # true
834
+ result.failure?(:division_by_zero) # false
835
+
836
+ result.failure?(:err)
837
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
838
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
839
+ ```
840
+
841
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
842
+
843
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
844
+
845
+ ##### `#on` and `#on_type`
846
+
847
+ If you use `#on` or `#on_type` to perform a block, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
848
+
849
+ ```ruby
850
+ result = Divide.new.call(10, 2)
851
+
852
+ result
853
+ .on(:invalid_arg, :division_by_zero) { |msg| puts msg }
854
+ .on(:division_completed) { |number| puts "The result is #{number}" }
855
+
856
+ # The code above will print 'The result is 5'
857
+
858
+ result.on(:number) { |_| :this_type_does_not_exist }
859
+ # type :number is not allowed. Allowed types: :numbers, :division_completed, :invalid_arg, :division_by_zero
860
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
861
+ ```
862
+
863
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
864
+
865
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
866
+
867
+ ##### `#on_success` and `#on_failure`
868
+
869
+ If you use `#on_success` or `#on_failure` to perform a block, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
870
+
871
+ ```ruby
872
+ result = Divide.new.call(10, '2')
873
+
874
+ result
875
+ .on_failure(:invalid_arg, :division_by_zero) { |msg| puts msg }
876
+ .on_success(:division_completed) { |number| puts "The result is #{number}" }
877
+
878
+ result
879
+ .on_success { |number| puts "The result is #{number}" }
880
+ .on_failure { |msg| puts msg }
881
+
882
+ # Both codes above will print 'arg2 must be numeric'
883
+
884
+ result.on_success(:ok) { |_| :this_type_does_not_exist }
885
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed
886
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
887
+
888
+ result.on_failure(:err) { |_| :this_type_does_not_exist }
889
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
890
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
891
+ ```
892
+
893
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
894
+
895
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
896
+
897
+ ##### `#handle`
898
+
899
+ The `BCDD::Result::Expectations` will also be applied on all the handlers defined by the `#handle` method/block.
900
+
901
+ ```ruby
902
+ result = Divide.call(10, 2)
903
+
904
+ result.handle do |on|
905
+ on.type(:ok) { |_| :this_type_does_not_exist }
906
+ end
907
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed, :invalid_arg, :division_by_zero (BCDD::Result::Expectations::Error::UnexpectedType)
908
+
909
+ result.handle do |on|
910
+ on.success(:ok) { |_| :this_type_does_not_exist }
911
+ end
912
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
913
+
914
+ result.handle do |on|
915
+ on.failure(:err) { |_| :this_type_does_not_exist }
916
+ end
917
+ # type :err is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
918
+ ```
919
+
920
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
921
+
922
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
923
+
924
+ #### Type checking - Result Creation
925
+
926
+ The `BCDD::Result::Expectations` will be used on the result creation `Success()` and `Failure()` methods. So, when the result type is valid/expected, the result will be created. Otherwise, an error will be raised.
927
+
928
+ This works for both modes (standalone and mixin).
929
+
930
+ ##### Mixin mode
931
+
932
+ ```ruby
933
+ module Divide
934
+ extend BCDD::Result::Expectations.mixin(success: :ok, failure: :err)
935
+
936
+ def self.call(arg1, arg2)
937
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
938
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
939
+
940
+ arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
941
+
942
+ Success(:division_completed, arg1 / arg2)
943
+ end
944
+ end
945
+
946
+ Divide.call('4', 2)
947
+ # type :invalid_arg is not allowed. Allowed types: :err
948
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
949
+
950
+ Divide.call(4, 2)
951
+ # type :division_completed is not allowed. Allowed types: :ok
952
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
953
+ ```
954
+
955
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
956
+
957
+ ##### Standalone mode
958
+
959
+ ```ruby
960
+ module Divide
961
+ Expected = BCDD::Result::Expectations.new(success: :ok, failure: :err)
962
+
963
+ def self.call(arg1, arg2)
964
+ arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
965
+ arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
966
+
967
+ arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
968
+
969
+ Expected::Success(:division_completed, arg1 / arg2)
970
+ end
971
+ end
972
+
973
+ Divide.call('4', 2)
974
+ # type :invalid_arg is not allowed. Allowed types: :err
975
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
976
+
977
+ Divide.call(4, 2)
978
+ # type :division_completed is not allowed. Allowed types: :ok
979
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
980
+ ```
981
+
982
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
983
+
984
+ #### Value checking - Result Creation
985
+
986
+ The `Result::Expectations` supports types of validations. The first is the type checking only, and the second is the type and value checking.
987
+
988
+ To define expectations for your result's values, you must declare a Hash with the type as the key and the value as the value. A value validator is any object that responds to `#===` (case equality operator).
989
+
990
+ **Mixin mode:**
991
+
992
+ ```ruby
993
+ module Divide
994
+ extend BCDD::Result::Expectations.mixin(
995
+ success: {
996
+ numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
997
+ division_completed: Numeric
998
+ },
999
+ failure: {
1000
+ invalid_arg: String,
1001
+ division_by_zero: String
1002
+ }
1003
+ )
1004
+
1005
+ def self.call(arg1, arg2)
1006
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
1007
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
1008
+
1009
+ arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
1010
+
1011
+ Success(:division_completed, arg1 / arg2)
1012
+ end
1013
+ end
1014
+ ```
1015
+
1016
+ **Standalone mode:**
1017
+
1018
+ ```ruby
1019
+ module Divide
1020
+ Expected = BCDD::Result::Expectations.new(
1021
+ success: {
1022
+ numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
1023
+ division_completed: Numeric
1024
+ },
1025
+ failure: {
1026
+ invalid_arg: String,
1027
+ division_by_zero: String
1028
+ }
1029
+ )
1030
+
1031
+ def self.call(arg1, arg2)
1032
+ arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
1033
+ arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
1034
+
1035
+ arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
1036
+
1037
+ Expected::Success(:division_completed, arg1 / arg2)
1038
+ end
1039
+ end
1040
+ ```
1041
+
1042
+ The value validation will only be performed through the methods `Success()` and `Failure()`.
1043
+
1044
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1045
+
1046
+ ##### Success()
1047
+
1048
+ ```ruby
1049
+ Divide::Expected::Success(:ok)
1050
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
1051
+
1052
+ Divide::Expected::Success(:numbers, [1])
1053
+ # value [1] is not allowed for :numbers type (BCDD::Result::Expectations::Error::UnexpectedValue)
1054
+
1055
+ Divide::Expected::Success(:division_completed, '2')
1056
+ # value "2" is not allowed for :division_completed type (BCDD::Result::Expectations::Error::UnexpectedValue)
1057
+ ```
1058
+
1059
+ ##### Failure()
1060
+
1061
+ ```ruby
1062
+ Divide::Expected::Failure(:err)
1063
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero (BCDD::Result::Expectations::Error::UnexpectedType)
1064
+
1065
+ Divide::Expected::Failure(:invalid_arg, :arg1_must_be_numeric)
1066
+ # value :arg1_must_be_numeric is not allowed for :invalid_arg type (BCDD::Result::Expectations::Error::UnexpectedValue)
1067
+
1068
+ Divide::Expected::Failure(:division_by_zero, msg: 'arg2 must not be zero')
1069
+ # value {:msg=>"arg2 must not be zero"} is not allowed for :division_by_zero type (BCDD::Result::Expectations::Error::UnexpectedValue)
1070
+ ```
1071
+
1072
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
526
1073
 
527
1074
  ## About
528
1075
 
529
1076
  [Rodrigo Serradura](https://github.com/serradura) created this project. He is the B/CDD process/method creator and has already made similar gems like the [u-case](https://github.com/serradura/u-case) and [kind](https://github.com/serradura/kind/blob/main/lib/kind/result.rb). This gem is a general-purpose abstraction/monad, but it also contains key features that serve as facilitators for adopting B/CDD in the code.
530
1077
 
531
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
1078
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
532
1079
 
533
1080
  ## Development
534
1081
 
@@ -536,19 +1083,19 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
536
1083
 
537
1084
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
538
1085
 
539
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
1086
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
540
1087
 
541
1088
  ## Contributing
542
1089
 
543
1090
  Bug reports and pull requests are welcome on GitHub at https://github.com/B-CDD/bcdd-result. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/B-CDD/bcdd-result/blob/master/CODE_OF_CONDUCT.md).
544
1091
 
545
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
1092
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
546
1093
 
547
1094
  ## License
548
1095
 
549
1096
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
550
1097
 
551
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
1098
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
552
1099
 
553
1100
  ## Code of Conduct
554
1101