teckel 0.6.0 → 0.7.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 +9 -0
- data/lib/teckel.rb +0 -1
- data/lib/teckel/chain/config.rb +32 -3
- data/lib/teckel/operation/runner.rb +17 -18
- data/lib/teckel/version.rb +1 -1
- data/spec/{chain_around_hook_spec.rb → chain/around_hook_spec.rb} +0 -0
- data/spec/doctest_helper.rb +1 -0
- data/spec/operation/fail_on_input_spec.rb +103 -0
- data/spec/operation/result_spec.rb +33 -12
- data/spec/operation/results_spec.rb +1 -1
- data/spec/operation_spec.rb +3 -3
- data/spec/result_spec.rb +4 -4
- data/spec/spec_helper.rb +3 -0
- metadata +12 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df9c73577c9c93fbaaf2e7661a07bdc3b1b0397832a62a8edbe4eea846c4c029
|
4
|
+
data.tar.gz: 037041bf0eb6ae8bc41e354772d3ce1d04002a35ecccc0b1ecefa33a85845c3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f6f601e816c972aed8e511b05fdbc418a48aab10c2ca246d108661cc25a8d514a41944b2e01b9294757a2a717696491f20f601f5f60e53faa45651aec48bc7f
|
7
|
+
data.tar.gz: 68bc46e5ed21cf5e639ee1fc00265b278f9062f42de9d179a07559862990a078df9dcb2ef26956eb629cf08e71409b9b05bd363a11930cb64ecdee533d63d2ea
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 0.7.0
|
4
|
+
|
5
|
+
- Breaking: `Teckel::Chain` will not be required by default. require manually if needed `require "teckel/chain"` [GH-24]
|
6
|
+
- Breaking: Internally, `Teckel::Operation::Runner` instead of `:success` and `:failure` now only uses `:halt` as it's throw-catch symbol. [GH-26]
|
7
|
+
- Add: Using the default `Teckel::Operation::Runner`, `input_constructor` and `result_constructor` will be executed
|
8
|
+
within the context of the operation instance. This allows for `input_constructor` to call `fail!` and `success!`
|
9
|
+
without ever `call`ing the operation. [GH-26]
|
10
|
+
|
11
|
+
|
3
12
|
## 0.6.0
|
4
13
|
|
5
14
|
- Breaking: Operations return values will be ignored. [GH-21]
|
data/lib/teckel.rb
CHANGED
data/lib/teckel/chain/config.rb
CHANGED
@@ -103,13 +103,42 @@ module Teckel
|
|
103
103
|
# @example
|
104
104
|
# class MyOperation
|
105
105
|
# include Teckel::Operation
|
106
|
+
# result!
|
107
|
+
#
|
108
|
+
# settings Struct.new(:say, :other)
|
109
|
+
# settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
|
110
|
+
#
|
111
|
+
# input none
|
112
|
+
# output Hash
|
113
|
+
# error none
|
114
|
+
#
|
115
|
+
# def call(_)
|
116
|
+
# success!(settings.to_h)
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# class Chain
|
121
|
+
# include Teckel::Chain
|
106
122
|
#
|
107
123
|
# class Result < Teckel::Operation::Result
|
108
|
-
# def initialize(value, success, step,
|
124
|
+
# def initialize(value, success, step, opts = {})
|
125
|
+
# super(value, success)
|
126
|
+
# @step = step
|
127
|
+
# @opts = opts
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# class << self
|
131
|
+
# alias :[] :new # Alias the default constructor to :new
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# attr_reader :opts, :step
|
109
135
|
# end
|
110
136
|
#
|
111
|
-
#
|
112
|
-
#
|
137
|
+
# result_constructor ->(value, success, step) {
|
138
|
+
# result.new(value, success, step, time: Time.now.to_i)
|
139
|
+
# }
|
140
|
+
#
|
141
|
+
# step :a, MyOperation
|
113
142
|
# end
|
114
143
|
def result_constructor(sym_or_proc = nil)
|
115
144
|
constructor = build_constructor(result, sym_or_proc) unless sym_or_proc.nil?
|
@@ -16,16 +16,24 @@ module Teckel
|
|
16
16
|
attr_reader :operation, :settings
|
17
17
|
|
18
18
|
def call(input = nil)
|
19
|
-
catch(:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
return out
|
19
|
+
catch(:halt) do
|
20
|
+
op = instance
|
21
|
+
op_input = op.instance_exec(input, &operation.input_constructor)
|
22
|
+
op.call(op_input)
|
23
|
+
nil # return values need to go through +success!+ or +fail!+
|
26
24
|
end
|
27
25
|
end
|
28
26
|
|
27
|
+
def instance
|
28
|
+
return @instance if instance_variable_defined?(:@instance)
|
29
|
+
|
30
|
+
op = operation.new
|
31
|
+
op.runner = self
|
32
|
+
op.settings = settings if settings != UNDEFINED
|
33
|
+
|
34
|
+
@instance = op
|
35
|
+
end
|
36
|
+
|
29
37
|
# This is just here to raise a meaningful error.
|
30
38
|
# @!visibility private
|
31
39
|
def with(*)
|
@@ -44,7 +52,7 @@ module Teckel
|
|
44
52
|
operation.output_constructor.call(*args)
|
45
53
|
end
|
46
54
|
|
47
|
-
throw :
|
55
|
+
throw :halt, instance.instance_exec(value, true, &operation.result_constructor)
|
48
56
|
end
|
49
57
|
|
50
58
|
# Halt any further execution with an error value
|
@@ -59,16 +67,7 @@ module Teckel
|
|
59
67
|
operation.error_constructor.call(*args)
|
60
68
|
end
|
61
69
|
|
62
|
-
throw :
|
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)
|
70
|
+
throw :halt, instance.instance_exec(value, false, &operation.result_constructor)
|
72
71
|
end
|
73
72
|
end
|
74
73
|
end
|
data/lib/teckel/version.rb
CHANGED
File without changes
|
data/spec/doctest_helper.rb
CHANGED
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/validation"
|
4
|
+
require 'support/dry_base'
|
5
|
+
require 'support/fake_models'
|
6
|
+
|
7
|
+
module TeckelOperationFailOnOInput
|
8
|
+
class NewUserContract < Dry::Validation::Contract
|
9
|
+
schema do
|
10
|
+
required(:name).filled(:string)
|
11
|
+
required(:age).value(:integer)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class CreateUser
|
16
|
+
include Teckel::Operation
|
17
|
+
|
18
|
+
result!
|
19
|
+
|
20
|
+
input(NewUserContract.new)
|
21
|
+
input_constructor(->(input){
|
22
|
+
result = self.class.input.call(input)
|
23
|
+
if result.success?
|
24
|
+
result.to_h
|
25
|
+
else
|
26
|
+
fail!(message: "Input data validation failed", errors: [result.errors.to_h])
|
27
|
+
end
|
28
|
+
})
|
29
|
+
|
30
|
+
output Types.Instance(User)
|
31
|
+
error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
|
32
|
+
|
33
|
+
def call(input)
|
34
|
+
user = User.new(name: input[:name], age: input[:age])
|
35
|
+
if user.save
|
36
|
+
success! user
|
37
|
+
else
|
38
|
+
fail!(message: "Could not save User", errors: user.errors)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
finalize!
|
43
|
+
end
|
44
|
+
|
45
|
+
class CreateUserIncorrectFailure
|
46
|
+
include Teckel::Operation
|
47
|
+
|
48
|
+
result!
|
49
|
+
|
50
|
+
input(->(input) { input }) # NoOp
|
51
|
+
input_constructor(->(_input) {
|
52
|
+
fail!("Input data validation failed")
|
53
|
+
})
|
54
|
+
|
55
|
+
output none
|
56
|
+
error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
|
57
|
+
|
58
|
+
def call(_); end
|
59
|
+
finalize!
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
RSpec.describe Teckel::Operation do
|
64
|
+
specify "successs" do
|
65
|
+
result = TeckelOperationFailOnOInput::CreateUser.call(name: "Bob", age: 23)
|
66
|
+
expect(result).to be_successful
|
67
|
+
expect(result.success).to be_a(User)
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "failing in input_constructor" do
|
71
|
+
let(:failure_input) do
|
72
|
+
{ name: "", age: "incorrect type" }
|
73
|
+
end
|
74
|
+
|
75
|
+
it "returns the failure thrown in input_constructor" do
|
76
|
+
result = TeckelOperationFailOnOInput::CreateUser.call(failure_input)
|
77
|
+
expect(result).to be_a(Teckel::Operation::Result)
|
78
|
+
expect(result).to be_failure
|
79
|
+
expect(result.failure).to eq(
|
80
|
+
message: "Input data validation failed",
|
81
|
+
errors: [
|
82
|
+
{ name: ["must be filled"], age: ["must be an integer"] }
|
83
|
+
]
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "does not run .call" do
|
88
|
+
expect(TeckelOperationFailOnOInput::CreateUser).to receive(:new).and_wrap_original do |m, *args|
|
89
|
+
op_instance = m.call(*args)
|
90
|
+
expect(op_instance).to_not receive(:call)
|
91
|
+
op_instance
|
92
|
+
end
|
93
|
+
|
94
|
+
TeckelOperationFailOnOInput::CreateUser.call(failure_input)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
specify "thrown failure needs to conform to :error" do
|
99
|
+
expect {
|
100
|
+
TeckelOperationFailOnOInput::CreateUserIncorrectFailure.call(name: "Bob", age: 23)
|
101
|
+
}.to raise_error(Dry::Types::ConstraintError, /violates constraints/)
|
102
|
+
end
|
103
|
+
end
|
@@ -3,25 +3,46 @@
|
|
3
3
|
RSpec.describe Teckel::Operation::Result do
|
4
4
|
let(:failure_value) { "some error" }
|
5
5
|
let(:failed_result) { Teckel::Operation::Result.new(failure_value, false) }
|
6
|
+
let(:failed_nil_result) { Teckel::Operation::Result.new(failure_value, nil) }
|
6
7
|
|
7
|
-
let(:success_value) { "some
|
8
|
-
let(:successful_result) { Teckel::Operation::Result.new(
|
8
|
+
let(:success_value) { "some success" }
|
9
|
+
let(:successful_result) { Teckel::Operation::Result.new(success_value, true) }
|
10
|
+
let(:successful_1_result) { Teckel::Operation::Result.new(success_value, 1) }
|
9
11
|
|
10
|
-
it { expect(successful_result.successful?).to
|
11
|
-
it { expect(
|
12
|
+
it { expect(successful_result.successful?).to eq(true) }
|
13
|
+
it { expect(successful_1_result.successful?).to eq(true) }
|
14
|
+
it { expect(failed_result.successful?).to eq(false) }
|
15
|
+
it { expect(failed_nil_result.successful?).to eq(false) }
|
12
16
|
|
13
|
-
it { expect(successful_result.failure?).to
|
14
|
-
it { expect(
|
17
|
+
it { expect(successful_result.failure?).to eq(false) }
|
18
|
+
it { expect(successful_1_result.failure?).to eq(false) }
|
19
|
+
it { expect(failed_result.failure?).to eq(true) }
|
20
|
+
it { expect(failed_nil_result.failure?).to eq(true) }
|
15
21
|
|
16
22
|
it { expect(successful_result.value).to eq(success_value) }
|
17
23
|
it { expect(failed_result.value).to eq(failure_value) }
|
18
24
|
|
19
25
|
describe "#success" do
|
20
|
-
it
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
26
|
+
it("on successful result, returns value") {
|
27
|
+
expect(successful_result.success).to eq(success_value)
|
28
|
+
}
|
29
|
+
|
30
|
+
describe "on failed result" do
|
31
|
+
it("with no fallbacks, returns nil") {
|
32
|
+
expect(failed_result.success).to eq(nil)
|
33
|
+
}
|
34
|
+
it("with default-argument, returns default-argument") {
|
35
|
+
expect(failed_result.success("other")).to eq("other")
|
36
|
+
}
|
37
|
+
it("with block, returns block return value") {
|
38
|
+
expect(failed_result.success { |value| "Failed: #{value}" } ).to eq("Failed: some error")
|
39
|
+
}
|
40
|
+
it("with default-argument and block given, returns default-argument, skips block") {
|
41
|
+
expect { |blk|
|
42
|
+
expect(failed_result.success("default", &blk)).to_not eq("default")
|
43
|
+
}.to(yield_control)
|
44
|
+
}
|
45
|
+
end
|
25
46
|
end
|
26
47
|
|
27
48
|
describe "#failure" do
|
@@ -29,6 +50,6 @@ RSpec.describe Teckel::Operation::Result do
|
|
29
50
|
|
30
51
|
it { expect(successful_result.failure).to eq(nil) }
|
31
52
|
it { expect(successful_result.failure("other")).to eq("other") }
|
32
|
-
it { expect(successful_result.failure { |value| "Failed: #{value}" } ).to eq("Failed: some
|
53
|
+
it { expect(successful_result.failure { |value| "Failed: #{value}" } ).to eq("Failed: some success") }
|
33
54
|
end
|
34
55
|
end
|
@@ -44,7 +44,7 @@ class CreateUserCustomResult
|
|
44
44
|
end
|
45
45
|
|
46
46
|
result MyResult
|
47
|
-
result_constructor ->(value, success) {
|
47
|
+
result_constructor ->(value, success) { MyResult.new(value, success, time: Time.now.to_i) }
|
48
48
|
|
49
49
|
input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
|
50
50
|
output Types.Instance(User)
|
data/spec/operation_spec.rb
CHANGED
@@ -101,7 +101,7 @@ module TeckelOperationKeywordContracts
|
|
101
101
|
attr_reader :name, :age
|
102
102
|
end
|
103
103
|
|
104
|
-
input_constructor ->(data) {
|
104
|
+
input_constructor ->(data) { Input.new(**data) }
|
105
105
|
|
106
106
|
Output = ::User
|
107
107
|
|
@@ -129,7 +129,7 @@ module TeckelOperationCreateUserSplatInit
|
|
129
129
|
include Teckel::Operation
|
130
130
|
|
131
131
|
input Struct.new(:name, :age)
|
132
|
-
input_constructor ->(data) { input.new(*data) }
|
132
|
+
input_constructor ->(data) { self.class.input.new(*data) }
|
133
133
|
|
134
134
|
Output = ::User
|
135
135
|
|
@@ -346,7 +346,7 @@ RSpec.describe Teckel::Operation do
|
|
346
346
|
|
347
347
|
expect {
|
348
348
|
op.with(more: :stuff_2)
|
349
|
-
}.to raise_error(Teckel::Error)
|
349
|
+
}.to raise_error(Teckel::Error, "Operation already has settings assigned.")
|
350
350
|
end
|
351
351
|
end
|
352
352
|
|
data/spec/result_spec.rb
CHANGED
@@ -12,11 +12,11 @@ end
|
|
12
12
|
|
13
13
|
RSpec.describe Teckel::Result do
|
14
14
|
describe "missing initialize" do
|
15
|
-
specify do
|
15
|
+
specify "raises NotImplementedError" do
|
16
16
|
result = TeckelResultTest::MissingResultImplementation["value", true]
|
17
|
-
expect { result.successful? }.to raise_error(NotImplementedError)
|
18
|
-
expect { result.failure? }.to raise_error(NotImplementedError)
|
19
|
-
expect { result.value }.to raise_error(NotImplementedError)
|
17
|
+
expect { result.successful? }.to raise_error(NotImplementedError, "Result object does not implement `successful?`")
|
18
|
+
expect { result.failure? }.to raise_error(NotImplementedError, "Result object does not implement `successful?`")
|
19
|
+
expect { result.value }.to raise_error(NotImplementedError, "Result object does not implement `value`")
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,12 +3,15 @@
|
|
3
3
|
require "bundler/setup"
|
4
4
|
if ENV['COVERAGE'] == 'true'
|
5
5
|
require 'simplecov'
|
6
|
+
require 'simplecov_json_formatter'
|
7
|
+
SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter
|
6
8
|
SimpleCov.start do
|
7
9
|
add_filter %r{^/spec/}
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|
11
13
|
require "teckel"
|
14
|
+
require "teckel/chain"
|
12
15
|
|
13
16
|
RSpec.configure do |config|
|
14
17
|
# Enable flags like --only-failures and --next-failure
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: teckel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Schulze
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -107,16 +107,17 @@ files:
|
|
107
107
|
- lib/teckel/operation/runner.rb
|
108
108
|
- lib/teckel/result.rb
|
109
109
|
- lib/teckel/version.rb
|
110
|
+
- spec/chain/around_hook_spec.rb
|
110
111
|
- spec/chain/default_settings_spec.rb
|
111
112
|
- spec/chain/inheritance_spec.rb
|
112
113
|
- spec/chain/none_input_spec.rb
|
113
114
|
- spec/chain/results_spec.rb
|
114
|
-
- spec/chain_around_hook_spec.rb
|
115
115
|
- spec/chain_spec.rb
|
116
116
|
- spec/config_spec.rb
|
117
117
|
- spec/doctest_helper.rb
|
118
118
|
- spec/operation/contract_trace_spec.rb
|
119
119
|
- spec/operation/default_settings_spec.rb
|
120
|
+
- spec/operation/fail_on_input_spec.rb
|
120
121
|
- spec/operation/inheritance_spec.rb
|
121
122
|
- spec/operation/result_spec.rb
|
122
123
|
- spec/operation/results_spec.rb
|
@@ -132,10 +133,11 @@ homepage: https://github.com/fnordfish/teckel
|
|
132
133
|
licenses:
|
133
134
|
- Apache-2.0
|
134
135
|
metadata:
|
135
|
-
changelog_uri: https://github.com/
|
136
|
-
source_code_uri: https://github.com/
|
137
|
-
bug_tracker_uri: https://github.com/
|
138
|
-
documentation_uri: https://www.rubydoc.info/gems/teckel/0.
|
136
|
+
changelog_uri: https://github.com/fnordfish/teckel/blob/master/CHANGELOG.md
|
137
|
+
source_code_uri: https://github.com/fnordfish/teckel
|
138
|
+
bug_tracker_uri: https://github.com/fnordfish/teckel/issues
|
139
|
+
documentation_uri: https://www.rubydoc.info/gems/teckel/0.7.0
|
140
|
+
user_docs_uri: https://fnordfish.github.io/teckel/
|
139
141
|
post_install_message:
|
140
142
|
rdoc_options: []
|
141
143
|
require_paths:
|
@@ -151,21 +153,22 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
153
|
- !ruby/object:Gem::Version
|
152
154
|
version: '0'
|
153
155
|
requirements: []
|
154
|
-
rubygems_version: 3.
|
156
|
+
rubygems_version: 3.2.3
|
155
157
|
signing_key:
|
156
158
|
specification_version: 4
|
157
159
|
summary: Operations with enforced in/out/err data structures
|
158
160
|
test_files:
|
161
|
+
- spec/chain/around_hook_spec.rb
|
159
162
|
- spec/chain/default_settings_spec.rb
|
160
163
|
- spec/chain/inheritance_spec.rb
|
161
164
|
- spec/chain/none_input_spec.rb
|
162
165
|
- spec/chain/results_spec.rb
|
163
|
-
- spec/chain_around_hook_spec.rb
|
164
166
|
- spec/chain_spec.rb
|
165
167
|
- spec/config_spec.rb
|
166
168
|
- spec/doctest_helper.rb
|
167
169
|
- spec/operation/contract_trace_spec.rb
|
168
170
|
- spec/operation/default_settings_spec.rb
|
171
|
+
- spec/operation/fail_on_input_spec.rb
|
169
172
|
- spec/operation/inheritance_spec.rb
|
170
173
|
- spec/operation/result_spec.rb
|
171
174
|
- spec/operation/results_spec.rb
|