bcdd-result 0.3.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 +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
|