teckel 0.5.0 → 0.6.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/CHANGELOG.md +6 -0
- data/lib/teckel/chain/config.rb +3 -3
- data/lib/teckel/operation.rb +19 -9
- data/lib/teckel/operation/config.rb +2 -0
- data/lib/teckel/operation/runner.rb +26 -21
- data/lib/teckel/version.rb +1 -1
- data/spec/chain/default_settings_spec.rb +18 -18
- data/spec/chain/inheritance_spec.rb +44 -44
- data/spec/chain/none_input_spec.rb +16 -16
- data/spec/chain/results_spec.rb +28 -28
- data/spec/chain_around_hook_spec.rb +54 -54
- data/spec/chain_spec.rb +46 -46
- data/spec/operation/contract_trace_spec.rb +116 -0
- data/spec/operation/default_settings_spec.rb +12 -12
- data/spec/operation/inheritance_spec.rb +38 -38
- data/spec/operation/results_spec.rb +67 -67
- data/spec/operation_spec.rb +218 -220
- data/spec/rb27/pattern_matching_spec.rb +2 -2
- data/spec/result_spec.rb +8 -6
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d55dd7fb782cfdcb3cc960423e46ab2ce8b49c9396273f10749105883d55ed6f
|
4
|
+
data.tar.gz: e3b7428d98daeb42cc086b4a5cd5081b5c3b4ca90c14b0cf3f4cd9576243d0e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0148d426e8e7124a08a3a7dd3c42487cac5944d17ba9cabb98a8c378a86a5d9813b7b62c483d2203809644528d2e6b6cc4de51476aa67261b1405f3f41576613'
|
7
|
+
data.tar.gz: 6792ad4806b977d21d0ea621e311306cb13c239d8604e35e61937e79ce4f5488cbb410aa6ae22e9e12e48b61529f97e924aab22e100717808885c0b0c76980fd
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 0.6.0
|
4
|
+
|
5
|
+
- Breaking: Operations return values will be ignored. [GH-21]
|
6
|
+
* You'll need to use `success!` or `failure!`
|
7
|
+
* `success!` and `failure!` are now implemented on the `Runner`, which makes it easier to change their behavior (including the one above).
|
8
|
+
|
3
9
|
## 0.5.0
|
4
10
|
|
5
11
|
- Fix: calling chain with settings and no input [GH-14]
|
data/lib/teckel/chain/config.rb
CHANGED
@@ -41,7 +41,7 @@ module Teckel
|
|
41
41
|
# output input
|
42
42
|
#
|
43
43
|
# def call(hsh)
|
44
|
-
# hsh
|
44
|
+
# success!(hsh)
|
45
45
|
# end
|
46
46
|
# end
|
47
47
|
#
|
@@ -139,7 +139,7 @@ module Teckel
|
|
139
139
|
# error none
|
140
140
|
#
|
141
141
|
# def call(_)
|
142
|
-
# settings.to_h
|
142
|
+
# success!(settings.to_h)
|
143
143
|
# end
|
144
144
|
# end
|
145
145
|
#
|
@@ -216,7 +216,7 @@ module Teckel
|
|
216
216
|
|
217
217
|
# @!visibility private
|
218
218
|
def inherited(subclass)
|
219
|
-
dup_config(subclass)
|
219
|
+
super dup_config(subclass)
|
220
220
|
end
|
221
221
|
|
222
222
|
# @!visibility private
|
data/lib/teckel/operation.rb
CHANGED
@@ -140,9 +140,6 @@ module Teckel
|
|
140
140
|
#
|
141
141
|
# # when using `output none`:
|
142
142
|
# # `success!` works, but `success!("data")` raises an error
|
143
|
-
# # same thing when using simple return values as success:
|
144
|
-
# # take care to not return anything
|
145
|
-
# nil
|
146
143
|
# end
|
147
144
|
# end
|
148
145
|
#
|
@@ -153,26 +150,39 @@ module Teckel
|
|
153
150
|
end
|
154
151
|
|
155
152
|
module InstanceMethods
|
153
|
+
# @!method call(input)
|
154
|
+
# @abstract
|
155
|
+
# @see Operation
|
156
|
+
# @see ClassMethods#call
|
157
|
+
#
|
158
|
+
# The entry point for your operation. It needs to always accept an input value, even when
|
159
|
+
# using +input none+.
|
160
|
+
# If your Operation expects to generate success or failure outputs, you need to use either
|
161
|
+
# {.success!} or {.fail!} respectively. Simple return values will get ignored by default. See
|
162
|
+
# {Teckel::Operation::Config#runner} and {Teckel::Operation::Runner} on how to overwrite.
|
163
|
+
|
156
164
|
# @!attribute [r] settings()
|
157
165
|
# @return [Class,nil] When executed with settings, an instance of the
|
158
166
|
# configured {.settings} class. Otherwise +nil+
|
159
167
|
# @see ClassMethods#settings
|
160
168
|
# @!visibility public
|
161
169
|
|
162
|
-
#
|
170
|
+
# Delegates to the configured Runner.
|
171
|
+
# The default behavior is to halt any further execution with a output value.
|
163
172
|
#
|
164
|
-
# @
|
173
|
+
# @see Teckel::Operation::Runner#success!
|
165
174
|
# @!visibility protected
|
166
175
|
def success!(*args)
|
167
|
-
|
176
|
+
runner.success!(*args)
|
168
177
|
end
|
169
178
|
|
170
|
-
#
|
179
|
+
# Delegates to the configured Runner.
|
180
|
+
# The default behavior is to halt any further execution with an error value.
|
171
181
|
#
|
172
|
-
# @
|
182
|
+
# @see Teckel::Operation::Runner#fail!
|
173
183
|
# @!visibility protected
|
174
184
|
def fail!(*args)
|
175
|
-
|
185
|
+
runner.fail!(*args)
|
176
186
|
end
|
177
187
|
end
|
178
188
|
|
@@ -362,12 +362,14 @@ module Teckel
|
|
362
362
|
# @!visibility private
|
363
363
|
def inherited(subclass)
|
364
364
|
subclass.instance_variable_set(:@config, @config.dup)
|
365
|
+
super subclass
|
365
366
|
end
|
366
367
|
|
367
368
|
# @!visibility private
|
368
369
|
def self.extended(base)
|
369
370
|
base.instance_exec do
|
370
371
|
@config = Teckel::Config.new
|
372
|
+
attr_accessor :runner
|
371
373
|
attr_accessor :settings
|
372
374
|
end
|
373
375
|
end
|
@@ -16,14 +16,14 @@ module Teckel
|
|
16
16
|
attr_reader :operation, :settings
|
17
17
|
|
18
18
|
def call(input = nil)
|
19
|
-
|
20
|
-
simple_return = UNDEFINED
|
19
|
+
catch(:failure) do
|
21
20
|
out = catch(:success) do
|
22
|
-
|
21
|
+
run operation.input_constructor.call(input)
|
22
|
+
return nil # :sic!: return values need to go through +success!+
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
|
+
return out
|
25
26
|
end
|
26
|
-
build_error(*err)
|
27
27
|
end
|
28
28
|
|
29
29
|
# This is just here to raise a meaningful error.
|
@@ -32,19 +32,11 @@ module Teckel
|
|
32
32
|
raise Teckel::Error, "Operation already has settings assigned."
|
33
33
|
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
op.call(input)
|
41
|
-
end
|
42
|
-
|
43
|
-
def build_input(input)
|
44
|
-
operation.input_constructor.call(input)
|
45
|
-
end
|
46
|
-
|
47
|
-
def build_output(*args)
|
35
|
+
# Halt any further execution with a output value
|
36
|
+
#
|
37
|
+
# @return a thing matching your {Teckel::Operation::Config#output output} definition
|
38
|
+
# @!visibility protected
|
39
|
+
def success!(*args)
|
48
40
|
value =
|
49
41
|
if args.size == 1 && operation.output === args.first # rubocop:disable Style/CaseEquality
|
50
42
|
args.first
|
@@ -52,10 +44,14 @@ module Teckel
|
|
52
44
|
operation.output_constructor.call(*args)
|
53
45
|
end
|
54
46
|
|
55
|
-
operation.result_constructor.call(value, true)
|
47
|
+
throw :success, operation.result_constructor.call(value, true)
|
56
48
|
end
|
57
49
|
|
58
|
-
|
50
|
+
# Halt any further execution with an error value
|
51
|
+
#
|
52
|
+
# @return a thing matching your {Teckel::Operation::Config#error error} definition
|
53
|
+
# @!visibility protected
|
54
|
+
def fail!(*args)
|
59
55
|
value =
|
60
56
|
if args.size == 1 && operation.error === args.first # rubocop:disable Style/CaseEquality
|
61
57
|
args.first
|
@@ -63,7 +59,16 @@ module Teckel
|
|
63
59
|
operation.error_constructor.call(*args)
|
64
60
|
end
|
65
61
|
|
66
|
-
operation.result_constructor.call(value, false)
|
62
|
+
throw :failure, operation.result_constructor.call(value, false)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def run(input)
|
68
|
+
op = @operation.new
|
69
|
+
op.runner = self
|
70
|
+
op.settings = settings if settings != UNDEFINED
|
71
|
+
op.call(input)
|
67
72
|
end
|
68
73
|
end
|
69
74
|
end
|
data/lib/teckel/version.rb
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
result!
|
3
|
+
module TeckelChainDefaultSettingsTest
|
4
|
+
class MyOperation
|
5
|
+
include Teckel::Operation
|
6
|
+
result!
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
settings Struct.new(:say, :other)
|
9
|
+
settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) } # ruby 2.4 way for `keyword_init: true`
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
input none
|
12
|
+
output Hash
|
13
|
+
error none
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
15
|
+
def call(_)
|
16
|
+
success! settings.to_h
|
19
17
|
end
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
20
|
+
class Chain
|
21
|
+
include Teckel::Chain
|
23
22
|
|
24
|
-
|
23
|
+
default_settings!(a: { say: "Chain Default" })
|
25
24
|
|
26
|
-
|
27
|
-
end
|
25
|
+
step :a, MyOperation
|
28
26
|
end
|
27
|
+
end
|
29
28
|
|
29
|
+
RSpec.describe Teckel::Chain do
|
30
30
|
specify "call chain without settings, uses default settings" do
|
31
31
|
result = TeckelChainDefaultSettingsTest::Chain.call
|
32
32
|
expect(result.success).to eq(say: "Chain Default", other: nil)
|
@@ -3,72 +3,72 @@
|
|
3
3
|
require 'support/dry_base'
|
4
4
|
require 'support/fake_models'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
LOG = [] # rubocop:disable Style/MutableConstant
|
9
|
-
|
10
|
-
class LoggingChain
|
11
|
-
include Teckel::Chain
|
6
|
+
module TeckelChainDefaultsViaBaseClass
|
7
|
+
LOG = [] # rubocop:disable Style/MutableConstant
|
12
8
|
|
13
|
-
|
14
|
-
|
15
|
-
result = nil
|
16
|
-
LOG << Benchmark.measure { result = chain.call(input) }
|
17
|
-
result
|
18
|
-
end
|
9
|
+
class LoggingChain
|
10
|
+
include Teckel::Chain
|
19
11
|
|
20
|
-
|
12
|
+
around do |chain, input|
|
13
|
+
require 'benchmark'
|
14
|
+
result = nil
|
15
|
+
LOG << Benchmark.measure { result = chain.call(input) }
|
16
|
+
result
|
21
17
|
end
|
22
18
|
|
23
|
-
|
24
|
-
|
19
|
+
freeze
|
20
|
+
end
|
25
21
|
|
26
|
-
|
22
|
+
class OperationA
|
23
|
+
include Teckel::Operation
|
27
24
|
|
28
|
-
|
29
|
-
output Types::Integer
|
30
|
-
error none
|
25
|
+
result!
|
31
26
|
|
32
|
-
|
33
|
-
|
34
|
-
|
27
|
+
input none
|
28
|
+
output Types::Integer
|
29
|
+
error none
|
35
30
|
|
36
|
-
|
31
|
+
def call(_)
|
32
|
+
success! rand(1000)
|
37
33
|
end
|
38
34
|
|
39
|
-
|
40
|
-
|
35
|
+
finalize!
|
36
|
+
end
|
41
37
|
|
42
|
-
|
38
|
+
class OperationB
|
39
|
+
include Teckel::Operation
|
43
40
|
|
44
|
-
|
45
|
-
output Types::String
|
46
|
-
error none
|
41
|
+
result!
|
47
42
|
|
48
|
-
|
49
|
-
|
50
|
-
|
43
|
+
input none
|
44
|
+
output Types::String
|
45
|
+
error none
|
51
46
|
|
52
|
-
|
47
|
+
def call(_)
|
48
|
+
success! ("a".."z").to_a.sample
|
53
49
|
end
|
54
50
|
|
55
|
-
|
56
|
-
|
51
|
+
finalize!
|
52
|
+
end
|
57
53
|
|
58
|
-
|
59
|
-
|
54
|
+
class ChainA < LoggingChain
|
55
|
+
step :roll, OperationA
|
60
56
|
|
61
|
-
|
62
|
-
|
57
|
+
finalize!
|
58
|
+
end
|
63
59
|
|
64
|
-
|
65
|
-
|
60
|
+
class ChainB < LoggingChain
|
61
|
+
step :say, OperationB
|
66
62
|
|
67
|
-
|
68
|
-
|
69
|
-
|
63
|
+
finalize!
|
64
|
+
end
|
65
|
+
|
66
|
+
class ChainC < ChainB
|
67
|
+
finalize!
|
70
68
|
end
|
69
|
+
end
|
71
70
|
|
71
|
+
RSpec.describe Teckel::Chain do
|
72
72
|
before do
|
73
73
|
TeckelChainDefaultsViaBaseClass::LOG.clear
|
74
74
|
end
|
@@ -1,29 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
result!
|
3
|
+
module TeckelChainNoneInputTest
|
4
|
+
class MyOperation
|
5
|
+
include Teckel::Operation
|
6
|
+
result!
|
8
7
|
|
9
|
-
|
8
|
+
settings Struct.new(:say)
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
input none
|
11
|
+
output String
|
12
|
+
error none
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
end
|
14
|
+
def call(_)
|
15
|
+
success!(settings&.say || "Called")
|
18
16
|
end
|
17
|
+
end
|
19
18
|
|
20
|
-
|
21
|
-
|
19
|
+
class Chain
|
20
|
+
include Teckel::Chain
|
22
21
|
|
23
|
-
|
24
|
-
end
|
22
|
+
step :a, MyOperation
|
25
23
|
end
|
24
|
+
end
|
26
25
|
|
26
|
+
RSpec.describe Teckel::Chain do
|
27
27
|
specify "call chain without input value" do
|
28
28
|
result = TeckelChainNoneInputTest::Chain.call
|
29
29
|
expect(result.success).to eq("Called")
|
data/spec/chain/results_spec.rb
CHANGED
@@ -3,47 +3,47 @@
|
|
3
3
|
require 'support/dry_base'
|
4
4
|
require 'support/fake_models'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
include ::Teckel::Operation
|
6
|
+
module TeckelChainResultTest
|
7
|
+
class Message
|
8
|
+
include ::Teckel::Operation
|
10
9
|
|
11
|
-
|
10
|
+
result!
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
input Types::Hash.schema(message: Types::String)
|
13
|
+
error none
|
14
|
+
output Types::String
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
end
|
16
|
+
def call(input)
|
17
|
+
success! input[:message].upcase
|
20
18
|
end
|
19
|
+
end
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
step :message, Message
|
21
|
+
class Chain
|
22
|
+
include Teckel::Chain
|
26
23
|
|
27
|
-
|
28
|
-
def initialize(value, success, step, opts = {})
|
29
|
-
super(value, success)
|
30
|
-
@step = step
|
31
|
-
@opts = opts
|
32
|
-
end
|
24
|
+
step :message, Message
|
33
25
|
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
class Result < Teckel::Operation::Result
|
27
|
+
def initialize(value, success, step, opts = {})
|
28
|
+
super(value, success)
|
29
|
+
@step = step
|
30
|
+
@opts = opts
|
31
|
+
end
|
37
32
|
|
38
|
-
|
33
|
+
class << self
|
34
|
+
alias :[] :new # Alias the default constructor to :new
|
39
35
|
end
|
40
36
|
|
41
|
-
|
42
|
-
result.new(value, success, step, time: Time.now.to_i)
|
43
|
-
}
|
37
|
+
attr_reader :opts, :step
|
44
38
|
end
|
39
|
+
|
40
|
+
result_constructor ->(value, success, step) {
|
41
|
+
result.new(value, success, step, time: Time.now.to_i)
|
42
|
+
}
|
45
43
|
end
|
44
|
+
end
|
46
45
|
|
46
|
+
RSpec.describe Teckel::Chain do
|
47
47
|
specify do
|
48
48
|
result = TeckelChainResultTest::Chain.call(message: "Hello World!")
|
49
49
|
expect(result).to be_successful
|