teckel 0.7.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.
@@ -1,483 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'support/dry_base'
4
- require 'support/fake_models'
5
-
6
- module TeckelOperationPredefinedClassesTest
7
- class CreateUserInput < Dry::Struct
8
- attribute :name, Types::String
9
- attribute :age, Types::Coercible::Integer
10
- end
11
-
12
- CreateUserOutput = Types.Instance(User)
13
-
14
- class CreateUserError < Dry::Struct
15
- attribute :message, Types::String
16
- attribute :status_code, Types::Integer
17
- attribute :meta, Types::Hash.optional
18
- end
19
-
20
- class CreateUser
21
- include Teckel::Operation
22
-
23
- input CreateUserInput
24
- output CreateUserOutput
25
- error CreateUserError
26
-
27
- def call(input)
28
- user = User.new(**input.attributes)
29
- if user.save
30
- success!(user)
31
- else
32
- fail!(
33
- message: "Could not create User",
34
- status_code: 400,
35
- meta: { validation: user.errors }
36
- )
37
- end
38
- end
39
- end
40
- end
41
-
42
- module TeckelOperationInlineClassesTest
43
- class CreateUser
44
- include Teckel::Operation
45
-
46
- class Input < Dry::Struct
47
- attribute :name, Types::String
48
- attribute :age, Types::Coercible::Integer
49
- end
50
-
51
- Output = Types.Instance(User)
52
-
53
- class Error < Dry::Struct
54
- attribute :message, Types::String
55
- attribute :status_code, Types::Integer
56
- attribute :meta, Types::Hash.optional
57
- end
58
-
59
- def call(input)
60
- user = User.new(**input.attributes)
61
- if user.save
62
- success!(user)
63
- else
64
- fail!(
65
- message: "Could not create User",
66
- status_code: 400,
67
- meta: { validation: user.errors }
68
- )
69
- end
70
- end
71
- end
72
- end
73
-
74
- module TeckelOperationAnnonClassesTest
75
- class CreateUser
76
- include ::Teckel::Operation
77
-
78
- input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
79
- output Types.Instance(User)
80
- error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
81
-
82
- def call(input)
83
- user = User.new(name: input[:name], age: input[:age])
84
- if user.save
85
- success!(user)
86
- else
87
- fail!(message: "Could not save User", errors: user.errors)
88
- end
89
- end
90
- end
91
- end
92
-
93
- module TeckelOperationKeywordContracts
94
- class MyOperation
95
- include Teckel::Operation
96
-
97
- class Input
98
- def initialize(name:, age:)
99
- @name, @age = name, age
100
- end
101
- attr_reader :name, :age
102
- end
103
-
104
- input_constructor ->(data) { Input.new(**data) }
105
-
106
- Output = ::User
107
-
108
- class Error
109
- def initialize(message, errors)
110
- @message, @errors = message, errors
111
- end
112
- attr_reader :message, :errors
113
- end
114
- error_constructor :new
115
-
116
- def call(input)
117
- user = ::User.new(name: input.name, age: input.age)
118
- if user.save
119
- success!(user)
120
- else
121
- fail!(message: "Could not save User", errors: user.errors)
122
- end
123
- end
124
- end
125
- end
126
-
127
- module TeckelOperationCreateUserSplatInit
128
- class MyOperation
129
- include Teckel::Operation
130
-
131
- input Struct.new(:name, :age)
132
- input_constructor ->(data) { self.class.input.new(*data) }
133
-
134
- Output = ::User
135
-
136
- class Error
137
- def initialize(message, errors)
138
- @message, @errors = message, errors
139
- end
140
- attr_reader :message, :errors
141
- end
142
- error_constructor :new
143
-
144
- def call(input)
145
- user = ::User.new(name: input.name, age: input.age)
146
- if user.save
147
- success!(user)
148
- else
149
- fail!(message: "Could not save User", errors: user.errors)
150
- end
151
- end
152
- end
153
- end
154
-
155
- module TeckelOperationGeneratedOutputTest
156
- class MyOperation
157
- include ::Teckel::Operation
158
-
159
- input none
160
- output Struct.new(:some_key)
161
- output_constructor ->(data) { output.new(*data.values_at(*output.members)) } # ruby 2.4 way for `keyword_init: true`
162
- error none
163
-
164
- def call(_input)
165
- success!(some_key: "some_value")
166
- end
167
- end
168
- end
169
-
170
- module TeckelOperationNoSettingsTest
171
- class MyOperation
172
- include ::Teckel::Operation
173
-
174
- input none
175
- output none
176
- error none
177
-
178
- def call(_input); end
179
- end
180
- MyOperation.finalize!
181
- end
182
-
183
- module TeckelOperationNoneDataTest
184
- class MyOperation
185
- include ::Teckel::Operation
186
-
187
- settings Struct.new(:fail_it, :fail_data, :success_it, :success_data)
188
- settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
189
-
190
- input none
191
- output none
192
- error none
193
-
194
- def call(_input)
195
- if settings&.fail_it
196
- if settings&.fail_data
197
- fail!(settings.fail_data)
198
- else
199
- fail!
200
- end
201
- elsif settings&.success_it
202
- if settings&.success_data
203
- success!(settings.success_data)
204
- else
205
- success!
206
- end
207
- else
208
- settings&.success_data
209
- end
210
- end
211
- end
212
- end
213
-
214
- module TeckelOperationInjectSettingsTest
215
- class MyOperation
216
- include ::Teckel::Operation
217
-
218
- settings Struct.new(:injected)
219
- settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
220
-
221
- input none
222
- output Array
223
- error none
224
-
225
- def call(_input)
226
- success!((settings&.injected || []) << :operation_data)
227
- end
228
- end
229
- end
230
-
231
- RSpec.describe Teckel::Operation do
232
- context "predefined classes" do
233
- specify "Input" do
234
- expect(TeckelOperationPredefinedClassesTest::CreateUser.input).to eq(TeckelOperationPredefinedClassesTest::CreateUserInput)
235
- end
236
-
237
- specify "Output" do
238
- expect(TeckelOperationPredefinedClassesTest::CreateUser.output).to eq(TeckelOperationPredefinedClassesTest::CreateUserOutput)
239
- end
240
-
241
- specify "Error" do
242
- expect(TeckelOperationPredefinedClassesTest::CreateUser.error).to eq(TeckelOperationPredefinedClassesTest::CreateUserError)
243
- end
244
-
245
- context "success" do
246
- specify do
247
- result = TeckelOperationPredefinedClassesTest::CreateUser.call(name: "Bob", age: 23)
248
- expect(result).to be_a(User)
249
- end
250
- end
251
-
252
- context "error" do
253
- specify do
254
- result = TeckelOperationPredefinedClassesTest::CreateUser.call(name: "Bob", age: 7)
255
- expect(result).to be_a(TeckelOperationPredefinedClassesTest::CreateUserError)
256
- expect(result).to have_attributes(
257
- message: "Could not create User",
258
- status_code: 400,
259
- meta: { validation: [{ age: "underage" }] }
260
- )
261
- end
262
- end
263
- end
264
-
265
- context "inline classes" do
266
- specify "Input" do
267
- expect(TeckelOperationInlineClassesTest::CreateUser.input).to be <= Dry::Struct
268
- end
269
-
270
- specify "Output" do
271
- expect(TeckelOperationInlineClassesTest::CreateUser.output).to be_a Dry::Types::Constrained
272
- end
273
-
274
- specify "Error" do
275
- expect(TeckelOperationInlineClassesTest::CreateUser.error).to be <= Dry::Struct
276
- end
277
-
278
- context "success" do
279
- specify do
280
- result = TeckelOperationInlineClassesTest::CreateUser.call(name: "Bob", age: 23)
281
- expect(result).to be_a(User)
282
- end
283
- end
284
-
285
- context "error" do
286
- specify do
287
- result = TeckelOperationInlineClassesTest::CreateUser.call(name: "Bob", age: 7)
288
- expect(result).to have_attributes(
289
- message: "Could not create User",
290
- status_code: 400,
291
- meta: { validation: [{ age: "underage" }] }
292
- )
293
- end
294
- end
295
- end
296
-
297
- context "annon classes" do
298
- specify "output" do
299
- expect(TeckelOperationAnnonClassesTest::CreateUser.call(name: "Bob", age: 23)).to be_a(User)
300
- end
301
-
302
- specify "errors" do
303
- expect(TeckelOperationAnnonClassesTest::CreateUser.call(name: "Bob", age: 10)).to eq(message: "Could not save User", errors: [{ age: "underage" }])
304
- end
305
- end
306
-
307
- context "keyword contracts" do
308
- specify do
309
- expect(TeckelOperationKeywordContracts::MyOperation.call(name: "Bob", age: 23)).to be_a(User)
310
- end
311
- end
312
-
313
- context "splat contracts" do
314
- specify do
315
- expect(TeckelOperationCreateUserSplatInit::MyOperation.call(["Bob", 23])).to be_a(User)
316
- end
317
- end
318
-
319
- context "generated output" do
320
- specify "result" do
321
- result = TeckelOperationGeneratedOutputTest::MyOperation.call
322
- expect(result).to be_a(Struct)
323
- expect(result.some_key).to eq("some_value")
324
- end
325
- end
326
-
327
- context "inject settings" do
328
- it "settings in operation instances are nil by default" do
329
- op = TeckelOperationInjectSettingsTest::MyOperation.new
330
- expect(op.settings).to be_nil
331
- end
332
-
333
- it "uses injected data" do
334
- result =
335
- TeckelOperationInjectSettingsTest::MyOperation.
336
- with(injected: [:stuff]).
337
- call
338
-
339
- expect(result).to eq([:stuff, :operation_data])
340
-
341
- expect(TeckelOperationInjectSettingsTest::MyOperation.call).to eq([:operation_data])
342
- end
343
-
344
- specify "calling `with` multiple times raises an error" do
345
- op = TeckelOperationInjectSettingsTest::MyOperation.with(injected: :stuff_1)
346
-
347
- expect {
348
- op.with(more: :stuff_2)
349
- }.to raise_error(Teckel::Error, "Operation already has settings assigned.")
350
- end
351
- end
352
-
353
- context "operation with no settings" do
354
- it "uses None as default settings class" do
355
- expect(TeckelOperationNoSettingsTest::MyOperation.settings).to eq(Teckel::Contracts::None)
356
- expect(TeckelOperationNoSettingsTest::MyOperation.new.settings).to be_nil
357
- end
358
-
359
- it "raises error when trying to set settings" do
360
- expect {
361
- TeckelOperationNoSettingsTest::MyOperation.with(any: :thing)
362
- }.to raise_error(ArgumentError, "None called with arguments")
363
- end
364
- end
365
-
366
- context "None in, out, err" do
367
- let(:operation) { TeckelOperationNoneDataTest::MyOperation }
368
-
369
- it "raises error when called with input data" do
370
- expect { operation.call("stuff") }.to raise_error(ArgumentError)
371
- end
372
-
373
- it "raises error when fail! with data" do
374
- expect {
375
- operation.with(fail_it: true, fail_data: "stuff").call
376
- }.to raise_error(ArgumentError)
377
- end
378
-
379
- it "returns nil as failure result when fail! without arguments" do
380
- expect(operation.with(fail_it: true).call).to be_nil
381
- end
382
-
383
- it "raises error when success! with data" do
384
- expect {
385
- operation.with(success_it: true, success_data: "stuff").call
386
- }.to raise_error(ArgumentError)
387
- end
388
-
389
- it "returns nil as success result when success! without arguments" do
390
- expect(operation.with(success_it: true).call).to be_nil
391
- end
392
-
393
- it "returns nil as success result when returning nil" do
394
- expect(operation.call).to be_nil
395
- end
396
- end
397
-
398
- describe "#finalize!" do
399
- let(:frozen_error) do
400
- # different ruby versions raise different errors
401
- defined?(FrozenError) ? FrozenError : RuntimeError
402
- end
403
-
404
- subject do
405
- Class.new do
406
- include ::Teckel::Operation
407
-
408
- input Struct.new(:input_data)
409
- output Struct.new(:output_data)
410
-
411
- def call(input)
412
- success!(input.input_data * 2)
413
- end
414
- end
415
- end
416
-
417
- it "fails b/c error config is missing" do
418
- expect {
419
- subject.finalize!
420
- }.to raise_error(Teckel::MissingConfigError, "Missing error config for #{subject}")
421
- end
422
-
423
- specify "#dup" do
424
- new_operation = subject.dup
425
- new_operation.error Struct.new(:error)
426
- expect { new_operation.finalize! }.to_not raise_error
427
-
428
- expect {
429
- subject.finalize!
430
- }.to raise_error(Teckel::MissingConfigError, "Missing error config for #{subject}")
431
- end
432
-
433
- specify "#clone" do
434
- new_operation = subject.clone
435
- new_operation.error Struct.new(:error)
436
- expect { new_operation.finalize! }.to_not raise_error
437
-
438
- expect {
439
- subject.finalize!
440
- }.to raise_error(Teckel::MissingConfigError, "Missing error config for #{subject}")
441
- end
442
-
443
- it "rejects any config changes" do
444
- subject.error Struct.new(:error)
445
- expect { subject.finalize! }.to_not raise_error
446
-
447
- # no more after finalize!
448
- subject.finalize!
449
-
450
- expect {
451
- subject.error Struct.new(:other_error)
452
- }.to raise_error(Teckel::FrozenConfigError, "Configuration error is already set")
453
- end
454
-
455
- it "runs" do
456
- subject.error Struct.new(:error)
457
- subject.finalize!
458
-
459
- result = subject.call("test")
460
- expect(result.output_data).to eq("testtest")
461
- end
462
-
463
- it "accepts mocks" do
464
- subject.error Struct.new(:error)
465
- subject.finalize!
466
-
467
- allow(subject).to receive(:call) { :mocked }
468
- expect(subject.call).to eq(:mocked)
469
- end
470
- end
471
-
472
- describe "overwriting configs is not allowed" do
473
- it "raises" do
474
- expect {
475
- Class.new do
476
- include ::Teckel::Operation
477
- input none
478
- input Struct.new(:name)
479
- end
480
- }.to raise_error Teckel::FrozenConfigError, "Configuration input is already set"
481
- end
482
- end
483
- end
@@ -1,193 +0,0 @@
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
data/spec/result_spec.rb DELETED
@@ -1,22 +0,0 @@
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