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 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleResult
4
+ VERSION = '0.1.0'
5
+ end
@@ -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: []