bcdd-result 0.2.0 → 0.4.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 +1 -1
- data/.rubocop_todo.yml +6 -3
- data/CHANGELOG.md +28 -0
- data/README.md +235 -29
- data/lib/bcdd/result/data.rb +28 -0
- data/lib/bcdd/result/error.rb +29 -31
- data/lib/bcdd/result/failure.rb +5 -1
- data/lib/bcdd/result/handler.rb +52 -0
- data/lib/bcdd/result/mixin.rb +13 -0
- data/lib/bcdd/result/success.rb +5 -1
- data/lib/bcdd/result/type.rb +17 -0
- data/lib/bcdd/result/version.rb +1 -1
- data/lib/bcdd/result.rb +64 -28
- data/lib/result.rb +5 -0
- data/sig/bcdd/result.rbs +73 -14
- metadata +12 -6
- data/lib/bcdd/resultable.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a6bfbf4821c7674fd0af6dbd4e88d274cbf14595eff278ef40ebdaf5bf4490a
|
4
|
+
data.tar.gz: 9cc905974762089c3f3827ac40aa730040ad9e88176e8cae0275954bedd6e7a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5446879d905a42e257b3dd5724b6dc53a455cf2ec8e3fd7a5f339f74216e8d87ce779912dd4124163a6af4a19052e558382e74e7b20a434420b8d2eae055ab37
|
7
|
+
data.tar.gz: c91588b64905a6d86b84dc04db128e42fb0c570cf1e973ccd76563b0126e0e30d31764e918aa93aa71b06115a95c984299d4a57c249391d366d8738469aa8ecb
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -1,19 +1,22 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2023-09-
|
3
|
+
# on 2023-09-29 01:50:30 UTC using RuboCop version 1.56.3.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
8
|
|
9
|
-
# Offense count:
|
9
|
+
# Offense count: 14
|
10
10
|
# Configuration parameters: AllowedConstants.
|
11
11
|
Style/Documentation:
|
12
12
|
Exclude:
|
13
13
|
- 'spec/**/*'
|
14
14
|
- 'test/**/*'
|
15
15
|
- 'lib/bcdd/result.rb'
|
16
|
+
- 'lib/bcdd/result/data.rb'
|
16
17
|
- 'lib/bcdd/result/error.rb'
|
17
18
|
- 'lib/bcdd/result/failure.rb'
|
19
|
+
- 'lib/bcdd/result/handler.rb'
|
20
|
+
- 'lib/bcdd/result/mixin.rb'
|
18
21
|
- 'lib/bcdd/result/success.rb'
|
19
|
-
- 'lib/bcdd/
|
22
|
+
- 'lib/bcdd/result/type.rb'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,33 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.4.0] - 2023-09-28
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Add `require 'result'` to define `Result` as an alias for `BCDD::Result`.
|
8
|
+
|
9
|
+
- Add support to pattern matching (Ruby 2.7+).
|
10
|
+
|
11
|
+
- Add `BCDD::Result#on_unknown` to execute a block if no other hook (`#on`, `#on_type`, `#on_failure`, `#on_success`) has been executed. Attention: always use it as the last hook.
|
12
|
+
|
13
|
+
- Add `BCDD::Result::Handler#unknown` to execute a block if no other handler (`#[]`, `#type`, `#failure`, `#success`) has been executed. Attention: always use it as the last handler.
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
|
17
|
+
- **(BREAKING)** Rename `BCDD::Resultable` to `BCDD::Result::Mixin`.
|
18
|
+
|
19
|
+
- **(BREAKING)** Change `BCDD::Result#data` to return a `BCDD::Result::Data` instead of the result value. This object exposes the result attributes (name, type, value) directly and as a hash (`to_h`/`to_hash`) and array (`to_a`/`to_ary`).
|
20
|
+
|
21
|
+
### Removed
|
22
|
+
|
23
|
+
- **(BREAKING)** Remove `BCDD::Result#data_or`.
|
24
|
+
|
25
|
+
## [0.3.0] - 2023-09-26
|
26
|
+
|
27
|
+
### Added
|
28
|
+
|
29
|
+
- Add `BCDD::Result#handle`. This method allows defining blocks for each hook (type, failure, success), but instead of returning the result itself, it will return the output of the first match/block execution.
|
30
|
+
|
3
31
|
## [0.2.0] - 2023-09-26
|
4
32
|
|
5
33
|
### Added
|
data/README.md
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
<p align="center">
|
2
2
|
<h1 align="center" id="-bcddresult">🔀 BCDD::Result</h1>
|
3
3
|
<p align="center"><i>Empower Ruby apps with a pragmatic use of Railway Oriented Programming.</i></p>
|
4
|
-
<
|
4
|
+
<p align="center">
|
5
|
+
<img src="https://img.shields.io/badge/ruby->%3D%202.7.0-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
|
6
|
+
<img src="https://badge.fury.io/rb/bcdd-result.svg" alt="bcdd-result gem version" height="18">
|
7
|
+
</p>
|
5
8
|
</p>
|
6
9
|
|
7
|
-
|
10
|
+
It's a general-purpose result monad that allows you to create objects representing a success (`BCDD::Result::Success`) or failure (`BCDD::Result::Failure`).
|
8
11
|
|
9
12
|
**What problem does it solve?**
|
10
13
|
|
@@ -14,8 +17,10 @@ Furthermore, this abstraction exposes several features that will be useful to ma
|
|
14
17
|
|
15
18
|
Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofit.com/rop/) pattern (superpower) in your code.
|
16
19
|
|
20
|
+
- [Ruby Version](#ruby-version)
|
17
21
|
- [Installation](#installation)
|
18
22
|
- [Usage](#usage)
|
23
|
+
- [`BCDD::Result` *versus* `Result`](#bcddresult-versus-result)
|
19
24
|
- [Reference](#reference)
|
20
25
|
- [Result Attributes](#result-attributes)
|
21
26
|
- [Receiving types in `result.success?` or `result.failure?`](#receiving-types-in-resultsuccess-or-resultfailure)
|
@@ -24,34 +29,48 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
|
|
24
29
|
- [`result.on_type`](#resulton_type)
|
25
30
|
- [`result.on_success`](#resulton_success)
|
26
31
|
- [`result.on_failure`](#resulton_failure)
|
32
|
+
- [`result.on_unknown`](#resulton_unknown)
|
33
|
+
- [`result.handle`](#resulthandle)
|
27
34
|
- [Result Value](#result-value)
|
28
35
|
- [`result.value_or`](#resultvalue_or)
|
29
|
-
|
36
|
+
- [Result Data](#result-data)
|
37
|
+
- [`result.data`](#resultdata)
|
30
38
|
- [Railway Oriented Programming](#railway-oriented-programming)
|
31
39
|
- [`result.and_then`](#resultand_then)
|
32
|
-
- [`BCDD::
|
40
|
+
- [`BCDD::Result::Mixin`](#bcddresultmixin)
|
33
41
|
- [Class example (instance methods)](#class-example-instance-methods)
|
34
42
|
- [Module example (singleton methods)](#module-example-singleton-methods)
|
35
43
|
- [Restrictions](#restrictions)
|
44
|
+
- [Pattern Matching](#pattern-matching)
|
45
|
+
- [`Array`/`Find` patterns](#arrayfind-patterns)
|
46
|
+
- [`Hash` patterns](#hash-patterns)
|
36
47
|
- [About](#about)
|
37
48
|
- [Development](#development)
|
38
49
|
- [Contributing](#contributing)
|
39
50
|
- [License](#license)
|
40
51
|
- [Code of Conduct](#code-of-conduct)
|
41
52
|
|
53
|
+
## Ruby Version
|
54
|
+
|
55
|
+
`>= 2.7.0`
|
56
|
+
|
42
57
|
## Installation
|
43
58
|
|
44
|
-
|
59
|
+
Add this line to your application's Gemfile:
|
45
60
|
|
46
|
-
|
61
|
+
```ruby
|
62
|
+
gem 'bcdd-result', require: 'bcdd/result'
|
63
|
+
```
|
64
|
+
|
65
|
+
And then execute:
|
66
|
+
|
67
|
+
$ bundle install
|
47
68
|
|
48
69
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
49
70
|
|
50
71
|
$ gem install bcdd-result
|
51
72
|
|
52
|
-
|
53
|
-
|
54
|
-
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
73
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
55
74
|
|
56
75
|
## Usage
|
57
76
|
|
@@ -71,7 +90,23 @@ BCDD::Result::Success(:ok) #
|
|
71
90
|
BCDD::Result::Failure(:err) #
|
72
91
|
```
|
73
92
|
|
74
|
-
<p align="right"
|
93
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
94
|
+
|
95
|
+
#### `BCDD::Result` *versus* `Result`
|
96
|
+
|
97
|
+
The `BCDD::Result` is the main module of this gem. It contains all the features, constants, and methods you will use to create and manipulate results.
|
98
|
+
|
99
|
+
The `Result` is an alias of `BCDD::Result`. It was created to facilitate the use of this gem in the code. So, instead of requiring `BCDD::Result` everywhere, you can require `Result` and use it as an alias.
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
require 'result'
|
103
|
+
|
104
|
+
Result::Success(:ok) # <BCDD::Result::Success type=:ok value=nil>
|
105
|
+
```
|
106
|
+
|
107
|
+
All the examples in this README that use `BCDD::Result` can also be used with `Result`.
|
108
|
+
|
109
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
75
110
|
|
76
111
|
## Reference
|
77
112
|
|
@@ -127,7 +162,7 @@ result.type # :no
|
|
127
162
|
result.value # nil
|
128
163
|
```
|
129
164
|
|
130
|
-
<p align="right"
|
165
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
131
166
|
|
132
167
|
#### Receiving types in `result.success?` or `result.failure?`
|
133
168
|
|
@@ -153,7 +188,7 @@ result.failure?(:err) # true
|
|
153
188
|
result.failure?(:error) # false
|
154
189
|
```
|
155
190
|
|
156
|
-
<p align="right"
|
191
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
157
192
|
|
158
193
|
### Result Hooks
|
159
194
|
|
@@ -172,7 +207,7 @@ def divide(arg1, arg2)
|
|
172
207
|
end
|
173
208
|
```
|
174
209
|
|
175
|
-
<p align="right"
|
210
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
176
211
|
|
177
212
|
#### `result.on`
|
178
213
|
|
@@ -210,7 +245,7 @@ result.object_id == output.object_id # true
|
|
210
245
|
|
211
246
|
*PS: The `divide()` implementation is [here](#result-hooks).*
|
212
247
|
|
213
|
-
<p align="right"
|
248
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
214
249
|
|
215
250
|
#### `result.on_type`
|
216
251
|
|
@@ -239,7 +274,7 @@ divide(4, 4).on_failure { |error| puts error }
|
|
239
274
|
|
240
275
|
*PS: The `divide()` implementation is [here](#result-hooks).*
|
241
276
|
|
242
|
-
<p align="right"
|
277
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
243
278
|
|
244
279
|
#### `result.on_failure`
|
245
280
|
|
@@ -261,7 +296,78 @@ divide(4, 0).on_failure(:invalid_arg) { |error| puts error }
|
|
261
296
|
|
262
297
|
*PS: The `divide()` implementation is [here](#result-hooks).*
|
263
298
|
|
264
|
-
<p align="right"
|
299
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
300
|
+
|
301
|
+
#### `result.on_unknown`
|
302
|
+
|
303
|
+
`BCDD::Result#on_unknown` will perform the block when no other hook (`#on`, `#on_type`, `#on_failure`, `#on_success`) has been executed.
|
304
|
+
|
305
|
+
Regardless of the block being executed, the return of the method will always be the result itself.
|
306
|
+
|
307
|
+
The result value will be exposed as the first argument of the block.
|
308
|
+
|
309
|
+
```ruby
|
310
|
+
divide(4, 2)
|
311
|
+
.on(:invalid_arg) { |msg| puts msg }
|
312
|
+
.on(:division_by_zero) { |msg| puts msg }
|
313
|
+
.on_unknown { |value, type| puts [type, value].inspect }
|
314
|
+
|
315
|
+
# The code above will print '[:division_completed, 2]' and return the result itself.
|
316
|
+
```
|
317
|
+
|
318
|
+
*PS: The `divide()` implementation is [here](#result-hooks).*
|
319
|
+
|
320
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
321
|
+
|
322
|
+
#### `result.handle`
|
323
|
+
|
324
|
+
This method will allow you to define blocks for each hook (type, failure, success), but instead of returning itself, it will return the output of the first match/block execution.
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
divide(4, 2).handle do |result|
|
328
|
+
result.success { |number| number }
|
329
|
+
result.failure(:invalid_arg) { |err| puts err }
|
330
|
+
result.type(:division_by_zero) { raise ZeroDivisionError }
|
331
|
+
result.unknown { raise NotImplementedError }
|
332
|
+
end
|
333
|
+
|
334
|
+
#or
|
335
|
+
|
336
|
+
divide(4, 2).handle do |on|
|
337
|
+
on.success { |number| number }
|
338
|
+
on.failure { |err| puts err }
|
339
|
+
on.unknown { raise NotImplementedError }
|
340
|
+
end
|
341
|
+
|
342
|
+
#or
|
343
|
+
|
344
|
+
divide(4, 2).handle do |on|
|
345
|
+
on.type(:invalid_arg) { |err| puts err }
|
346
|
+
on.type(:division_by_zero) { raise ZeroDivisionError }
|
347
|
+
on.type(:division_completed) { |number| number }
|
348
|
+
on.unknown { raise NotImplementedError }
|
349
|
+
end
|
350
|
+
|
351
|
+
# or
|
352
|
+
|
353
|
+
divide(4, 2).handle do |on|
|
354
|
+
on[:invalid_arg] { |err| puts err }
|
355
|
+
on[:division_by_zero] { raise ZeroDivisionError }
|
356
|
+
on[:division_completed] { |number| number }
|
357
|
+
on.unknown { raise NotImplementedError }
|
358
|
+
end
|
359
|
+
|
360
|
+
# The [] syntax 👆 is an alias of #type.
|
361
|
+
```
|
362
|
+
|
363
|
+
**Notes:**
|
364
|
+
* You can define multiple types to be handled by the same hook/block
|
365
|
+
* If the type is missing, it will perform the block for any success or failure handler.
|
366
|
+
* The `#type` and `#[]` handlers will require at least one type/argument.
|
367
|
+
|
368
|
+
*PS: The `divide()` implementation is [here](#result-hooks).*
|
369
|
+
|
370
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
265
371
|
|
266
372
|
### Result Value
|
267
373
|
|
@@ -294,11 +400,50 @@ divide(100, 0).value_or { 0 } # 0
|
|
294
400
|
|
295
401
|
*PS: The `divide()` implementation is [here](#result-hooks).*
|
296
402
|
|
297
|
-
<p align="right"
|
403
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
404
|
+
|
405
|
+
### Result Data
|
406
|
+
|
407
|
+
#### `result.data`
|
298
408
|
|
299
|
-
|
409
|
+
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`).
|
300
410
|
|
301
|
-
|
411
|
+
This is helpful if you need to access the result attributes generically or want to use Ruby features like splat (`*`) and double splat (`**`) operators.
|
412
|
+
|
413
|
+
See the examples below to understand how to use it.
|
414
|
+
|
415
|
+
```ruby
|
416
|
+
result = BCDD::Result::Success(:ok, 1)
|
417
|
+
|
418
|
+
success_data = result.data # #<BCDD::Result::Data name=:success type=:ok value=1>
|
419
|
+
|
420
|
+
success_data.name # :success
|
421
|
+
success_data.type # :ok
|
422
|
+
success_data.value # 1
|
423
|
+
|
424
|
+
success_data.to_h # {:name=>:success, :type=>:ok, :value=>1}
|
425
|
+
success_data.to_a # [:success, :ok, 1]
|
426
|
+
|
427
|
+
name, type, value = success_data
|
428
|
+
|
429
|
+
[name, type, value] # [:success, :ok, 1]
|
430
|
+
|
431
|
+
def print_to_ary(name, type, value)
|
432
|
+
puts [name, type, value].inspect
|
433
|
+
end
|
434
|
+
|
435
|
+
def print_to_hash(name:, type:, value:)
|
436
|
+
puts [name, type, value].inspect
|
437
|
+
end
|
438
|
+
|
439
|
+
print_to_ary(*success_data) # [:success, :ok, 1]
|
440
|
+
|
441
|
+
print_to_hash(**success_data) # [:success, :ok, 1]
|
442
|
+
```
|
443
|
+
|
444
|
+
> **NOTE:** The example above uses a success result, but the same is valid for a failure result.
|
445
|
+
|
446
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
302
447
|
|
303
448
|
### Railway Oriented Programming
|
304
449
|
|
@@ -360,9 +505,9 @@ Divide.call(2, 2)
|
|
360
505
|
#<BCDD::Result::Success type=:division_completed data=1>
|
361
506
|
```
|
362
507
|
|
363
|
-
<p align="right"
|
508
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
364
509
|
|
365
|
-
#### `BCDD::
|
510
|
+
#### `BCDD::Result::Mixin`
|
366
511
|
|
367
512
|
It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`.
|
368
513
|
|
@@ -374,7 +519,7 @@ And because of this, you can use the `#and_then` method to call methods from the
|
|
374
519
|
|
375
520
|
```ruby
|
376
521
|
class Divide
|
377
|
-
include BCDD::
|
522
|
+
include BCDD::Result::Mixin
|
378
523
|
|
379
524
|
attr_reader :arg1, :arg2
|
380
525
|
|
@@ -422,7 +567,7 @@ Divide.new(4, '2').call #<BCDD::Result::Failure type=:invalid_arg value="arg2 mu
|
|
422
567
|
|
423
568
|
```ruby
|
424
569
|
module Divide
|
425
|
-
extend BCDD::
|
570
|
+
extend BCDD::Result::Mixin
|
426
571
|
extend self
|
427
572
|
|
428
573
|
def call(arg1, arg2)
|
@@ -462,17 +607,78 @@ Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must b
|
|
462
607
|
|
463
608
|
The unique condition for using the `#and_then` to call methods is that they must use the `Success()` and `Failure()` to produce their results.
|
464
609
|
|
465
|
-
If you use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or
|
610
|
+
If you use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or use result from another `BCDD::Result::Mixin` instance, the `#and_then` will raise an error because the subjects will be different.
|
466
611
|
|
467
612
|
> **Note**: You still can use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
|
468
613
|
|
469
|
-
<p align="right"
|
614
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
615
|
+
|
616
|
+
### Pattern Matching
|
617
|
+
|
618
|
+
The `BCDD::Result` also provides support to pattern matching.
|
619
|
+
|
620
|
+
In the further examples, I will use the `Divide` lambda to exemplify its usage.
|
621
|
+
|
622
|
+
```ruby
|
623
|
+
Divide = lambda do |arg1, arg2|
|
624
|
+
arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
|
625
|
+
arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
|
626
|
+
|
627
|
+
return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
|
628
|
+
|
629
|
+
BCDD::Result::Success(:division_completed, arg1 / arg2)
|
630
|
+
end
|
631
|
+
```
|
632
|
+
|
633
|
+
#### `Array`/`Find` patterns
|
634
|
+
|
635
|
+
```ruby
|
636
|
+
case Divide.call(4, 2)
|
637
|
+
in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
|
638
|
+
in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
|
639
|
+
in BCDD::Result::Success[:division_completed, value] then puts value
|
640
|
+
end
|
641
|
+
|
642
|
+
# The code above will print: 2
|
643
|
+
|
644
|
+
case Divide.call(4, 0)
|
645
|
+
in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
|
646
|
+
in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
|
647
|
+
in BCDD::Result::Success[:division_completed, value] then puts value
|
648
|
+
end
|
649
|
+
|
650
|
+
# The code above will print: arg2 must not be zero
|
651
|
+
```
|
652
|
+
|
653
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
654
|
+
|
655
|
+
#### `Hash` patterns
|
656
|
+
|
657
|
+
```ruby
|
658
|
+
case Divide.call(10, 2)
|
659
|
+
in { failure: { invalid_arg: msg } } then puts msg
|
660
|
+
in { failure: { division_by_zero: msg } } then puts msg
|
661
|
+
in { success: { division_completed: value } } then puts value
|
662
|
+
end
|
663
|
+
|
664
|
+
# The code above will print: 5
|
665
|
+
|
666
|
+
case Divide.call('10', 2)
|
667
|
+
in { failure: { invalid_arg: msg } } then puts msg
|
668
|
+
in { failure: { division_by_zero: msg } } then puts msg
|
669
|
+
in { success: { division_completed: value } } then puts value
|
670
|
+
end
|
671
|
+
|
672
|
+
# The code above will print: arg1 must be numeric
|
673
|
+
```
|
674
|
+
|
675
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
470
676
|
|
471
677
|
## About
|
472
678
|
|
473
679
|
[Rodrigo Serradura](https://github.com/serradura) created this project. He is the B/CDD process/method creator and has already made similar gems like the [u-case](https://github.com/serradura/u-case) and [kind](https://github.com/serradura/kind/blob/main/lib/kind/result.rb). This gem is a general-purpose abstraction/monad, but it also contains key features that serve as facilitators for adopting B/CDD in the code.
|
474
680
|
|
475
|
-
<p align="right"
|
681
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
476
682
|
|
477
683
|
## Development
|
478
684
|
|
@@ -480,19 +686,19 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
480
686
|
|
481
687
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
482
688
|
|
483
|
-
<p align="right"
|
689
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
484
690
|
|
485
691
|
## Contributing
|
486
692
|
|
487
693
|
Bug reports and pull requests are welcome on GitHub at https://github.com/B-CDD/bcdd-result. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/B-CDD/bcdd-result/blob/master/CODE_OF_CONDUCT.md).
|
488
694
|
|
489
|
-
<p align="right"
|
695
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
490
696
|
|
491
697
|
## License
|
492
698
|
|
493
699
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
494
700
|
|
495
|
-
<p align="right"
|
701
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
496
702
|
|
497
703
|
## Code of Conduct
|
498
704
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result
|
4
|
+
class Data
|
5
|
+
attr_reader :name, :type, :value, :to_h, :to_a
|
6
|
+
|
7
|
+
def initialize(result)
|
8
|
+
@name = result.send(:name)
|
9
|
+
@type = result.type
|
10
|
+
@value = result.value
|
11
|
+
|
12
|
+
@to_h = { name: name, type: type, value: value }
|
13
|
+
@to_a = [name, type, value]
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
format(
|
18
|
+
'#<%<class_name>s name=%<name>p type=%<type>p value=%<value>p>',
|
19
|
+
class_name: self.class.name, name: name, type: type, value: value
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias to_ary to_a
|
24
|
+
alias to_hash to_h
|
25
|
+
end
|
26
|
+
|
27
|
+
private_constant :Data
|
28
|
+
end
|
data/lib/bcdd/result/error.rb
CHANGED
@@ -1,46 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class BCDD::Result
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
3
|
+
class BCDD::Result::Error < StandardError
|
4
|
+
def self.build(**_kargs)
|
5
|
+
new
|
6
|
+
end
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
class NotImplemented < self
|
9
|
+
end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
11
|
+
class MissingTypeArgument < self
|
12
|
+
def initialize(_arg = nil)
|
13
|
+
super('A type (argument) is required to invoke the #on/#on_type method')
|
16
14
|
end
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
class UnexpectedOutcome < self
|
18
|
+
def self.build(outcome:, origin:)
|
19
|
+
message =
|
20
|
+
"Unexpected outcome: #{outcome.inspect}. The #{origin} must return this object wrapped by " \
|
21
|
+
'BCDD::Result::Success or BCDD::Result::Failure'
|
23
22
|
|
24
|
-
|
25
|
-
end
|
23
|
+
new(message)
|
26
24
|
end
|
25
|
+
end
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
27
|
+
class WrongResultSubject < self
|
28
|
+
def self.build(given_result:, expected_subject:)
|
29
|
+
message =
|
30
|
+
"You cannot call #and_then and return a result that does not belong to the subject!\n" \
|
31
|
+
"Expected subject: #{expected_subject.inspect}\n" \
|
32
|
+
"Given subject: #{given_result.send(:subject).inspect}\n" \
|
33
|
+
"Given result: #{given_result.inspect}"
|
35
34
|
|
36
|
-
|
37
|
-
end
|
35
|
+
new(message)
|
38
36
|
end
|
37
|
+
end
|
39
38
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
39
|
+
class WrongSubjectMethodArity < self
|
40
|
+
def self.build(subject:, method:)
|
41
|
+
new("#{subject.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0 or 1.")
|
44
42
|
end
|
45
43
|
end
|
46
44
|
end
|
data/lib/bcdd/result/failure.rb
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result
|
4
|
+
class Handler
|
5
|
+
UNDEFINED = ::Object.new
|
6
|
+
|
7
|
+
def initialize(result)
|
8
|
+
@outcome = UNDEFINED
|
9
|
+
|
10
|
+
@_type = result._type
|
11
|
+
@result = result
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](*types, &block)
|
15
|
+
raise Error::MissingTypeArgument if types.empty?
|
16
|
+
|
17
|
+
self.outcome = block if _type.in?(types, allow_empty: false)
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure(*types, &block)
|
21
|
+
self.outcome = block if result.failure? && _type.in?(types, allow_empty: true)
|
22
|
+
end
|
23
|
+
|
24
|
+
def success(*types, &block)
|
25
|
+
self.outcome = block if result.success? && _type.in?(types, allow_empty: true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def unknown(&block)
|
29
|
+
self.outcome = block unless outcome?
|
30
|
+
end
|
31
|
+
|
32
|
+
alias type []
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :_type, :result
|
37
|
+
|
38
|
+
def outcome?
|
39
|
+
@outcome != UNDEFINED
|
40
|
+
end
|
41
|
+
|
42
|
+
def outcome=(block)
|
43
|
+
@outcome = block.call(result.value, result.type) unless outcome?
|
44
|
+
end
|
45
|
+
|
46
|
+
def outcome
|
47
|
+
@outcome if outcome?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private_constant :Handler
|
52
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result
|
4
|
+
module Mixin
|
5
|
+
def Success(type, value = nil)
|
6
|
+
Success.new(type: type, value: value, subject: self)
|
7
|
+
end
|
8
|
+
|
9
|
+
def Failure(type, value = nil)
|
10
|
+
Failure.new(type: type, value: value, subject: self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/bcdd/result/success.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result
|
4
|
+
class Type
|
5
|
+
attr_reader :to_sym
|
6
|
+
|
7
|
+
def initialize(type)
|
8
|
+
@to_sym = type.to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def in?(types, allow_empty: false)
|
12
|
+
(allow_empty && types.empty?) || types.any?(to_sym)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private_constant :Type
|
17
|
+
end
|
data/lib/bcdd/result/version.rb
CHANGED
data/lib/bcdd/result.rb
CHANGED
@@ -2,20 +2,32 @@
|
|
2
2
|
|
3
3
|
require_relative 'result/version'
|
4
4
|
require_relative 'result/error'
|
5
|
+
require_relative 'result/type'
|
6
|
+
require_relative 'result/data'
|
7
|
+
require_relative 'result/handler'
|
5
8
|
require_relative 'result/failure'
|
6
9
|
require_relative 'result/success'
|
7
|
-
|
8
|
-
require_relative 'resultable'
|
10
|
+
require_relative 'result/mixin'
|
9
11
|
|
10
12
|
class BCDD::Result
|
11
|
-
|
13
|
+
attr_accessor :unknown
|
14
|
+
|
15
|
+
attr_reader :_type, :value, :subject
|
12
16
|
|
13
17
|
protected :subject
|
14
18
|
|
19
|
+
private :unknown, :unknown=
|
20
|
+
|
15
21
|
def initialize(type:, value:, subject: nil)
|
16
|
-
@
|
22
|
+
@_type = Type.new(type)
|
17
23
|
@value = value
|
18
24
|
@subject = subject
|
25
|
+
|
26
|
+
self.unknown = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def type
|
30
|
+
_type.to_sym
|
19
31
|
end
|
20
32
|
|
21
33
|
def success?(_type = nil)
|
@@ -30,31 +42,22 @@ class BCDD::Result
|
|
30
42
|
raise Error::NotImplemented
|
31
43
|
end
|
32
44
|
|
33
|
-
def
|
34
|
-
|
35
|
-
end
|
36
|
-
alias eql? ==
|
37
|
-
|
38
|
-
def hash
|
39
|
-
[self.class, type, value].hash
|
40
|
-
end
|
45
|
+
def on(*types, &block)
|
46
|
+
raise Error::MissingTypeArgument if types.empty?
|
41
47
|
|
42
|
-
|
43
|
-
format('#<%<class_name>s type=%<type>p value=%<value>p>', class_name: self.class.name, type: type, value: value)
|
48
|
+
tap { known(block) if _type.in?(types, allow_empty: false) }
|
44
49
|
end
|
45
50
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
tap { yield(value, type) if expected_type?(types) }
|
51
|
+
def on_success(*types, &block)
|
52
|
+
tap { known(block) if success? && _type.in?(types, allow_empty: true) }
|
50
53
|
end
|
51
54
|
|
52
|
-
def
|
53
|
-
tap {
|
55
|
+
def on_failure(*types, &block)
|
56
|
+
tap { known(block) if failure? && _type.in?(types, allow_empty: true) }
|
54
57
|
end
|
55
58
|
|
56
|
-
def
|
57
|
-
tap { yield(value, type) if
|
59
|
+
def on_unknown
|
60
|
+
tap { yield(value, type) if unknown }
|
58
61
|
end
|
59
62
|
|
60
63
|
def and_then(method_name = nil)
|
@@ -67,18 +70,51 @@ class BCDD::Result
|
|
67
70
|
ensure_result_object(result, origin: :block)
|
68
71
|
end
|
69
72
|
|
70
|
-
|
71
|
-
|
73
|
+
def handle
|
74
|
+
handler = Handler.new(self)
|
75
|
+
|
76
|
+
yield handler
|
77
|
+
|
78
|
+
handler.send(:outcome)
|
79
|
+
end
|
80
|
+
|
81
|
+
def data
|
82
|
+
Data.new(self)
|
83
|
+
end
|
84
|
+
|
85
|
+
def ==(other)
|
86
|
+
self.class == other.class && type == other.type && value == other.value
|
87
|
+
end
|
88
|
+
|
89
|
+
def hash
|
90
|
+
[self.class, type, value].hash
|
91
|
+
end
|
92
|
+
|
93
|
+
def inspect
|
94
|
+
format('#<%<class_name>s type=%<type>p value=%<value>p>', class_name: self.class.name, type: type, value: value)
|
95
|
+
end
|
96
|
+
|
97
|
+
def deconstruct
|
98
|
+
[type, value]
|
99
|
+
end
|
100
|
+
|
101
|
+
def deconstruct_keys(_keys)
|
102
|
+
{ name => { type => value } }
|
103
|
+
end
|
104
|
+
|
105
|
+
alias eql? ==
|
72
106
|
alias on_type on
|
73
107
|
|
74
108
|
private
|
75
109
|
|
76
|
-
def
|
77
|
-
|
110
|
+
def name
|
111
|
+
raise Error::NotImplemented
|
78
112
|
end
|
79
113
|
|
80
|
-
def
|
81
|
-
|
114
|
+
def known(block)
|
115
|
+
self.unknown = false
|
116
|
+
|
117
|
+
block.call(value, type)
|
82
118
|
end
|
83
119
|
|
84
120
|
def call_subject_method(method_name)
|
data/lib/result.rb
ADDED
data/sig/bcdd/result.rbs
CHANGED
@@ -5,38 +5,46 @@ module BCDD
|
|
5
5
|
end
|
6
6
|
|
7
7
|
class BCDD::Result
|
8
|
-
|
8
|
+
private attr_accessor unknown: bool
|
9
|
+
|
10
|
+
attr_reader _type: BCDD::Result::Type
|
9
11
|
attr_reader value: untyped
|
10
12
|
attr_reader subject: untyped
|
11
13
|
|
12
14
|
def initialize: (type: Symbol, value: untyped, ?subject: untyped) -> void
|
13
15
|
|
16
|
+
def type: -> Symbol
|
17
|
+
|
14
18
|
def success?: (?Symbol type) -> bool
|
15
19
|
def failure?: (?Symbol type) -> bool
|
16
20
|
|
17
21
|
def value_or: { () -> untyped } -> untyped
|
18
22
|
|
19
|
-
def ==: (untyped) -> bool
|
20
|
-
alias eql? ==
|
21
|
-
|
22
|
-
def hash: -> Integer
|
23
|
-
|
24
|
-
def inspect: -> String
|
25
|
-
|
26
23
|
def on: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
|
27
24
|
def on_success: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
|
28
25
|
def on_failure: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
|
26
|
+
def on_unknown: () { (untyped, Symbol) -> void } -> BCDD::Result
|
29
27
|
|
30
28
|
def and_then: (?Symbol method_name) { (untyped) -> untyped } -> BCDD::Result
|
31
29
|
|
32
|
-
|
33
|
-
|
30
|
+
def handle: { (BCDD::Result::Handler) -> void } -> untyped
|
31
|
+
|
32
|
+
def data: -> BCDD::Result::Data
|
33
|
+
|
34
|
+
def ==: (untyped) -> bool
|
35
|
+
def hash: -> Integer
|
36
|
+
def inspect: -> String
|
37
|
+
|
38
|
+
def deconstruct: -> [Symbol, [Symbol, untyped]]
|
39
|
+
def deconstruct_keys: (Array[Symbol]) -> Hash[Symbol, Hash[Symbol, untyped]]
|
40
|
+
|
41
|
+
alias eql? ==
|
34
42
|
alias on_type on
|
35
43
|
|
36
44
|
private
|
37
45
|
|
38
|
-
def
|
39
|
-
def
|
46
|
+
def name: -> Symbol
|
47
|
+
def known: (Proc) -> untyped
|
40
48
|
def call_subject_method: (Symbol) -> BCDD::Result
|
41
49
|
def ensure_result_object: (untyped, origin: Symbol) -> BCDD::Result
|
42
50
|
end
|
@@ -55,14 +63,65 @@ class BCDD::Result
|
|
55
63
|
def self.Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
|
56
64
|
end
|
57
65
|
|
58
|
-
|
59
|
-
module
|
66
|
+
class BCDD::Result
|
67
|
+
module Mixin
|
60
68
|
def Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
|
61
69
|
|
62
70
|
def Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
|
63
71
|
end
|
64
72
|
end
|
65
73
|
|
74
|
+
class BCDD::Result
|
75
|
+
class Handler
|
76
|
+
UNDEFINED: Object
|
77
|
+
|
78
|
+
def initialize: (BCDD::Result) -> void
|
79
|
+
|
80
|
+
def []: (*Symbol) { (untyped, Symbol) -> void } -> untyped
|
81
|
+
def failure: (*Symbol) { (untyped, Symbol) -> void } -> untyped
|
82
|
+
def success: (*Symbol) { (untyped, Symbol) -> void } -> untyped
|
83
|
+
def unknown: () { (untyped, Symbol) -> void } -> untyped
|
84
|
+
|
85
|
+
alias type []
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
attr_reader _type: BCDD::Result::Type
|
90
|
+
attr_reader result: BCDD::Result
|
91
|
+
|
92
|
+
def outcome?: -> bool
|
93
|
+
def outcome=: (Proc) -> void
|
94
|
+
def outcome: -> untyped
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class BCDD::Result
|
99
|
+
class Type
|
100
|
+
attr_reader to_sym: Symbol
|
101
|
+
|
102
|
+
def initialize: (Symbol) -> void
|
103
|
+
|
104
|
+
def in?: (Array[Symbol], allow_empty: bool) -> bool
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class BCDD::Result
|
109
|
+
class Data
|
110
|
+
attr_reader name: Symbol
|
111
|
+
attr_reader type: Symbol
|
112
|
+
attr_reader value: untyped
|
113
|
+
attr_reader to_h: Hash[Symbol, untyped]
|
114
|
+
attr_reader to_a: [Symbol, Symbol, untyped]
|
115
|
+
|
116
|
+
def initialize: (BCDD::Result) -> void
|
117
|
+
|
118
|
+
def inspect: -> String
|
119
|
+
|
120
|
+
alias to_ary to_a
|
121
|
+
alias to_hash to_h
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
66
125
|
class BCDD::Result
|
67
126
|
class Error < ::StandardError
|
68
127
|
def self.build: (**untyped) -> BCDD::Result::Error
|
metadata
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bcdd-result
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
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-09-
|
11
|
+
date: 2023-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
|
13
|
+
description: |-
|
14
|
+
Empower Ruby apps with a pragmatic use of Railway Oriented Programming.
|
15
|
+
|
16
|
+
It's a general-purpose result monad that allows you to create objects representing a success (BCDD::Result::Success) or failure (BCDD::Result::Failure).
|
15
17
|
email:
|
16
18
|
- rodrigo.serradura@gmail.com
|
17
19
|
executables: []
|
@@ -27,11 +29,15 @@ files:
|
|
27
29
|
- Rakefile
|
28
30
|
- Steepfile
|
29
31
|
- lib/bcdd/result.rb
|
32
|
+
- lib/bcdd/result/data.rb
|
30
33
|
- lib/bcdd/result/error.rb
|
31
34
|
- lib/bcdd/result/failure.rb
|
35
|
+
- lib/bcdd/result/handler.rb
|
36
|
+
- lib/bcdd/result/mixin.rb
|
32
37
|
- lib/bcdd/result/success.rb
|
38
|
+
- lib/bcdd/result/type.rb
|
33
39
|
- lib/bcdd/result/version.rb
|
34
|
-
- lib/
|
40
|
+
- lib/result.rb
|
35
41
|
- sig/bcdd/result.rbs
|
36
42
|
homepage: https://github.com/b-cdd/result
|
37
43
|
licenses:
|
@@ -60,5 +66,5 @@ requirements: []
|
|
60
66
|
rubygems_version: 3.4.19
|
61
67
|
signing_key:
|
62
68
|
specification_version: 4
|
63
|
-
summary: A result abstraction (monad based) for Ruby.
|
69
|
+
summary: A pragmatic result abstraction (monad based) for Ruby.
|
64
70
|
test_files: []
|
data/lib/bcdd/resultable.rb
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module BCDD::Resultable
|
4
|
-
def Success(type, value = nil)
|
5
|
-
BCDD::Result::Success.new(type: type, value: value, subject: self)
|
6
|
-
end
|
7
|
-
|
8
|
-
def Failure(type, value = nil)
|
9
|
-
BCDD::Result::Failure.new(type: type, value: value, subject: self)
|
10
|
-
end
|
11
|
-
end
|