bcdd-result 0.3.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 +4 -3
- data/CHANGELOG.md +22 -0
- data/README.md +176 -26
- 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 +8 -4
- data/lib/bcdd/result/mixin.rb +13 -0
- data/lib/bcdd/result/success.rb +5 -1
- data/lib/bcdd/result/version.rb +1 -1
- data/lib/bcdd/result.rb +52 -22
- data/lib/result.rb +5 -0
- data/sig/bcdd/result.rbs +42 -20
- metadata +10 -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,21 +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'
|
|
18
19
|
- 'lib/bcdd/result/handler.rb'
|
|
20
|
+
- 'lib/bcdd/result/mixin.rb'
|
|
19
21
|
- 'lib/bcdd/result/success.rb'
|
|
20
22
|
- 'lib/bcdd/result/type.rb'
|
|
21
|
-
- 'lib/bcdd/resultable.rb'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
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
|
+
|
|
3
25
|
## [0.3.0] - 2023-09-26
|
|
4
26
|
|
|
5
27
|
### 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
|
|
|
@@ -17,6 +20,7 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
|
|
|
17
20
|
- [Ruby Version](#ruby-version)
|
|
18
21
|
- [Installation](#installation)
|
|
19
22
|
- [Usage](#usage)
|
|
23
|
+
- [`BCDD::Result` *versus* `Result`](#bcddresult-versus-result)
|
|
20
24
|
- [Reference](#reference)
|
|
21
25
|
- [Result Attributes](#result-attributes)
|
|
22
26
|
- [Receiving types in `result.success?` or `result.failure?`](#receiving-types-in-resultsuccess-or-resultfailure)
|
|
@@ -25,16 +29,21 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
|
|
|
25
29
|
- [`result.on_type`](#resulton_type)
|
|
26
30
|
- [`result.on_success`](#resulton_success)
|
|
27
31
|
- [`result.on_failure`](#resulton_failure)
|
|
32
|
+
- [`result.on_unknown`](#resulton_unknown)
|
|
28
33
|
- [`result.handle`](#resulthandle)
|
|
29
34
|
- [Result Value](#result-value)
|
|
30
35
|
- [`result.value_or`](#resultvalue_or)
|
|
31
|
-
|
|
36
|
+
- [Result Data](#result-data)
|
|
37
|
+
- [`result.data`](#resultdata)
|
|
32
38
|
- [Railway Oriented Programming](#railway-oriented-programming)
|
|
33
39
|
- [`result.and_then`](#resultand_then)
|
|
34
|
-
- [`BCDD::
|
|
40
|
+
- [`BCDD::Result::Mixin`](#bcddresultmixin)
|
|
35
41
|
- [Class example (instance methods)](#class-example-instance-methods)
|
|
36
42
|
- [Module example (singleton methods)](#module-example-singleton-methods)
|
|
37
43
|
- [Restrictions](#restrictions)
|
|
44
|
+
- [Pattern Matching](#pattern-matching)
|
|
45
|
+
- [`Array`/`Find` patterns](#arrayfind-patterns)
|
|
46
|
+
- [`Hash` patterns](#hash-patterns)
|
|
38
47
|
- [About](#about)
|
|
39
48
|
- [Development](#development)
|
|
40
49
|
- [Contributing](#contributing)
|
|
@@ -61,7 +70,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
61
70
|
|
|
62
71
|
$ gem install bcdd-result
|
|
63
72
|
|
|
64
|
-
<p align="right"
|
|
73
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
65
74
|
|
|
66
75
|
## Usage
|
|
67
76
|
|
|
@@ -81,7 +90,23 @@ BCDD::Result::Success(:ok) #
|
|
|
81
90
|
BCDD::Result::Failure(:err) #
|
|
82
91
|
```
|
|
83
92
|
|
|
84
|
-
<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>
|
|
85
110
|
|
|
86
111
|
## Reference
|
|
87
112
|
|
|
@@ -137,7 +162,7 @@ result.type # :no
|
|
|
137
162
|
result.value # nil
|
|
138
163
|
```
|
|
139
164
|
|
|
140
|
-
<p align="right"
|
|
165
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
141
166
|
|
|
142
167
|
#### Receiving types in `result.success?` or `result.failure?`
|
|
143
168
|
|
|
@@ -163,7 +188,7 @@ result.failure?(:err) # true
|
|
|
163
188
|
result.failure?(:error) # false
|
|
164
189
|
```
|
|
165
190
|
|
|
166
|
-
<p align="right"
|
|
191
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
167
192
|
|
|
168
193
|
### Result Hooks
|
|
169
194
|
|
|
@@ -182,7 +207,7 @@ def divide(arg1, arg2)
|
|
|
182
207
|
end
|
|
183
208
|
```
|
|
184
209
|
|
|
185
|
-
<p align="right"
|
|
210
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
186
211
|
|
|
187
212
|
#### `result.on`
|
|
188
213
|
|
|
@@ -220,7 +245,7 @@ result.object_id == output.object_id # true
|
|
|
220
245
|
|
|
221
246
|
*PS: The `divide()` implementation is [here](#result-hooks).*
|
|
222
247
|
|
|
223
|
-
<p align="right"
|
|
248
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
224
249
|
|
|
225
250
|
#### `result.on_type`
|
|
226
251
|
|
|
@@ -249,7 +274,7 @@ divide(4, 4).on_failure { |error| puts error }
|
|
|
249
274
|
|
|
250
275
|
*PS: The `divide()` implementation is [here](#result-hooks).*
|
|
251
276
|
|
|
252
|
-
<p align="right"
|
|
277
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
253
278
|
|
|
254
279
|
#### `result.on_failure`
|
|
255
280
|
|
|
@@ -271,7 +296,28 @@ divide(4, 0).on_failure(:invalid_arg) { |error| puts error }
|
|
|
271
296
|
|
|
272
297
|
*PS: The `divide()` implementation is [here](#result-hooks).*
|
|
273
298
|
|
|
274
|
-
<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>
|
|
275
321
|
|
|
276
322
|
#### `result.handle`
|
|
277
323
|
|
|
@@ -282,6 +328,7 @@ divide(4, 2).handle do |result|
|
|
|
282
328
|
result.success { |number| number }
|
|
283
329
|
result.failure(:invalid_arg) { |err| puts err }
|
|
284
330
|
result.type(:division_by_zero) { raise ZeroDivisionError }
|
|
331
|
+
result.unknown { raise NotImplementedError }
|
|
285
332
|
end
|
|
286
333
|
|
|
287
334
|
#or
|
|
@@ -289,6 +336,7 @@ end
|
|
|
289
336
|
divide(4, 2).handle do |on|
|
|
290
337
|
on.success { |number| number }
|
|
291
338
|
on.failure { |err| puts err }
|
|
339
|
+
on.unknown { raise NotImplementedError }
|
|
292
340
|
end
|
|
293
341
|
|
|
294
342
|
#or
|
|
@@ -297,6 +345,7 @@ divide(4, 2).handle do |on|
|
|
|
297
345
|
on.type(:invalid_arg) { |err| puts err }
|
|
298
346
|
on.type(:division_by_zero) { raise ZeroDivisionError }
|
|
299
347
|
on.type(:division_completed) { |number| number }
|
|
348
|
+
on.unknown { raise NotImplementedError }
|
|
300
349
|
end
|
|
301
350
|
|
|
302
351
|
# or
|
|
@@ -305,6 +354,7 @@ divide(4, 2).handle do |on|
|
|
|
305
354
|
on[:invalid_arg] { |err| puts err }
|
|
306
355
|
on[:division_by_zero] { raise ZeroDivisionError }
|
|
307
356
|
on[:division_completed] { |number| number }
|
|
357
|
+
on.unknown { raise NotImplementedError }
|
|
308
358
|
end
|
|
309
359
|
|
|
310
360
|
# The [] syntax 👆 is an alias of #type.
|
|
@@ -317,7 +367,7 @@ end
|
|
|
317
367
|
|
|
318
368
|
*PS: The `divide()` implementation is [here](#result-hooks).*
|
|
319
369
|
|
|
320
|
-
<p align="right"
|
|
370
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
321
371
|
|
|
322
372
|
### Result Value
|
|
323
373
|
|
|
@@ -350,11 +400,50 @@ divide(100, 0).value_or { 0 } # 0
|
|
|
350
400
|
|
|
351
401
|
*PS: The `divide()` implementation is [here](#result-hooks).*
|
|
352
402
|
|
|
353
|
-
<p align="right"
|
|
403
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
404
|
+
|
|
405
|
+
### Result Data
|
|
406
|
+
|
|
407
|
+
#### `result.data`
|
|
408
|
+
|
|
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`).
|
|
410
|
+
|
|
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]
|
|
354
440
|
|
|
355
|
-
|
|
441
|
+
print_to_hash(**success_data) # [:success, :ok, 1]
|
|
442
|
+
```
|
|
356
443
|
|
|
357
|
-
|
|
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>
|
|
358
447
|
|
|
359
448
|
### Railway Oriented Programming
|
|
360
449
|
|
|
@@ -416,9 +505,9 @@ Divide.call(2, 2)
|
|
|
416
505
|
#<BCDD::Result::Success type=:division_completed data=1>
|
|
417
506
|
```
|
|
418
507
|
|
|
419
|
-
<p align="right"
|
|
508
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
420
509
|
|
|
421
|
-
#### `BCDD::
|
|
510
|
+
#### `BCDD::Result::Mixin`
|
|
422
511
|
|
|
423
512
|
It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`.
|
|
424
513
|
|
|
@@ -430,7 +519,7 @@ And because of this, you can use the `#and_then` method to call methods from the
|
|
|
430
519
|
|
|
431
520
|
```ruby
|
|
432
521
|
class Divide
|
|
433
|
-
include BCDD::
|
|
522
|
+
include BCDD::Result::Mixin
|
|
434
523
|
|
|
435
524
|
attr_reader :arg1, :arg2
|
|
436
525
|
|
|
@@ -478,7 +567,7 @@ Divide.new(4, '2').call #<BCDD::Result::Failure type=:invalid_arg value="arg2 mu
|
|
|
478
567
|
|
|
479
568
|
```ruby
|
|
480
569
|
module Divide
|
|
481
|
-
extend BCDD::
|
|
570
|
+
extend BCDD::Result::Mixin
|
|
482
571
|
extend self
|
|
483
572
|
|
|
484
573
|
def call(arg1, arg2)
|
|
@@ -518,17 +607,78 @@ Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must b
|
|
|
518
607
|
|
|
519
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.
|
|
520
609
|
|
|
521
|
-
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.
|
|
522
611
|
|
|
523
612
|
> **Note**: You still can use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
|
|
524
613
|
|
|
525
|
-
<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>
|
|
526
676
|
|
|
527
677
|
## About
|
|
528
678
|
|
|
529
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.
|
|
530
680
|
|
|
531
|
-
<p align="right"
|
|
681
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
532
682
|
|
|
533
683
|
## Development
|
|
534
684
|
|
|
@@ -536,19 +686,19 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
|
536
686
|
|
|
537
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).
|
|
538
688
|
|
|
539
|
-
<p align="right"
|
|
689
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
540
690
|
|
|
541
691
|
## Contributing
|
|
542
692
|
|
|
543
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).
|
|
544
694
|
|
|
545
|
-
<p align="right"
|
|
695
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
546
696
|
|
|
547
697
|
## License
|
|
548
698
|
|
|
549
699
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
550
700
|
|
|
551
|
-
<p align="right"
|
|
701
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
|
552
702
|
|
|
553
703
|
## Code of Conduct
|
|
554
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
data/lib/bcdd/result/handler.rb
CHANGED
|
@@ -25,6 +25,10 @@ class BCDD::Result
|
|
|
25
25
|
self.outcome = block if result.success? && _type.in?(types, allow_empty: true)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def unknown(&block)
|
|
29
|
+
self.outcome = block unless outcome?
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
alias type []
|
|
29
33
|
|
|
30
34
|
private
|
|
@@ -35,13 +39,13 @@ class BCDD::Result
|
|
|
35
39
|
@outcome != UNDEFINED
|
|
36
40
|
end
|
|
37
41
|
|
|
38
|
-
def outcome
|
|
39
|
-
@outcome if outcome?
|
|
40
|
-
end
|
|
41
|
-
|
|
42
42
|
def outcome=(block)
|
|
43
43
|
@outcome = block.call(result.value, result.type) unless outcome?
|
|
44
44
|
end
|
|
45
|
+
|
|
46
|
+
def outcome
|
|
47
|
+
@outcome if outcome?
|
|
48
|
+
end
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
private_constant :Handler
|
|
@@ -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
data/lib/bcdd/result/version.rb
CHANGED
data/lib/bcdd/result.rb
CHANGED
|
@@ -3,21 +3,27 @@
|
|
|
3
3
|
require_relative 'result/version'
|
|
4
4
|
require_relative 'result/error'
|
|
5
5
|
require_relative 'result/type'
|
|
6
|
+
require_relative 'result/data'
|
|
6
7
|
require_relative 'result/handler'
|
|
7
8
|
require_relative 'result/failure'
|
|
8
9
|
require_relative 'result/success'
|
|
9
|
-
|
|
10
|
-
require_relative 'resultable'
|
|
10
|
+
require_relative 'result/mixin'
|
|
11
11
|
|
|
12
12
|
class BCDD::Result
|
|
13
|
+
attr_accessor :unknown
|
|
14
|
+
|
|
13
15
|
attr_reader :_type, :value, :subject
|
|
14
16
|
|
|
15
17
|
protected :subject
|
|
16
18
|
|
|
19
|
+
private :unknown, :unknown=
|
|
20
|
+
|
|
17
21
|
def initialize(type:, value:, subject: nil)
|
|
18
22
|
@_type = Type.new(type)
|
|
19
23
|
@value = value
|
|
20
24
|
@subject = subject
|
|
25
|
+
|
|
26
|
+
self.unknown = true
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
def type
|
|
@@ -36,31 +42,22 @@ class BCDD::Result
|
|
|
36
42
|
raise Error::NotImplemented
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
end
|
|
42
|
-
alias eql? ==
|
|
43
|
-
|
|
44
|
-
def hash
|
|
45
|
-
[self.class, type, value].hash
|
|
46
|
-
end
|
|
45
|
+
def on(*types, &block)
|
|
46
|
+
raise Error::MissingTypeArgument if types.empty?
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
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) }
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
tap { yield(value, type) if _type.in?(types, allow_empty: false) }
|
|
51
|
+
def on_success(*types, &block)
|
|
52
|
+
tap { known(block) if success? && _type.in?(types, allow_empty: true) }
|
|
56
53
|
end
|
|
57
54
|
|
|
58
|
-
def
|
|
59
|
-
tap {
|
|
55
|
+
def on_failure(*types, &block)
|
|
56
|
+
tap { known(block) if failure? && _type.in?(types, allow_empty: true) }
|
|
60
57
|
end
|
|
61
58
|
|
|
62
|
-
def
|
|
63
|
-
tap { yield(value, type) if
|
|
59
|
+
def on_unknown
|
|
60
|
+
tap { yield(value, type) if unknown }
|
|
64
61
|
end
|
|
65
62
|
|
|
66
63
|
def and_then(method_name = nil)
|
|
@@ -81,12 +78,45 @@ class BCDD::Result
|
|
|
81
78
|
handler.send(:outcome)
|
|
82
79
|
end
|
|
83
80
|
|
|
84
|
-
|
|
85
|
-
|
|
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? ==
|
|
86
106
|
alias on_type on
|
|
87
107
|
|
|
88
108
|
private
|
|
89
109
|
|
|
110
|
+
def name
|
|
111
|
+
raise Error::NotImplemented
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def known(block)
|
|
115
|
+
self.unknown = false
|
|
116
|
+
|
|
117
|
+
block.call(value, type)
|
|
118
|
+
end
|
|
119
|
+
|
|
90
120
|
def call_subject_method(method_name)
|
|
91
121
|
method = subject.method(method_name)
|
|
92
122
|
|
data/lib/result.rb
ADDED
data/sig/bcdd/result.rbs
CHANGED
|
@@ -5,6 +5,8 @@ module BCDD
|
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
class BCDD::Result
|
|
8
|
+
private attr_accessor unknown: bool
|
|
9
|
+
|
|
8
10
|
attr_reader _type: BCDD::Result::Type
|
|
9
11
|
attr_reader value: untyped
|
|
10
12
|
attr_reader subject: untyped
|
|
@@ -18,27 +20,31 @@ class BCDD::Result
|
|
|
18
20
|
|
|
19
21
|
def value_or: { () -> untyped } -> untyped
|
|
20
22
|
|
|
21
|
-
def ==: (untyped) -> bool
|
|
22
|
-
alias eql? ==
|
|
23
|
-
|
|
24
|
-
def hash: -> Integer
|
|
25
|
-
|
|
26
|
-
def inspect: -> String
|
|
27
|
-
|
|
28
23
|
def on: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
|
|
29
24
|
def on_success: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
|
|
30
25
|
def on_failure: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
|
|
26
|
+
def on_unknown: () { (untyped, Symbol) -> void } -> BCDD::Result
|
|
31
27
|
|
|
32
28
|
def and_then: (?Symbol method_name) { (untyped) -> untyped } -> BCDD::Result
|
|
33
29
|
|
|
34
30
|
def handle: { (BCDD::Result::Handler) -> void } -> untyped
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
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? ==
|
|
38
42
|
alias on_type on
|
|
39
43
|
|
|
40
44
|
private
|
|
41
45
|
|
|
46
|
+
def name: -> Symbol
|
|
47
|
+
def known: (Proc) -> untyped
|
|
42
48
|
def call_subject_method: (Symbol) -> BCDD::Result
|
|
43
49
|
def ensure_result_object: (untyped, origin: Symbol) -> BCDD::Result
|
|
44
50
|
end
|
|
@@ -57,6 +63,14 @@ class BCDD::Result
|
|
|
57
63
|
def self.Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
|
|
58
64
|
end
|
|
59
65
|
|
|
66
|
+
class BCDD::Result
|
|
67
|
+
module Mixin
|
|
68
|
+
def Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
|
|
69
|
+
|
|
70
|
+
def Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
60
74
|
class BCDD::Result
|
|
61
75
|
class Handler
|
|
62
76
|
UNDEFINED: Object
|
|
@@ -66,6 +80,7 @@ class BCDD::Result
|
|
|
66
80
|
def []: (*Symbol) { (untyped, Symbol) -> void } -> untyped
|
|
67
81
|
def failure: (*Symbol) { (untyped, Symbol) -> void } -> untyped
|
|
68
82
|
def success: (*Symbol) { (untyped, Symbol) -> void } -> untyped
|
|
83
|
+
def unknown: () { (untyped, Symbol) -> void } -> untyped
|
|
69
84
|
|
|
70
85
|
alias type []
|
|
71
86
|
|
|
@@ -75,18 +90,8 @@ class BCDD::Result
|
|
|
75
90
|
attr_reader result: BCDD::Result
|
|
76
91
|
|
|
77
92
|
def outcome?: -> bool
|
|
78
|
-
|
|
79
|
-
def outcome: -> untyped
|
|
80
|
-
|
|
81
93
|
def outcome=: (Proc) -> void
|
|
82
|
-
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
module BCDD
|
|
86
|
-
module Resultable
|
|
87
|
-
def Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
|
|
88
|
-
|
|
89
|
-
def Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
|
|
94
|
+
def outcome: -> untyped
|
|
90
95
|
end
|
|
91
96
|
end
|
|
92
97
|
|
|
@@ -100,6 +105,23 @@ class BCDD::Result
|
|
|
100
105
|
end
|
|
101
106
|
end
|
|
102
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
|
+
|
|
103
125
|
class BCDD::Result
|
|
104
126
|
class Error < ::StandardError
|
|
105
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,13 +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
|
|
32
35
|
- lib/bcdd/result/handler.rb
|
|
36
|
+
- lib/bcdd/result/mixin.rb
|
|
33
37
|
- lib/bcdd/result/success.rb
|
|
34
38
|
- lib/bcdd/result/type.rb
|
|
35
39
|
- lib/bcdd/result/version.rb
|
|
36
|
-
- lib/
|
|
40
|
+
- lib/result.rb
|
|
37
41
|
- sig/bcdd/result.rbs
|
|
38
42
|
homepage: https://github.com/b-cdd/result
|
|
39
43
|
licenses:
|
|
@@ -62,5 +66,5 @@ requirements: []
|
|
|
62
66
|
rubygems_version: 3.4.19
|
|
63
67
|
signing_key:
|
|
64
68
|
specification_version: 4
|
|
65
|
-
summary: A result abstraction (monad based) for Ruby.
|
|
69
|
+
summary: A pragmatic result abstraction (monad based) for Ruby.
|
|
66
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
|