teckel 0.2.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -0
  3. data/LICENSE_LOGO +4 -0
  4. data/README.md +4 -4
  5. data/lib/teckel.rb +9 -4
  6. data/lib/teckel/chain.rb +31 -341
  7. data/lib/teckel/chain/config.rb +275 -0
  8. data/lib/teckel/chain/result.rb +38 -0
  9. data/lib/teckel/chain/runner.rb +62 -0
  10. data/lib/teckel/chain/step.rb +18 -0
  11. data/lib/teckel/config.rb +25 -28
  12. data/lib/teckel/contracts.rb +19 -0
  13. data/lib/teckel/operation.rb +84 -302
  14. data/lib/teckel/operation/config.rb +396 -0
  15. data/lib/teckel/operation/result.rb +92 -0
  16. data/lib/teckel/operation/runner.rb +74 -0
  17. data/lib/teckel/result.rb +52 -53
  18. data/lib/teckel/version.rb +1 -1
  19. data/spec/chain/around_hook_spec.rb +100 -0
  20. data/spec/chain/default_settings_spec.rb +39 -0
  21. data/spec/chain/inheritance_spec.rb +116 -0
  22. data/spec/chain/none_input_spec.rb +36 -0
  23. data/spec/chain/results_spec.rb +53 -0
  24. data/spec/chain_spec.rb +180 -0
  25. data/spec/config_spec.rb +26 -0
  26. data/spec/doctest_helper.rb +8 -0
  27. data/spec/operation/contract_trace_spec.rb +116 -0
  28. data/spec/operation/default_settings_spec.rb +94 -0
  29. data/spec/operation/fail_on_input_spec.rb +103 -0
  30. data/spec/operation/inheritance_spec.rb +94 -0
  31. data/spec/operation/result_spec.rb +55 -0
  32. data/spec/operation/results_spec.rb +117 -0
  33. data/spec/operation_spec.rb +483 -0
  34. data/spec/rb27/pattern_matching_spec.rb +193 -0
  35. data/spec/result_spec.rb +22 -0
  36. data/spec/spec_helper.rb +28 -0
  37. data/spec/support/dry_base.rb +8 -0
  38. data/spec/support/fake_db.rb +12 -0
  39. data/spec/support/fake_models.rb +20 -0
  40. data/spec/teckel_spec.rb +7 -0
  41. metadata +68 -28
  42. data/.codeclimate.yml +0 -3
  43. data/.github/workflows/ci.yml +0 -92
  44. data/.github/workflows/pages.yml +0 -50
  45. data/.gitignore +0 -15
  46. data/.rspec +0 -3
  47. data/.rubocop.yml +0 -12
  48. data/.ruby-version +0 -1
  49. data/DEVELOPMENT.md +0 -32
  50. data/Gemfile +0 -16
  51. data/Rakefile +0 -35
  52. data/bin/console +0 -15
  53. data/bin/rake +0 -29
  54. data/bin/rspec +0 -29
  55. data/bin/rubocop +0 -18
  56. data/bin/setup +0 -8
  57. data/lib/teckel/none.rb +0 -18
  58. data/lib/teckel/operation/results.rb +0 -72
  59. data/teckel.gemspec +0 -32
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+ @warning = Warning[:experimental]
3
+ Warning[:experimental] = false
4
+
5
+ require 'support/dry_base'
6
+ require 'support/fake_models'
7
+
8
+ RSpec.describe "Ruby 2.7 pattern matches for Result and Chain" do
9
+ module TeckelChainPatternMatchingTest
10
+ class CreateUser
11
+ include ::Teckel::Operation
12
+
13
+ result!
14
+
15
+ input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer.optional)
16
+ output Types.Instance(User)
17
+ error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
18
+
19
+ def call(input)
20
+ user = User.new(name: input[:name], age: input[:age])
21
+ if user.save
22
+ success!(user)
23
+ else
24
+ fail!(message: "Could not save User", errors: user.errors)
25
+ end
26
+ end
27
+ end
28
+
29
+ class LogUser
30
+ include ::Teckel::Operation
31
+
32
+ result!
33
+
34
+ input Types.Instance(User)
35
+ error none
36
+ output input
37
+
38
+ def call(usr)
39
+ Logger.new(File::NULL).info("User #{usr.name} created")
40
+ success! usr
41
+ end
42
+ end
43
+
44
+ class AddFriend
45
+ include ::Teckel::Operation
46
+
47
+ result!
48
+
49
+ settings Struct.new(:fail_befriend)
50
+
51
+ input Types.Instance(User)
52
+ output Types::Hash.schema(user: Types.Instance(User), friend: Types.Instance(User))
53
+ error Types::Hash.schema(message: Types::String)
54
+
55
+ def call(user)
56
+ if settings&.fail_befriend
57
+ fail!(message: "Did not find a friend.")
58
+ else
59
+ success!(user: user, friend: User.new(name: "A friend", age: 42))
60
+ end
61
+ end
62
+ end
63
+
64
+ class Chain
65
+ include Teckel::Chain
66
+
67
+ step :create, CreateUser
68
+ step :log, LogUser
69
+ step :befriend, AddFriend
70
+ end
71
+ end
72
+
73
+ describe "Operation Result" do
74
+ context "success" do
75
+ specify "pattern matching with keys" do
76
+ x =
77
+ case TeckelChainPatternMatchingTest::AddFriend.call(User.new(name: "bob", age: 23))
78
+ in { success: false, value: value }
79
+ ["Failed", value]
80
+ in { success: true, value: value }
81
+ ["Success result", value]
82
+ else
83
+ raise "Unexpected Result"
84
+ end
85
+
86
+ expect(x).to contain_exactly("Success result", hash_including(:friend, :user))
87
+ end
88
+ end
89
+
90
+ context "failure" do
91
+ specify "pattern matching with keys" do
92
+ result =
93
+ TeckelChainPatternMatchingTest::AddFriend.
94
+ with(befriend: :fail).
95
+ call(User.new(name: "bob", age: 23))
96
+
97
+ x =
98
+ case result
99
+ in { success: false, value: value }
100
+ ["Failed", value]
101
+ in { success: true, value: value }
102
+ ["Success result", value]
103
+ else
104
+ raise "Unexpected Result"
105
+ end
106
+
107
+ expect(x).to contain_exactly("Failed", hash_including(:message))
108
+ end
109
+
110
+ specify "pattern matching array" do
111
+ result =
112
+ TeckelChainPatternMatchingTest::AddFriend.
113
+ with(befriend: :fail).
114
+ call(User.new(name: "bob", age: 23))
115
+
116
+ x =
117
+ case result
118
+ in [false, value]
119
+ ["Failed", value]
120
+ in [true, value]
121
+ ["Success result", value]
122
+ else
123
+ raise "Unexpected Result"
124
+ end
125
+ expect(x).to contain_exactly("Failed", hash_including(:message))
126
+ end
127
+ end
128
+ end
129
+
130
+ describe "Chain" do
131
+ context "success" do
132
+ specify "pattern matching with keys" do
133
+ x =
134
+ case TeckelChainPatternMatchingTest::Chain.call(name: "Bob", age: 23)
135
+ in { success: false, step: :befriend, value: value }
136
+ ["Failed", value]
137
+ in { success: true, value: value }
138
+ ["Success result", value]
139
+ else
140
+ raise "Unexpected Result"
141
+ end
142
+ expect(x).to contain_exactly("Success result", hash_including(:friend, :user))
143
+ end
144
+
145
+ specify "pattern matching array" do
146
+ x =
147
+ case TeckelChainPatternMatchingTest::Chain.call(name: "Bob", age: 23)
148
+ in [false, :befriend, value]
149
+ "Failed in befriend with #{value}"
150
+ in [true, _, value]
151
+ "Success result"
152
+ end
153
+ expect(x).to eq("Success result")
154
+ end
155
+ end
156
+
157
+ context "failure" do
158
+ specify "pattern matching with keys" do
159
+ result =
160
+ TeckelChainPatternMatchingTest::Chain.
161
+ with(befriend: :fail).
162
+ call(name: "bob", age: 23)
163
+
164
+ x =
165
+ case result
166
+ in { success: false, step: :befriend, value: value }
167
+ "Failed in befriend with #{value}"
168
+ in { success: true, value: value }
169
+ "Success result"
170
+ end
171
+ expect(x).to eq("Failed in befriend with #{ {message: "Did not find a friend."} }")
172
+ end
173
+
174
+ specify "pattern matching array" do
175
+ result =
176
+ TeckelChainPatternMatchingTest::Chain.
177
+ with(befriend: :fail).
178
+ call(name: "Bob", age: 23)
179
+
180
+ x =
181
+ case result
182
+ in [false, :befriend, value]
183
+ "Failed in befriend with #{value}"
184
+ in [true, value]
185
+ "Success result"
186
+ end
187
+ expect(x).to eq("Failed in befriend with #{ {message: "Did not find a friend."} }")
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ Warning[:experimental] = @warning
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'support/dry_base'
4
+ require 'support/fake_models'
5
+
6
+ module TeckelResultTest
7
+ class MissingResultImplementation
8
+ include Teckel::Result
9
+ def initialize(value, success); end
10
+ end
11
+ end
12
+
13
+ RSpec.describe Teckel::Result do
14
+ describe "missing initialize" do
15
+ specify "raises NotImplementedError" do
16
+ result = TeckelResultTest::MissingResultImplementation["value", true]
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
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ if ENV['COVERAGE'] == 'true'
5
+ require 'simplecov'
6
+ require 'simplecov_json_formatter'
7
+ SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter
8
+ SimpleCov.start do
9
+ add_filter %r{^/spec/}
10
+ end
11
+ end
12
+
13
+ require "teckel"
14
+ require "teckel/chain"
15
+
16
+ RSpec.configure do |config|
17
+ # Enable flags like --only-failures and --next-failure
18
+ config.example_status_persistence_file_path = ".rspec_status"
19
+
20
+ # Disable RSpec exposing methods globally on `Module` and `main`
21
+ config.disable_monkey_patching!
22
+
23
+ config.formatter = config.files_to_run.size > 1 ? :progress : :documentation
24
+
25
+ config.expect_with :rspec do |c|
26
+ c.syntax = :expect
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-types'
4
+ require 'dry-struct'
5
+
6
+ module Types
7
+ include Dry.Types()
8
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeDB
4
+ Rollback = Class.new(RuntimeError)
5
+
6
+ def self.transaction
7
+ yield
8
+ rescue Rollback
9
+ # doing rollback ...
10
+ raise
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User
4
+ def initialize(name:, age:)
5
+ @name, @age = name, age
6
+ end
7
+ attr_reader :name, :age
8
+
9
+ def save
10
+ !underage?
11
+ end
12
+
13
+ def errors
14
+ underage? ? [{ age: "underage" }] : nil
15
+ end
16
+
17
+ def underage?
18
+ @age <= 18
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Teckel do
4
+ it "has a version number" do
5
+ expect(Teckel::VERSION).not_to be nil
6
+ end
7
+ end
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.2.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Schulze
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-14 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
@@ -88,39 +88,57 @@ executables: []
88
88
  extensions: []
