simple-result 0.1.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 +7 -0
- data/README.md +138 -0
- data/lib/simple_result/version.rb +5 -0
- data/lib/simple_result.rb +60 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e32f46fc1e26c2db6e5590d44a2d5b94559d6139d678415f13f9b9894fb98bb1
|
4
|
+
data.tar.gz: d447f8140e20c734674a3e6edf62a1a6fcf7be8ba4fc8007f2c53667c9d8e2ac
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e819e42f964243a839906ac6cfa603e43d9edd8692691ccde25a6aa888f8538e382f0b125d3648dc6ecf148b2c1408f3371ea396023965d36d67875514c1086f
|
7
|
+
data.tar.gz: bfe4bfe6a2e92b99c08a557fea5ca29ced83d04ad53e152325b83bc77ecd1045e164940bc54c3612a07868cea3c5a29e16c576b763ac8250303911171b002f12
|
data/README.md
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# SimpleResult
|
2
|
+
|
3
|
+
A simple, idiomatic Ruby implementation of a response monad (`Success` or `Failure`) for handling success and failure states inspired by https://github.com/maxveldink/sorbet-result.
|
4
|
+
|
5
|
+
This is a very simple implementation kept on purpose to be less than 100 LOC (currently at 60) and with no other dependencies than `zeitwerk`.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'simple_result'
|
13
|
+
```
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
The recommended usage is to use the `Success` and `Failure` helpers to create responses. You can also use the scoped classes 'SimpleResult::Success' and 'SimpleResult::Failure' if you want.
|
18
|
+
|
19
|
+
### Basic Usage
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'simple_result'
|
23
|
+
|
24
|
+
success = Success("Hello, World!")
|
25
|
+
success.success? # => true
|
26
|
+
success.payload # => "Hello, World!"
|
27
|
+
|
28
|
+
failure = Failure("Something went wrong")
|
29
|
+
failure.failure? # => true
|
30
|
+
failure.error # => "Something went wrong"
|
31
|
+
|
32
|
+
# Blank responses
|
33
|
+
Success() # => Success with nil payload
|
34
|
+
Failure() # => Failure with nil error
|
35
|
+
```
|
36
|
+
|
37
|
+
### Chaining Operations
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
Success("hello")
|
41
|
+
.and_then { |value| value.upcase }
|
42
|
+
.and_then { |value| "#{value}!" }
|
43
|
+
# => "HELLO!"
|
44
|
+
|
45
|
+
# Failure short-circuiting
|
46
|
+
Failure("error")
|
47
|
+
.and_then { |value| value.upcase }
|
48
|
+
.and_then { |value| "#{value}!" }
|
49
|
+
# => Failure("error")
|
50
|
+
```
|
51
|
+
|
52
|
+
#### A more common case of chaining operations might look like this
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
def validate(value)
|
56
|
+
# validation
|
57
|
+
Success(value)
|
58
|
+
end
|
59
|
+
|
60
|
+
def transform(value)
|
61
|
+
transformed_value = value.upcase
|
62
|
+
|
63
|
+
Success(transformed_value)
|
64
|
+
end
|
65
|
+
|
66
|
+
def present(value)
|
67
|
+
presenter = "#{value}!!"
|
68
|
+
|
69
|
+
Success(presenter)
|
70
|
+
end
|
71
|
+
|
72
|
+
validate("hello")
|
73
|
+
.and_then { |value| transform(value) }
|
74
|
+
.and_then { |value| present(value) }
|
75
|
+
# => "HELLO!"
|
76
|
+
|
77
|
+
# Or since Ruby 3.4 you can also use the implicit `it` block parameter
|
78
|
+
validate("hello")
|
79
|
+
.and_then { transform(it) }
|
80
|
+
.and_then { present(it) }
|
81
|
+
|
82
|
+
# or if you prefer to write without paranthesis in a more DSL like style:
|
83
|
+
validate("hello")
|
84
|
+
.and_then { transform it }
|
85
|
+
.and_then { present it }
|
86
|
+
```
|
87
|
+
|
88
|
+
### Error Handling
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
# Handle errors only when they occur
|
92
|
+
Success("data").on_error { |error| puts "Error: #{error}" }
|
93
|
+
# => Success("data") - block not called
|
94
|
+
|
95
|
+
Failure("oops").on_error { |error| puts "Error: #{error}" }
|
96
|
+
# => Prints "Error: oops", returns Failure("oops")
|
97
|
+
```
|
98
|
+
|
99
|
+
### Fallback Values
|
100
|
+
|
101
|
+
The fallback will be called only when the result is nil or an error.
|
102
|
+
Please be carefull when using blank Success and Failure values with `payload_or_fallback` because `Success().payload_or_fallback` will always return the fallback value.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
# Use payload or fallback
|
106
|
+
Success("value").payload_or_fallback { "default" }
|
107
|
+
# => "value"
|
108
|
+
|
109
|
+
Success(nil).payload_or_fallback { "default" }
|
110
|
+
# => "default"
|
111
|
+
|
112
|
+
Failure("error").payload_or_fallback { "default" }
|
113
|
+
# => "default"
|
114
|
+
```
|
115
|
+
|
116
|
+
## Development
|
117
|
+
|
118
|
+
Run tests:
|
119
|
+
|
120
|
+
```bash
|
121
|
+
ruby test/test_simple_result.rb
|
122
|
+
```
|
123
|
+
|
124
|
+
Run RuboCop:
|
125
|
+
|
126
|
+
```bash
|
127
|
+
rubocop
|
128
|
+
```
|
129
|
+
|
130
|
+
Start an interactive console:
|
131
|
+
|
132
|
+
```bash
|
133
|
+
bin/console
|
134
|
+
```
|
135
|
+
|
136
|
+
## License
|
137
|
+
|
138
|
+
The gem is available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zeitwerk'
|
4
|
+
loader = Zeitwerk::Loader.for_gem
|
5
|
+
loader.setup
|
6
|
+
|
7
|
+
module SimpleResult
|
8
|
+
ResponseError = Class.new(StandardError)
|
9
|
+
ErrorNotPresentOnSuccess = Class.new(ResponseError)
|
10
|
+
PayloadNotPresentOnFailure = Class.new(ResponseError)
|
11
|
+
NotImplemented = Class.new(ResponseError)
|
12
|
+
|
13
|
+
Response = Data.define(:payload, :error) do
|
14
|
+
def initialize(payload: nil, error: nil) = super
|
15
|
+
|
16
|
+
def success? = raise NotImplemented, 'success? not implemented'
|
17
|
+
def failure? = raise NotImplemented, 'failure? not implemented'
|
18
|
+
def payload_or_fallback(&) = payload || yield
|
19
|
+
def and_then(&) = raise NotImplementedError, 'and_then not implemented'
|
20
|
+
end
|
21
|
+
|
22
|
+
class Success < Response
|
23
|
+
def self.blank = new(payload: nil)
|
24
|
+
def initialize(payload: nil) = super(payload: payload, error: nil)
|
25
|
+
|
26
|
+
def success? = true
|
27
|
+
def failure? = false
|
28
|
+
def error = raise(ResponseError, 'Error not present on success')
|
29
|
+
|
30
|
+
def and_then(&) = yield(payload)
|
31
|
+
def on_error(&) = self
|
32
|
+
|
33
|
+
def inspect = "#<data #{self.class.name} payload=#{payload.inspect}>"
|
34
|
+
|
35
|
+
def pretty_print(pp) = pp.text "#<data #{self.class.name} payload=#{payload.inspect}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
class Failure < Response
|
39
|
+
def self.blank = new(error: nil)
|
40
|
+
def initialize(error: nil) = super(payload: nil, error: error)
|
41
|
+
|
42
|
+
def success? = false
|
43
|
+
def failure? = true
|
44
|
+
def payload = raise(ResponseError, 'Payload not present on failure')
|
45
|
+
|
46
|
+
def payload_or_fallback(&) = yield
|
47
|
+
def and_then(&) = self
|
48
|
+
|
49
|
+
def on_error(&)
|
50
|
+
yield(error)
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect = "#<data #{self.class.name} error=#{error.inspect}>"
|
55
|
+
def pretty_print(pp) = pp.text "#<data #{self.class.name} error=#{error.inspect}>"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def Success(payload = nil) = SimpleResult::Success.new(payload) # rubocop:disable Naming/MethodName -- These are helpers methods defined specifically like this to look similar with the classes
|
60
|
+
def Failure(error = nil) = SimpleResult::Failure.new(error:) # rubocop:disable Naming/MethodName -- These are helpers methods defined specifically like this to look similar with the classes
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple-result
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lucian Ghinda
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: zeitwerk
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '2.6'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '2.6'
|
26
|
+
description: A one file less than 100LOC, idiomatic Ruby implementationof a response
|
27
|
+
monad for handling success and failure states
|
28
|
+
email:
|
29
|
+
- lucian@shortruby.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- README.md
|
35
|
+
- lib/simple_result.rb
|
36
|
+
- lib/simple_result/version.rb
|
37
|
+
homepage: https://github.com/lucianghinda/simple_result
|
38
|
+
licenses:
|
39
|
+
- MIT
|
40
|
+
metadata:
|
41
|
+
homepage_uri: https://github.com/lucianghinda/simple_result
|
42
|
+
source_code_uri: https://github.com/lucianghinda/simple-result
|
43
|
+
changelog_uri: https://github.com/lucianghinda/web-author/CHANGELOG.md
|
44
|
+
rubygems_mfa_required: 'true'
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 3.4.4
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubygems_version: 3.6.9
|
60
|
+
specification_version: 4
|
61
|
+
summary: A simple response monad implementation
|
62
|
+
test_files: []
|