bcdd-result 0.6.0 → 0.7.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +24 -11
  3. data/CHANGELOG.md +28 -0
  4. data/README.md +585 -160
  5. data/lib/bcdd/result/context/expectations/mixin.rb +35 -0
  6. data/lib/bcdd/result/context/expectations.rb +41 -0
  7. data/lib/bcdd/result/context/failure.rb +9 -0
  8. data/lib/bcdd/result/context/mixin.rb +38 -0
  9. data/lib/bcdd/result/context/success.rb +15 -0
  10. data/lib/bcdd/result/context.rb +74 -0
  11. data/lib/bcdd/result/{expectations/contract → contract}/disabled.rb +2 -2
  12. data/lib/bcdd/result/{expectations → contract}/error.rb +5 -3
  13. data/lib/bcdd/result/{expectations/contract → contract}/evaluator.rb +2 -2
  14. data/lib/bcdd/result/{expectations/contract → contract}/for_types.rb +2 -2
  15. data/lib/bcdd/result/{expectations/contract → contract}/for_types_and_values.rb +10 -8
  16. data/lib/bcdd/result/contract/interface.rb +21 -0
  17. data/lib/bcdd/result/{expectations → contract}/type_checker.rb +1 -1
  18. data/lib/bcdd/result/contract.rb +43 -0
  19. data/lib/bcdd/result/error.rb +7 -9
  20. data/lib/bcdd/result/expectations/mixin.rb +14 -9
  21. data/lib/bcdd/result/expectations.rb +28 -37
  22. data/lib/bcdd/result/failure/methods.rb +21 -0
  23. data/lib/bcdd/result/failure.rb +2 -16
  24. data/lib/bcdd/result/mixin.rb +8 -3
  25. data/lib/bcdd/result/success/methods.rb +21 -0
  26. data/lib/bcdd/result/success.rb +2 -16
  27. data/lib/bcdd/result/version.rb +1 -1
  28. data/lib/bcdd/result.rb +6 -4
  29. data/sig/bcdd/result.rbs +227 -83
  30. metadata +18 -10
  31. data/lib/bcdd/result/expectations/contract/interface.rb +0 -21
  32. data/lib/bcdd/result/expectations/contract.rb +0 -25
