bcdd-result 0.6.0 → 0.7.0

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