bcdd-result 0.8.0 → 0.9.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b49559a4258169692f89f3263c14485cde2e17e36c90ccde9bf88dab355d458
4
- data.tar.gz: b62c215886f572f942a752767db89544ae5e483a496975882fcc9108d99edc0f
3
+ metadata.gz: 78ccef0bd217127baf1bf96cabf4074c2ab2e8bb7befffd7c7f2f3df4631bf27
4
+ data.tar.gz: '0429c37305f2265dd13517c7a64e977929f6a4bde4b70421de01c8a418b67656'
5
5
  SHA512:
6
- metadata.gz: 2794daec03ab43d9d9549887dae87d0138195f0c85f65ddcc1015d74a9a940c639e7dc73086c9f948de00c8ce95d576105a3ad9e671b068d56d39e4ef075c3ff
7
- data.tar.gz: 197572b1a2dc61a72de744af1bbb50a642731dc4e248133164da36fb8ff8d257fe89c2874798ea9f77533aa462e386dd002cc2dd25f241ca12cd02ff8d080fa4
6
+ metadata.gz: f8756ac806f830d458b3c830c15ac667cbb29f1737fa97cb59870541e2f3e8000839ab775c16559370499a7055db7f0938bb8a71071db9b5392507af5f0ec7de
7
+ data.tar.gz: 1003e299eec64ddb62a51b1f8d358b9dca2b58add3f8b4b9a4c46a5a70cb825907b2de7339c4f5e94817fb92e48c3a702eae67cd6eeca80942c60bdc80dff071
data/.rubocop.yml CHANGED
@@ -58,6 +58,7 @@ Metrics/BlockLength:
58
58
 
59
59
  Metrics/ClassLength:
60
60
  Exclude:
61
+ - lib/bcdd/result.rb
61
62
  - test/**/*.rb
62
63
 
63
64
  Minitest/MultipleAssertions:
data/CHANGELOG.md CHANGED
@@ -1,30 +1,65 @@
1
1
  - [\[Unreleased\]](#unreleased)
2
- - [\[0.8.0\] - 2023-12-11](#080---2023-12-11)
3
- - [Added](#added)
2
+ - [\[0.9.1\] - 2023-12-12](#091---2023-12-12)
4
3
  - [Changed](#changed)
4
+ - [Fixed](#fixed)
5
+ - [\[0.9.0\] - 2023-12-12](#090---2023-12-12)
6
+ - [Added](#added)
7
+ - [Changed](#changed-1)
8
+ - [\[0.8.0\] - 2023-12-11](#080---2023-12-11)
9
+ - [Added](#added-1)
10
+ - [Changed](#changed-2)
5
11
  - [Removed](#removed)
6
12
  - [\[0.7.0\] - 2023-10-27](#070---2023-10-27)
7
- - [Added](#added-1)
8
- - [Changed](#changed-1)
9
- - [\[0.6.0\] - 2023-10-11](#060---2023-10-11)
10
13
  - [Added](#added-2)
11
- - [Changed](#changed-2)
12
- - [\[0.5.0\] - 2023-10-09](#050---2023-10-09)
14
+ - [Changed](#changed-3)
15
+ - [\[0.6.0\] - 2023-10-11](#060---2023-10-11)
13
16
  - [Added](#added-3)
14
- - [\[0.4.0\] - 2023-09-28](#040---2023-09-28)
17
+ - [Changed](#changed-4)
18
+ - [\[0.5.0\] - 2023-10-09](#050---2023-10-09)
15
19
  - [Added](#added-4)
16
- - [Changed](#changed-3)
20
+ - [\[0.4.0\] - 2023-09-28](#040---2023-09-28)
21
+ - [Added](#added-5)
22
+ - [Changed](#changed-5)
17
23
  - [Removed](#removed-1)
18
24
  - [\[0.3.0\] - 2023-09-26](#030---2023-09-26)
19
- - [Added](#added-5)
20
- - [\[0.2.0\] - 2023-09-26](#020---2023-09-26)
21
25
  - [Added](#added-6)
26
+ - [\[0.2.0\] - 2023-09-26](#020---2023-09-26)
27
+ - [Added](#added-7)
22
28
  - [Removed](#removed-2)
23
29
  - [\[0.1.0\] - 2023-09-25](#010---2023-09-25)
24
- - [Added](#added-7)
30
+ - [Added](#added-8)
25
31
 
26
32
  ## [Unreleased]
27
33
 
34
+ ## [0.9.1] - 2023-12-12
35
+
36
+ ### Changed
37
+
38
+ - **(BREAKING)** Make `BCDD::Result::Context::Success#and_expose()` to produce a halted success by default. You can turn this off by passing `halted: false`.
39
+
40
+ ### Fixed
41
+
42
+ - Make `BCDD::Result::Context#and_then(&block)` accumulate the result value.
43
+
44
+ ## [0.9.0] - 2023-12-12
45
+
46
+ ### Added
47
+
48
+ - Add new `BCDD::Result.config.constant_alias` options. `Context` and `BCDD::Context` are now available as aliases for `BCDD::Result::Context`.
49
+ ```ruby
50
+ BCDD::Result.config.constant_alias.enable!('Context')
51
+
52
+ BCDD::Result.config.constant_alias.enable!('BCDD::Context')
53
+ ```
54
+
55
+ - Add `BCDD::Result#halted?` to check if the result is halted. Failure results are halted by default, but you can halt a successful result by enabling the `:continue` addon.
56
+
57
+ ### Changed
58
+
59
+ - **(BREAKING)** Change the `:continue` addon to halt the step chain on the first `Success()` result. So, if you want to advance to the next step, you must use `Continue(value)` instead of `Success(type, value)`. Otherwise, the step chain will be halted. (Implementation of the following proposal: https://github.com/B-CDD/result/issues/14)
60
+
61
+ - **(BREAKING)** Rename `BCDD::Result::Data#name` to `BCDD::Result::Data#kind`. The new word is more appropriate as it represents a result's kind (success or failure).
62
+
28
63
  ## [0.8.0] - 2023-12-11
29
64
 
30
65
  ### Added
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <p align="center">
2
2
  <h1 align="center" id="-bcddresult">🔀 BCDD::Result</h1>
3
- <p align="center"><i>Empower Ruby apps with pragmatic use of Result monad, Railway Oriented Programming, and B/CDD.</i></p>
3
+ <p align="center"><i>Empower Ruby apps with pragmatic use of Result pattern (monad), Railway Oriented Programming, and B/CDD.</i></p>
4
4
  <p align="center">
5
5
  <img src="https://img.shields.io/badge/ruby->%3D%202.7.0-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
6
6
  <a href="https://rubygems.org/gems/bcdd-result"><img src="https://badge.fury.io/rb/bcdd-result.svg" alt="bcdd-result gem version" height="18"></a>
@@ -63,6 +63,7 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
63
63
  - [`BCDD::Result::Expectations.mixin` add-ons](#bcddresultexpectationsmixin-add-ons)
64
64
  - [`BCDD::Result::Context`](#bcddresultcontext)
65
65
  - [Defining successes and failures](#defining-successes-and-failures)
66
+ - [Constant aliases](#constant-aliases)
66
67
  - [`BCDD::Result::Context.mixin`](#bcddresultcontextmixin)
67
68
  - [Class example (Instance Methods)](#class-example-instance-methods-1)
68
69
  - [`and_expose`](#and_expose)
@@ -71,7 +72,7 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
71
72
  - [Mixin add-ons](#mixin-add-ons)
72
73
  - [`BCDD::Result.configuration`](#bcddresultconfiguration)
73
74
  - [`config.addon.enable!(:continue)`](#configaddonenablecontinue)
74
- - [`config.constant_alias.enable!('Result')`](#configconstant_aliasenableresult)
75
+ - [`config.constant_alias.enable!('Result', 'BCDD::Context')`](#configconstant_aliasenableresult-bcddcontext)
75
76
  - [`config.pattern_matching.disable!(:nil_as_valid_value_checking)`](#configpattern_matchingdisablenil_as_valid_value_checking)
76
77
  - [`config.feature.disable!(:expectations)`](#configfeaturedisableexpectations)
77
78
  - [`BCDD::Result.config`](#bcddresultconfig)
@@ -482,7 +483,7 @@ divide(100, 0).value_or { 0 } # 0
482
483
 
483
484
  #### `result.data`
484
485
 
485
- The `BCDD::Result#data` exposes the result attributes (name, type, value) directly and as a hash (`to_h`/`to_hash`) and array (`to_a`/`to_ary`).
486
+ The `BCDD::Result#data` exposes the result attributes (kind, type, value) directly and as a hash (`to_h`/`to_hash`) and array (`to_a`/`to_ary`).
486
487
 
487
488
  This is helpful if you need to access the result attributes generically or want to use Ruby features like splat (`*`) and double splat (`**`) operators.
488
489
 
@@ -491,25 +492,25 @@ See the examples below to understand how to use it.
491
492
  ```ruby
492
493
  result = BCDD::Result::Success(:ok, 1)
493
494
 
494
- success_data = result.data # #<BCDD::Result::Data name=:success type=:ok value=1>
495
+ success_data = result.data # #<BCDD::Result::Data kind=:success type=:ok value=1>
495
496
 
496
- success_data.name # :success
497
+ success_data.kind # :success
497
498
  success_data.type # :ok
498
499
  success_data.value # 1
499
500
 
500
- success_data.to_h # {:name=>:success, :type=>:ok, :value=>1}
501
+ success_data.to_h # {:kind=>:success, :type=>:ok, :value=>1}
501
502
  success_data.to_a # [:success, :ok, 1]
502
503
 
503
- name, type, value = success_data
504
+ kind, type, value = success_data
504
505
 
505
- [name, type, value] # [:success, :ok, 1]
506
+ [kind, type, value] # [:success, :ok, 1]
506
507
 
507
- def print_to_ary(name, type, value)
508
- puts [name, type, value].inspect
508
+ def print_to_ary(kind, type, value)
509
+ puts [kind, type, value].inspect
509
510
  end
510
511
 
511
- def print_to_hash(name:, type:, value:)
512
- puts [name, type, value].inspect
512
+ def print_to_hash(kind:, type:, value:)
513
+ puts [kind, type, value].inspect
513
514
  end
514
515
 
515
516
  print_to_ary(*success_data) # [:success, :ok, 1]
@@ -917,7 +918,9 @@ The `BCDD::Result.mixin` also accepts the `config:` argument. It is a hash that
917
918
 
918
919
  **continue**
919
920
 
920
- This addon will create the `Continue(value)` method, which will know how to produce a `Success(:continued, value)`. It is useful when you want to perform a sequence of operations but want to avoid returning a specific result for each step.
921
+ This addon will create the `Continue(value)` method and change the `Success()` behavior to halt the step chain.
922
+
923
+ So, if you want to advance to the next step, you must use `Continue(value)` instead of `Success(type, value)`. Otherwise, the step chain will be halted.
921
924
 
922
925
  ```ruby
923
926
  module Divide
@@ -1350,7 +1353,9 @@ The `BCDD::Result::Expectations.mixin` also accepts the `config:` argument. It i
1350
1353
 
1351
1354
  **Continue**
1352
1355
 
1353
- It is similar to `BCDD::Result.mixin(config: { addon: { continue: true } })`, the key difference is that the `Continue(value)` will be ignored by the expectations. This is extremely useful when you want to use `Continue(value)` to chain operations, but you don't want to declare N success types in the expectations.
1356
+ It is similar to `BCDD::Result.mixin(config: { addon: { continue: true } })`. The key difference is that the expectations will ignore the `Continue(value)`.
1357
+
1358
+ Based on this, use the `Success()` to produce a terminal result and `Continue()` to produce a result that will be used in the next step.
1354
1359
 
1355
1360
  ```ruby
1356
1361
  class Divide
@@ -1435,6 +1440,22 @@ BCDD::Result::Context::Success(:ok, **{ message: 'hashes can be converted to key
1435
1440
 
1436
1441
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1437
1442
 
1443
+ #### Constant aliases
1444
+
1445
+ You can configure `Context` or `BCDD::Context` as an alias for `BCDD::Result::Context`. This is helpful to define a standard way to avoid the full constant name/path in your code.
1446
+
1447
+ ```ruby
1448
+ BCDD::Result.configuration do |config|
1449
+ config.context_alias.enable!('BCDD::Context')
1450
+
1451
+ # or
1452
+
1453
+ config.context_alias.enable!('Context')
1454
+ end
1455
+ ```
1456
+
1457
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1458
+
1438
1459
  #### `BCDD::Result::Context.mixin`
1439
1460
 
1440
1461
  As in the `BCDD::Result`, you can use the `BCDD::Result::Context.mixin` to add the `Success()` and `Failure()` methods to your classes/modules.
@@ -1548,6 +1569,8 @@ Divide.new.call(10, 5)
1548
1569
  #<BCDD::Result::Context::Success type=:ok value={:number=>2, :number1=>10, :number2=>5}>
1549
1570
  ```
1550
1571
 
1572
+ > 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`.
1573
+
1551
1574
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1552
1575
 
1553
1576
  ##### Module example (Singleton Methods)
@@ -1655,7 +1678,9 @@ The `BCDD::Result::Context.mixin` and `BCDD::Result::Context::Expectations.mixin
1655
1678
 
1656
1679
  **Continue**
1657
1680
 
1658
- The `BCDD::Result::Context.mixin(config: { addon: { continue: true } })` or `BCDD::Result::Context::Expectations.mixin(config: { addon: { continue: true } })` 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.
1681
+ The `BCDD::Result::Context.mixin(config: { addon: { continue: true } })` or `BCDD::Result::Context::Expectations.mixin(config: { addon: { continue: true } })` creates the `Continue(value)` method and change the `Success()` behavior to halt the step chain.
1682
+
1683
+ So, if you want to advance to the next step, you must use `Continue(**value)` instead of `Success(type, **value)`. Otherwise, the step chain will be halted.
1659
1684
 
1660
1685
  Let's use a mix of `BCDD::Result::Context` features to see in action with this add-on:
1661
1686
 
@@ -1730,11 +1755,11 @@ The `BCDD::Result.configuration` allows you to configure default behaviors for `
1730
1755
  BCDD::Result.configuration do |config|
1731
1756
  config.addon.enable!(:continue)
1732
1757
 
1733
- config.constant_alias.enable!('Result')
1758
+ config.constant_alias.enable!('Result', 'BCDD::Context')
1734
1759
 
1735
1760
  config.pattern_matching.disable!(:nil_as_valid_value_checking)
1736
1761
 
1737
- config.feature.disable!(:expectations) if ::Rails.env.production?
1762
+ # config.feature.disable!(:expectations) if ::Rails.env.production?
1738
1763
  end
1739
1764
  ```
1740
1765
 
@@ -1744,11 +1769,15 @@ Let's see what each configuration in the example above does:
1744
1769
 
1745
1770
  #### `config.addon.enable!(:continue)`
1746
1771
 
1747
- This configuration enables the `Continue()` method for `BCDD::Result` and `BCDD::Result::Context`. Link to documentations: [(1)](#add-ons) [(2)](#mixin-add-ons).
1772
+ This configuration enables the `Continue()` method for `BCDD::Result`, `BCDD::Result::Context`, `BCDD::Result::Expectation`, and `BCDD::Result::Context::Expectation`. Link to documentations: [(1)](#add-ons) [(2)](#mixin-add-ons).
1748
1773
 
1749
- #### `config.constant_alias.enable!('Result')`
1774
+ #### `config.constant_alias.enable!('Result', 'BCDD::Context')`
1750
1775
 
1751
- This configuration make `Result` a constant alias for `BCDD::Result`. Link to [documentation](#bcddresult-versus-result).
1776
+ This configuration make `Result` a constant alias for `BCDD::Result`, and `BCDD::Context` a constant alias for `BCDD::Result::Context`.
1777
+
1778
+ Link to documentations:
1779
+ - [Result alias](#bcddresult-versus-result)
1780
+ - [Context aliases](#constant-aliases)
1752
1781
 
1753
1782
  #### `config.pattern_matching.disable!(:nil_as_valid_value_checking)`
1754
1783
 
@@ -1789,15 +1818,14 @@ BCDD::Result.config.addon.options
1789
1818
 
1790
1819
  ```ruby
1791
1820
  BCDD::Result.config.constant_alias.enabled?('Result')
1821
+ BCDD::Result.config.constant_alias.enabled?('Context')
1822
+ BCDD::Result.config.constant_alias.enabled?('BCDD::Context')
1792
1823
 
1793
1824
  BCDD::Result.config.constant_alias.options
1794
1825
  # {
1795
- # "Result"=>{
1796
- # :enabled=>false,
1797
- # :affects=>[
1798
- # "Object"
1799
- # ]
1800
- # }
1826
+ # "Result"=>{:enabled=>false, :affects=>["Object"]},
1827
+ # "Context"=>{:enabled=>false, :affects=>["Object"]},
1828
+ # "BCDD::Context"=>{:enabled=>false, :affects=>["BCDD"]}
1801
1829
  # }
1802
1830
  ```
1803
1831
 
@@ -3,16 +3,18 @@
3
3
  class BCDD::Result
4
4
  class Config
5
5
  module ConstantAlias
6
- RESULT = 'Result'
7
-
8
- OPTIONS = {
9
- RESULT => { default: false, affects: %w[Object] }
10
- }.transform_values!(&:freeze).freeze
11
-
12
6
  MAPPING = {
13
- RESULT => { target: ::Object, name: :Result, value: ::BCDD::Result }
7
+ 'Result' => { target: ::Object, name: :Result, value: ::BCDD::Result },
8
+ 'Context' => { target: ::Object, name: :Context, value: ::BCDD::Result::Context },
9
+ 'BCDD::Context' => { target: ::BCDD, name: :Context, value: ::BCDD::Result::Context }
14
10
  }.transform_values!(&:freeze).freeze
15
11
 
12
+ OPTIONS = MAPPING.to_h do |option_name, mapping|
13
+ affects = mapping.fetch(:target).name.freeze
14
+
15
+ [option_name, { default: false, affects: [affects].freeze }]
16
+ end.freeze
17
+
16
18
  Listener = ->(option_name, boolean) do
17
19
  mapping = MAPPING.fetch(option_name)
18
20
 
@@ -13,13 +13,14 @@ class BCDD::Result
13
13
  default_flags.merge(config_flags).slice(*default_flags.keys)
14
14
  end
15
15
 
16
- def self.filter_map(all_flags, config:, from:)
16
+ def self.select(all_flags, config:, from:)
17
17
  with_defaults(all_flags, config)
18
- .filter_map { |name, truthy| from[name] if truthy }
18
+ .filter_map { |name, truthy| [name, from[name]] if truthy }
19
+ .to_h
19
20
  end
20
21
 
21
22
  def self.addon(map:, from:)
22
- filter_map(map, config: :addon, from: from)
23
+ select(map, config: :addon, from: from)
23
24
  end
24
25
  end
25
26
  end
@@ -4,7 +4,7 @@ class BCDD::Result::Context
4
4
  module Expectations::Mixin
5
5
  Factory = BCDD::Result::Expectations::Mixin::Factory
6
6
 
7
- METHODS = BCDD::Result::Expectations::Mixin::METHODS
7
+ Methods = BCDD::Result::Expectations::Mixin::Methods
8
8
 
9
9
  module Addons
10
10
  module Continuable
@@ -15,11 +15,11 @@ class BCDD::Result::Context
15
15
  private_class_method :mixin!, :mixin_module, :result_factory_without_expectations
16
16
 
17
17
  def Success(type, **value)
18
- Success.new(type: type, value: value, subject: subject, expectations: contract)
18
+ _ResultAs(Success, type, value)
19
19
  end
20
20
 
21
21
  def Failure(type, **value)
22
- Failure.new(type: type, value: value, subject: subject, expectations: contract)
22
+ _ResultAs(Failure, type, value)
23
23
  end
24
24
  end
25
25
  end
@@ -3,7 +3,7 @@
3
3
  class BCDD::Result::Context::Failure < BCDD::Result::Context
4
4
  include BCDD::Result::Failure::Methods
5
5
 
6
- def and_expose(_type, _keys)
6
+ def and_expose(_type, _keys, **_options)
7
7
  self
8
8
  end
9
9
  end
@@ -6,18 +6,26 @@ class BCDD::Result::Context
6
6
 
7
7
  module Methods
8
8
  def Success(type, **value)
9
- Success.new(type: type, value: value, subject: self)
9
+ _ResultAs(Success, type, value)
10
10
  end
11
11
 
12
12
  def Failure(type, **value)
13
- Failure.new(type: type, value: value, subject: self)
13
+ _ResultAs(Failure, type, value)
14
+ end
15
+
16
+ private def _ResultAs(kind_class, type, value, halted: nil)
17
+ kind_class.new(type: type, value: value, subject: self, halted: halted)
14
18
  end
15
19
  end
16
20
 
17
21
  module Addons
18
22
  module Continuable
23
+ def Success(type, **value)
24
+ _ResultAs(Success, type, value, halted: true)
25
+ end
26
+
19
27
  private def Continue(**value)
20
- Success.new(type: :continued, value: value, subject: self)
28
+ _ResultAs(Success, :continued, value)
21
29
  end
22
30
  end
23
31
 
@@ -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
@@ -15,7 +15,7 @@ class BCDD::Result
15
15
  Failure.new(type: type, value: value)
16
16
  end
17
17
 
18
- def initialize(type:, value:, subject: nil, expectations: nil)
18
+ def initialize(type:, value:, subject: nil, expectations: nil, halted: nil)
19
19
  value.is_a?(::Hash) or raise ::ArgumentError, 'value must be a Hash'
20
20
 
21
21
  @acc = {}
@@ -40,7 +40,7 @@ class BCDD::Result
40
40
  -1
41
41
  end
42
42
 
43
- def call_subject_method(method_name, context)
43
+ def call_and_then_subject_method(method_name, context)
44
44
  method = subject.method(method_name)
45
45
 
46
46
  acc.merge!(value.merge(context))
@@ -55,6 +55,12 @@ class BCDD::Result
55
55
  ensure_result_object(result, origin: :method)
56
56
  end
57
57
 
58
+ def call_and_then_block(block)
59
+ acc.merge!(value)
60
+
61
+ call_and_then_block!(block, acc)
62
+ end
63
+
58
64
  def ensure_result_object(result, origin:)
59
65
  raise_unexpected_outcome_error(result, origin) unless result.is_a?(Context)
60
66
 
@@ -35,7 +35,7 @@ class BCDD::Result
35
35
  private
36
36
 
37
37
  def for(data)
38
- case data.name
38
+ case data.kind
39
39
  when :unknown then Contract::Disabled
40
40
  when :success then success
41
41
  else failure
@@ -2,26 +2,26 @@
2
2
 
3
3
  class BCDD::Result
4
4
  class Data
5
- attr_reader :name, :type, :value
5
+ attr_reader :kind, :type, :value
6
6
 
7
- def initialize(name, type, value)
8
- @name = name
7
+ def initialize(kind, type, value)
8
+ @kind = kind
9
9
  @type = type.to_sym
10
10
  @value = value
11
11
  end
12
12
 
13
13
  def to_h
14
- { name: name, type: type, value: value }
14
+ { kind: kind, type: type, value: value }
15
15
  end
16
16
 
17
17
  def to_a
18
- [name, type, value]
18
+ [kind, type, value]
19
19
  end
20
20
 
21
21
  def inspect
22
22
  format(
23
- '#<%<class_name>s name=%<name>p type=%<type>p value=%<value>p>',
24
- class_name: self.class.name, name: name, type: type, value: value
23
+ '#<%<class_name>s kind=%<kind>p type=%<type>p value=%<value>p>',
24
+ class_name: self.class.name, kind: kind, type: type, value: value
25
25
  )
26
26
  end
27
27
 
@@ -11,21 +11,29 @@ class BCDD::Result
11
11
  end
12
12
  end
13
13
 
14
- METHODS = <<~RUBY
15
- def Success(...)
16
- _Result.Success(...)
17
- end
14
+ module Methods
15
+ BASE = <<~RUBY
16
+ def Success(...)
17
+ _Result.Success(...)
18
+ end
18
19
 
19
- def Failure(...)
20
- _Result.Failure(...)
21
- end
20
+ def Failure(...)
21
+ _Result.Failure(...)
22
+ end
23
+ RUBY
22
24
 
23
- private
25
+ FACTORY = <<~RUBY
26
+ private def _Result
27
+ @_Result ||= Result.with(subject: self, halted: %<halted>s)
28
+ end
29
+ RUBY
30
+
31
+ def self.to_eval(addons)
32
+ halted = addons.key?(:continue) ? 'true' : 'nil'
24
33
 
25
- def _Result
26
- @_Result ||= Result.with(subject: self)
34
+ "#{BASE}\n#{format(FACTORY, halted: halted)}"
27
35
  end
28
- RUBY
36
+ end
29
37
 
30
38
  module Addons
31
39
  module Continuable
@@ -15,8 +15,8 @@ class BCDD::Result
15
15
 
16
16
  mod = mixin_module::Factory.module!
17
17
  mod.const_set(:Result, new(success: success, failure: failure, config: config).freeze)
18
- mod.module_eval(mixin_module::METHODS)
19
- mod.send(:include, *addons) unless addons.empty?
18
+ mod.module_eval(mixin_module::Methods.to_eval(addons), __FILE__, __LINE__ + 1)
19
+ mod.send(:include, *addons.values) unless addons.empty?
20
20
  mod
21
21
  end
22
22
 
@@ -38,28 +38,38 @@ class BCDD::Result
38
38
 
39
39
  private_class_method :mixin!, :mixin_module, :result_factory_without_expectations
40
40
 
41
- def initialize(subject: nil, success: nil, failure: nil, contract: nil, config: nil)
41
+ def initialize(subject: nil, contract: nil, halted: nil, **options)
42
+ @halted = halted
43
+
42
44
  @subject = subject
43
45
 
44
46
  @contract = contract if contract.is_a?(Contract::Evaluator)
45
47
 
46
- @contract ||= Contract.new(success: success, failure: failure, config: config).freeze
48
+ @contract ||= Contract.new(
49
+ success: options[:success],
50
+ failure: options[:failure],
51
+ config: options[:config]
52
+ ).freeze
47
53
  end
48
54
 
49
55
  def Success(type, value = nil)
50
- Success.new(type: type, value: value, subject: subject, expectations: contract)
56
+ _ResultAs(Success, type, value)
51
57
  end
52
58
 
53
59
  def Failure(type, value = nil)
54
- Failure.new(type: type, value: value, subject: subject, expectations: contract)
60
+ _ResultAs(Failure, type, value)
55
61
  end
56
62
 
57
- def with(subject:)
58
- self.class.new(subject: subject, contract: contract)
63
+ def with(subject:, halted: nil)
64
+ self.class.new(subject: subject, halted: halted, contract: contract)
59
65
  end
60
66
 
61
67
  private
62
68
 
63
- attr_reader :subject, :contract
69
+ def _ResultAs(kind_class, type, value)
70
+ kind_class.new(type: type, value: value, subject: subject, expectations: contract, halted: halted)
71
+ end
72
+
73
+ attr_reader :subject, :halted, :contract
64
74
  end
65
75
  end
@@ -15,7 +15,7 @@ module BCDD::Result::Failure::Methods
15
15
 
16
16
  private
17
17
 
18
- def name
18
+ def kind
19
19
  :failure
20
20
  end
21
21
  end
@@ -13,18 +13,26 @@ class BCDD::Result
13
13
 
14
14
  module Methods
15
15
  def Success(type, value = nil)
16
- Success.new(type: type, value: value, subject: self)
16
+ _ResultAs(Success, type, value)
17
17
  end
18
18
 
19
19
  def Failure(type, value = nil)
20
- Failure.new(type: type, value: value, subject: self)
20
+ _ResultAs(Failure, type, value)
21
+ end
22
+
23
+ private def _ResultAs(kind_class, type, value, halted: nil)
24
+ kind_class.new(type: type, value: value, subject: self, halted: halted)
21
25
  end
22
26
  end
23
27
 
24
28
  module Addons
25
29
  module Continuable
30
+ def Success(type, value = nil)
31
+ _ResultAs(Success, type, value, halted: true)
32
+ end
33
+
26
34
  private def Continue(value)
27
- Success(:continued, value)
35
+ _ResultAs(Success, :continued, value)
28
36
  end
29
37
  end
30
38
 
@@ -42,7 +50,7 @@ class BCDD::Result
42
50
  mod = mixin_module::Factory.module!
43
51
  mod.send(:include, mixin_module::Methods)
44
52
  mod.const_set(:Result, result_factory)
45
- mod.send(:include, *addons) unless addons.empty?
53
+ mod.send(:include, *addons.values) unless addons.empty?
46
54
  mod
47
55
  end
48
56
 
@@ -15,7 +15,7 @@ module BCDD::Result::Success::Methods
15
15
 
16
16
  private
17
17
 
18
- def name
18
+ def kind
19
19
  :success
20
20
  end
21
21
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BCDD
4
4
  class Result
5
- VERSION = '0.8.0'
5
+ VERSION = '0.9.1'
6
6
  end
7
7
  end
data/lib/bcdd/result.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  require_relative 'result/version'
4
4
  require_relative 'result/error'
5
5
  require_relative 'result/data'
6
- require_relative 'result/config'
7
6
  require_relative 'result/handler'
8
7
  require_relative 'result/failure'
9
8
  require_relative 'result/success'
@@ -11,11 +10,12 @@ require_relative 'result/mixin'
11
10
  require_relative 'result/contract'
12
11
  require_relative 'result/expectations'
13
12
  require_relative 'result/context'
13
+ require_relative 'result/config'
14
14
 
15
15
  class BCDD::Result
16
16
  attr_accessor :unknown
17
17
 
18
- attr_reader :subject, :data, :type_checker
18
+ attr_reader :subject, :data, :type_checker, :halted
19
19
 
20
20
  protected :subject
21
21
 
@@ -31,16 +31,21 @@ class BCDD::Result
31
31
  config.freeze
32
32
  end
33
33
 
34
- def initialize(type:, value:, subject: nil, expectations: nil)
35
- data = Data.new(name, type, value)
34
+ def initialize(type:, value:, subject: nil, expectations: nil, halted: nil)
35
+ data = Data.new(kind, type, value)
36
36
 
37
37
  @type_checker = Contract.evaluate(data, expectations)
38
38
  @subject = subject
39
+ @halted = halted || kind == :failure
39
40
  @data = data
40
41
 
41
42
  self.unknown = true
42
43
  end
43
44
 
45
+ def halted?
46
+ halted
47
+ end
48
+
44
49
  def type
45
50
  data.type
46
51
  end
@@ -79,16 +84,12 @@ class BCDD::Result
79
84
  tap { yield(value, type) if unknown }
80
85
  end
81
86
 
82
- def and_then(method_name = nil, context = nil)
83
- return self if failure?
87
+ def and_then(method_name = nil, context = nil, &block)
88
+ return self if halted?
84
89
 
85
- method_name && block_given? and raise ::ArgumentError, 'method_name and block are mutually exclusive'
90
+ method_name && block and raise ::ArgumentError, 'method_name and block are mutually exclusive'
86
91
 
87
- return call_subject_method(method_name, context) if method_name
88
-
89
- result = yield(value)
90
-
91
- ensure_result_object(result, origin: :block)
92
+ method_name ? call_and_then_subject_method(method_name, context) : call_and_then_block(block)
92
93
  end
93
94
 
94
95
  def handle
@@ -116,7 +117,7 @@ class BCDD::Result
116
117
  end
117
118
 
118
119
  def deconstruct_keys(_keys)
119
- { name => { type => value } }
120
+ { kind => { type => value } }
120
121
  end
121
122
 
122
123
  alias eql? ==
@@ -124,7 +125,7 @@ class BCDD::Result
124
125
 
125
126
  private
126
127
 
127
- def name
128
+ def kind
128
129
  :unknown
129
130
  end
130
131
 
@@ -134,7 +135,7 @@ class BCDD::Result
134
135
  block.call(value, type)
135
136
  end
136
137
 
137
- def call_subject_method(method_name, context)
138
+ def call_and_then_subject_method(method_name, context)
138
139
  method = subject.method(method_name)
139
140
 
140
141
  result =
@@ -148,6 +149,16 @@ class BCDD::Result
148
149
  ensure_result_object(result, origin: :method)
149
150
  end
150
151
 
152
+ def call_and_then_block(block)
153
+ call_and_then_block!(block, value)
154
+ end
155
+
156
+ def call_and_then_block!(block, value)
157
+ result = block.call(value)
158
+
159
+ ensure_result_object(result, origin: :block)
160
+ end
161
+
151
162
  def ensure_result_object(result, origin:)
152
163
  raise Error::UnexpectedOutcome.build(outcome: result, origin: origin) unless result.is_a?(::BCDD::Result)
153
164
 
data/sig/bcdd/result.rbs CHANGED
@@ -10,6 +10,7 @@ class BCDD::Result
10
10
 
11
11
  attr_reader data: BCDD::Result::Data
12
12
  attr_reader subject: untyped
13
+ attr_reader halted: bool
13
14
 
14
15
  def self.config: -> BCDD::Result::Config
15
16
  def self.configuration: { (BCDD::Result::Config) -> void } -> BCDD::Result::Config
@@ -18,12 +19,14 @@ class BCDD::Result
18
19
  type: Symbol,
19
20
  value: untyped,
20
21
  ?subject: untyped,
21
- ?expectations: BCDD::Result::Contract::Evaluator
22
+ ?expectations: BCDD::Result::Contract::Evaluator,
23
+ ?halted: bool
22
24
  ) -> void
23
25
 
24
26
  def type: -> Symbol
25
27
  def value: -> untyped
26
28
 
29
+ def halted?: -> bool
27
30
  def success?: (?Symbol type) -> bool
28
31
  def failure?: (?Symbol type) -> bool
29
32
 
@@ -50,9 +53,11 @@ class BCDD::Result
50
53
 
51
54
  private
52
55
 
53
- def name: -> Symbol
56
+ def kind: -> Symbol
54
57
  def known: (Proc) -> untyped
55
- def call_subject_method: (Symbol, untyped) -> BCDD::Result
58
+ def call_and_then_subject_method: (Symbol, untyped) -> BCDD::Result
59
+ def call_and_then_block: (untyped) -> BCDD::Result
60
+ def call_and_then_block!: (untyped, untyped) -> BCDD::Result
56
61
  def ensure_result_object: (untyped, origin: Symbol) -> BCDD::Result
57
62
  end
58
63
 
@@ -65,7 +70,8 @@ class BCDD::Result
65
70
  def value: -> untyped
66
71
 
67
72
  private
68
- def name: -> Symbol
73
+
74
+ def kind: -> Symbol
69
75
  def type_checker: -> BCDD::Result::Contract::TypeChecker
70
76
  end
71
77
 
@@ -85,7 +91,8 @@ class BCDD::Result
85
91
  def value: -> untyped
86
92
 
87
93
  private
88
- def name: -> Symbol
94
+
95
+ def kind: -> Symbol
89
96
  def type_checker: -> BCDD::Result::Contract::TypeChecker
90
97
  end
91
98
 
@@ -105,6 +112,10 @@ class BCDD::Result
105
112
  def Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
106
113
 
107
114
  def Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
115
+
116
+ private
117
+
118
+ def _ResultAs: (singleton(BCDD::Result), Symbol, untyped, ?halted: bool) -> untyped
108
119
  end
109
120
 
110
121
  module Addons
@@ -118,7 +129,7 @@ class BCDD::Result
118
129
 
119
130
  OPTIONS: Hash[Symbol, Module]
120
131
 
121
- def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Array[Module]
132
+ def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Hash[Symbol, Module]
122
133
  end
123
134
  end
124
135
 
@@ -131,7 +142,7 @@ end
131
142
 
132
143
  class BCDD::Result
133
144
  class Data
134
- attr_reader name: Symbol
145
+ attr_reader kind: Symbol
135
146
  attr_reader type: Symbol
136
147
  attr_reader value: untyped
137
148
  attr_reader to_h: Hash[Symbol, untyped]
@@ -365,18 +376,16 @@ class BCDD::Result::Expectations
365
376
 
366
377
  def self.new: (
367
378
  ?subject: untyped,
368
- ?success: Hash[Symbol, untyped] | Array[Symbol],
369
- ?failure: Hash[Symbol, untyped] | Array[Symbol],
370
379
  ?contract: BCDD::Result::Contract::Evaluator,
371
- ?config: Hash[Symbol, Hash[Symbol, bool]]
380
+ ?halted: bool,
381
+ **untyped
372
382
  ) -> (BCDD::Result::Expectations | untyped)
373
383
 
374
384
  def initialize: (
375
385
  ?subject: untyped,
376
- ?success: Hash[Symbol, untyped] | Array[Symbol],
377
- ?failure: Hash[Symbol, untyped] | Array[Symbol],
378
386
  ?contract: BCDD::Result::Contract::Evaluator,
379
- ?config: Hash[Symbol, Hash[Symbol, bool]]
387
+ ?halted: bool,
388
+ **untyped
380
389
  ) -> void
381
390
 
382
391
  def Success: (Symbol, ?untyped) -> BCDD::Result::Success
@@ -386,8 +395,11 @@ class BCDD::Result::Expectations
386
395
 
387
396
  private
388
397
 
398
+ def _ResultAs: (singleton(BCDD::Result), Symbol, untyped) -> untyped
399
+
389
400
  attr_reader subject: untyped
390
401
  attr_reader contract: BCDD::Result::Contract::Evaluator
402
+ attr_reader halted: bool
391
403
  end
392
404
 
393
405
  module BCDD::Result::Expectations::Mixin
@@ -395,7 +407,12 @@ module BCDD::Result::Expectations::Mixin
395
407
  def self.module!: -> Module
396
408
  end
397
409
 
398
- METHODS: String
410
+ module Methods
411
+ BASE: String
412
+ FACTORY: String
413
+
414
+ def self.to_eval: (Hash[Symbol, untyped]) -> String
415
+ end
399
416
 
400
417
  module Addons
401
418
  module Continuable
@@ -404,7 +421,7 @@ module BCDD::Result::Expectations::Mixin
404
421
 
405
422
  OPTIONS: Hash[Symbol, Module]
406
423
 
407
- def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Array[Module]
424
+ def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Hash[Symbol, Module]
408
425
  end
409
426
  end
410
427
 
@@ -419,14 +436,15 @@ class BCDD::Result::Context < BCDD::Result
419
436
  type: Symbol,
420
437
  value: untyped,
421
438
  ?subject: untyped,
422
- ?expectations: BCDD::Result::Contract::Evaluator
439
+ ?expectations: BCDD::Result::Contract::Evaluator,
440
+ ?halted: bool
423
441
  ) -> void
424
442
 
425
443
  def and_then: (?Symbol, **untyped) ?{ (Hash[Symbol, untyped]) -> untyped } -> BCDD::Result::Context
426
444
 
427
445
  private
428
446
 
429
- def call_subject_method: (Symbol, Hash[Symbol, untyped]) -> BCDD::Result::Context
447
+ def call_and_then_subject_method: (Symbol, Hash[Symbol, untyped]) -> BCDD::Result::Context
430
448
  def ensure_result_object: (untyped, origin: Symbol) -> BCDD::Result::Context
431
449
 
432
450
  def raise_unexpected_outcome_error: (BCDD::Result::Context | untyped, Symbol) -> void
@@ -436,7 +454,7 @@ class BCDD::Result::Context
436
454
  class Success < BCDD::Result::Context
437
455
  include BCDD::Result::Success::Methods
438
456
 
439
- def and_expose: (Symbol, Array[Symbol]) -> BCDD::Result::Context::Success
457
+ def and_expose: (Symbol, Array[Symbol], halted: bool) -> BCDD::Result::Context::Success
440
458
  end
441
459
 
442
460
  def self.Success: (Symbol, **untyped) -> BCDD::Result::Context::Success
@@ -446,7 +464,7 @@ class BCDD::Result::Context
446
464
  class Failure < BCDD::Result::Context
447
465
  include BCDD::Result::Failure::Methods
448
466
 
449
- def and_expose: (Symbol, Array[Symbol]) -> BCDD::Result::Context::Failure
467
+ def and_expose: (Symbol, Array[Symbol], **untyped) -> BCDD::Result::Context::Failure
450
468
  end
451
469
 
452
470
  def self.Failure: (Symbol, **untyped) -> BCDD::Result::Context::Failure
@@ -460,6 +478,10 @@ class BCDD::Result::Context
460
478
  def Success: (Symbol, **untyped) -> BCDD::Result::Context::Success
461
479
 
462
480
  def Failure: (Symbol, **untyped) -> BCDD::Result::Context::Failure
481
+
482
+ private
483
+
484
+ def _ResultAs: (singleton(BCDD::Result::Context), Symbol, untyped, ?halted: bool) -> untyped
463
485
  end
464
486
 
465
487
  module Addons
@@ -473,7 +495,7 @@ class BCDD::Result::Context
473
495
 
474
496
  OPTIONS: Hash[Symbol, Module]
475
497
 
476
- def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Array[Module]
498
+ def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Hash[Symbol, Module]
477
499
  end
478
500
  end
479
501
 
@@ -492,7 +514,7 @@ class BCDD::Result::Context::Expectations < BCDD::Result::Expectations
492
514
  end
493
515
 
494
516
  module BCDD::Result::Context::Expectations::Mixin
495
- METHODS: String
517
+ Methods: singleton(BCDD::Result::Expectations::Mixin::Methods)
496
518
  Factory: singleton(BCDD::Result::Expectations::Mixin::Factory)
497
519
 
498
520
  module Addons
@@ -502,7 +524,7 @@ module BCDD::Result::Context::Expectations::Mixin
502
524
 
503
525
  OPTIONS: Hash[Symbol, Module]
504
526
 
505
- def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Array[Module]
527
+ def self.options: (Hash[Symbol, Hash[Symbol, bool]]) -> Hash[Symbol, Module]
506
528
  end
507
529
  end
508
530
 
@@ -563,10 +585,8 @@ class BCDD::Result::Config::Switcher
563
585
  end
564
586
 
565
587
  module BCDD::Result::Config::ConstantAlias
566
- RESULT: String
567
-
568
- OPTIONS: Hash[String, Hash[Symbol, untyped]]
569
588
  MAPPING: Hash[String, Hash[Symbol, untyped]]
589
+ OPTIONS: Hash[String, Hash[Symbol, untyped]]
570
590
  Listener: Proc
571
591
 
572
592
  def self.switcher: -> BCDD::Result::Config::Switcher
@@ -578,14 +598,14 @@ module BCDD::Result::Config::Options
578
598
  Symbol
579
599
  ) -> Hash[Symbol, bool]
580
600
 
581
- def self.filter_map: (
601
+ def self.select: (
582
602
  Hash[Symbol, Hash[Symbol, bool]],
583
603
  config: Symbol,
584
604
  from: Hash[Symbol, untyped]
585
- ) -> Array[untyped]
605
+ ) -> Hash[Symbol, untyped]
586
606
 
587
607
  def self.addon: (
588
608
  map: Hash[Symbol, Hash[Symbol, bool]],
589
609
  from: Hash[Symbol, Module]
590
- ) -> Array[Module]
610
+ ) -> Hash[Symbol, Module]
591
611
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bcdd-result
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-12-11 00:00:00.000000000 Z
11
+ date: 2023-12-12 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Empower Ruby apps with pragmatic use of Result monad, Railway Oriented
14
- Programming, and B/CDD.
13
+ description: Empower Ruby apps with pragmatic use of Result pattern (monad), Railway
14
+ Oriented Programming, and B/CDD.
15
15
  email:
16
16
  - rodrigo.serradura@gmail.com
17
17
  executables: []
@@ -86,6 +86,6 @@ requirements: []
86
86
  rubygems_version: 3.4.19
87
87
  signing_key:
88
88
  specification_version: 4
89
- summary: Empower Ruby apps with pragmatic use of Result monad, Railway Oriented Programming,
90
- and B/CDD.
89
+ summary: Empower Ruby apps with pragmatic use of Result pattern (monad), Railway Oriented
90
+ Programming, and B/CDD.
91
91
  test_files: []