data/README.md CHANGED
@@ -35,6 +35,9 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
35
35
  - [`result.value_or`](#resultvalue_or)
36
36
  - [Result Data](#result-data)
37
37
  - [`result.data`](#resultdata)
38
+ - [Pattern Matching](#pattern-matching)
39
+ - [`Array`/`Find` patterns](#arrayfind-patterns)
40
+ - [`Hash` patterns](#hash-patterns)
38
41
  - [Railway Oriented Programming](#railway-oriented-programming)
39
42
  - [`result.and_then`](#resultand_then)
40
43
  - [`BCDD::Result.mixin`](#bcddresultmixin)
@@ -42,12 +45,8 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
42
45
  - [Module example (Singleton Methods)](#module-example-singleton-methods)
43
46
  - [Important Requirement](#important-requirement)
44
47
  - [Dependency Injection](#dependency-injection)
45
- - [Addons](#addons)
46
- - [Pattern Matching](#pattern-matching)
47
- - [`Array`/`Find` patterns](#arrayfind-patterns)
48
- - [`Hash` patterns](#hash-patterns)
49
- - [BCDD::Result::Expectations](#bcddresultexpectations)
50
- - [`BCDD::Result::Expectations`](#bcddresultexpectations-1)
48
+ - [Add-ons](#add-ons)
49
+ - [`BCDD::Result::Expectations`](#bcddresultexpectations)
51
50
  - [Standalone *versus* Mixin mode](#standalone-versus-mixin-mode)
52
51
  - [Type checking - Result Hooks](#type-checking---result-hooks)
53
52
  - [`#success?` and `#failure?`](#success-and-failure)
@@ -60,7 +59,16 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
60
59
  - [Value checking - Result Creation](#value-checking---result-creation)
61
60
  - [Success()](#success)
62
61
  - [Failure()](#failure)
63
- - [`BCDD::Result::Expectations.mixin` Addons](#bcddresultexpectationsmixin-addons)
62
+ - [Pattern Matching Support](#pattern-matching-support)
63
+ - [`BCDD::Result::Expectations.mixin` add-ons](#bcddresultexpectationsmixin-add-ons)
64
+ - [`BCDD::Result::Context`](#bcddresultcontext)
65
+ - [Defining successes and failures](#defining-successes-and-failures)
66
+ - [`BCDD::Result::Context.mixin`](#bcddresultcontextmixin)
67
+ - [Class example (Instance Methods)](#class-example-instance-methods-1)
68
+ - [`and_expose`](#and_expose)
69
+ - [Module example (Singleton Methods)](#module-example-singleton-methods-1)
70
+ - [`BCDD::Result::Context::Expectations`](#bcddresultcontextexpectations)
71
+ - [Mixin add-ons](#mixin-add-ons)
64
72
  - [About](#about)
65
73
  - [Development](#development)
66
74
  - [Contributing](#contributing)
@@ -209,9 +217,8 @@ result.failure?(:error) # false
209
217
 
210
218
  ### Result Hooks
211
219
 
212
- Result hooks are methods that allow you to perform a block of code depending on the result type.
213
-
214
- To exemplify the them, I will implement a method that knows how to divide two numbers.
220
+ Result hooks are methods that allow you to execute a block of code based on the type of result obtained.
221
+ To demonstrate their use, I will implement a function that can divide two numbers.
215
222
 
216
223
  ```ruby
217
224
  def divide(arg1, arg2)
@@ -228,14 +235,15 @@ end
228
235
 
229
236
  #### `result.on`
230
237
 
231
- `BCDD::Result#on` will perform the block when the type matches the result type.
238
+ When you use `BCDD::Result#on`, the block will be executed only when the type matches the result type.
232
239
 
233
- Regardless of the block being executed, the return of the method will always be the result itself.
240
+ However, even if the block is executed, the method will always return the result itself.
234
241
 
235
- The result value will be exposed as the first argument of the block.
242
+ The value of the result will be available as the first argument of the block.
236
243
 
237
244
  ```ruby
238
245
  result = divide(nil, 2)
246
+ #<BCDD::Result::Failure type=:invalid_arg data='arg1 must be numeric'>
239
247
 
240
248
  output =
241
249
  result
@@ -268,21 +276,40 @@ result.object_id == output.object_id # true
268
276
 
269
277
  `BCDD::Result#on_type` is an alias of `BCDD::Result#on`.
270
278
 
279
+ ```ruby
280
+ result = divide(nil, 2)
281
+ #<BCDD::Result::Failure type=:invalid_arg data='arg1 must be numeric'>
282
+
283
+ output =
284
+ result
285
+ .on_type(:invalid_arg) { |msg| puts msg }
286
+ .on_type(:division_by_zero) { |msg| puts msg }
287
+ .on_type(:division_completed) { |number| puts number }
288
+
289
+ # The code above will print 'arg1 must be numeric' and return the result itself.
290
+
291
+ result.object_id == output.object_id # true
292
+ ```
293
+
294
+ *PS: The `divide()` implementation is [here](#result-hooks).*
295
+
296
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
297
+
271
298
  #### `result.on_success`
272
299
 
273
- `BCDD::Result#on_success` is very similar to the `BCDD::Result#on` hook. The main differences are:
300
+ The `BCDD::Result#on_success` method is quite similar to the `BCDD::Result#on` hook, but with a few key differences:
274
301
 
275
- 1. Only perform the block when the result is a success.
276
- 2. If the type is missing it will perform the block for any success.
302
+ 1. It will only execute the block of code if the result is a success.
303
+ 2. If the type declaration is not included, the method will execute the block for any successful result, regardless of its type.
277
304
 
278
305
  ```ruby
279
- # It performs the block and return itself.
306
+ # It executes the block and return itself.
280
307
 
281
308
  divide(4, 2).on_success { |number| puts number }
282
309
 
283
310
  divide(4, 2).on_success(:division_completed) { |number| puts number }
284
311
 
285
- # It doesn't perform the block, but return itself.
312
+ # It doesn't execute the block, but return itself.
286
313
 
287
314
  divide(4, 4).on_success(:ok) { |value| puts value }
288
315
 
@@ -295,16 +322,19 @@ divide(4, 4).on_failure { |error| puts error }
295
322
 
296
323
  #### `result.on_failure`
297
324
 
298
- Is the opposite of `Result#on_success`.
325
+ It is the opposite of `Result#on_success`:
326
+
327
+ 1. It will only execute the block of code if the result is a failure.
328
+ 2. If the type declaration is not included, the method will execute the block for any failed result, regardless of its type.
299
329
 
300
330
  ```ruby
301
- # It performs the block and return itself.
331
+ # It executes the block and return itself.
302
332
 
303
333
  divide(nil, 2).on_failure { |error| puts error }
304
334
 
305
335
  divide(4, 0).on_failure(:invalid_arg, :division_by_zero) { |error| puts error }
306
336
 
307
- # It doesn't perform the block, but return itself.
337
+ # It doesn't execute the block, but return itself.
308
338
 
309
339
  divide(4, 0).on_success { |number| puts number }
310
340
 
@@ -317,11 +347,11 @@ divide(4, 0).on_failure(:invalid_arg) { |error| puts error }
317
347
 
318
348
  #### `result.on_unknown`
319
349
 
320
- `BCDD::Result#on_unknown` will perform the block when no other hook (`#on`, `#on_type`, `#on_failure`, `#on_success`) has been executed.
350
+ `BCDD::Result#on_unknown` will execute the block when no other hook (`#on`, `#on_type`, `#on_failure`, `#on_success`) has been executed.
321
351
 
322
- Regardless of the block being executed, the return of the method will always be the result itself.
352
+ Regardless of the block being executed, the method will always return the result itself.
323
353
 
324
- The result value will be exposed as the first argument of the block.
354
+ The value of the result will be available as the first argument of the block.
325
355
 
326
356
  ```ruby
327
357
  divide(4, 2)
@@ -338,7 +368,7 @@ divide(4, 2)
338
368
 
339
369
  #### `result.handle`
340
370
 
341
- This method will allow you to define blocks for each hook (type, failure, success), but instead of returning itself, it will return the output of the first match/block execution.
371
+ This method lets you define blocks for each hook (type, failure, or success), but instead of returning itself, it will return the output of the first match/block execution.
342
372
 
343
373
  ```ruby
344
374
  divide(4, 2).handle do |result|
@@ -379,8 +409,8 @@ end
379
409
 
380
410
  **Notes:**
381
411
  * You can define multiple types to be handled by the same hook/block
382
- * If the type is missing, it will perform the block for any success or failure handler.
383
- * The `#type` and `#[]` handlers will require at least one type/argument.
412
+ * If the type is missing, it will execute the block for any success or failure handler.
413
+ * The `#type` and `#[]` handlers require at least one type/argument.
384
414
 
385
415
  *PS: The `divide()` implementation is [here](#result-hooks).*
386
416
 
@@ -388,13 +418,13 @@ end
388
418
 
389
419
  ### Result Value
390
420
 
391
- The most simple way to get the result value is by calling `BCDD::Result#value`.
421
+ To access the result value, you can simply call `BCDD::Result#value`.
392
422
 
393
- 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`.
423
+ However, there may be instances where you need to retrieve the value of a successful result or a default value if the result is a failure. In such cases, you can make use of `BCDD::Result#value_or`.
394
424
 
395
425
  #### `result.value_or`
396
426
 
397
- `BCCD::Result#value_or` returns the value when the result is a success, but if is a failure the block will be performed, and its outcome will be the output.
427
+ `BCCD::Result#value_or` returns the value when the result is a success. However, if it is a failure, the given block will be executed, and its outcome will be returned.
398
428
 
399
429
  ```ruby
400
430
  def divide(arg1, arg2)
@@ -462,16 +492,75 @@ print_to_hash(**success_data) # [:success, :ok, 1]
462
492
 
463
493
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
464
494
 
465
- ### Railway Oriented Programming
495
+ ### Pattern Matching
466
496
 
467
- This feature/pattern is also known as ["Railway Oriented Programming"](https://fsharpforfunandprofit.com/rop/).
497
+ The `BCDD::Result` also provides support to pattern matching.
468
498
 
469
- The idea is to chain blocks and creates a pipeline of operations that can be interrupted by a failure.
499
+ In the further examples, I will use the `Divide` lambda to exemplify its usage.
470
500
 
471
- In other words, the block will be executed only if the result is a success.
472
- So, if some block returns a failure, the following blocks will be skipped.
501
+ ```ruby
502
+ Divide = lambda do |arg1, arg2|
503
+ arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
504
+ arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
505
+
506
+ return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
507
+
508
+ BCDD::Result::Success(:division_completed, arg1 / arg2)
509
+ end
510
+ ```
473
511
 
474
- Due to this characteristic, you can use this feature to express some logic as a sequence of operations. And have the guarantee that the process will stop by the first failure detection, and if everything is ok, the final result will be a success.
512
+ #### `Array`/`Find` patterns
513
+
514
+ ```ruby
515
+ case Divide.call(4, 2)
516
+ in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
517
+ in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
518
+ in BCDD::Result::Success[:division_completed, value] then puts value
519
+ end
520
+
521
+ # The code above will print: 2
522
+
523
+ case Divide.call(4, 0)
524
+ in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
525
+ in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
526
+ in BCDD::Result::Success[:division_completed, value] then puts value
527
+ end
528
+
529
+ # The code above will print: arg2 must not be zero
530
+ ```
531
+
532
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
533
+
534
+ #### `Hash` patterns
535
+
536
+ ```ruby
537
+ case Divide.call(10, 2)
538
+ in { failure: { invalid_arg: msg } } then puts msg
539
+ in { failure: { division_by_zero: msg } } then puts msg
540
+ in { success: { division_completed: value } } then puts value
541
+ end
542
+
543
+ # The code above will print: 5
544
+
545
+ case Divide.call('10', 2)
546
+ in { failure: { invalid_arg: msg } } then puts msg
547
+ in { failure: { division_by_zero: msg } } then puts msg
548
+ in { success: { division_completed: value } } then puts value
549
+ end
550
+
551
+ # The code above will print: arg1 must be numeric
552
+ ```
553
+
554
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
555
+
556
+ ### Railway Oriented Programming
557
+
558
+ ["Railway Oriented Programming (ROP)"](https://fsharpforfunandprofit.com/rop/) is a programming technique that involves linking blocks together to form a sequence of operations, also known as a pipeline.
559
+ If a failure occurs in any of the blocks, the pipeline is interrupted and subsequent blocks are skipped.
560
+
561
+ The ROP technique allows you to structure your code in a way that expresses your logic as a series of operations, with the added benefit of stopping the process at the first detection of failure.
562
+
563
+ If all blocks successfully execute, the final result of the pipeline will be a success.
475
564
 
476
565
  #### `result.and_then`
477
566
 
@@ -526,9 +615,9 @@ Divide.call(2, 2)
526
615
 
527
616
  #### `BCDD::Result.mixin`
528
617
 
529
- This method produces 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.
618
+ This method generates a module that can be included or 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 former will utilize the target object (which has received the include/extend) as the result's subject.
530
619
 
531
- And because of this, you can use the `#and_then` method to call methods from the result's subject.
620
+ As a result, you can utilize the `#and_then` method to invoke methods from the result's subject.
532
621
 
533
622
  ##### Class example (Instance Methods)
534
623
 
@@ -621,11 +710,64 @@ Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must b
621
710
 
622
711
  ##### Important Requirement
623
712
 
624
- The unique condition for using the `#and_then` to call methods is that they must use the `Success()` and `Failure()` to produce their results.
713
+ To use the `#and_then` method to call methods, they must use `Success()` and `Failure()` to produce the results.
714
+
715
+ If you try to use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or results from another `BCDD::Result.mixin` instance with `#and_then`, it will raise an error because the subjects will be different.
716
+
717
+ **Note:** You can still use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
718
+
719
+ ```ruby
720
+ module ValidateNonZero
721
+ extend self, BCDD::Result.mixin
625
722
 
626
- 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.
723
+ def call(numbers)
724
+ return Success(:ok, numbers) unless numbers.last.zero?
627
725
 
628
- > **Note**: You still can use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
726
+ Failure(:division_by_zero, 'arg2 must not be zero')
727
+ end
728
+ end
729
+
730
+ class Divide
731
+ include BCDD::Result.mixin
732
+
733
+ attr_reader :arg1, :arg2
734
+
735
+ def initialize(arg1, arg2)
736
+ @arg1 = arg1
737
+ @arg2 = arg2
738
+ end
739
+
740
+ def call
741
+ validate_numbers
742
+ .and_then(:validate_non_zero)
743
+ .and_then(:divide)
744
+ end
745
+
746
+ private
747
+
748
+ def validate_numbers
749
+ arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric') # This will raise an error
750
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
751
+
752
+ BCDD::Result::Success(:ok, [arg1, arg2]) # This will raise an error
753
+ end
754
+
755
+ def validate_non_zero(numbers)
756
+ ValidateNonZero.call(numbers) # This will raise an error
757
+
758
+ # This would work:
759
+ # In this case we are handling the other subject result and returning our own
760
+ # ValidateNonZero.call(numbers).handle do |on|
761
+ # on.success { |numbers| Success(:ok, numbers) }
762
+ # on.failure { |err| Failure(:division_by_zero, err) }
763
+ # end
764
+ end
765
+
766
+ def divide((number1, number2))
767
+ Success(:division_completed, number1 / number2)
768
+ end
769
+ end
770
+ ```
629
771
 
630
772
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
631
773
 
@@ -686,7 +828,7 @@ Divide.call(4, 2, logger: Logger.new(IO::NULL))
686
828
 
687
829
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
688
830
 
689
- ##### Addons
831
+ ##### Add-ons
690
832
 
691
833
  The `BCDD::Result.mixin` also accepts the `with:` argument. It is a hash that will be used to define the methods that will be added to the target object.
692
834
 
@@ -727,96 +869,6 @@ end
727
869
 
728
870
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
729
871
 
730
- ### Pattern Matching
731
-
732
- The `BCDD::Result` also provides support to pattern matching.
733
-
734
- In the further examples, I will use the `Divide` lambda to exemplify its usage.
735
-
736
- ```ruby
737
- Divide = lambda do |arg1, arg2|
738
- arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
739
- arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
740
-
741
- return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
742
-
743
- BCDD::Result::Success(:division_completed, arg1 / arg2)
744
- end
745
- ```
746
-
747
- #### `Array`/`Find` patterns
748
-
749
- ```ruby
750
- case Divide.call(4, 2)
751
- in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
752
- in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
753
- in BCDD::Result::Success[:division_completed, value] then puts value
754
- end
755
-
756
- # The code above will print: 2
757
-
758
- case Divide.call(4, 0)
759
- in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
760
- in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
761
- in BCDD::Result::Success[:division_completed, value] then puts value
762
- end
763
-
764
- # The code above will print: arg2 must not be zero
765
- ```
766
-
767
- <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
768
-
769
- #### `Hash` patterns
770
-
771
- ```ruby
772
- case Divide.call(10, 2)
773
- in { failure: { invalid_arg: msg } } then puts msg
774
- in { failure: { division_by_zero: msg } } then puts msg
775
- in { success: { division_completed: value } } then puts value
776
- end
777
-
778
- # The code above will print: 5
779
-
780
- case Divide.call('10', 2)
781
- in { failure: { invalid_arg: msg } } then puts msg
782
- in { failure: { division_by_zero: msg } } then puts msg
783
- in { success: { division_completed: value } } then puts value
784
- end
785
-
786
- # The code above will print: arg1 must be numeric
787
- ```
788
-
789
- <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
790
-
791
- #### BCDD::Result::Expectations
792
-
793
- I'd like you to please read the following section to understand how to use this feature.
794
-
795
- 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.
796
-
797
- ```ruby
798
- module Divide
799
- extend BCDD::Result::Expectations.mixin(
800
- success: {
801
- numbers: ->(value) { value in [Numeric, Numeric] },
802
- division_completed: ->(value) { value in (Integer | Float) }
803
- },
804
- failure: { invalid_arg: String, division_by_zero: String }
805
- )
806
-
807
- def self.call(arg1, arg2)
808
- arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
809
- arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
810
-
811
- arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
812
-
813
- Success(:division_completed, arg1 / arg2)
814
- end
815
- end
816
- ```
817
-
818
- <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
819
-
820
872
  ### `BCDD::Result::Expectations`
821
873
 
822
874
  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.
@@ -829,18 +881,18 @@ The _**standalone mode**_ creates an object that knows how to produce and valida
829
881
 
830
882
  ```ruby
831
883
  module Divide
832
- Expected = BCDD::Result::Expectations.new(
884
+ Result = BCDD::Result::Expectations.new(
833
885
  success: %i[numbers division_completed],
834
886
  failure: %i[invalid_arg division_by_zero]
835
887
  )
836
888
 
837
889
  def self.call(arg1, arg2)
838
- arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
839
- arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
890
+ arg1.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg1 must be numeric')
891
+ arg2.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg2 must be numeric')
840
892
 
841
- arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
893
+ arg2.zero? and return Result::Failure(:division_by_zero, 'arg2 must not be zero')
842
894
 
843
- Expected::Success(:division_completed, arg1 / arg2)
895
+ Result::Success(:division_completed, arg1 / arg2)
844
896
  end
845
897
  end
846
898
  ```
@@ -850,11 +902,11 @@ In the code above, we define a constant `Divide::Expected`. And because of this
850
902
  Look what happens if you try to create a result without one of the expected types.
851
903
 
852
904
  ```ruby
853
- Divide::Expected::Success(:ok)
905
+ Divide::Result::Success(:ok)
854
906
  # type :ok is not allowed. Allowed types: :numbers, :division_completed
855
907
  # (BCDD::Result::Expectations::Error::UnexpectedType)
856
908
 
857
- Divide::Expected::Failure(:err)
909
+ Divide::Result::Failure(:err)
858
910
  # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
859
911
  # (BCDD::Result::Expectations::Error::UnexpectedType)
860
912
  ```
@@ -906,11 +958,11 @@ Now that you know the two modes, let's understand how expectations can be benefi
906
958
 
907
959
  #### Type checking - Result Hooks
908
960
 
909
- 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`.
961
+ The `BCDD::Result::Expectations` will check if the type of the result is valid. This checking will be performed in all methods that depend on the results type, such as `#success?`, `#failure?`, `#on`, `#on_type`, `#on_success`, `#on_failure`, and `#handle`.
910
962
 
911
963
  ##### `#success?` and `#failure?`
912
964
 
913
- 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.
965
+ When checking whether a result is a success or failure, `BCDD::Result::Expectations` will also verify if the result type is valid/expected. In case of an invalid type, an error will be raised.
914
966
 
915
967
  **Success example:**
916
968
 
@@ -946,7 +998,7 @@ result.failure?(:err)
946
998
 
947
999
  ##### `#on` and `#on_type`
948
1000
 
949
- 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.
1001
+ If you use `#on` or `#on_type` to execute a block, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
950
1002
 
951
1003
  ```ruby
952
1004
  result = Divide.new.call(10, 2)
@@ -968,7 +1020,7 @@ result.on(:number) { |_| :this_type_does_not_exist }
968
1020
 
969
1021
  ##### `#on_success` and `#on_failure`
970
1022
 
971
- 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.
1023
+ If you use `#on_success` or `#on_failure` to execute a block, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
972
1024
 
973
1025
  ```ruby
974
1026
  result = Divide.new.call(10, '2')
@@ -1060,15 +1112,15 @@ Divide.call(4, 2)
1060
1112
 
1061
1113
  ```ruby
1062
1114
  module Divide
1063
- Expected = BCDD::Result::Expectations.new(success: :ok, failure: :err)
1115
+ Result = BCDD::Result::Expectations.new(success: :ok, failure: :err)
1064
1116
 
1065
1117
  def self.call(arg1, arg2)
1066
- arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
1067
- arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
1118
+ arg1.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg1 must be numeric')
1119
+ arg2.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg2 must be numeric')
1068
1120
 
1069
- arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
1121
+ arg2.zero? and return Result::Failure(:division_by_zero, 'arg2 must not be zero')
1070
1122
 
1071
- Expected::Success(:division_completed, arg1 / arg2)
1123
+ Result::Success(:division_completed, arg1 / arg2)
1072
1124
  end
1073
1125
  end
1074
1126
 
@@ -1119,7 +1171,7 @@ end
1119
1171
 
1120
1172
  ```ruby
1121
1173
  module Divide
1122
- Expected = BCDD::Result::Expectations.new(
1174
+ Result = BCDD::Result::Expectations.new(
1123
1175
  success: {
1124
1176
  numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
1125
1177
  division_completed: Numeric
@@ -1131,12 +1183,12 @@ module Divide
1131
1183
  )
1132
1184
 
1133
1185
  def self.call(arg1, arg2)
1134
- arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
1135
- arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
1186
+ arg1.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg1 must be numeric')
1187
+ arg2.is_a?(Numeric) or return Result::Failure(:invalid_arg, 'arg2 must be numeric')
1136
1188
 
1137
- arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
1189
+ arg2.zero? and return Result::Failure(:division_by_zero, 'arg2 must not be zero')
1138
1190
 
1139
- Expected::Success(:division_completed, arg1 / arg2)
1191
+ Result::Success(:division_completed, arg1 / arg2)
1140
1192
  end
1141
1193
  end
1142
1194
  ```
@@ -1148,32 +1200,75 @@ The value validation will only be performed through the methods `Success()` and
1148
1200
  ##### Success()
1149
1201
 
1150
1202
  ```ruby
1151
- Divide::Expected::Success(:ok)
1203
+ Divide::Result::Success(:ok)
1152
1204
  # type :ok is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
1153
1205
 
1154
- Divide::Expected::Success(:numbers, [1])
1206
+ Divide::Result::Success(:numbers, [1])
1155
1207
  # value [1] is not allowed for :numbers type (BCDD::Result::Expectations::Error::UnexpectedValue)
1156
1208
 
1157
- Divide::Expected::Success(:division_completed, '2')
1209
+ Divide::Result::Success(:division_completed, '2')
1158
1210
  # value "2" is not allowed for :division_completed type (BCDD::Result::Expectations::Error::UnexpectedValue)
1159
1211
  ```
1160
1212
 
1161
1213
  ##### Failure()
1162
1214
 
1163
1215
  ```ruby
1164
- Divide::Expected::Failure(:err)
1216
+ Divide::Result::Failure(:err)
1165
1217
  # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero (BCDD::Result::Expectations::Error::UnexpectedType)
1166
1218
 
1167
- Divide::Expected::Failure(:invalid_arg, :arg1_must_be_numeric)
1219
+ Divide::Result::Failure(:invalid_arg, :arg1_must_be_numeric)
1168
1220
  # value :arg1_must_be_numeric is not allowed for :invalid_arg type (BCDD::Result::Expectations::Error::UnexpectedValue)
1169
1221
 
1170
- Divide::Expected::Failure(:division_by_zero, msg: 'arg2 must not be zero')
1222
+ Divide::Result::Failure(:division_by_zero, msg: 'arg2 must not be zero')
1171
1223
  # value {:msg=>"arg2 must not be zero"} is not allowed for :division_by_zero type (BCDD::Result::Expectations::Error::UnexpectedValue)
1172
1224
  ```
1173
1225
 
1174
1226
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1175
1227
 
1176
- #### `BCDD::Result::Expectations.mixin` Addons
1228
+ ##### Pattern Matching Support
1229
+
1230
+ The value checking has support for handling pattern-matching errors, and the cleanest way to do it is using the one-line pattern matching operators (`=>` since Ruby 3.0) and (`in` Ruby 2.7).
1231
+
1232
+ How does this operator work? They raise an error when the pattern does not match but returns nil when it matches.
1233
+
1234
+ Because of this, you will need to enable `nil` as a valid value checking. You can do it by calling the `BCDD::Result::Contract.nil_as_valid_value_checking!` method.
1235
+
1236
+ **Attention:**
1237
+
1238
+ If you decide to enable this, you will do it at the beginning of your code or in an initializer. And remember, this will affect all kinds of result expectations (`BCDD::Result::Expectations` and `BCDD::Result::Context::Expectations`). So, it is recommended to use it only when you are using pattern matching for **ALL** the result's value validations.
1239
+
1240
+ ```ruby
1241
+ #
1242
+ # Put this line in an initializer or at the beginning of your code.
1243
+ # It is required if you decide to use pattern matching to validate all of your result's values.
1244
+ #
1245
+ BCDD::Result::Contract.nil_as_valid_value_checking!
1246
+
1247
+ module Divide
1248
+ extend BCDD::Result::Expectations.mixin(
1249
+ success: {
1250
+ division_completed: ->(value) { value => (Integer | Float) }
1251
+ },
1252
+ failure: { invalid_arg: String, division_by_zero: String }
1253
+ )
1254
+
1255
+ def self.call(arg1, arg2)
1256
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
1257
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
1258
+
1259
+ arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
1260
+
1261
+ Success(:division_completed, String(arg1 / arg2))
1262
+ end
1263
+ end
1264
+
1265
+ Divide.call(10, 5)
1266
+ # value "5" is not allowed for :division_completed type ("5": Float === "5" does not return true) (BCDD::Result::Contract::Error::UnexpectedValue)
1267
+ ```
1268
+
1269
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1270
+
1271
+ #### `BCDD::Result::Expectations.mixin` add-ons
1177
1272
 
1178
1273
  The `BCDD::Result::Expectations.mixin` also accepts the `with:` argument. It is a hash that will be used to define the methods that will be added to the target object.
1179
1274
 
@@ -1228,6 +1323,336 @@ result.success?(:ok)
1228
1323
 
1229
1324
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1230
1325
 
1326
+ ### `BCDD::Result::Context`
1327
+
1328
+ The `BCDD::Result::Context` is a `BCDD::Result`, meaning it has all the features of the `BCDD::Result`. The main difference is that it only accepts keyword arguments as a value, which applies to the `and_then`: The called methods must receive keyword arguments, and the dependency injection will be performed through keyword arguments.
1329
+
1330
+ As the input/output are hashes, the results of each `and_then` call will automatically accumulate. This is useful in operations chaining, as the result of the previous operations will be automatically available for the next one. Because of this behavior, the `BCDD::Result::Context` has the `#and_expose` method to expose only the desired keys from the accumulated result.
1331
+
1332
+ #### Defining successes and failures
1333
+
1334
+ As the `BCDD::Result`, you can declare success and failures directly from `BCDD::Result::Context`.
1335
+
1336
+ ```ruby
1337
+ BCDD::Result::Context::Success(:ok, a: 1, b: 2)
1338
+ #<BCDD::Result::Context::Success type=:ok value={:a=>1, :b=>2}>
1339
+
1340
+ BCDD::Result::Context::Failure(:err, message: 'something went wrong')
1341
+ #<BCDD::Result::Context::Failure type=:err value={:message=>"something went wrong"}>
1342
+ ```
1343
+
1344
+ But different from `BCDD::Result` that accepts any value, the `BCDD::Result::Context` only takes keyword arguments.
1345
+
1346
+ ```ruby
1347
+ BCDD::Result::Context::Success(:ok, [1, 2])
1348
+ # wrong number of arguments (given 2, expected 1) (ArgumentError)
1349
+
1350
+ BCDD::Result::Context::Failure(:err, { message: 'something went wrong' })
1351
+ # wrong number of arguments (given 2, expected 1) (ArgumentError)
1352
+
1353
+ #
1354
+ # Use ** to convert a hash to keyword arguments
1355
+ #
1356
+ BCDD::Result::Context::Success(:ok, **{ message: 'hashes can be converted to keyword arguments' })
1357
+ #<BCDD::Result::Context::Success type=:ok value={:message=>"hashes can be converted to keyword arguments"}>
1358
+ ```
1359
+
1360
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1361
+
1362
+ #### `BCDD::Result::Context.mixin`
1363
+
1364
+ As in the `BCDD::Result`, you can use the `BCDD::Result::Context.mixin` to add the `Success()` and `Failure()` methods to your classes/modules.
1365
+
1366
+ Let's see this feature and the data accumulation in action:
1367
+
1368
+ ##### Class example (Instance Methods)
1369
+
1370
+ ```ruby
1371
+ class Divide
1372
+ require 'logger'
1373
+
1374
+ include BCDD::Result::Context.mixin
1375
+
1376
+ def call(arg1, arg2, logger: ::Logger.new(STDOUT))
1377
+ validate_numbers(arg1, arg2)
1378
+ .and_then(:validate_non_zero)
1379
+ .and_then(:divide, logger: logger)
1380
+ end
1381
+
1382
+ private
1383
+
1384
+ def validate_numbers(arg1, arg2)
1385
+ arg1.is_a?(::Numeric) or return Failure(:err, message: 'arg1 must be numeric')
1386
+ arg2.is_a?(::Numeric) or return Failure(:err, message: 'arg2 must be numeric')
1387
+
1388
+ Success(:ok, number1: arg1, number2: arg2)
1389
+ end
1390
+
1391
+ def validate_non_zero(number2:, **)
1392
+ return Success(:ok) if number2.nonzero?
1393
+
1394
+ Failure(:err, message: 'arg2 must not be zero')
1395
+ end
1396
+
1397
+ #
1398
+ # The logger was injected via #and_then and keyword arguments
1399
+ #
1400
+ def divide(number1:, number2:, logger:)
1401
+ result = number1 / number2
1402
+
1403
+ logger.info("The division result is #{result}")
1404
+
1405
+ Success(:ok, number: result)
1406
+ end
1407
+ end
1408
+
1409
+ Divide.new.call(10, 5)
1410
+ # I, [2023-10-27T01:51:46.905004 #76915] INFO -- : The division result is 2
1411
+ #<BCDD::Result::Context::Success type=:ok value={:number=>2}>
1412
+
1413
+ Divide.new.call('10', 5)
1414
+ #<BCDD::Result::Context::Failure type=:err value={:message=>"arg1 must be numeric"}>
1415
+
1416
+ Divide.new.call(10, '5')
1417
+ #<BCDD::Result::Context::Failure type=:err value={:message=>"arg2 must be numeric"}>
1418
+
1419
+ Divide.new.call(10, 0)
1420
+ #<BCDD::Result::Context::Failure type=:err value={:message=>"arg2 must not be zero"}>
1421
+ ```
1422
+
1423
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1424
+
1425
+ ##### `and_expose`
1426
+
1427
+ This allows you to expose only the desired keys from the accumulated result. It can be used with any `BCDD::Result::Context` object.
1428
+
1429
+ Let's add it to the previous example:
1430
+
1431
+ ```ruby
1432
+ class Divide
1433
+ include BCDD::Result::Context.mixin
1434
+
1435
+ def call(arg1, arg2)
1436
+ validate_numbers(arg1, arg2)
1437
+ .and_then(:validate_non_zero)
1438
+ .and_then(:divide)
1439
+ end
1440
+
1441
+ private
1442
+
1443
+ def validate_numbers(arg1, arg2)
1444
+ arg1.is_a?(::Numeric) or return Failure(:err, message: 'arg1 must be numeric')
1445
+ arg2.is_a?(::Numeric) or return Failure(:err, message: 'arg2 must be numeric')
1446
+
1447
+ Success(:ok, number1: arg1, number2: arg2)
1448
+ end
1449
+
1450
+ def validate_non_zero(number2:, **)
1451
+ return Success(:ok) if number2.nonzero?
1452
+
1453
+ Failure(:err, message: 'arg2 must not be zero')
1454
+ end
1455
+
1456
+ def divide(**input)
1457
+ Success(:ok, number: input.values.reduce(:/), **input)
1458
+ end
1459
+ end
1460
+
1461
+ Divide.new.call(10, 5)
1462
+ #<BCDD::Result::Context::Success type=:division_completed value={:number=>2}>
1463
+ ```
1464
+
1465
+ As you can see, even with `divide` success exposing the division number with all the accumulated data (`**input`), the `#and_expose` could generate a new success with a new type and only with the desired keys.
1466
+
1467
+ Remove the `#and_expose` call to see the difference. This will be the outcome:
1468
+
1469
+ ```ruby
1470
+ Divide.new.call(10, 5)
1471
+ #<BCDD::Result::Context::Success type=:ok value={:number=>2, :number1=>10, :number2=>5}>
1472
+ ```
1473
+
1474
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1475
+
1476
+ ##### Module example (Singleton Methods)
1477
+
1478
+ Yes, the `BCDD::Result::Context.mixin` can produce singleton methods. Look for an example using a module (but it could be a class, too).
1479
+
1480
+ ```ruby
1481
+ module Divide
1482
+ extend self, BCDD::Result::Context.mixin
1483
+
1484
+ def call(arg1, arg2)
1485
+ validate_numbers(arg1, arg2)
1486
+ .and_then(:validate_non_zero)
1487
+ .and_then(:divide)
1488
+ .and_expose(:division_completed, [:number])
1489
+ end
1490
+
1491
+ private
1492
+
1493
+ def validate_numbers(arg1, arg2)
1494
+ arg1.is_a?(::Numeric) or return Failure(:err, message: 'arg1 must be numeric')
1495
+ arg2.is_a?(::Numeric) or return Failure(:err, message: 'arg2 must be numeric')
1496
+
1497
+ Success(:ok, number1: arg1, number2: arg2)
1498
+ end
1499
+
1500
+ def validate_non_zero(number2:, **)
1501
+ return Success(:ok) if number2.nonzero?
1502
+
1503
+ Failure(:err, message: 'arg2 must not be zero')
1504
+ end
1505
+
1506
+ def divide(number1:, number2:)
1507
+ Success(:ok, number: number1 / number2)
1508
+ end
1509
+ end
1510
+
1511
+ Divide.call(10, 5)
1512
+ #<BCDD::Result::Context::Success type=:division_completed value={:number=>2}>
1513
+
1514
+ Divide.call('10', 5)
1515
+ #<BCDD::Result::Context::Failure type=:err value={:message=>"arg1 must be numeric"}>
1516
+
1517
+ Divide.call(10, '5')
1518
+ #<BCDD::Result::Context::Failure type=:err value={:message=>"arg2 must be numeric"}>
1519
+
1520
+ Divide.call(10, 0)
1521
+ #<BCDD::Result::Context::Failure type=:err value={:message=>"arg2 must not be zero"}>
1522
+ ```
1523
+
1524
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1525
+
1526
+ #### `BCDD::Result::Context::Expectations`
1527
+
1528
+ The `BCDD::Result::Context::Expectations` is a `BCDD::Result::Expectations` with the `BCDD::Result::Context` features.
1529
+
1530
+ This is an example using the mixin mode, but the standalone mode is also supported.
1531
+
1532
+ ```ruby
1533
+ #
1534
+ # Put this line in an initializer or at the beginning of your code.
1535
+ # It is required if you decide to use pattern matching to validate all of your result's values.
1536
+ #
1537
+ BCDD::Result::Contract.nil_as_valid_value_checking!
1538
+
1539
+ class Divide
1540
+ include BCDD::Result::Context::Expectations.mixin(
1541
+ success: {
1542
+ division_completed: ->(value) { value => { number: Numeric } }
1543
+ },
1544
+ failure: {
1545
+ invalid_arg: ->(value) { value => { message: String } },
1546
+ division_by_zero: ->(value) { value => { message: String } }
1547
+ }
1548
+ )
1549
+
1550
+ def call(arg1, arg2)
1551
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric')
1552
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric')
1553
+
1554
+ arg2.zero? and return Failure(:division_by_zero, message: 'arg2 must not be zero')
1555
+
1556
+ Success(:division_completed, number: arg1 / arg2)
1557
+ end
1558
+ end
1559
+
1560
+ Divide.new.call(10, 5)
1561
+ #<BCDD::Result::Context::Success type=:division_completed value={:number=>2}>
1562
+ ```
1563
+
1564
+ As in the `BCDD::Result::Expectations.mixin`, the `BCDD::Result::Context::Expectations.mixin` will add a Result constant in the target class. It can generate success/failure results, which ensure the mixin expectations.
1565
+
1566
+ Let's see this using previous example:
1567
+
1568
+ ```ruby
1569
+ Divide::Result::Success(:division_completed, number: 2)
1570
+ #<BCDD::Result::Context::Success type=:division_completed value={:number=>2}>
1571
+
1572
+ Divide::Result::Success(:division_completed, number: '2')
1573
+ # value {:number=>"2"} is not allowed for :division_completed type ({:number=>"2"}: Numeric === "2" does not return true) (BCDD::Result::Contract::Error::UnexpectedValue)
1574
+ ```
1575
+
1576
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1577
+
1578
+ #### Mixin add-ons
1579
+
1580
+ The `BCDD::Result::Context.mixin` and `BCDD::Result::Context::Expectations.mixin` also accepts the `with:` argument. And it works the same way as the `BCDD::Result` mixins.
1581
+
1582
+ **Continue**
1583
+
1584
+ The `BCDD::Result::Context.mixin(with: :Continue)` or `BCDD::Result::Context::Expectations.mixin(with: :Continue)` adds a `Continue(**input)` that will be ignored by the expectations. This is extremely useful when you want to use `Continue()` to chain operations, but you don't want to declare N success types in the expectations.
1585
+
1586
+ Let's use a mix of `BCDD::Result::Context` features to see in action with this add-on:
1587
+
1588
+ ```ruby
1589
+ #
1590
+ # Put this line in an initializer or at the beginning of your code.
1591
+ # It is required if you decide to use pattern matching to validate all of your result's values.
1592
+ #
1593
+ BCDD::Result::Contract.nil_as_valid_value_checking!
1594
+
1595
+ module Divide
1596
+ require 'logger'
1597
+
1598
+ extend self, BCDD::Result::Context::Expectations.mixin(
1599
+ with: :Continue,
1600
+ success: {
1601
+ division_completed: ->(value) { value => { number: Numeric } }
1602
+ },
1603
+ failure: {
1604
+ invalid_arg: ->(value) { value => { message: String } },
1605
+ division_by_zero: ->(value) { value => { message: String } }
1606
+ }
1607
+ )
1608
+
1609
+ def call(arg1, arg2, logger: ::Logger.new(STDOUT))
1610
+ validate_numbers(arg1, arg2)
1611
+ .and_then(:validate_non_zero)
1612
+ .and_then(:divide, logger: logger)
1613
+ .and_expose(:division_completed, [:number])
1614
+ end
1615
+
1616
+ private
1617
+
1618
+ def validate_numbers(arg1, arg2)
1619
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg1 must be numeric')
1620
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, message: 'arg2 must be numeric')
1621
+
1622
+ Continue(number1: arg1, number2: arg2)
1623
+ end
1624
+
1625
+ def validate_non_zero(number2:, **)
1626
+ return Continue() if number2.nonzero?
1627
+
1628
+ Failure(:division_by_zero, message: 'arg2 must not be zero')
1629
+ end
1630
+
1631
+ def divide(number1:, number2:, logger:)
1632
+ result = number1 / number2
1633
+
1634
+ logger.info("The division result is #{result}")
1635
+
1636
+ Continue(number: result)
1637
+ end
1638
+ end
1639
+
1640
+ Divide.call(14, 2)
1641
+ # I, [2023-10-27T02:01:05.812388 #77823] INFO -- : The division result is 7
1642
+ #<BCDD::Result::Context::Success type=:division_completed value={:number=>7}>
1643
+
1644
+ Divide.call('14', 2)
1645
+ #<BCDD::Result::Context::Failure type=:invalid_arg value={:message=>"arg1 must be numeric"}>
1646
+
1647
+ Divide.call(14, '2')
1648
+ #<BCDD::Result::Context::Failure type=:invalid_arg value={:message=>"arg2 must be numeric"}>
1649
+
1650
+ Divide.call(14, 0)
1651
+ #<BCDD::Result::Context::Failure type=:division_by_zero value={:message=>"arg2 must not be zero"}>
1652
+ ```
1653
+
1654
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1655
+
1231
1656
  ## About
1232
1657
 
1233
1658
  [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.