bcdd-result 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|