bcdd-result 0.1.0 → 0.2.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 +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:
|