teckel 0.8.0 → 0.9.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 +28 -7
- data/lib/teckel/chain/config.rb +20 -19
- data/lib/teckel/chain/result.rb +5 -9
- data/lib/teckel/chain/runner.rb +8 -6
- data/lib/teckel/chain.rb +15 -11
- data/lib/teckel/config.rb +15 -9
- data/lib/teckel/contracts.rb +3 -3
- data/lib/teckel/operation/config.rb +31 -28
- data/lib/teckel/operation/result.rb +8 -8
- data/lib/teckel/operation/runner.rb +1 -1
- data/lib/teckel/operation.rb +5 -5
- data/lib/teckel/result.rb +2 -2
- data/lib/teckel/version.rb +2 -1
- data/lib/teckel.rb +4 -1
- metadata +19 -54
- data/spec/chain/around_hook_spec.rb +0 -100
- data/spec/chain/default_settings_spec.rb +0 -39
- data/spec/chain/inheritance_spec.rb +0 -116
- data/spec/chain/none_input_spec.rb +0 -36
- data/spec/chain/results_spec.rb +0 -53
- data/spec/chain_spec.rb +0 -255
- data/spec/config_spec.rb +0 -26
- data/spec/doctest_helper.rb +0 -8
- data/spec/operation/config_spec.rb +0 -227
- data/spec/operation/contract_trace_spec.rb +0 -118
- data/spec/operation/default_settings_spec.rb +0 -120
- data/spec/operation/fail_on_input_spec.rb +0 -103
- data/spec/operation/inheritance_spec.rb +0 -94
- data/spec/operation/result_spec.rb +0 -49
- data/spec/operation/results_spec.rb +0 -117
- data/spec/operation_spec.rb +0 -531
- data/spec/rb27/pattern_matching_spec.rb +0 -193
- data/spec/result_spec.rb +0 -22
- data/spec/spec_helper.rb +0 -35
- data/spec/support/dry_base.rb +0 -8
- data/spec/support/fake_db.rb +0 -12
- data/spec/support/fake_models.rb +0 -20
- data/spec/teckel_spec.rb +0 -7
@@ -1,120 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module TeckelOperationDefaultSettings
|
4
|
-
class BaseOperation
|
5
|
-
include ::Teckel::Operation
|
6
|
-
|
7
|
-
input none
|
8
|
-
output Symbol
|
9
|
-
error none
|
10
|
-
|
11
|
-
def call(_input)
|
12
|
-
success! settings.injected
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
RSpec.describe Teckel::Operation do
|
18
|
-
context "default settings" do
|
19
|
-
shared_examples "operation with default settings" do |operation|
|
20
|
-
subject { operation }
|
21
|
-
|
22
|
-
it "with no settings" do
|
23
|
-
expect(subject.call).to eq(:default_value)
|
24
|
-
end
|
25
|
-
|
26
|
-
it "with settings" do
|
27
|
-
expect(subject.with(:injected_value).call).to eq(:injected_value)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
describe "with default constructor and clever Settings class" do
|
32
|
-
it_behaves_like(
|
33
|
-
"operation with default settings",
|
34
|
-
Class.new(TeckelOperationDefaultSettings::BaseOperation) do
|
35
|
-
settings(Class.new do
|
36
|
-
def initialize(injected = nil)
|
37
|
-
@injected = injected
|
38
|
-
end
|
39
|
-
|
40
|
-
def injected
|
41
|
-
@injected || :default_value
|
42
|
-
end
|
43
|
-
|
44
|
-
class << self
|
45
|
-
alias :[] :new # make us respond to the default constructor
|
46
|
-
end
|
47
|
-
end)
|
48
|
-
|
49
|
-
default_settings!
|
50
|
-
end
|
51
|
-
)
|
52
|
-
end
|
53
|
-
|
54
|
-
describe "with custom constructor and clever Settings class" do
|
55
|
-
it_behaves_like(
|
56
|
-
"operation with default settings",
|
57
|
-
Class.new(TeckelOperationDefaultSettings::BaseOperation) do
|
58
|
-
settings(Class.new do
|
59
|
-
def initialize(injected = nil)
|
60
|
-
@injected = injected
|
61
|
-
end
|
62
|
-
|
63
|
-
def injected
|
64
|
-
@injected || :default_value
|
65
|
-
end
|
66
|
-
end)
|
67
|
-
|
68
|
-
settings_constructor :new
|
69
|
-
default_settings!
|
70
|
-
end
|
71
|
-
)
|
72
|
-
end
|
73
|
-
|
74
|
-
describe "with default constructor and simple Settings class" do
|
75
|
-
it_behaves_like(
|
76
|
-
"operation with default settings",
|
77
|
-
Class.new(TeckelOperationDefaultSettings::BaseOperation) do
|
78
|
-
settings Struct.new(:injected)
|
79
|
-
|
80
|
-
default_settings! -> { settings.new(:default_value) }
|
81
|
-
end
|
82
|
-
)
|
83
|
-
|
84
|
-
it_behaves_like(
|
85
|
-
"operation with default settings",
|
86
|
-
Class.new(TeckelOperationDefaultSettings::BaseOperation) do
|
87
|
-
settings Struct.new(:injected)
|
88
|
-
|
89
|
-
default_settings!(:default_value)
|
90
|
-
end
|
91
|
-
)
|
92
|
-
|
93
|
-
it_behaves_like(
|
94
|
-
"operation with default settings",
|
95
|
-
Class.new(TeckelOperationDefaultSettings::BaseOperation) do
|
96
|
-
settings Struct.new(:injected)
|
97
|
-
|
98
|
-
default_settings!("default_value")
|
99
|
-
|
100
|
-
output_constructor ->(out) { out&.to_sym }
|
101
|
-
end
|
102
|
-
)
|
103
|
-
end
|
104
|
-
|
105
|
-
describe "with default constructor and simple Settings class responding to passed default setting" do
|
106
|
-
it_behaves_like(
|
107
|
-
"operation with default settings",
|
108
|
-
Class.new(TeckelOperationDefaultSettings::BaseOperation) do
|
109
|
-
settings(Struct.new(:injected) do
|
110
|
-
def self.default
|
111
|
-
new(:default_value)
|
112
|
-
end
|
113
|
-
end)
|
114
|
-
|
115
|
-
default_settings!(:default)
|
116
|
-
end
|
117
|
-
)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
@@ -1,103 +0,0 @@
|
|
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
|
@@ -1,94 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module TeckelOperationDefaultsViaBaseClass
|
4
|
-
DefaultError = Struct.new(:message, :status_code)
|
5
|
-
Settings = Struct.new(:fail_it)
|
6
|
-
|
7
|
-
class ApplicationOperation
|
8
|
-
include Teckel::Operation
|
9
|
-
|
10
|
-
settings Settings
|
11
|
-
settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
|
12
|
-
|
13
|
-
error DefaultError
|
14
|
-
error_constructor ->(data) { error.new(*data.values_at(*error.members)) }
|
15
|
-
|
16
|
-
result!
|
17
|
-
|
18
|
-
# Freeze the base class to make sure it's inheritable configuration is not altered
|
19
|
-
freeze
|
20
|
-
end
|
21
|
-
|
22
|
-
class OperationA < ApplicationOperation
|
23
|
-
input Struct.new(:input_data_a)
|
24
|
-
output Struct.new(:output_data_a)
|
25
|
-
|
26
|
-
def call(input)
|
27
|
-
if settings&.fail_it
|
28
|
-
fail!(message: settings.fail_it, status_code: 400)
|
29
|
-
else
|
30
|
-
success!(input.input_data_a * 2)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
finalize!
|
35
|
-
end
|
36
|
-
|
37
|
-
class OperationB < ApplicationOperation
|
38
|
-
input Struct.new(:input_data_b)
|
39
|
-
output Struct.new(:output_data_b)
|
40
|
-
|
41
|
-
def call(input)
|
42
|
-
if settings&.fail_it
|
43
|
-
fail!(message: settings.fail_it, status_code: 500)
|
44
|
-
else
|
45
|
-
success!(input.input_data_b * 4)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
finalize!
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
RSpec.describe Teckel::Operation do
|
54
|
-
context "default settings via base class" do
|
55
|
-
let(:operation_a) { TeckelOperationDefaultsViaBaseClass::OperationA }
|
56
|
-
let(:operation_b) { TeckelOperationDefaultsViaBaseClass::OperationB }
|
57
|
-
|
58
|
-
it "inherits config" do
|
59
|
-
expect(operation_a.result).to eq(Teckel::Operation::Result)
|
60
|
-
expect(operation_a.settings).to eq(TeckelOperationDefaultsViaBaseClass::Settings)
|
61
|
-
|
62
|
-
expect(operation_b.result).to eq(Teckel::Operation::Result)
|
63
|
-
expect(operation_b.settings).to eq(TeckelOperationDefaultsViaBaseClass::Settings)
|
64
|
-
end
|
65
|
-
|
66
|
-
context "operation_a" do
|
67
|
-
it "can run" do
|
68
|
-
result = operation_a.call(10)
|
69
|
-
expect(result.success.to_h).to eq(output_data_a: 20)
|
70
|
-
end
|
71
|
-
|
72
|
-
it "can fail" do
|
73
|
-
result = operation_a.with(fail_it: "D'oh!").call(10)
|
74
|
-
expect(result.failure.to_h).to eq(
|
75
|
-
message: "D'oh!", status_code: 400
|
76
|
-
)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
context "operation_b" do
|
81
|
-
it "can run" do
|
82
|
-
result = operation_b.call(10)
|
83
|
-
expect(result.success.to_h).to eq(output_data_b: 40)
|
84
|
-
end
|
85
|
-
|
86
|
-
it "can fail" do
|
87
|
-
result = operation_b.with(fail_it: "D'oh!").call(10)
|
88
|
-
expect(result.failure.to_h).to eq(
|
89
|
-
message: "D'oh!", status_code: 500
|
90
|
-
)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Teckel::Operation::Result do
|
4
|
-
let(:failure_value) { "some error" }
|
5
|
-
let(:failed_result) { Teckel::Operation::Result.new(failure_value, false) }
|
6
|
-
|
7
|
-
let(:success_value) { "some success" }
|
8
|
-
let(:successful_result) { Teckel::Operation::Result.new(success_value, true) }
|
9
|
-
|
10
|
-
it { expect(successful_result.successful?).to eq(true) }
|
11
|
-
it { expect(failed_result.successful?).to eq(false) }
|
12
|
-
|
13
|
-
it { expect(successful_result.failure?).to eq(false) }
|
14
|
-
it { expect(failed_result.failure?).to eq(true) }
|
15
|
-
|
16
|
-
it { expect(successful_result.value).to eq(success_value) }
|
17
|
-
it { expect(failed_result.value).to eq(failure_value) }
|
18
|
-
|
19
|
-
describe "#success" do
|
20
|
-
it("on successful result, returns value") {
|
21
|
-
expect(successful_result.success).to eq(success_value)
|
22
|
-
}
|
23
|
-
|
24
|
-
describe "on failed result" do
|
25
|
-
it("with no fallbacks, returns nil") {
|
26
|
-
expect(failed_result.success).to eq(nil)
|
27
|
-
}
|
28
|
-
it("with default-argument, returns default-argument") {
|
29
|
-
expect(failed_result.success("other")).to eq("other")
|
30
|
-
}
|
31
|
-
it("with block, returns block return value") {
|
32
|
-
expect(failed_result.success { |value| "Failed: #{value}" } ).to eq("Failed: some error")
|
33
|
-
}
|
34
|
-
it("with default-argument and block given, returns default-argument, skips block") {
|
35
|
-
expect { |blk|
|
36
|
-
expect(failed_result.success("default", &blk)).to_not eq("default")
|
37
|
-
}.to(yield_control)
|
38
|
-
}
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
describe "#failure" do
|
43
|
-
it { expect(failed_result.failure).to eq(failure_value) }
|
44
|
-
|
45
|
-
it { expect(successful_result.failure).to eq(nil) }
|
46
|
-
it { expect(successful_result.failure("other")).to eq("other") }
|
47
|
-
it { expect(successful_result.failure { |value| "Failed: #{value}" } ).to eq("Failed: some success") }
|
48
|
-
end
|
49
|
-
end
|
@@ -1,117 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'support/dry_base'
|
4
|
-
require 'support/fake_models'
|
5
|
-
|
6
|
-
class CreateUserWithResult
|
7
|
-
include Teckel::Operation
|
8
|
-
|
9
|
-
result!
|
10
|
-
|
11
|
-
input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
|
12
|
-
output Types.Instance(User)
|
13
|
-
error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
|
14
|
-
|
15
|
-
def call(input)
|
16
|
-
user = User.new(name: input[:name], age: input[:age])
|
17
|
-
if user.save
|
18
|
-
success! user
|
19
|
-
else
|
20
|
-
fail!(message: "Could not save User", errors: user.errors)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class CreateUserCustomResult
|
26
|
-
include Teckel::Operation
|
27
|
-
|
28
|
-
class MyResult
|
29
|
-
include Teckel::Result # makes sure this can be used in a Chain
|
30
|
-
|
31
|
-
def initialize(value, success, opts = {})
|
32
|
-
@value, @success, @opts = value, success, opts
|
33
|
-
end
|
34
|
-
|
35
|
-
# implementing Teckel::Result
|
36
|
-
def successful?
|
37
|
-
@success
|
38
|
-
end
|
39
|
-
|
40
|
-
# implementing Teckel::Result
|
41
|
-
attr_reader :value
|
42
|
-
|
43
|
-
attr_reader :opts
|
44
|
-
end
|
45
|
-
|
46
|
-
result MyResult
|
47
|
-
result_constructor ->(value, success) { MyResult.new(value, success, time: Time.now.to_i) }
|
48
|
-
|
49
|
-
input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
|
50
|
-
output Types.Instance(User)
|
51
|
-
error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
|
52
|
-
|
53
|
-
def call(input)
|
54
|
-
user = User.new(name: input[:name], age: input[:age])
|
55
|
-
if user.save
|
56
|
-
success! user
|
57
|
-
else
|
58
|
-
fail!(message: "Could not save User", errors: user.errors)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
class CreateUserOverwritingResult
|
64
|
-
include Teckel::Operation
|
65
|
-
|
66
|
-
class Result
|
67
|
-
include Teckel::Result # makes sure this can be used in a Chain
|
68
|
-
|
69
|
-
def initialize(value, success); end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
RSpec.describe Teckel::Operation do
|
74
|
-
context "with build in result object" do
|
75
|
-
specify "output" do
|
76
|
-
result = CreateUserWithResult.call(name: "Bob", age: 23)
|
77
|
-
expect(result).to be_a(Teckel::Result)
|
78
|
-
expect(result).to be_successful
|
79
|
-
expect(result.success).to be_a(User)
|
80
|
-
end
|
81
|
-
|
82
|
-
specify "errors" do
|
83
|
-
result = CreateUserWithResult.call(name: "Bob", age: 10)
|
84
|
-
expect(result).to be_a(Teckel::Result)
|
85
|
-
expect(result).to be_failure
|
86
|
-
expect(result.failure).to eq(message: "Could not save User", errors: [{ age: "underage" }])
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
context "using custom result" do
|
91
|
-
specify "output" do
|
92
|
-
result = CreateUserCustomResult.call(name: "Bob", age: 23)
|
93
|
-
expect(result).to be_a(CreateUserCustomResult::MyResult)
|
94
|
-
expect(result).to be_successful
|
95
|
-
expect(result.value).to be_a(User)
|
96
|
-
|
97
|
-
expect(result.opts).to include(time: kind_of(Integer))
|
98
|
-
end
|
99
|
-
|
100
|
-
specify "errors" do
|
101
|
-
result = CreateUserCustomResult.call(name: "Bob", age: 10)
|
102
|
-
expect(result).to be_a(CreateUserCustomResult::MyResult)
|
103
|
-
expect(result).to be_failure
|
104
|
-
expect(result.value).to eq(message: "Could not save User", errors: [{ age: "underage" }])
|
105
|
-
|
106
|
-
expect(result.opts).to include(time: kind_of(Integer))
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
context "overwriting Result" do
|
111
|
-
it "uses the class definition" do
|
112
|
-
expect(CreateUserOverwritingResult.result).to_not eq(Teckel::Operation::Result)
|
113
|
-
expect(CreateUserOverwritingResult.result).to eq(CreateUserOverwritingResult::Result)
|
114
|
-
expect(CreateUserOverwritingResult.result_constructor).to eq(CreateUserOverwritingResult::Result.method(:[]))
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|