89
89
  extra_rdoc_files: []
90
90
  files:
91
- - ".codeclimate.yml"
92
- - ".github/workflows/ci.yml"
93
- - ".github/workflows/pages.yml"
94
- - ".gitignore"
95
- - ".rspec"
96
- - ".rubocop.yml"
97
- - ".ruby-version"
98
91
  - ".yardopts"
99
92
  - CHANGELOG.md
100
- - DEVELOPMENT.md
101
- - Gemfile
102
93
  - LICENSE
94
+ - LICENSE_LOGO
103
95
  - README.md
104
- - Rakefile
105
- - bin/console
106
- - bin/rake
107
- - bin/rspec
108
- - bin/rubocop
109
- - bin/setup
110
96
  - lib/teckel.rb
111
97
  - lib/teckel/chain.rb
98
+ - lib/teckel/chain/config.rb
99
+ - lib/teckel/chain/result.rb
100
+ - lib/teckel/chain/runner.rb
101
+ - lib/teckel/chain/step.rb
112
102
  - lib/teckel/config.rb
113
- - lib/teckel/none.rb
103
+ - lib/teckel/contracts.rb
114
104
  - lib/teckel/operation.rb
115
- - lib/teckel/operation/results.rb
105
+ - lib/teckel/operation/config.rb
106
+ - lib/teckel/operation/result.rb
107
+ - lib/teckel/operation/runner.rb
116
108
  - lib/teckel/result.rb
