bcdd-result 0.9.0 → 0.10.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +34 -11
- data/README.md +170 -5
- data/Steepfile +2 -2
- data/lib/bcdd/result/config/switchers/addons.rb +20 -0
- data/lib/bcdd/result/config/{constant_alias.rb → switchers/constant_aliases.rb} +4 -4
- data/lib/bcdd/result/config/switchers/features.rb +28 -0
- data/lib/bcdd/result/config/switchers/pattern_matching.rb +20 -0
- data/lib/bcdd/result/config.rb +8 -28
- data/lib/bcdd/result/context/failure.rb +1 -1
- data/lib/bcdd/result/context/success.rb +2 -2
- data/lib/bcdd/result/context.rb +12 -11
- data/lib/bcdd/result/transitions/tracking/disabled.rb +17 -0
- data/lib/bcdd/result/transitions/tracking/enabled.rb +80 -0
- data/lib/bcdd/result/transitions/tracking.rb +20 -0
- data/lib/bcdd/result/transitions/tree.rb +95 -0
- data/lib/bcdd/result/transitions.rb +30 -0
- data/lib/bcdd/result/version.rb +1 -1
- data/lib/bcdd/result.rb +35 -18
- data/sig/bcdd/result/config.rbs +100 -0
- data/sig/bcdd/result/context.rbs +102 -0
- data/sig/bcdd/result/contract.rbs +119 -0
- data/sig/bcdd/result/data.rbs +16 -0
- data/sig/bcdd/result/error.rbs +31 -0
- data/sig/bcdd/result/expectations.rbs +67 -0
- data/sig/bcdd/result/handler.rbs +47 -0
- data/sig/bcdd/result/mixin.rbs +37 -0
- data/sig/bcdd/result/transitions.rbs +89 -0
- data/sig/bcdd/result/version.rbs +5 -0
- data/sig/bcdd/result.rbs +6 -516
- metadata +22 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 7bf2a03ac7efd3e75e03b2f5617518cb07f40e03cc970c612f3d4dfcdad153ad
         | 
| 4 | 
            +
              data.tar.gz: 2717e6e600101401f420164aed020689e9c07dd7f7296c78ec5290562620ca3c
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: c1761571f7d0c0a6fd4601a9f8a4c8c18688085226cd061057f95c93737da80ac58e0a99a3803a0e0f93aa5040530975d9b16a7b54df79487ca625fd67d405d6
         | 
| 7 | 
            +
              data.tar.gz: 4b22f37d2282de4c0a6e3781e86c9c63457efa83a7d5dc07db4288b9fe19c5b3868cd13a34a1cfc515776cf466e2095bde9d10503f87b8f546ceab9408ef9617
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -9,6 +9,10 @@ AllCops: | |
| 9 9 | 
             
              NewCops: enable
         | 
| 10 10 | 
             
              TargetRubyVersion: 2.7
         | 
| 11 11 |  | 
| 12 | 
            +
            Lint/RescueException:
         | 
| 13 | 
            +
              Exclude:
         | 
| 14 | 
            +
                - lib/bcdd/result/transitions.rb
         | 
| 15 | 
            +
             | 
| 12 16 | 
             
            Layout/LineLength:
         | 
| 13 17 | 
             
              Max: 120
         | 
| 14 18 |  | 
| @@ -70,6 +74,10 @@ Minitest/AssertEmptyLiteral: | |
| 70 74 | 
             
            Minitest/AssertOperator:
         | 
| 71 75 | 
             
              Enabled: false
         | 
| 72 76 |  | 
| 77 | 
            +
            Minitest/AssertWithExpectedArgument:
         | 
| 78 | 
            +
              Exclude:
         | 
| 79 | 
            +
                - test/test_helper.rb
         | 
| 80 | 
            +
             | 
| 73 81 | 
             
            Naming/FileName:
         | 
| 74 82 | 
             
              Exclude:
         | 
