bcdd-result 0.1.0 → 0.2.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 +4 -0
- data/.rubocop_todo.yml +3 -3
- data/CHANGELOG.md +94 -0
- data/README.md +150 -22
- data/lib/bcdd/result/error.rb +26 -4
- data/lib/bcdd/result/version.rb +1 -1
- data/lib/bcdd/result.rb +32 -6
- data/lib/bcdd/resultable.rb +11 -0
- data/sig/bcdd/result.rbs +30 -5
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a41105b84f902004736a171bd42c943923ee188d14fe6630bf379fb3e3ef158
|
4
|
+
data.tar.gz: 7b879ee4c04c2b60e95d1c2af18e28c8e7f1edb475c0cdb8274c56f6a577198a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48e9dd67d1f69a4b9aae0bbf1166aab532ca2f8cdc8883bf303b5a41f702a77c8531c4ae2baa96bf256a828e9db0624d631c1259e0870c9ee22ecd65b4ed2ce9
|
7
|
+
data.tar.gz: cb2a0e618b21a4bf98c61b0a4dabfac322efe4293eab18c84cf729df920c49bf47557dcf20ceb01c42ac7c4673fa6fb55c235a2e93f2555a2021cbbfb3f37b36
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2023-09-
|
3
|
+
# on 2023-09-25 17:14:03 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: 9
|
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/base.rb'
|
17
16
|
- 'lib/bcdd/result/error.rb'
|
18
17
|
- 'lib/bcdd/result/failure.rb'
|
19
18
|
- 'lib/bcdd/result/success.rb'
|
19
|
+
- 'lib/bcdd/resultable.rb'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,99 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.2.0] - 2023-09-26
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Add `BCDD::Resultable`. This module can add `Success()` and `Failure()` in any object. The target object will be the subject of the result object produced by these methods.
|
8
|
+
|
9
|
+
**Classes (instance methods)**
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class Divide
|
13
|
+
include BCDD::Resultable
|
14
|
+
|
15
|
+
attr_reader :arg1, :arg2
|
16
|
+
|
17
|
+
def initialize(arg1, arg2)
|
18
|
+
@arg1 = arg1
|
19
|
+
@arg2 = arg2
|
20
|
+
end
|
21
|
+
|
22
|
+
def call
|
23
|
+
validate_numbers
|
24
|
+
.and_then(:validate_non_zero)
|
25
|
+
.and_then(:divide)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def validate_numbers
|
31
|
+
arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
|
32
|
+
arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
|
33
|
+
|
34
|
+
Success(:ok, [arg1, arg2])
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_non_zero(numbers)
|
38
|
+
return Success(:ok, numbers) unless numbers.last.zero?
|
39
|
+
|
40
|
+
Failure(:division_by_zero, 'arg2 must not be zero')
|
41
|
+
end
|
42
|
+
|
43
|
+
def divide((number1, number2))
|
44
|
+
Success(:division_completed, number1 / number2)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
**Module (singleton methods)**
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
module Divide
|
53
|
+
extend BCDD::Resultable
|
54
|
+
extend self
|
55
|
+
|
56
|
+
def call(arg1, arg2)
|
57
|
+
validate_numbers(arg1, arg2)
|
58
|
+
.and_then(:validate_non_zero)
|
59
|
+
.and_then(:divide)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def validate_numbers(arg1, arg2)
|
65
|
+
arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
|
66
|
+
arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
|
67
|
+
|
68
|
+
Success(:ok, [arg1, arg2])
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_non_zero(numbers)
|
72
|
+
return Success(:ok, numbers) unless numbers.last.zero?
|
73
|
+
|
74
|
+
Failure(:division_by_zero, 'arg2 must not be zero')
|
75
|
+
end
|
76
|
+
|
77
|
+
def divide((number1, number2))
|
78
|
+
Success(:division_completed, number1 / number2)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
- Make the `BCDD::Result#initialize` enabled to receive a subject.
|
84
|
+
|
85
|
+
- Make the `BCDD::Result#and_then` method receive a method name (symbol) and perform it on the result subject (added by `BCDD::Resultable`). The called method must return a result; otherwise, an error (`BCDD::Result::Error::UnexpectedOutcome`) will be raised.
|
86
|
+
|
87
|
+
- Add `BCDD::Result::Error::UnexpectedOutcome` to represent an unexpected outcome.
|
88
|
+
|
89
|
+
- Add `BCDD::Result::Error::WrongResultSubject` to represent a wrong result subject. When using `BCDD::Resultable`, the result subject must be the same as the target object.
|
90
|
+
|
91
|
+
- Add `BCDD::Result::Error::WrongSubjectMethodArity` to represent a wrong subject method arity. Valid arities are 0 and 1.
|
92
|
+
|
93
|
+
### Removed
|
94
|
+
|
95
|
+
- **(BREAKING)** Remove `BCDD::Result::Error::UnexpectedBlockOutcome`. It was replaced by `BCDD::Result::Error::UnexpectedOutcome`.
|
96
|
+
|
3
97
|
## [0.1.0] - 2023-09-25
|
4
98
|
|
5
99
|
### Added
|
data/README.md
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
<p align="center">
|
2
|
+
<h1 align="center" id="-bcddresult">🔀 BCDD::Result</h1>
|
3
|
+
<p align="center"><i>Empower Ruby apps with a pragmatic use of Railway Oriented Programming.</i></p>
|
4
|
+
<br>
|
5
|
+
</p>
|
2
6
|
|
3
7
|
A general-purpose result monad that allows you to create objects that represent a success (`BCDD::Result::Success`) or failure (`BCDD::Result::Failure`).
|
4
8
|
|
@@ -6,7 +10,9 @@ A general-purpose result monad that allows you to create objects that represent
|
|
6
10
|
|
7
11
|
It allows you to consistently represent the concept of success and failure throughout your codebase.
|
8
12
|
|
9
|
-
Furthermore, this abstraction exposes several
|
13
|
+
Furthermore, this abstraction exposes several features that will be useful to make the application flow react cleanly and securely to the result represented by these objects.
|
14
|
+
|
15
|
+
Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofit.com/rop/) pattern (superpower) in your code.
|
10
16
|
|
11
17
|
- [Installation](#installation)
|
12
18
|
- [Usage](#usage)
|
@@ -18,18 +24,21 @@ Furthermore, this abstraction exposes several methods that will be useful to mak
|
|
18
24
|
- [`result.on_type`](#resulton_type)
|
19
25
|
- [`result.on_success`](#resulton_success)
|
20
26
|
- [`result.on_failure`](#resulton_failure)
|
21
|
-
- [Result
|
27
|
+
- [Result Value](#result-value)
|
22
28
|
- [`result.value_or`](#resultvalue_or)
|
23
29
|
- [`result.data_or`](#resultdata_or)
|
24
30
|
- [Railway Oriented Programming](#railway-oriented-programming)
|
25
31
|
- [`result.and_then`](#resultand_then)
|
32
|
+
- [`BCDD::Resultable`](#bcddresultable)
|
33
|
+
- [Class example (instance methods)](#class-example-instance-methods)
|
34
|
+
- [Module example (singleton methods)](#module-example-singleton-methods)
|
35
|
+
- [Restrictions](#restrictions)
|
26
36
|
- [About](#about)
|
27
37
|
- [Development](#development)
|
28
38
|
- [Contributing](#contributing)
|
29
39
|
- [License](#license)
|
30
40
|
- [Code of Conduct](#code-of-conduct)
|
31
41
|
|
32
|
-
|
33
42
|
## Installation
|
34
43
|
|
35
44
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -40,6 +49,10 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
40
49
|
|
41
50
|
$ gem install bcdd-result
|
42
51
|
|
52
|
+
> **NOTE:** This gem is compatible with Ruby >= 2.7.0
|
53
|
+
|
54
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
55
|
+
|
43
56
|
## Usage
|
44
57
|
|
45
58
|
To create a result, you must define a type (symbol) and its value (any kind of object). e.g.,
|
@@ -58,7 +71,7 @@ BCDD::Result::Success(:ok) #
|
|
58
71
|
BCDD::Result::Failure(:err) #
|
59
72
|
```
|
60
73
|
|
61
|
-
<p align="right">(<a href="
|
74
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
62
75
|
|
63
76
|
## Reference
|
64
77
|
|
@@ -114,7 +127,7 @@ result.type # :no
|
|
114
127
|
result.value # nil
|
115
128
|
```
|
116
129
|
|
117
|
-
<p align="right">(<a href="
|
130
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
118
131
|
|
119
132
|
#### Receiving types in `result.success?` or `result.failure?`
|
120
133
|
|
@@ -140,7 +153,7 @@ result.failure?(:err) # true
|
|
140
153
|
result.failure?(:error) # false
|
141
154
|
```
|
142
155
|
|
143
|
-
<p align="right">(<a href="
|
156
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
144
157
|
|
145
158
|
### Result Hooks
|
146
159
|
|
@@ -159,6 +172,8 @@ def divide(arg1, arg2)
|
|
159
172
|
end
|
160
173
|
```
|
161
174
|
|
175
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
176
|
+
|
162
177
|
#### `result.on`
|
163
178
|
|
164
179
|
`BCDD::Result#on` will perform the block when the type matches the result type.
|
@@ -193,7 +208,9 @@ output =
|
|
193
208
|
result.object_id == output.object_id # true
|
194
209
|
```
|
195
210
|
|
196
|
-
|
211
|
+
*PS: The `divide()` implementation is [here](#result-hooks).*
|
212
|
+
|
213
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
197
214
|
|
198
215
|
#### `result.on_type`
|
199
216
|
|
@@ -220,7 +237,9 @@ divide(4, 4).on_success(:ok) { |value| puts value }
|
|
220
237
|
divide(4, 4).on_failure { |error| puts error }
|
221
238
|
```
|
222
239
|
|
223
|
-
|
240
|
+
*PS: The `divide()` implementation is [here](#result-hooks).*
|
241
|
+
|
242
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
224
243
|
|
225
244
|
#### `result.on_failure`
|
226
245
|
|
@@ -240,9 +259,11 @@ divide(4, 0).on_success { |number| puts number }
|
|
240
259
|
divide(4, 0).on_failure(:invalid_arg) { |error| puts error }
|
241
260
|
```
|
242
261
|
|
243
|
-
|
262
|
+
*PS: The `divide()` implementation is [here](#result-hooks).*
|
263
|
+
|
264
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
244
265
|
|
245
|
-
### Result
|
266
|
+
### Result Value
|
246
267
|
|
247
268
|
The most simple way to get the result value is by calling `BCDD::Result#value` or `BCDD::Result#data`.
|
248
269
|
|
@@ -271,17 +292,16 @@ divide(4, '2').value_or { 0 } # 0
|
|
271
292
|
divide(100, 0).value_or { 0 } # 0
|
272
293
|
```
|
273
294
|
|
274
|
-
|
295
|
+
*PS: The `divide()` implementation is [here](#result-hooks).*
|
296
|
+
|
297
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
275
298
|
|
276
299
|
#### `result.data_or`
|
277
300
|
|
278
301
|
`BCDD::Result#data_or` is an alias of `BCDD::Result#value_or`.
|
279
302
|
|
280
|
-
|
281
303
|
### Railway Oriented Programming
|
282
304
|
|
283
|
-
#### `result.and_then`
|
284
|
-
|
285
305
|
This feature/pattern is also known as ["Railway Oriented Programming"](https://fsharpforfunandprofit.com/rop/).
|
286
306
|
|
287
307
|
The idea is to chain blocks and creates a pipeline of operations that can be interrupted by a failure.
|
@@ -289,7 +309,9 @@ The idea is to chain blocks and creates a pipeline of operations that can be int
|
|
289
309
|
In other words, the block will be executed only if the result is a success.
|
290
310
|
So, if some block returns a failure, the following blocks will be skipped.
|
291
311
|
|
292
|
-
Due to this characteristic, you can use this feature to express some logic as a sequence of operations. And have the guarantee that the process will stop by the first failure detection, and if everything is ok, the final result will be a success.
|
312
|
+
Due to this characteristic, you can use this feature to express some logic as a sequence of operations. And have the guarantee that the process will stop by the first failure detection, and if everything is ok, the final result will be a success.
|
313
|
+
|
314
|
+
#### `result.and_then`
|
293
315
|
|
294
316
|
```ruby
|
295
317
|
module Divide
|
@@ -313,7 +335,7 @@ module Divide
|
|
313
335
|
def validate_non_zero(numbers)
|
314
336
|
return BCDD::Result::Success(:ok, numbers) unless numbers.last.zero?
|
315
337
|
|
316
|
-
BCDD::Result::Failure(:division_by_zero,
|
338
|
+
BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero')
|
317
339
|
end
|
318
340
|
|
319
341
|
def divide((number1, number2))
|
@@ -338,13 +360,119 @@ Divide.call(2, 2)
|
|
338
360
|
#<BCDD::Result::Success type=:division_completed data=1>
|
339
361
|
```
|
340
362
|
|
341
|
-
<p align="right">(<a href="
|
363
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
364
|
+
|
365
|
+
#### `BCDD::Resultable`
|
366
|
+
|
367
|
+
It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`.
|
368
|
+
|
369
|
+
The main difference between these methods and `BCDD::Result::Success()`/`BCDD::Result::Failure()` is that the first ones will use the target object as the result's subject.
|
370
|
+
|
371
|
+
And because of this, you can use the `#and_then` method to call methods from the target object (result's subject).
|
372
|
+
|
373
|
+
##### Class example (instance methods)
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
class Divide
|
377
|
+
include BCDD::Resultable
|
378
|
+
|
379
|
+
attr_reader :arg1, :arg2
|
380
|
+
|
381
|
+
def initialize(arg1, arg2)
|
382
|
+
@arg1 = arg1
|
383
|
+
@arg2 = arg2
|
384
|
+
end
|
385
|
+
|
386
|
+
def call
|
387
|
+
validate_numbers
|
388
|
+
.and_then(:validate_non_zero)
|
389
|
+
.and_then(:divide)
|
390
|
+
end
|
391
|
+
|
392
|
+
private
|
393
|
+
|
394
|
+
def validate_numbers
|
395
|
+
arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
|
396
|
+
arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
|
397
|
+
|
398
|
+
# As arg1 and arg2 are instance methods, they will be available in the instance scope.
|
399
|
+
# So, in this case, I'm passing them as an array to show how the next method can receive the value as its argument.
|
400
|
+
Success(:ok, [arg1, arg2])
|
401
|
+
end
|
402
|
+
|
403
|
+
def validate_non_zero(numbers)
|
404
|
+
return Success(:ok, numbers) unless numbers.last.zero?
|
405
|
+
|
406
|
+
Failure(:division_by_zero, 'arg2 must not be zero')
|
407
|
+
end
|
408
|
+
|
409
|
+
def divide((number1, number2))
|
410
|
+
Success(:division_completed, number1 / number2)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
Divide.new(4, 2).call #<BCDD::Result::Success type=:division_completed value=2>
|
415
|
+
|
416
|
+
Divide.new(4, 0).call #<BCDD::Result::Failure type=:division_by_zero value="arg2 must not be zero">
|
417
|
+
Divide.new('4', 2).call #<BCDD::Result::Failure type=:invalid_arg value="arg1 must be numeric">
|
418
|
+
Divide.new(4, '2').call #<BCDD::Result::Failure type=:invalid_arg value="arg2 must be numeric">
|
419
|
+
```
|
420
|
+
|
421
|
+
##### Module example (singleton methods)
|
422
|
+
|
423
|
+
```ruby
|
424
|
+
module Divide
|
425
|
+
extend BCDD::Resultable
|
426
|
+
extend self
|
427
|
+
|
428
|
+
def call(arg1, arg2)
|
429
|
+
validate_numbers(arg1, arg2)
|
430
|
+
.and_then(:validate_non_zero)
|
431
|
+
.and_then(:divide)
|
432
|
+
end
|
433
|
+
|
434
|
+
private
|
435
|
+
|
436
|
+
def validate_numbers(arg1, arg2)
|
437
|
+
arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
|
438
|
+
arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
|
439
|
+
|
440
|
+
Success(:ok, [arg1, arg2])
|
441
|
+
end
|
442
|
+
|
443
|
+
def validate_non_zero(numbers)
|
444
|
+
return Success(:ok, numbers) unless numbers.last.zero?
|
445
|
+
|
446
|
+
Failure(:division_by_zero, 'arg2 must not be zero')
|
447
|
+
end
|
448
|
+
|
449
|
+
def divide((number1, number2))
|
450
|
+
Success(:division_completed, number1 / number2)
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
Divide.call(4, 2) #<BCDD::Result::Success type=:division_completed value=2>
|
455
|
+
|
456
|
+
Divide.call(4, 0) #<BCDD::Result::Failure type=:division_by_zero value="arg2 must not be zero">
|
457
|
+
Divide.call('4', 2) #<BCDD::Result::Failure type=:invalid_arg value="arg1 must be numeric">
|
458
|
+
Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must be numeric">
|
459
|
+
```
|
460
|
+
|
461
|
+
##### Restrictions
|
462
|
+
|
463
|
+
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
|
+
|
465
|
+
If you use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or call another `BCDD::Resultable` object, the `#and_then` will raise an error because the subjects will be different.
|
466
|
+
|
467
|
+
> **Note**: You still can use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
|
468
|
+
|
469
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
342
470
|
|
343
471
|
## About
|
344
472
|
|
345
473
|
[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.
|
346
474
|
|
347
|
-
<p align="right">(<a href="
|
475
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
348
476
|
|
349
477
|
## Development
|
350
478
|
|
@@ -352,19 +480,19 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
352
480
|
|
353
481
|
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).
|
354
482
|
|
355
|
-
<p align="right">(<a href="
|
483
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
356
484
|
|
357
485
|
## Contributing
|
358
486
|
|
359
487
|
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).
|
360
488
|
|
361
|
-
<p align="right">(<a href="
|
489
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
362
490
|
|
363
491
|
## License
|
364
492
|
|
365
493
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
366
494
|
|
367
|
-
<p align="right">(<a href="
|
495
|
+
<p align="right">(<a href="#-bcddresult">⬆️ back to top</a>)</p>
|
368
496
|
|
369
497
|
## Code of Conduct
|
370
498
|
|
data/lib/bcdd/result/error.rb
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
class BCDD::Result
|
4
4
|
class Error < ::StandardError
|
5
|
+
def self.build(**_kargs)
|
6
|
+
new
|
7
|
+
end
|
8
|
+
|
5
9
|
class NotImplemented < self
|
6
10
|
end
|
7
11
|
|
@@ -11,13 +15,31 @@ class BCDD::Result
|
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
14
|
-
class
|
15
|
-
def
|
18
|
+
class UnexpectedOutcome < self
|
19
|
+
def self.build(outcome:, origin:)
|
16
20
|
message =
|
17
|
-
"Unexpected outcome: #{
|
21
|
+
"Unexpected outcome: #{outcome.inspect}. The #{origin} must return this object wrapped by " \
|
18
22
|
'BCDD::Result::Success or BCDD::Result::Failure'
|
19
23
|
|
20
|
-
|
24
|
+
new(message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class WrongResultSubject < self
|
29
|
+
def self.build(given_result:, expected_subject:)
|
30
|
+
message =
|
31
|
+
"You cannot call #and_then and return a result that does not belong to the subject!\n" \
|
32
|
+
"Expected subject: #{expected_subject.inspect}\n" \
|
33
|
+
"Given subject: #{given_result.send(:subject).inspect}\n" \
|
34
|
+
"Given result: #{given_result.inspect}"
|
35
|
+
|
36
|
+
new(message)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class WrongSubjectMethodArity < self
|
41
|
+
def self.build(subject:, method:)
|
42
|
+
new("#{subject.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0 or 1.")
|
21
43
|
end
|
22
44
|
end
|
23
45
|
end
|
data/lib/bcdd/result/version.rb
CHANGED
data/lib/bcdd/result.rb
CHANGED
@@ -5,12 +5,17 @@ require_relative 'result/error'
|
|
5
5
|
require_relative 'result/failure'
|
6
6
|
require_relative 'result/success'
|
7
7
|
|
8
|
+
require_relative 'resultable'
|
9
|
+
|
8
10
|
class BCDD::Result
|
9
|
-
attr_reader :type, :value
|
11
|
+
attr_reader :type, :value, :subject
|
12
|
+
|
13
|
+
protected :subject
|
10
14
|
|
11
|
-
def initialize(type:, value:)
|
15
|
+
def initialize(type:, value:, subject: nil)
|
12
16
|
@type = type.to_sym
|
13
17
|
@value = value
|
18
|
+
@subject = subject
|
14
19
|
end
|
15
20
|
|
16
21
|
def success?(_type = nil)
|
@@ -52,14 +57,14 @@ class BCDD::Result
|
|
52
57
|
tap { yield(value, type) if failure? && allowed_to_handle?(types) }
|
53
58
|
end
|
54
59
|
|
55
|
-
def and_then
|
60
|
+
def and_then(method_name = nil)
|
56
61
|
return self if failure?
|
57
62
|
|
58
|
-
|
63
|
+
return call_subject_method(method_name) if method_name
|
59
64
|
|
60
|
-
|
65
|
+
result = yield(value)
|
61
66
|
|
62
|
-
|
67
|
+
ensure_result_object(result, origin: :block)
|
63
68
|
end
|
64
69
|
|
65
70
|
alias data value
|
@@ -75,4 +80,25 @@ class BCDD::Result
|
|
75
80
|
def allowed_to_handle?(types)
|
76
81
|
types.empty? || expected_type?(types)
|
77
82
|
end
|
83
|
+
|
84
|
+
def call_subject_method(method_name)
|
85
|
+
method = subject.method(method_name)
|
86
|
+
|
87
|
+
result =
|
88
|
+
case method.arity
|
89
|
+
when 0 then subject.send(method_name)
|
90
|
+
when 1 then subject.send(method_name, value)
|
91
|
+
else raise Error::WrongSubjectMethodArity.build(subject: subject, method: method)
|
92
|
+
end
|
93
|
+
|
94
|
+
ensure_result_object(result, origin: :method)
|
95
|
+
end
|
96
|
+
|
97
|
+
def ensure_result_object(result, origin:)
|
98
|
+
raise Error::UnexpectedOutcome.build(outcome: result, origin: origin) unless result.is_a?(::BCDD::Result)
|
99
|
+
|
100
|
+
return result if result.subject.equal?(subject)
|
101
|
+
|
102
|
+
raise Error::WrongResultSubject.build(given_result: result, expected_subject: subject)
|
103
|
+
end
|
78
104
|
end
|
@@ -0,0 +1,11 @@
|
|
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
|
data/sig/bcdd/result.rbs
CHANGED
@@ -7,8 +7,9 @@ end
|
|
7
7
|
class BCDD::Result
|
8
8
|
attr_reader type: Symbol
|
9
9
|
attr_reader value: untyped
|
10
|
+
attr_reader subject: untyped
|
10
11
|
|
11
|
-
def initialize: (type: Symbol, value: untyped) -> void
|
12
|
+
def initialize: (type: Symbol, value: untyped, ?subject: untyped) -> void
|
12
13
|
|
13
14
|
def success?: (?Symbol type) -> bool
|
14
15
|
def failure?: (?Symbol type) -> bool
|
@@ -26,7 +27,7 @@ class BCDD::Result
|
|
26
27
|
def on_success: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
|
27
28
|
def on_failure: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
|
28
29
|
|
29
|
-
def and_then: { (untyped) -> untyped } -> BCDD::Result
|
30
|
+
def and_then: (?Symbol method_name) { (untyped) -> untyped } -> BCDD::Result
|
30
31
|
|
31
32
|
alias data value
|
32
33
|
alias data_or value_or
|
@@ -36,31 +37,55 @@ class BCDD::Result
|
|
36
37
|
|
37
38
|
def expected_type?: (Array[Symbol]) -> bool
|
38
39
|
def allowed_to_handle?: (Array[Symbol]) -> bool
|
40
|
+
def call_subject_method: (Symbol) -> BCDD::Result
|
41
|
+
def ensure_result_object: (untyped, origin: Symbol) -> BCDD::Result
|
39
42
|
end
|
40
43
|
|
41
44
|
class BCDD::Result
|
42
45
|
class Failure < BCDD::Result
|
43
46
|
end
|
44
47
|
|
45
|
-
def self.Success: (Symbol type, untyped value) -> BCDD::Result::Success
|
48
|
+
def self.Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
|
46
49
|
end
|
47
50
|
|
48
51
|
class BCDD::Result
|
49
52
|
class Success < BCDD::Result
|
50
53
|
end
|
51
54
|
|
52
|
-
def self.Failure: (Symbol type, untyped value) -> BCDD::Result::Failure
|
55
|
+
def self.Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
|
56
|
+
end
|
57
|
+
|
58
|
+
module BCDD
|
59
|
+
module Resultable
|
60
|
+
def Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
|
61
|
+
|
62
|
+
def Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
|
63
|
+
end
|
53
64
|
end
|
54
65
|
|
55
66
|
class BCDD::Result
|
56
67
|
class Error < ::StandardError
|
68
|
+
def self.build: (**untyped) -> BCDD::Result::Error
|
69
|
+
|
57
70
|
class NotImplemented < BCDD::Result::Error
|
58
71
|
end
|
59
72
|
|
60
73
|
class MissingTypeArgument < BCDD::Result::Error
|
61
74
|
end
|
62
75
|
|
63
|
-
class
|
76
|
+
class UnexpectedOutcome < BCDD::Result::Error
|
77
|
+
def self.build: (outcome: untyped, origin: Symbol)
|
78
|
+
-> BCDD::Result::Error::UnexpectedOutcome
|
79
|
+
end
|
80
|
+
|
81
|
+
class WrongResultSubject < BCDD::Result::Error
|
82
|
+
def self.build: (given_result: BCDD::Result, expected_subject: untyped)
|
83
|
+
-> BCDD::Result::Error::WrongResultSubject
|
84
|
+
end
|
85
|
+
|
86
|
+
class WrongSubjectMethodArity < BCDD::Result::Error
|
87
|
+
def self.build: (subject: untyped, method: ::Method)
|
88
|
+
-> BCDD::Result::Error::WrongSubjectMethodArity
|
64
89
|
end
|
65
90
|
end
|
66
91
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bcdd-result
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.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-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A general-purpose result monad that allows you to create objects that
|
14
14
|
represent a success (BCDD::Result::Success) or failure (BCDD::Result::Failure).
|
@@ -31,6 +31,7 @@ files:
|
|
31
31
|
- lib/bcdd/result/failure.rb
|
32
32
|
- lib/bcdd/result/success.rb
|
33
33
|
- lib/bcdd/result/version.rb
|
34
|
+
- lib/bcdd/resultable.rb
|
34
35
|
- sig/bcdd/result.rbs
|
35
36
|
homepage: https://github.com/b-cdd/result
|
36
37
|
licenses:
|