117
109
  - lib/teckel/version.rb
118
- - teckel.gemspec
119
- homepage: https://github.com/dotless-de/teckel
110
+ - spec/chain/around_hook_spec.rb
111
+ - spec/chain/default_settings_spec.rb
112
+ - spec/chain/inheritance_spec.rb
113
+ - spec/chain/none_input_spec.rb
114
+ - spec/chain/results_spec.rb
115
+ - spec/chain_spec.rb
116
+ - spec/config_spec.rb
117
+ - spec/doctest_helper.rb
118
+ - spec/operation/contract_trace_spec.rb
119
+ - spec/operation/default_settings_spec.rb
120
+ - spec/operation/fail_on_input_spec.rb
121
+ - spec/operation/inheritance_spec.rb
122
+ - spec/operation/result_spec.rb
123
+ - spec/operation/results_spec.rb
124
+ - spec/operation_spec.rb
125
+ - spec/rb27/pattern_matching_spec.rb
126
+ - spec/result_spec.rb
127
+ - spec/spec_helper.rb
128
+ - spec/support/dry_base.rb
129
+ - spec/support/fake_db.rb
130
+ - spec/support/fake_models.rb
131
+ - spec/teckel_spec.rb
132
+ homepage: https://github.com/fnordfish/teckel
120
133
  licenses:
121
134
  - Apache-2.0
122
- metadata: {}
123
- post_install_message:
135
+ metadata:
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/
141
+ post_install_message:
124
142
  rdoc_options: []
125
143
  require_paths:
126
144
  - lib
@@ -128,15 +146,37 @@ required_ruby_version: !ruby/object:Gem::Requirement
128
146
  requirements:
129
147
  - - ">="
130
148
  - !ruby/object:Gem::Version
131
- version: '0'
149
+ version: 2.4.0
132
150
  required_rubygems_version: !ruby/object:Gem::Requirement
133
151
  requirements:
134
152
  - - ">="
135
153
  - !ruby/object:Gem::Version
136
154
  version: '0'
137
155
  requirements: []
138
- rubygems_version: 3.1.2
139
- signing_key:
156
+ rubygems_version: 3.2.3
157
+ signing_key:
140
158
  specification_version: 4
141
159
  summary: Operations with enforced in/out/err data structures
142
- test_files: []
160
+ test_files:
161
+ - spec/chain/around_hook_spec.rb
162
+ - spec/chain/default_settings_spec.rb
163
+ - spec/chain/inheritance_spec.rb
164
+ - spec/chain/none_input_spec.rb
165
+ - spec/chain/results_spec.rb
166
+ - spec/chain_spec.rb
167
+ - spec/config_spec.rb
168
+ - spec/doctest_helper.rb
169
+ - spec/operation/contract_trace_spec.rb
170
+ - spec/operation/default_settings_spec.rb
171
+ - spec/operation/fail_on_input_spec.rb
172
+ - spec/operation/inheritance_spec.rb
173
+ - spec/operation/result_spec.rb
174
+ - spec/operation/results_spec.rb
175
+ - spec/operation_spec.rb
176
+ - spec/rb27/pattern_matching_spec.rb
177
+ - spec/result_spec.rb
178
+ - spec/spec_helper.rb
179
+ - spec/support/dry_base.rb
180
+ - spec/support/fake_db.rb
181
+ - spec/support/fake_models.rb
182
+ - spec/teckel_spec.rb
@@ -1,3 +0,0 @@
1
- plugins:
2
- rubocop:
3
- enabled: true
@@ -1,92 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- paths:
6
- - .github/workflows/ci.yml
7
- - lib/**
8
- - spec/**
9
- - Gemfile
10
- - "*.gemspec"
11
- - ".rubocop.yml"
12
-
13
- jobs:
14
- rubocop:
15
- runs-on: ubuntu-latest
16
- steps:
17
- - uses: actions/checkout@v1
18
- - name: Set up Ruby
19
- uses: actions/setup-ruby@v1
20
- with:
21
- ruby-version: 2.4.x
22
- - name: Install bundler
23
- run: gem install bundler
24
- - name: Run rubocop
25
- run: bin/rubocop -ESD
26
-
27
- cruby:
28
- runs-on: ubuntu-latest
29
- strategy:
30
- fail-fast: false
31
- matrix:
32
- ruby: ["2.6.x", "2.5.x", "2.4.x"]
33
-
34
- steps:
35
- - uses: actions/checkout@v1
36
- - name: Set up Ruby
37
- uses: actions/setup-ruby@v1
38
- with:
39
- ruby-version: ${{matrix.ruby}}
40
- - name: Bundle install
41
- run: |
42
- gem install bundler
43
- bundle install --jobs 4 --retry 3 --without tools docs benchmarks
44
- - name: Run all tests
45
- run: bundle exec rake
46
-
47
- other-ruby:
48
- runs-on: ubuntu-latest
49
- strategy:
50
- fail-fast: false
51
- matrix:
52
- image: ["jruby:9.2.9", "ruby:2.7"]
53
- include:
54
- - image: "ruby:2.7"
55
- coverage: "true"
56
- container:
57
- image: ${{matrix.image}}
58
-
59
- steps:
60
- - uses: actions/checkout@v1
61
- - name: Install git
62
- run: |
63
- apt-get update
64
- apt-get install -y --no-install-recommends git
65
- - name: Download test reporter
66
- if: "matrix.coverage == 'true'"
67
- run: |
68
- mkdir -p tmp/
69
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./tmp/cc-test-reporter
70
- chmod +x ./tmp/cc-test-reporter
71
- ./tmp/cc-test-reporter before-build
72
- - name: Bundle install
73
- env:
74
- COVERAGE: ${{matrix.coverage}}
75
- run: |
76
- gem install bundler
77
- bundle install --jobs 4 --retry 3 --without tools docs benchmarks
78
- - name: Run all tests
79
- env:
80
- COVERAGE: ${{matrix.coverage}}
81
- run: bundle exec rake
82
- - name: Send coverage results
83
- if: "matrix.coverage == 'true'"
84
- env:
85
- CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}}
86
- GIT_COMMIT_SHA: ${{github.sha}}
87
- GIT_BRANCH: ${{github.ref}}
88
- GIT_COMMITTED_AT: ${{github.event.head_commit.timestamp}}
89
- run: |
90
- GIT_BRANCH=`ruby -e "puts ENV['GITHUB_REF'].split('/', 3).last"` \
91
- GIT_COMMITTED_AT=`ruby -r time -e "puts Time.iso8601(ENV['GIT_COMMITTED_AT']).to_i"` \
92
- ./tmp/cc-test-reporter after-build -t simplecov