| 75 83 | 
             
                - lib/bcdd-result.rb
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,33 +1,56 @@ | |
| 1 1 | 
             
            - [\[Unreleased\]](#unreleased)
         | 
| 2 | 
            -
            - [\[0. | 
| 2 | 
            +
            - [\[0.10.0\] - 2023-12-31](#0100---2023-12-31)
         | 
| 3 3 | 
             
              - [Added](#added)
         | 
| 4 | 
            +
            - [\[0.9.1\] - 2023-12-12](#091---2023-12-12)
         | 
| 4 5 | 
             
              - [Changed](#changed)
         | 
| 5 | 
            -
            - [ | 
| 6 | 
            +
              - [Fixed](#fixed)
         | 
| 7 | 
            +
            - [\[0.9.0\] - 2023-12-12](#090---2023-12-12)
         | 
| 6 8 | 
             
              - [Added](#added-1)
         | 
| 7 9 | 
             
              - [Changed](#changed-1)
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            - [\[0.7.0\] - 2023-10-27](#070---2023-10-27)
         | 
| 10 | 
            +
            - [\[0.8.0\] - 2023-12-11](#080---2023-12-11)
         | 
| 10 11 | 
             
              - [Added](#added-2)
         | 
| 11 12 | 
             
              - [Changed](#changed-2)
         | 
| 12 | 
            -
            - [ | 
| 13 | 
            +
              - [Removed](#removed)
         | 
| 14 | 
            +
            - [\[0.7.0\] - 2023-10-27](#070---2023-10-27)
         | 
| 13 15 | 
             
              - [Added](#added-3)
         | 
| 14 16 | 
             
              - [Changed](#changed-3)
         | 
| 15 | 
            -
            - [\[0. | 
| 17 | 
            +
            - [\[0.6.0\] - 2023-10-11](#060---2023-10-11)
         | 
| 16 18 | 
             
              - [Added](#added-4)
         | 
| 17 | 
            -
            - [\[0.4.0\] - 2023-09-28](#040---2023-09-28)
         | 
| 18 | 
            -
              - [Added](#added-5)
         | 
| 19 19 | 
             
              - [Changed](#changed-4)
         | 
| 20 | 
            +
            - [\[0.5.0\] - 2023-10-09](#050---2023-10-09)
         | 
| 21 | 
            +
              - [Added](#added-5)
         | 
| 22 | 
            +
            - [\[0.4.0\] - 2023-09-28](#040---2023-09-28)
         | 
| 23 | 
            +
              - [Added](#added-6)
         | 
| 24 | 
            +
              - [Changed](#changed-5)
         | 
| 20 25 | 
             
              - [Removed](#removed-1)
         | 
| 21 26 | 
             
            - [\[0.3.0\] - 2023-09-26](#030---2023-09-26)
         | 
| 22 | 
            -
              - [Added](#added-6)
         | 
| 23 | 
            -
            - [\[0.2.0\] - 2023-09-26](#020---2023-09-26)
         | 
| 24 27 | 
             
              - [Added](#added-7)
         | 
| 28 | 
            +
            - [\[0.2.0\] - 2023-09-26](#020---2023-09-26)
         | 
| 29 | 
            +
              - [Added](#added-8)
         | 
| 25 30 | 
             
              - [Removed](#removed-2)
         | 
| 26 31 | 
             
            - [\[0.1.0\] - 2023-09-25](#010---2023-09-25)
         | 
| 27 | 
            -
              - [Added](#added- | 
| 32 | 
            +
              - [Added](#added-9)
         | 
| 28 33 |  | 
| 29 34 | 
             
            ## [Unreleased]
         | 
| 30 35 |  | 
| 36 | 
            +
            ## [0.10.0] - 2023-12-31
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            ### Added
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            - Add `BCDD::Result.transitions(&block)` to track all transitions in the same or between different operations. When there is a nesting of transition blocks, this mechanism will be able to correlate parent and child blocks and present the duration of all operations in milliseconds.
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            - Add `BCDD::Result.config.feature.disable!(:transitions)` and `BCDD::Result.config.feature.enable!(:transitions)` to turn on/off the `BCDD::Result.transitions` feature.
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            ## [0.9.1] - 2023-12-12
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            ### Changed
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            - **(BREAKING)** Make `BCDD::Result::Context::Success#and_expose()` to produce a halted success by default. You can turn this off by passing `halted: false`.
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            ### Fixed
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            - Make `BCDD::Result::Context#and_then(&block)` accumulate the result value.
         | 
| 53 | 
            +
             | 
| 31 54 | 
             
            ## [0.9.0] - 2023-12-12
         | 
| 32 55 |  | 
| 33 56 | 
             
            ### Added
         | 
    
        data/README.md
    CHANGED
    
    | @@ -70,6 +70,8 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi | |
| 70 70 | 
             
                  - [Module example (Singleton Methods)](#module-example-singleton-methods-1)
         | 
| 71 71 | 
             
                - [`BCDD::Result::Context::Expectations`](#bcddresultcontextexpectations)
         | 
| 72 72 | 
             
                - [Mixin add-ons](#mixin-add-ons)
         | 
| 73 | 
            +
              - [`BCDD::Result.transitions`](#bcddresulttransitions)
         | 
| 74 | 
            +
                - [Configuration](#configuration)
         | 
| 73 75 | 
             
              - [`BCDD::Result.configuration`](#bcddresultconfiguration)
         | 
| 74 76 | 
             
                - [`config.addon.enable!(:continue)`](#configaddonenablecontinue)
         | 
| 75 77 | 
             
                - [`config.constant_alias.enable!('Result', 'BCDD::Context')`](#configconstant_aliasenableresult-bcddcontext)
         | 
| @@ -983,7 +985,7 @@ module Divide | |
| 983 985 | 
             
            end
         | 
| 984 986 | 
             
            ```
         | 
| 985 987 |  | 
| 986 | 
            -
            In the code above, we define a constant `Divide:: | 
| 988 | 
            +
            In the code above, we define a constant `Divide::Result`. And because of this (it is a constant), we can use it inside and outside the module.
         | 
| 987 989 |  | 
| 988 990 | 
             
            Look what happens if you try to create a result without one of the expected types.
         | 
| 989 991 |  | 
| @@ -1223,7 +1225,7 @@ Divide.call(4, 2) | |
| 1223 1225 |  | 
| 1224 1226 | 
             
            #### Value checking - Result Creation
         | 
| 1225 1227 |  | 
| 1226 | 
            -
            The `Result::Expectations` supports types of validations. The first is the type checking only, and the second is the type and value checking.
         | 
| 1228 | 
            +
            The `Result::Expectations` supports two types of validations. The first is the type checking only, and the second is the type and value checking.
         | 
| 1227 1229 |  | 
| 1228 1230 | 
             
            To define expectations for your result's values, you must declare a Hash with the type as the key and the value as the value. A value validator is any object that responds to `#===` (case equality operator).
         | 
| 1229 1231 |  | 
| @@ -1569,11 +1571,13 @@ Divide.new.call(10, 5) | |
| 1569 1571 | 
             
            #<BCDD::Result::Context::Success type=:ok value={:number=>2, :number1=>10, :number2=>5}>
         | 
| 1570 1572 | 
             
            ```
         | 
| 1571 1573 |  | 
| 1574 | 
            +
            > PS: The `#and_expose` produces a halted success by default. This means the next step will not be executed even if you call `#and_then` after `#and_expose`. To change this behavior, you can pass `halted: false` to `#and_expose`.
         | 
| 1575 | 
            +
             | 
| 1572 1576 | 
             
            <p align="right"><a href="#-bcddresult">⬆️  back to top</a></p>
         | 
| 1573 1577 |  | 
| 1574 1578 | 
             
            ##### Module example (Singleton Methods)
         | 
| 1575 1579 |  | 
| 1576 | 
            -
             | 
| 1580 | 
            +
            `BCDD::Result::Context.mixin` can also produce singleton methods. Below is an example using a module (but it could be a class, too).
         | 
| 1577 1581 |  | 
| 1578 1582 | 
             
            ```ruby
         | 
| 1579 1583 | 
             
            module Divide
         | 
| @@ -1658,7 +1662,7 @@ Divide.new.call(10, 5) | |
| 1658 1662 |  | 
| 1659 1663 | 
             
            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.
         | 
| 1660 1664 |  | 
| 1661 | 
            -
            Let's see this using previous example:
         | 
| 1665 | 
            +
            Let's see this using the previous example:
         | 
| 1662 1666 |  | 
| 1663 1667 | 
             
            ```ruby
         | 
| 1664 1668 | 
             
            Divide::Result::Success(:division_completed, number: 2)
         | 
| @@ -1745,6 +1749,160 @@ Divide.call(14, 0) | |
| 1745 1749 | 
             
            #<BCDD::Result::Context::Failure type=:division_by_zero value={:message=>"arg2 must not be zero"}>
         | 
| 1746 1750 | 
             
            ```
         | 
| 1747 1751 |  | 
| 1752 | 
            +
            ### `BCDD::Result.transitions`
         | 
| 1753 | 
            +
             | 
| 1754 | 
            +
            Use `BCDD::Result.transitions(&block)` to track all transitions in the same or between different operations. When there is a nesting of transition blocks, this mechanism will be able to correlate parent and child blocks and present the duration of all operations in milliseconds.
         | 
| 1755 | 
            +
             | 
| 1756 | 
            +
            When you wrap the creation of the result with `BCDD::Result.transitions`, the final result will expose all the transition records through the `BCDD::Result#transitions` method.
         | 
| 1757 | 
            +
             | 
| 1758 | 
            +
            ```ruby
         | 
| 1759 | 
            +
            class Division
         | 
| 1760 | 
            +
              include BCDD::Result.mixin(config: { addon: { continue: true } })
         | 
| 1761 | 
            +
             | 
| 1762 | 
            +
              def call(arg1, arg2)
         | 
| 1763 | 
            +
                BCDD::Result.transitions(name: 'Division', desc: 'divide two numbers') do
         | 
| 1764 | 
            +
                  require_numbers(arg1, arg2)
         | 
| 1765 | 
            +
                    .and_then(:check_for_zeros)
         | 
| 1766 | 
            +
                    .and_then(:divide)
         | 
| 1767 | 
            +
                end
         | 
| 1768 | 
            +
              end
         | 
| 1769 | 
            +
             | 
| 1770 | 
            +
              private
         | 
| 1771 | 
            +
             | 
| 1772 | 
            +
              ValidNumber = ->(arg) { arg.is_a?(Numeric) && arg != Float::NAN && arg != Float::INFINITY }
         | 
| 1773 | 
            +
             | 
| 1774 | 
            +
              def require_numbers(arg1, arg2)
         | 
| 1775 | 
            +
                ValidNumber[arg1] or return Failure(:invalid_arg, 'arg1 must be a valid number')
         | 
| 1776 | 
            +
                ValidNumber[arg2] or return Failure(:invalid_arg, 'arg2 must be a valid number')
         | 
| 1777 | 
            +
             | 
| 1778 | 
            +
                Continue([arg1, arg2])
         | 
| 1779 | 
            +
              end
         | 
| 1780 | 
            +
             | 
| 1781 | 
            +
              def check_for_zeros(numbers)
         | 
| 1782 | 
            +
                num1, num2 = numbers
         | 
| 1783 | 
            +
             | 
| 1784 | 
            +
                return Failure(:division_by_zero, 'num2 cannot be zero') if num2.zero?
         | 
| 1785 | 
            +
             | 
| 1786 | 
            +
                num1.zero? ? Success(:division_completed, 0) : Continue(numbers)
         | 
| 1787 | 
            +
              end
         | 
| 1788 | 
            +
             | 
| 1789 | 
            +
              def divide((num1, num2))
         | 
| 1790 | 
            +
                Success(:division_completed, num1 / num2)
         | 
| 1791 | 
            +
              end
         | 
| 1792 | 
            +
            end
         | 
| 1793 | 
            +
             | 
| 1794 | 
            +
            module SumDivisionsByTwo
         | 
| 1795 | 
            +
              extend self, BCDD::Result.mixin
         | 
| 1796 | 
            +
             | 
| 1797 | 
            +
              def call(*numbers)
         | 
| 1798 | 
            +
                BCDD::Result.transitions(name: 'SumDivisionsByTwo') do
         | 
| 1799 | 
            +
                  divisions = numbers.map { |number| Division.new.call(number, 2) }
         | 
| 1800 | 
            +
             | 
| 1801 | 
            +
                  if divisions.any?(&:failure?)
         | 
| 1802 | 
            +
                    Failure(:errors, divisions.select(&:failure?).map(&:value))
         | 
| 1803 | 
            +
                  else
         | 
| 1804 | 
            +
                    Success(:sum, divisions.sum(&:value))
         | 
| 1805 | 
            +
                  end
         | 
| 1806 | 
            +
                end
         | 
| 1807 | 
            +
              end
         | 
| 1808 | 
            +
            end
         | 
| 1809 | 
            +
            ```
         | 
| 1810 | 
            +
             | 
| 1811 | 
            +
            Let's see the result of the `SumDivisionsByTwo` call:
         | 
| 1812 | 
            +
             | 
| 1813 | 
            +
            ```ruby
         | 
| 1814 | 
            +
            result = SumDivisionsByTwo.call(20, 10)
         | 
| 1815 | 
            +
            # => #<BCDD::Result::Success type=:sum value=15>
         | 
| 1816 | 
            +
             | 
| 1817 | 
            +
            result.transitions
         | 
| 1818 | 
            +
            {
         | 
| 1819 | 
            +
              :version =>1,
         | 
| 1820 | 
            +
              :metadata => {
         | 
| 1821 | 
            +
                :duration => 0,                       # milliseconds
         | 
| 1822 | 
            +
                :tree_map => [0, [[1, []], [2, []]]], # represents the tree of transitions using the id of each transition block
         | 
| 1823 | 
            +
              },
         | 
| 1824 | 
            +
              :records => [
         | 
| 1825 | 
            +
                {
         | 
| 1826 | 
            +
                  :root => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1827 | 
            +
                  :parent => {:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1828 | 
            +
                  :current => {:id=>1, :name=>"Division", :desc=>"divide two numbers"},
         | 
| 1829 | 
            +
                  :result => {:kind=>:success, :type=>:continued, :value=>[20, 2]},
         | 
| 1830 | 
            +
                  :and_then => {},
         | 
| 1831 | 
            +
                  :time => 2023-12-31 22:19:33.281619 UTC
         | 
| 1832 | 
            +
                },
         | 
| 1833 | 
            +
                {
         | 
| 1834 | 
            +
                  :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1835 | 
            +
                  :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1836 | 
            +
                  :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
         | 
| 1837 | 
            +
                  :result=>{:kind=>:success, :type=>:continued, :value=>[20, 2]},
         | 
| 1838 | 
            +
                  :and_then=>{:type=>:method, :arg=>nil, :subject=>#<Division:0x0000000103e5f7c8>, :method_name=>:check_for_zeros},
         | 
| 1839 | 
            +
                  :time=>2023-12-31 22:19:33.281693 UTC
         | 
| 1840 | 
            +
                },
         | 
| 1841 | 
            +
                {
         | 
| 1842 | 
            +
                  :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1843 | 
            +
                  :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1844 | 
            +
                  :current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
         | 
| 1845 | 
            +
                  :result=>{:kind=>:success, :type=>:division_completed, :value=>10},
         | 
| 1846 | 
            +
                  :and_then=>{:type=>:method, :arg=>nil, :subject=>#<Division:0x0000000103e5f7c8>, :method_name=>:divide},
         | 
| 1847 | 
            +
                  :time=>2023-12-31 22:19:33.281715 UTC
         | 
| 1848 | 
            +
                },
         | 
| 1849 | 
            +
                {
         | 
| 1850 | 
            +
                  :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1851 | 
            +
                  :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1852 | 
            +
                  :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
         | 
| 1853 | 
            +
                  :result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]},
         | 
| 1854 | 
            +
                  :and_then=>{},
         | 
| 1855 | 
            +
                  :time=>2023-12-31 22:19:33.281747 UTC
         | 
| 1856 | 
            +
                },
         | 
| 1857 | 
            +
                {
         | 
| 1858 | 
            +
                  :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1859 | 
            +
                  :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1860 | 
            +
                  :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
         | 
| 1861 | 
            +
                  :result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]},
         | 
| 1862 | 
            +
                  :and_then=>{:type=>:method, :arg=>nil, :subject=>#<Division:0x0000000103e5f1b0>, :method_name=>:check_for_zeros},
         | 
| 1863 | 
            +
                  :time=>2023-12-31 22:19:33.281755 UTC
         | 
| 1864 | 
            +
                },
         | 
| 1865 | 
            +
                {
         | 
| 1866 | 
            +
                  :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1867 | 
            +
                  :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1868 | 
            +
                  :current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
         | 
| 1869 | 
            +
                  :result=>{:kind=>:success, :type=>:division_completed, :value=>5},
         | 
| 1870 | 
            +
                  :and_then=>{:type=>:method, :arg=>nil, :subject=>#<Division:0x0000000103e5f1b0>, :method_name=>:divide},
         | 
| 1871 | 
            +
                  :time=>2023-12-31 22:19:33.281763 UTC
         | 
| 1872 | 
            +
                },
         | 
| 1873 | 
            +
                {
         | 
| 1874 | 
            +
                  :root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1875 | 
            +
                  :parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1876 | 
            +
                  :current=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
         | 
| 1877 | 
            +
                  :result=>{:kind=>:success, :type=>:sum, :value=>15},
         | 
| 1878 | 
            +
                  :and_then=>{},
         | 
| 1879 | 
            +
                  :time=>2023-12-31 22:19:33.281784 UTC
         | 
| 1880 | 
            +
                }
         | 
| 1881 | 
            +
              ]
         | 
| 1882 | 
            +
            }
         | 
| 1883 | 
            +
            ```
         | 
| 1884 | 
            +
             | 
| 1885 | 
            +
            <p align="right"><a href="#-bcddresult">⬆️  back to top</a></p>
         | 
| 1886 | 
            +
             | 
| 1887 | 
            +
            #### Configuration
         | 
| 1888 | 
            +
             | 
| 1889 | 
            +
            You can use `BCDD::Result.config.feature.disable!(:transitions)` and `BCDD::Result.config.feature.enable!(:transitions)` to turn on/off the `BCDD::Result.transitions` feature.
         | 
| 1890 | 
            +
             | 
| 1891 | 
            +
            ```ruby
         | 
| 1892 | 
            +
            BCDD::Result.configuration do |config|
         | 
| 1893 | 
            +
              config.feature.disable!(:transitions)
         | 
| 1894 | 
            +
            end
         | 
| 1895 | 
            +
             | 
| 1896 | 
            +
            result = SumDivisionsByTwo.call(20, 10)
         | 
| 1897 | 
            +
            # => #<BCDD::Result::Success type=:sum value=15>
         | 
| 1898 | 
            +
             | 
| 1899 | 
            +
            result.transitions
         | 
| 1900 | 
            +
             | 
| 1901 | 
            +
            {:version=>1, :records=>[], :metadata=>{:duration=>0, :tree_map=>[]}}
         | 
| 1902 | 
            +
            ```
         | 
| 1903 | 
            +
             | 
| 1904 | 
            +
            <p align="right"><a href="#-bcddresult">⬆️  back to top</a></p>
         | 
| 1905 | 
            +
             | 
| 1748 1906 | 
             
            ### `BCDD::Result.configuration`
         | 
| 1749 1907 |  | 
| 1750 1908 | 
             
            The `BCDD::Result.configuration` allows you to configure default behaviors for `BCDD::Result` and `BCDD::Result::Context` through a configuration block. After using it, the configuration is frozen, ensuring the expected behaviors for your application.
         | 
| @@ -1791,7 +1949,7 @@ PS: I'm using `::Rails.env.production?` to check the environment, but you can us | |
| 1791 1949 |  | 
| 1792 1950 | 
             
            ### `BCDD::Result.config`
         | 
| 1793 1951 |  | 
| 1794 | 
            -
            The `BCDD::Result.config` allows you to access the current configuration. | 
| 1952 | 
            +
            The `BCDD::Result.config` allows you to access the current configuration.
         | 
| 1795 1953 |  | 
| 1796 1954 | 
             
            **BCDD::Result.config.addon**
         | 
| 1797 1955 |  | 
| @@ -1857,6 +2015,13 @@ BCDD::Result.config.feature.options | |
| 1857 2015 | 
             
            #       "BCDD::Result::Expectations,
         | 
| 1858 2016 | 
             
            #       "BCDD::Result::Context::Expectations"
         | 
| 1859 2017 | 
             
            #     ]
         | 
| 2018 | 
            +
            #   },
         | 
| 2019 | 
            +
            #   :transitions=>{
         | 
| 2020 | 
            +
            #     :enabled=>true,
         | 
| 2021 | 
            +
            #     :affects=>[
         | 
| 2022 | 
            +
            #       "BCDD::Result",
         | 
| 2023 | 
            +
            #       "BCDD::Result::Context"
         | 
| 2024 | 
            +
            #     ]
         | 
| 1860 2025 | 
             
            #   }
         | 
| 1861 2026 | 
             
            # }
         | 
| 1862 2027 | 
             
            ```
         | 
    
        data/Steepfile
    CHANGED
    
    | @@ -10,8 +10,8 @@ target :lib do | |
| 10 10 | 
             
              # check 'app/models/**/*.rb'        # Glob
         | 
| 11 11 | 
             
              # ignore 'lib/templates/*.rb'
         | 
| 12 12 |  | 
| 13 | 
            -
              library 'singleton' | 
| 14 | 
            -
              # library 'strong_json' | 
| 13 | 
            +
              library 'singleton', 'securerandom' # Standard libraries
         | 
| 14 | 
            +
              # library 'strong_json'             # Gems
         | 
| 15 15 |  | 
| 16 16 | 
             
              # configure_code_diagnostics(D::Ruby.default)      # `default` diagnostics setting (applies by default)
         | 
| 17 17 | 
             
              # configure_code_diagnostics(D::Ruby.strict)       # `strict` diagnostics setting
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class BCDD::Result
         | 
| 4 | 
            +
              class Config
         | 
| 5 | 
            +
                module Addons
         | 
| 6 | 
            +
                  OPTIONS = {
         | 
| 7 | 
            +
                    continue: {
         | 
| 8 | 
            +
                      default: false,
         | 
| 9 | 
            +
                      affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
         | 
| 10 | 
            +
                    }
         | 
| 11 | 
            +
                  }.transform_values!(&:freeze).freeze
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def self.switcher
         | 
| 14 | 
            +
                    Switcher.new(options: OPTIONS)
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                private_constant :Addons
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -2,7 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            class BCDD::Result
         | 
| 4 4 | 
             
              class Config
         | 
| 5 | 
            -
                module  | 
| 5 | 
            +
                module ConstantAliases
         | 
| 6 6 | 
             
                  MAPPING = {
         | 
| 7 7 | 
             
                    'Result' => { target: ::Object, name: :Result, value: ::BCDD::Result },
         | 
| 8 8 | 
             
                    'Context' => { target: ::Object, name: :Context, value: ::BCDD::Result::Context },
         | 
| @@ -15,14 +15,14 @@ class BCDD::Result | |
| 15 15 | 
             
                    [option_name, { default: false, affects: [affects].freeze }]
         | 
| 16 16 | 
             
                  end.freeze
         | 
| 17 17 |  | 
| 18 | 
            -
                  Listener = ->(option_name,  | 
| 18 | 
            +
                  Listener = ->(option_name, bool) do
         | 
| 19 19 | 
             
                    mapping = MAPPING.fetch(option_name)
         | 
| 20 20 |  | 
| 21 21 | 
             
                    target, name, value = mapping.fetch_values(:target, :name, :value)
         | 
| 22 22 |  | 
| 23 23 | 
             
                    defined = target.const_defined?(name, false)
         | 
| 24 24 |  | 
| 25 | 
            -
                     | 
| 25 | 
            +
                    bool ? defined || target.const_set(name, value) : defined && target.send(:remove_const, name)
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 |  | 
| 28 28 | 
             
                  def self.switcher
         | 
| @@ -30,6 +30,6 @@ class BCDD::Result | |
| 30 30 | 
             
                  end
         | 
| 31 31 | 
             
                end
         | 
| 32 32 |  | 
| 33 | 
            -
                private_constant : | 
| 33 | 
            +
                private_constant :ConstantAliases
         | 
| 34 34 | 
             
              end
         | 
| 35 35 | 
             
            end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class BCDD::Result
         | 
| 4 | 
            +
              class Config
         | 
| 5 | 
            +
                module Features
         | 
| 6 | 
            +
                  OPTIONS = {
         | 
| 7 | 
            +
                    expectations: {
         | 
| 8 | 
            +
                      default: true,
         | 
| 9 | 
            +
                      affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
         | 
| 10 | 
            +
                    },
         | 
| 11 | 
            +
                    transitions: {
         | 
| 12 | 
            +
                      default: true,
         | 
| 13 | 
            +
                      affects: %w[BCDD::Result BCDD::Result::Context]
         | 
| 14 | 
            +
                    }
         | 
| 15 | 
            +
                  }.transform_values!(&:freeze).freeze
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  Listener = ->(option_name, _bool) do
         | 
| 18 | 
            +
                    Thread.current[Transitions::THREAD_VAR_NAME] = nil if option_name == :transitions
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def self.switcher
         | 
| 22 | 
            +
                    Switcher.new(options: OPTIONS, listener: Listener)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                private_constant :Features
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class BCDD::Result
         | 
| 4 | 
            +
              class Config
         | 
| 5 | 
            +
                module PatternMatching
         | 
| 6 | 
            +
                  OPTIONS = {
         | 
| 7 | 
            +
                    nil_as_valid_value_checking: {
         | 
| 8 | 
            +
                      default: false,
         | 
| 9 | 
            +
                      affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
         | 
| 10 | 
            +
                    }
         | 
| 11 | 
            +
                  }.transform_values!(&:freeze).freeze
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def self.switcher
         | 
| 14 | 
            +
                    Switcher.new(options: OPTIONS)
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                private_constant :PatternMatching
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
    
        data/lib/bcdd/result/config.rb
    CHANGED
    
    | @@ -4,40 +4,22 @@ require 'singleton' | |
| 4 4 |  | 
| 5 5 | 
             
            require_relative 'config/options'
         | 
| 6 6 | 
             
            require_relative 'config/switcher'
         | 
| 7 | 
            -
            require_relative 'config/ | 
| 7 | 
            +
            require_relative 'config/switchers/addons'
         | 
| 8 | 
            +
            require_relative 'config/switchers/constant_aliases'
         | 
| 9 | 
            +
            require_relative 'config/switchers/features'
         | 
| 10 | 
            +
            require_relative 'config/switchers/pattern_matching'
         | 
| 8 11 |  | 
| 9 12 | 
             
            class BCDD::Result
         | 
| 10 13 | 
             
              class Config
         | 
| 11 14 | 
             
                include Singleton
         | 
| 12 15 |  | 
| 13 | 
            -
                ADDON = {
         | 
| 14 | 
            -
                  continue: {
         | 
| 15 | 
            -
                    default: false,
         | 
| 16 | 
            -
                    affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
         | 
| 17 | 
            -
                  }
         | 
| 18 | 
            -
                }.transform_values!(&:freeze).freeze
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                FEATURE = {
         | 
| 21 | 
            -
                  expectations: {
         | 
| 22 | 
            -
                    default: true,
         | 
| 23 | 
            -
                    affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
         | 
| 24 | 
            -
                  }
         | 
| 25 | 
            -
                }.transform_values!(&:freeze).freeze
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                PATTERN_MATCHING = {
         | 
| 28 | 
            -
                  nil_as_valid_value_checking: {
         | 
| 29 | 
            -
                    default: false,
         | 
| 30 | 
            -
                    affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
         | 
| 31 | 
            -
                  }
         | 
| 32 | 
            -
                }.transform_values!(&:freeze).freeze
         | 
| 33 | 
            -
             | 
| 34 16 | 
             
                attr_reader :addon, :feature, :constant_alias, :pattern_matching
         | 
| 35 17 |  | 
| 36 18 | 
             
                def initialize
         | 
| 37 | 
            -
                  @addon =  | 
| 38 | 
            -
                  @feature =  | 
| 39 | 
            -
                  @constant_alias =  | 
| 40 | 
            -
                  @pattern_matching =  | 
| 19 | 
            +
                  @addon = Addons.switcher
         | 
| 20 | 
            +
                  @feature = Features.switcher
         | 
| 21 | 
            +
                  @constant_alias = ConstantAliases.switcher
         | 
| 22 | 
            +
                  @pattern_matching = PatternMatching.switcher
         | 
| 41 23 | 
             
                end
         | 
| 42 24 |  | 
| 43 25 | 
             
                def freeze
         | 
| @@ -65,7 +47,5 @@ class BCDD::Result | |
| 65 47 | 
             
                def inspect
         | 
| 66 48 | 
             
                  "#<#{self.class.name} options=#{options.keys.sort.inspect}>"
         | 
| 67 49 | 
             
                end
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                private_constant :ADDON, :FEATURE, :PATTERN_MATCHING
         | 
| 70 50 | 
             
              end
         | 
| 71 51 | 
             
            end
         | 
| @@ -3,13 +3,13 @@ | |
| 3 3 | 
             
            class BCDD::Result::Context::Success < BCDD::Result::Context
         | 
| 4 4 | 
             
              include ::BCDD::Result::Success::Methods
         | 
| 5 5 |  | 
| 6 | 
            -
              def and_expose(type, keys)
         | 
| 6 | 
            +
              def and_expose(type, keys, halted: true)
         | 
| 7 7 | 
             
                unless keys.is_a?(::Array) && !keys.empty? && keys.all?(::Symbol)
         | 
| 8 8 | 
             
                  raise ::ArgumentError, 'keys must be an Array of Symbols'
         | 
| 9 9 | 
             
                end
         | 
| 10 10 |  | 
| 11 11 | 
             
                exposed_value = acc.merge(value).slice(*keys)
         | 
| 12 12 |  | 
| 13 | 
            -
                self.class.new(type: type, value: exposed_value, subject: subject)
         | 
| 13 | 
            +
                self.class.new(type: type, value: exposed_value, subject: subject, halted: halted)
         | 
| 14 14 | 
             
              end
         | 
| 15 15 | 
             
            end
         | 
    
        data/lib/bcdd/result/context.rb
    CHANGED
    
    | @@ -40,19 +40,20 @@ class BCDD::Result | |
| 40 40 | 
             
                  -1
         | 
| 41 41 | 
             
                end
         | 
| 42 42 |  | 
| 43 | 
            -
                def  | 
| 44 | 
            -
                   | 
| 45 | 
            -
             | 
| 46 | 
            -
                   | 
| 43 | 
            +
                def call_and_then_subject_method!(method, context_data)
         | 
| 44 | 
            +
                  acc.merge!(value.merge(context_data))
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  case SubjectMethodArity[method]
         | 
| 47 | 
            +
                  when 0 then subject.send(method.name)
         | 
| 48 | 
            +
                  when 1 then subject.send(method.name, **acc)
         | 
| 49 | 
            +
                  else raise Error::InvalidSubjectMethodArity.build(subject: subject, method: method, max_arity: 1)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 47 52 |  | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
                    when 0 then subject.send(method_name)
         | 
| 51 | 
            -
                    when 1 then subject.send(method_name, **acc)
         | 
| 52 | 
            -
                    else raise Error::InvalidSubjectMethodArity.build(subject: subject, method: method, max_arity: 1)
         | 
| 53 | 
            -
                    end
         | 
| 53 | 
            +
                def call_and_then_block!(block)
         | 
| 54 | 
            +
                  acc.merge!(value)
         | 
| 54 55 |  | 
| 55 | 
            -
                   | 
| 56 | 
            +
                  block.call(acc)
         | 
| 56 57 | 
             
                end
         | 
| 57 58 |  | 
| 58 59 | 
             
                def ensure_result_object(result, origin:)
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module BCDD::Result::Transitions
         | 
| 4 | 
            +
              module Tracking::Disabled
         | 
| 5 | 
            +
                def self.start(name:, desc:); end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def self.finish(result:); end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def self.reset!; end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def self.record(result); end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def self.record_and_then(_type, _data, _subject)
         | 
| 14 | 
            +
                  yield
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,80 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module BCDD::Result::Transitions
         | 
| 4 | 
            +
              class Tracking::Enabled
         | 
| 5 | 
            +
                attr_accessor :tree, :records, :root_started_at
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                private :tree, :tree=, :records, :records=, :root_started_at, :root_started_at=
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def start(name:, desc:)
         | 
| 10 | 
            +
                  name_and_desc = [name, desc]
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  tree.frozen? ? root_start(name_and_desc) : tree.insert!(name_and_desc)
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def finish(result:)
         | 
| 16 | 
            +
                  node = tree.current
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  tree.move_up!
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  return unless node.root?
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  duration = (now_in_milliseconds - root_started_at)
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  metadata = { duration: duration, tree_map: tree.nested_ids }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  result.send(:transitions=, version: Tracking::VERSION, records: records, metadata: metadata)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  reset!
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def reset!
         | 
| 32 | 
            +
                  self.tree = Tracking::EMPTY_TREE
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def record(result)
         | 
| 36 | 
            +
                  return if tree.frozen?
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  track(result, time: ::Time.now.getutc)
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def record_and_then(type_arg, arg, subject)
         | 
| 42 | 
            +
                  type = type_arg.instance_of?(::Method) ? :method : type_arg
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  unless tree.frozen?
         | 
| 45 | 
            +
                    current_and_then = { type: type, arg: arg, subject: subject }
         | 
| 46 | 
            +
                    current_and_then[:method_name] = type_arg.name if type == :method
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    tree.current.value[1] = current_and_then
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  yield
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                private
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                TreeNodeValueNormalizer = ->(id, (nam, des)) { [{ id: id, name: nam, desc: des }, Tracking::EMPTY_HASH] }
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def root_start(name_and_desc)
         | 
| 59 | 
            +
                  self.root_started_at = now_in_milliseconds
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  self.records = []
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  self.tree = Tree.new(name_and_desc, normalizer: TreeNodeValueNormalizer)
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def track(result, time:)
         | 
| 67 | 
            +
                  result = result.data.to_h
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  root, = tree.root_value
         | 
| 70 | 
            +
                  parent, = tree.parent_value
         | 
| 71 | 
            +
                  current, and_then = tree.current_value
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  records << { root: root, parent: parent, current: current, result: result, and_then: and_then, time: time }
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                def now_in_milliseconds
         | 
| 77 | 
            +
                  Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
            end
         |