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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d55dd7fb782cfdcb3cc960423e46ab2ce8b49c9396273f10749105883d55ed6f
4
- data.tar.gz: e3b7428d98daeb42cc086b4a5cd5081b5c3b4ca90c14b0cf3f4cd9576243d0e3
3
+ metadata.gz: df9c73577c9c93fbaaf2e7661a07bdc3b1b0397832a62a8edbe4eea846c4c029
4
+ data.tar.gz: 037041bf0eb6ae8bc41e354772d3ce1d04002a35ecccc0b1ecefa33a85845c3b
5
5
  SHA512:
6
- metadata.gz: '0148d426e8e7124a08a3a7dd3c42487cac5944d17ba9cabb98a8c378a86a5d9813b7b62c483d2203809644528d2e6b6cc4de51476aa67261b1405f3f41576613'
7
- data.tar.gz: 6792ad4806b977d21d0ea621e311306cb13c239d8604e35e61937e79ce4f5488cbb410aa6ae22e9e12e48b61529f97e924aab22e100717808885c0b0c76980fd
6
+ metadata.gz: 2f6f601e816c972aed8e511b05fdbc418a48aab10c2ca246d108661cc25a8d514a41944b2e01b9294757a2a717696491f20f601f5f60e53faa45651aec48bc7f
7
+ data.tar.gz: 68bc46e5ed21cf5e639ee1fc00265b278f9062f42de9d179a07559862990a078df9dcb2ef26956eb629cf08e71409b9b05bd363a11930cb64ecdee533d63d2ea
@@ -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]
@@ -19,4 +19,3 @@ require_relative "teckel/config"
19
19
  require_relative "teckel/contracts"
20
20
  require_relative "teckel/result"
21
21
  require_relative "teckel/operation"
22
- require_relative "teckel/chain"
@@ -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, options = {}); end
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
- # # If you need more control over how to build a new +Settings+ instance
112
- # result_constructor ->(value, success, step) { result.new(value, success, step, {foo: :bar}) }
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(:failure) do
20
- out = catch(:success) do
21
- run operation.input_constructor.call(input)
22
- return nil # :sic!: return values need to go through +success!+
23
- end
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 :success, operation.result_constructor.call(value, true)
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 :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)
70
+ throw :halt, instance.instance_exec(value, false, &operation.result_constructor)
72
71
  end
73
72
  end
74
73
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Teckel
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -5,3 +5,4 @@ require_relative 'support/fake_db'
5
5
  require_relative 'support/fake_models'
6
6
 
7
7
  require "teckel"
8
+ require "teckel/chain"
@@ -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 error" }
8
- let(:successful_result) { Teckel::Operation::Result.new(failure_value, true) }
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 be(true) }
11
- it { expect(failed_result.successful?).to be(false) }
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 be(false) }
14
- it { expect(failed_result.failure?).to be(true) }
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 { expect(successful_result.success).to eq(success_value) }
21
-
22
- it { expect(failed_result.success).to eq(nil) }
23
- it { expect(failed_result.success("other")).to eq("other") }
24
- it { expect(failed_result.success { |value| "Failed: #{value}" } ).to eq("Failed: some error") }
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 error") }
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) { result.new(value, success, time: Time.now.to_i) }
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)
@@ -101,7 +101,7 @@ module TeckelOperationKeywordContracts
101
101
  attr_reader :name, :age
102
102
  end
103
103
 
104
- input_constructor ->(data) { input.new(**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
 
@@ -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
@@ -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.6.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: 2020-11-03 00:00:00.000000000 Z
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/github.com/fnordfish/blob/master/CHANGELOG.md
136
- source_code_uri: https://github.com/github.com/fnordfish
137
- bug_tracker_uri: https://github.com/github.com/fnordfish/issues
138
- documentation_uri: https://www.rubydoc.info/gems/teckel/0.6.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.0.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