sorbet_typed-short_circuit 1.0.2 → 1.0.3
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/.cz.yaml +2 -1
- data/CHANGELOG.md +11 -0
- data/README.md +189 -0
- data/lib/sorbet_typed/short_circuit/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ea0d37eb463742ceea59c842b41be2e3b6a4d713a345ab75d2f5951253bcbbb
|
|
4
|
+
data.tar.gz: 03a370f7e2176db0571b0c9c26f5839be331e5f6dc923dca8275ed5ae30f6ced
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: abcea411c26737957a5a1574127fc69dccd5f7ead0628adf19056d4b5f1c70fa5df21fe58870fcdfd81f8172bb6b6a2090a967b10f5c8adf1b90be57545857e2
|
|
7
|
+
data.tar.gz: ce68d1218e72944b9b4bec1563d02f5e412e2bd6bf390c50763d5a119cf8a0c2290892bb3a38e7c65dc72bd4d18b9fb281f22a824522e4757a9280a146eb54e0
|
data/.cz.yaml
CHANGED
|
@@ -10,11 +10,12 @@ commitizen:
|
|
|
10
10
|
name: cz_conventional_commits
|
|
11
11
|
pre_bump_hooks:
|
|
12
12
|
- mise x -- bundle install
|
|
13
|
+
- mise x -- bun install
|
|
13
14
|
- mise run update
|
|
14
15
|
- mise run format
|
|
15
16
|
tag_format: v$version
|
|
16
17
|
update_changelog_on_bump: true
|
|
17
|
-
version: 1.0.
|
|
18
|
+
version: 1.0.3
|
|
18
19
|
version_files:
|
|
19
20
|
- lib/sorbet_typed/short_circuit/version.rb
|
|
20
21
|
version_scheme: semver2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## v1.0.3 (2025-11-18)
|
|
2
|
+
|
|
3
|
+
### Fix
|
|
4
|
+
|
|
5
|
+
- **deps**: update sorbet to v0.6.12778
|
|
6
|
+
- **deps**: update sorbet to v0.6.12768
|
|
7
|
+
- **deps**: update sorbet to v0.6.12765
|
|
8
|
+
- **deps**: update sorbet to v0.6.12753
|
|
9
|
+
- **deps**: update dependency markdownlint-cli2 to v0.19.0
|
|
10
|
+
- **deps**: update dependency cspell to v9.3.1
|
|
11
|
+
|
|
1
12
|
## v1.0.2 (2025-11-10)
|
|
2
13
|
|
|
3
14
|
### Fix
|
data/README.md
CHANGED
|
@@ -1,3 +1,192 @@
|
|
|
1
1
|
# SorbetTyped::ShortCircuit
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/sorbet_typed-short_circuit)
|
|
4
|
+
|
|
5
|
+
Fully sorbet typed implementation of a fast-fail pattern based on signaling. It allows to chain multiple step-methods
|
|
6
|
+
which each could fail and abort the whole process while keeping return values flexible to communicate eventual errors or
|
|
7
|
+
further use some result. It should prevent `if...else` patterns after every part of a bigger process to ensure a step
|
|
8
|
+
worked or bubble up failure message if not.
|
|
9
|
+
|
|
10
|
+
Using this, you can break down more complex processes into smaller steps and make methods smaller and more readable
|
|
11
|
+
while keeping full type safety.
|
|
12
|
+
|
|
13
|
+
Inspiration for this comes from [dry-monads](https://dry-rb.org/gems/dry-monads/). The focus lies on strict type-safety
|
|
14
|
+
while iteratively calling different steps of a bigger process and not relying on any instance-state or custom DSL.
|
|
15
|
+
|
|
16
|
+
- [Installation](#installation)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [Basic Usage](#basic-usage)
|
|
19
|
+
- [Step Method Signature](#step-method-signature)
|
|
20
|
+
- [Signaling a failure](#signaling-a-failure)
|
|
21
|
+
- [Circuit Interface](#circuit-interface)
|
|
22
|
+
- [Passing Circuit Breakers as Method Parameters](#passing-circuit-breakers-as-method-parameters)
|
|
23
|
+
- [Nesting Circuits](#nesting-circuits)
|
|
24
|
+
- [Development](#development)
|
|
25
|
+
- [Contributing](#contributing)
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bundle add sorbet_typed-short_circuit
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
gem install sorbet_typed-short_circuit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
The following examples demonstrate the basics of how you can use this gem. For a detailed look at what you can and
|
|
44
|
+
cannot do, you could look at [the type checker specs](spec/typechecking/sorbet_typed/short_circuit_spec.rb) and
|
|
45
|
+
[usage specs](lib/sorbet_typed/short_circuit_spec.rb).
|
|
46
|
+
|
|
47
|
+
### Basic Usage
|
|
48
|
+
|
|
49
|
+
Initialize a new circuit with defined Success- and Failure-Type and call the run method. This method takes a block which
|
|
50
|
+
gets a `CircuitBreaker` as parameter. The circuit breaker stops the block execution early, if it receives a `Shorted`
|
|
51
|
+
signal.
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
circuit = SorbetTyped::ShortCircuit[
|
|
55
|
+
Symbol, # this will be the return type, when the block executes successfully
|
|
56
|
+
T::Array[String] # this will be the type of the Signal-Payload, when the block exits early
|
|
57
|
+
].new
|
|
58
|
+
|
|
59
|
+
result = circuit.run do |circuit_breaker|
|
|
60
|
+
foo = circuit_breaker.(get_foo) # breaks the block, if `get_foo` returns a shorted signal. Otherwise returns a predictable success type.
|
|
61
|
+
bar = circuit_breaker.(do_bar(foo:)) # breaks the block, if `do_bar` returns a shorted signal.
|
|
62
|
+
|
|
63
|
+
transform_baz(bar:) # must return the success type or a shorted signal. CircuitBreaker not needed, because it is the last block statement
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
T.reveal_type(result) # => T.any(Symbol, SorbetTyped::ShortCircuit::Signals::Shorted[T::Array[String]])
|
|
67
|
+
|
|
68
|
+
if result.is_a? SorbetTyped::ShortCircuit::Signals::Shorted
|
|
69
|
+
T.reveal_type(result.payload) # => T::Array[String]
|
|
70
|
+
else
|
|
71
|
+
T.reveal_type(result) # => Symbol
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Step Method Signature
|
|
76
|
+
|
|
77
|
+
For step methods to be useful for the circuit breaker, they should return a union of any type you want to return and a
|
|
78
|
+
shorted signal corresponding to the circuits failure type. Passing a signal type with unfitting payload type, will
|
|
79
|
+
result in a type error.
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
extend T::Sig
|
|
83
|
+
|
|
84
|
+
sig { returns(T.any(String, SorbetTyped::ShortCircuit::Signals::Shorted[T::Array[String]])) }
|
|
85
|
+
def get_foo
|
|
86
|
+
# ...
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Signaling a failure
|
|
91
|
+
|
|
92
|
+
You can generate a shorting signal using the `::short` method of the `SorbetTyped::ShortCircuit` class. Passing this
|
|
93
|
+
signal to a circuit breaker will breaker the corresponding circuit. The payload type of the signal will be automatically
|
|
94
|
+
inferred from the passed parameter.
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
extend T::Sig
|
|
98
|
+
|
|
99
|
+
sig { params(foo: String).returns(T.any(String, SorbetTyped::ShortCircuit::Signals::Shorted[T::Array[String]])) }
|
|
100
|
+
def do_bar(foo:)
|
|
101
|
+
if foo == 'foo'
|
|
102
|
+
return 'bar'
|
|
103
|
+
else
|
|
104
|
+
SorbetTyped::ShortCircuit.short(['Unexpected value for foo:', foo])
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Circuit Interface
|
|
110
|
+
|
|
111
|
+
After initializing a circuit, it will respond to the following methods:
|
|
112
|
+
|
|
113
|
+
- `#run` => Run a number of methods and use the circuit breaker to exit early. Returns either a successful value or the
|
|
114
|
+
shorted signal
|
|
115
|
+
- `#run_without_signal` => Like run, but already extracts the signal payload on failure. Useful, if you don't want to
|
|
116
|
+
use the return value for flow-control. E.g. when success and failure use the same type, like a custom result-object
|
|
117
|
+
that gets filled with different information depending on the outcome.
|
|
118
|
+
- `#last_run_short_circuited?` => `true` or `false`, depending on the outcome of the last run. Not really useful with
|
|
119
|
+
sorbet, as you cannot use it for flow control. But it's there.
|
|
120
|
+
|
|
121
|
+
### Passing Circuit Breakers as Method Parameters
|
|
122
|
+
|
|
123
|
+
Because the gems implementation relies heavily on generics and uses a raise/rescue pattern to implement the early exit,
|
|
124
|
+
there are few edge-cases where directly using internal classes like the `CircuitBreaker` would allow to return different
|
|
125
|
+
types than originally defined. This would introduce bugs, as static typing is circumvented and runtime type checks catch
|
|
126
|
+
them pretty late, as generics get removed from runtime checks.
|
|
127
|
+
|
|
128
|
+
To circumvent this, the custom error and the circuit breaker class are private constants, making accidental access
|
|
129
|
+
harder.
|
|
130
|
+
|
|
131
|
+
If you want to build some abstractions around the short circuit class, that need the circuit breaker to be passed around
|
|
132
|
+
to some method, you cannot use its private class within the type signature. Instead, use
|
|
133
|
+
`SorbetTyped::ShortCircuit::CircuitBreakerType`, which is a module without any behavior, but the circuit breaker
|
|
134
|
+
fulfills it and can be passed as parameter to methods accepting this type.
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
extend T::Sig
|
|
138
|
+
|
|
139
|
+
sig { params(circuit_breaker: SorbetTyped::ShortCircuit::CircuitBreakerType[String]).returns(T.any(NilClass, SorbetTyped::ShortCircuit::Signals::Shorted[String])) }
|
|
140
|
+
def foo(circuit_breaker:)
|
|
141
|
+
circuit_breaker.([SorbetTyped::ShortCircuit.short('shorted'), nil].sample)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
SorbetTyped::ShortCircuit[NilClass, String].new.run do |circuit_breaker|
|
|
145
|
+
foo(circuit_breaker:)
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Nesting Circuits
|
|
150
|
+
|
|
151
|
+
It is possible to nest circuits. Especially when splitting your logic over multiple methods, each of them might use a
|
|
152
|
+
circuit themselves to portray a part of the process. When doing this, each circuit creates its own circuit breaker,
|
|
153
|
+
which can only break its original circuit. This is to prevent accidentally breaking inner circuits using outer circuit
|
|
154
|
+
breakers, leading to incorrect types being returned.
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
SorbetTyped::ShortCircuit[String, Symbol].new.run do |outer_circuit_breaker|
|
|
158
|
+
SorbetTyped::ShortCircuit[Integer, NilClass].new.run do |inner_circuit_breaker|
|
|
159
|
+
outer_circuit_breaker.(SorbetTyped::ShortCircuit.short(:failure)) # shorted signal must fulfill failure type of the outer circuit
|
|
160
|
+
|
|
161
|
+
# this will never be reached, as the breaker always gets a shorted signal.
|
|
162
|
+
# Sorbet detects this as unreachable
|
|
163
|
+
'foo'
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# this will never be reached, as the outer breaker used within the inner
|
|
167
|
+
# circuit will still break the outer circuit. But sorbet cannot statically
|
|
168
|
+
# detect this as unreachable.
|
|
169
|
+
'bar'
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
The project uses [mise-en-place](https://mise.jdx.dev/) as development tool.
|
|
176
|
+
|
|
177
|
+
After checking out the repo, run `mise run setup` to install dependencies. Then, run `mise test` to run the tests. You
|
|
178
|
+
can also run `mise task ls` for a list of available tasks.
|
|
179
|
+
|
|
180
|
+
RSpec is used as test suite. Spec files can and should be placed right beside their associated class files.
|
|
181
|
+
|
|
182
|
+
Use the command `mise run console` (or `./bin/console`) to start an interactive ruby console session with the gem
|
|
183
|
+
already loaded.
|
|
184
|
+
|
|
185
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, run
|
|
186
|
+
`mise run deploy`, which will update the version number, create a release commit, tag it and push the built gem to
|
|
187
|
+
[rubygems.org](https://rubygems.org).
|
|
188
|
+
|
|
189
|
+
## Contributing
|
|
190
|
+
|
|
191
|
+
Bug reports and merge requests are welcome on GitLab at
|
|
192
|
+
[gitlab.com/sorbet_typed/short_circuit](https://gitlab.com/sorbet_typed/short_circuit).
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sorbet_typed-short_circuit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Richard Kramer
|
|
@@ -18,7 +18,7 @@ dependencies:
|
|
|
18
18
|
version: '0.6'
|
|
19
19
|
- - "<="
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
|
-
version: 0.6.
|
|
21
|
+
version: 0.6.12778
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -28,7 +28,7 @@ dependencies:
|
|
|
28
28
|
version: '0.6'
|
|
29
29
|
- - "<="
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
|
-
version: 0.6.
|
|
31
|
+
version: 0.6.12778
|
|
32
32
|
description: ''
|
|
33
33
|
email:
|
|
34
34
|
- mail@richardkramer.de
|