bcdd-result 0.8.0 → 0.9.1

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