functional-light-service 6.0.0 → 6.1.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: a02972ac6e701ec1ed5ceafcea5880e961ccbaf23ff886fe8f490c5bb514e6e1
4
- data.tar.gz: 0a35af1df1d0820de886bae015cc84846dc0809d4602d12528c7f716b537a2f9
3
+ metadata.gz: 436934533c3db4aa6ac6e3ce7410b67454fe0e7f3f8b516e9b6ba7674e86fc29
4
+ data.tar.gz: c4a36634905f4253a0d32bf9068b70a236f67291e43e20d02f3617679a797ef7
5
5
  SHA512:
6
- metadata.gz: 1ec467e5b62e74629391dfaf9b2e0ede5534338b3ce1411f9c0f7c4a9c38b7aa880701ddd98e4fd5f8a61fa7e8dc741633c626a6b422c544084c825e883b1453
7
- data.tar.gz: e526d9bd6c3736ea555db60add374336dd6a9e957f7fae265bee44282ffe0dbb2fb43bba03eaafd210aab7bcf1f9d1f7af96391fa34b680dacfe4cd148fff4ca
6
+ metadata.gz: 8ba109d1688a3e2e3c1904682d55e800cd841fa7870364f04916d9f5528ac0e954fe28f650261b1217c0d0819f0df0c1143331fd82f72a1dfc6726549a95cc82
7
+ data.tar.gz: 4da5cc5beae3e452b7400ce696a05a7ab5e69c354d27cd3c3e918c86ef47166789a7349a9967215ca53f35cb4d00dac986ac109d673cee00e07abace42b6569f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 6.1.0 (2026-07-03)
2
+
3
+ ### Added
4
+ - Sequencer: do-notation per Result con in_sequence/get/let/and_then/observe/and_yield, portato dalla gem deterministic (MIT) e incluso nel Prelude ( 2026-07-03 ) [ sphynx79]
5
+
6
+
7
+
1
8
  ## 6.0.0 (2026-07-03)
2
9
 
3
10
  Release maggiore guidata da un audit tecnico completo (vedi `AUDIT-functional-light-service.md`
data/README.md CHANGED
@@ -28,6 +28,7 @@
28
28
  * [Usage](#functional-usage)
29
29
  * [Result: Success & Failure](#functional-usage-success-failure)
30
30
  * [Result Chaining](#functional-usage-chaining)
31
+ * [Sequencing (do-notation)](#functional-usage-sequencing)
31
32
  * [Complex Example in a Builder Action](#functional-usage-complex-action)
32
33
  * [Pattern matching](#functional-usage-pattern-matching)
33
34
  * [Option](#functional-usage-option)
@@ -1150,6 +1151,39 @@ Success(params) >>
1150
1151
  build_response
1151
1152
  ```
1152
1153
 
1154
+ ### Sequencing (do-notation) – `in_sequence` <a name="functional-usage-sequencing"></a>
1155
+
1156
+ When a pipeline needs the intermediate values of earlier steps, chaining alone
1157
+ gets awkward. `in_sequence` (ported from the [deterministic](https://github.com/pzol/deterministic)
1158
+ gem, MIT License) gives you a do-notation style block: each step returns a
1159
+ `Result`, the sequence short-circuits on the first `Failure`, and values bound
1160
+ with `get`/`let` are available to all subsequent steps by name.
1161
+
1162
+ ```ruby
1163
+ class DownloadRemit
1164
+ include FunctionalLightService::Prelude
1165
+
1166
+ def call(row)
1167
+ in_sequence do
1168
+ get(:url) { extract_url(row) } # binds the Success value to :url
1169
+ get(:file) { fetch(url) } # :url is available here
1170
+ let(:name) { File.basename(url) } # binds a plain (non-Result) value
1171
+ and_then { validate(file) } # step without binding
1172
+ observe { logger.info("got #{name}") } # side effect, return value ignored
1173
+ and_yield { Success(name) } # final result of the sequence
1174
+ end
1175
+ end
1176
+ end
1177
+ ```
1178
+
1179
+ * `get(:name) { ... }` – runs a step returning a `Result`; on `Success` binds the
1180
+ unwrapped value to `name`, on `Failure` stops the sequence and returns it.
1181
+ * `let(:name) { ... }` – binds the block's plain return value (no `Result` involved).
1182
+ * `and_then { ... }` – runs a step returning a `Result` without binding its value.
1183
+ * `observe { ... }` – runs a side effect; its return value is ignored.
1184
+ * `and_yield { ... }` – mandatory final step; its `Result` is the value of the
1185
+ whole `in_sequence` block.
1186
+
1153
1187
  #### Complex Example in a Builder Action <a name="functional-usage-complex-action"></a>
1154
1188
 
1155
1189
  ```ruby
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module FunctionalLightService
6
+ # Do-notation for Result: chains steps that return Success/Failure,
7
+ # short-circuiting on the first Failure. Ported from the deterministic
8
+ # gem (MIT License, Copyright (c) Piotr Zolnierek and contributors,
9
+ # https://github.com/pzol/deterministic).
10
+ #
11
+ # in_sequence do
12
+ # get(:user) { fetch_user(id) } # binds the Success value to :user
13
+ # let(:name) { user.fetch(:name) } # binds a plain (non-Result) value
14
+ # and_then { validate(user) } # step without binding
15
+ # observe { log(name) } # side effect, return value ignored
16
+ # and_yield { Success(name) } # final result of the sequence
17
+ # end
18
+ module Sequencer
19
+ class InvalidSequenceError < StandardError; end
20
+
21
+ module Operation
22
+ Get = Struct.new(:block, :name)
23
+ Let = Struct.new(:block, :name)
24
+ AndThen = Struct.new(:block)
25
+ Observe = Struct.new(:block)
26
+ AndYield = Struct.new(:block)
27
+ end
28
+
29
+ def in_sequence(&)
30
+ sequencer = Sequencer.new(self)
31
+ sequencer.instance_eval(&)
32
+ sequencer.yield
33
+ end
34
+
35
+ class Sequencer
36
+ def initialize(instance)
37
+ @operations = []
38
+ @operation_wrapper = OperationWrapper.new(instance)
39
+ end
40
+
41
+ def get(name, &block)
42
+ raise ArgumentError, 'no block given' unless block_given?
43
+ raise InvalidSequenceError, 'and_yield already called' if @sequenced_operations
44
+
45
+ @operations << Operation::Get.new(block, name)
46
+ end
47
+
48
+ def let(name, &block)
49
+ raise ArgumentError, 'no block given' unless block_given?
50
+ raise InvalidSequenceError, 'and_yield already called' if @sequenced_operations
51
+
52
+ @operations << Operation::Let.new(block, name)
53
+ end
54
+
55
+ def and_then(&block)
56
+ raise ArgumentError, 'no block given' unless block_given?
57
+ raise InvalidSequenceError, 'and_yield already called' if @sequenced_operations
58
+
59
+ @operations << Operation::AndThen.new(block)
60
+ end
61
+
62
+ def observe(&block)
63
+ raise ArgumentError, 'no block given' unless block_given?
64
+ raise InvalidSequenceError, 'and_yield already called' if @sequenced_operations
65
+
66
+ @operations << Operation::Observe.new(block)
67
+ end
68
+
69
+ def and_yield(&block)
70
+ raise ArgumentError, 'no block given' unless block_given?
71
+ raise InvalidSequenceError, 'and_yield already called' if @sequenced_operations
72
+
73
+ @operations << Operation::AndYield.new(block)
74
+
75
+ prepare_sequenced_operations
76
+ end
77
+
78
+ def yield
79
+ raise InvalidSequenceError, 'and_yield not called' unless @sequenced_operations
80
+
81
+ @operation_wrapper.instance_eval(&@sequenced_operations)
82
+ end
83
+
84
+ private
85
+
86
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
87
+ def prepare_sequenced_operations
88
+ operations = @operations
89
+
90
+ @sequenced_operations = ->(_) do
91
+ operations.reduce(Result::Success.new(nil)) do |last_result, operation|
92
+ last_result.map do
93
+ case operation
94
+ when Operation::Get
95
+ result = instance_eval(&operation.block)
96
+ result.map do |output|
97
+ # Runs in the context of the OperationWrapper, so the
98
+ # bound value is stored within the wrapper itself.
99
+ @gotten_results[operation.name] = output
100
+ result
101
+ end
102
+ when Operation::Let
103
+ @gotten_results[operation.name] = instance_eval(&operation.block)
104
+ last_result
105
+ when Operation::Observe
106
+ instance_eval(&operation.block)
107
+ last_result
108
+ when Operation::AndThen, Operation::AndYield
109
+ instance_eval(&operation.block)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
116
+ end
117
+
118
+ # OperationWrapper proxies all method calls to the wrapped instance, but
119
+ # first checks if the name of the called method matches a value stored
120
+ # within @gotten_results and returns the value if it does.
121
+ class OperationWrapper < SimpleDelegator
122
+ def initialize(*args)
123
+ super
124
+ @gotten_results = {}
125
+ end
126
+
127
+ def method_missing(name, *args, **kwargs, &)
128
+ if @gotten_results.key?(name)
129
+ @gotten_results[name]
130
+ else
131
+ super
132
+ end
133
+ end
134
+
135
+ def respond_to_missing?(name, include_private = false)
136
+ @gotten_results.key?(name) || super
137
+ end
138
+ end
139
+ end
140
+
141
+ module Prelude
142
+ include Sequencer
143
+ end
144
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FunctionalLightService
4
- VERSION = "6.0.0"
4
+ VERSION = "6.1.0"
5
5
  end
@@ -12,6 +12,7 @@ require 'functional-light-service/functional/enum'
12
12
  require 'functional-light-service/functional/result'
13
13
  require 'functional-light-service/functional/option'
14
14
  require 'functional-light-service/functional/null'
15
+ require 'functional-light-service/functional/sequencer'
15
16
  require 'functional-light-service/errors'
16
17
  require 'functional-light-service/configuration'
17
18
  require 'functional-light-service/localization_adapter'
@@ -0,0 +1,506 @@
1
+ require 'spec_helper'
2
+
3
+ describe FunctionalLightService::Sequencer do
4
+ include FunctionalLightService::Prelude::Result
5
+
6
+ let(:test_class) { Class.new { include FunctionalLightService::Sequencer } }
7
+ let(:test_instance) { test_class.new }
8
+ let(:arbitrary_success) { Success(double) }
9
+
10
+ # This mock method makes #arbitrary_success available within the operations
11
+ # in the test sequences
12
+ before { allow(test_instance).to receive(:arbitrary_success).and_return(arbitrary_success) }
13
+
14
+ it 'requires #and_yield to be specified' do
15
+ expect do
16
+ in_sequence do
17
+ get(:_) { arbitrary_success }
18
+ end
19
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield not called')
20
+ end
21
+
22
+ it 'does not allow calling #and_yield multiple times' do
23
+ expect do
24
+ in_sequence do
25
+ and_yield { arbitrary_success }
26
+ and_yield { arbitrary_success }
27
+ end
28
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield already called')
29
+ end
30
+
31
+ it 'does not allow calling #get after #and_yield' do
32
+ expect do
33
+ in_sequence do
34
+ and_yield { arbitrary_success }
35
+ get(:_) { arbitrary_success }
36
+ end
37
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield already called')
38
+ end
39
+
40
+ it 'does not allow calling #let after #and_yield' do
41
+ expect do
42
+ in_sequence do
43
+ and_yield { arbitrary_success }
44
+ let(:_) { arbitrary_success }
45
+ end
46
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield already called')
47
+ end
48
+
49
+ it 'does not allow calling #and_then after #and_yield' do
50
+ expect do
51
+ in_sequence do
52
+ and_yield { arbitrary_success }
53
+ and_then { arbitrary_success }
54
+ end
55
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield already called')
56
+ end
57
+
58
+ it 'does not allow calling #observe after #and_yield' do
59
+ expect do
60
+ in_sequence do
61
+ and_yield { arbitrary_success }
62
+ observe { arbitrary_success }
63
+ end
64
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield already called')
65
+ end
66
+
67
+ it 'requires a block for every operation' do
68
+ expect { in_sequence { get(:_) } }.to raise_error(ArgumentError, 'no block given')
69
+ expect { in_sequence { let(:_) } }.to raise_error(ArgumentError, 'no block given')
70
+ expect { in_sequence { and_then } }.to raise_error(ArgumentError, 'no block given')
71
+ expect { in_sequence { observe } }.to raise_error(ArgumentError, 'no block given')
72
+ expect { in_sequence { and_yield } }.to raise_error(ArgumentError, 'no block given')
73
+ end
74
+
75
+ context 'when #and_yield succeeds' do
76
+ let(:yielder_result) { Success('yield') }
77
+ before { allow(test_instance).to receive(:yielder).and_return(yielder_result) }
78
+
79
+ it "returns and_yield's result" do
80
+ result = in_sequence do
81
+ and_yield { yielder }
82
+ end
83
+ expect(result).to eq(yielder_result)
84
+ end
85
+
86
+ it 'ignores the return value of the #in_sequence block' do
87
+ result = in_sequence do
88
+ and_yield { yielder }
89
+ 'a different return value'
90
+ end
91
+ expect(result).to eq(yielder_result)
92
+ end
93
+ end
94
+
95
+ context 'when #and_yield fails' do
96
+ let(:yielder_result) { Failure('yield') }
97
+ before { allow(test_instance).to receive(:yielder).and_return(yielder_result) }
98
+
99
+ it "returns and_yield's result" do
100
+ result = in_sequence do
101
+ and_yield { yielder }
102
+ end
103
+ expect(result).to eq(yielder_result)
104
+ end
105
+ end
106
+
107
+ context 'when #get succeeds' do
108
+ let(:getter_result) { Success('get') }
109
+ before { allow(test_instance).to receive(:getter).and_return(getter_result) }
110
+
111
+ it 'its result is available in a subsequent #get' do
112
+ allow(test_instance).to receive(:second_getter).and_return(arbitrary_success)
113
+
114
+ in_sequence do
115
+ get(:get_result) { getter }
116
+ get(:_) { second_getter(get_result) }
117
+ and_yield { arbitrary_success }
118
+ end
119
+
120
+ expect(test_instance).to have_received(:second_getter).with(getter_result.value)
121
+ end
122
+
123
+ it 'its result is not available in a previous #get' do
124
+ allow(test_instance).to receive(:second_getter)
125
+
126
+ expect do
127
+ in_sequence do
128
+ get(:_) { second_getter(get_result) }
129
+ get(:get_result) { getter }
130
+ and_yield { arbitrary_success }
131
+ end
132
+ end.to raise_error(NameError)
133
+ end
134
+
135
+ it 'its result is available in a subsequent #and_then' do
136
+ allow(test_instance).to receive(:and_then_function).and_return(arbitrary_success)
137
+
138
+ in_sequence do
139
+ get(:get_result) { getter }
140
+ and_then { and_then_function(get_result) }
141
+ and_yield { arbitrary_success }
142
+ end
143
+
144
+ expect(test_instance).to have_received(:and_then_function).with(getter_result.value)
145
+ end
146
+
147
+ it 'its result is available in a subsequent #observe' do
148
+ allow(test_instance).to receive(:observer)
149
+
150
+ in_sequence do
151
+ get(:get_result) { getter }
152
+ observe { observer(get_result) }
153
+ and_yield { arbitrary_success }
154
+ end
155
+
156
+ expect(test_instance).to have_received(:observer).with(getter_result.value)
157
+ end
158
+
159
+ it 'its result is available in #and_yield' do
160
+ allow(test_instance).to receive(:yielder).and_return(arbitrary_success)
161
+
162
+ in_sequence do
163
+ get(:get_result) { getter }
164
+ and_yield { yielder(get_result) }
165
+ end
166
+
167
+ expect(test_instance).to have_received(:yielder).with(getter_result.value)
168
+ end
169
+ end
170
+
171
+ context 'when multiple #gets succeed' do
172
+ let(:first_getter_result) { Success('get1') }
173
+ let(:second_getter_result) { Success('get2') }
174
+
175
+ before do
176
+ allow(test_instance).to receive(:first_getter).and_return(first_getter_result)
177
+ allow(test_instance).to receive(:second_getter).and_return(second_getter_result)
178
+ end
179
+
180
+ it 'both results are available in subsequent operations' do
181
+ allow(test_instance).to receive(:yielder).and_return(arbitrary_success)
182
+
183
+ in_sequence do
184
+ get(:first_get_result) { first_getter }
185
+ get(:second_get_result) { second_getter }
186
+ and_yield { yielder(first_get_result, second_get_result) }
187
+ end
188
+
189
+ expect(test_instance).to have_received(:yielder)
190
+ .with(first_getter_result.value, second_getter_result.value)
191
+ end
192
+ end
193
+
194
+ context 'when #get fails' do
195
+ let(:getter_result) { Failure('get') }
196
+ before { allow(test_instance).to receive(:getter).and_return(getter_result) }
197
+
198
+ it 'does not invoke subsequent #gets' do
199
+ allow(test_instance).to receive(:second_getter).and_return(arbitrary_success)
200
+
201
+ in_sequence do
202
+ get(:get_result) { getter }
203
+ get(:_) { second_getter(get_result) }
204
+ and_yield { arbitrary_success }
205
+ end
206
+
207
+ expect(test_instance).not_to have_received(:second_getter)
208
+ end
209
+
210
+ it 'does not invoke subsequent #and_thens' do
211
+ allow(test_instance).to receive(:and_then_function).and_return(arbitrary_success)
212
+
213
+ in_sequence do
214
+ get(:get_result) { getter }
215
+ and_then { and_then_function(get_result) }
216
+ and_yield { arbitrary_success }
217
+ end
218
+
219
+ expect(test_instance).not_to have_received(:and_then_function)
220
+ end
221
+
222
+ it 'does not invoke subsequent #observes' do
223
+ allow(test_instance).to receive(:observer)
224
+
225
+ in_sequence do
226
+ get(:get_result) { getter }
227
+ observe { observer(get_result) }
228
+ and_yield { arbitrary_success }
229
+ end
230
+
231
+ expect(test_instance).not_to have_received(:observer)
232
+ end
233
+
234
+ it 'does not invoke #and_yield' do
235
+ allow(test_instance).to receive(:yielder).and_return(arbitrary_success)
236
+
237
+ in_sequence do
238
+ get(:get_result) { getter }
239
+ and_yield { yielder }
240
+ end
241
+
242
+ expect(test_instance).not_to have_received(:yielder)
243
+ end
244
+
245
+ it 'returns the failure' do
246
+ result = in_sequence do
247
+ get(:get_result) { getter }
248
+ and_yield { arbitrary_success }
249
+ end
250
+
251
+ expect(result).to eq(getter_result)
252
+ end
253
+ end
254
+
255
+ context 'when #let succeeds' do
256
+ let(:object) { { :a => 'a' } }
257
+ before { allow(test_instance).to receive(:object).and_return(object) }
258
+
259
+ it 'its result is available in a subsequent #let' do
260
+ allow(test_instance).to receive(:test_function)
261
+
262
+ in_sequence do
263
+ let(:a) { object.fetch(:a) }
264
+ let(:_) { test_function(a) }
265
+ and_yield { arbitrary_success }
266
+ end
267
+
268
+ expect(test_instance).to have_received(:test_function).with(object.fetch(:a))
269
+ end
270
+
271
+ it 'its result is available in a subsequent #and_then' do
272
+ allow(test_instance).to receive(:and_then_function).and_return(arbitrary_success)
273
+
274
+ in_sequence do
275
+ let(:a) { object.fetch(:a) }
276
+ and_then { and_then_function(a) }
277
+ and_yield { arbitrary_success }
278
+ end
279
+
280
+ expect(test_instance).to have_received(:and_then_function).with(object.fetch(:a))
281
+ end
282
+
283
+ it 'its result is available in a subsequent #observe' do
284
+ allow(test_instance).to receive(:observer)
285
+
286
+ in_sequence do
287
+ let(:a) { object.fetch(:a) }
288
+ observe { observer(a) }
289
+ and_yield { arbitrary_success }
290
+ end
291
+
292
+ expect(test_instance).to have_received(:observer).with(object.fetch(:a))
293
+ end
294
+
295
+ it 'its result is available in #and_yield' do
296
+ allow(test_instance).to receive(:yielder).and_return(arbitrary_success)
297
+
298
+ in_sequence do
299
+ let(:a) { object.fetch(:a) }
300
+ and_yield { yielder(a) }
301
+ end
302
+
303
+ expect(test_instance).to have_received(:yielder).with(object.fetch(:a))
304
+ end
305
+ end
306
+
307
+ context 'when #let raises an error' do
308
+ let(:object) { { :a => 'a' } }
309
+ before { allow(test_instance).to receive(:object).and_return(object) }
310
+
311
+ it 'bubbles the raised error' do
312
+ expect do
313
+ in_sequence do
314
+ let(:b) { object.fetch(:b) }
315
+ and_yield { arbitrary_success }
316
+ end
317
+ end.to raise_error(KeyError)
318
+ end
319
+
320
+ it 'does not invoke #and_yield' do
321
+ allow(test_instance).to receive(:yielder).and_return(arbitrary_success)
322
+
323
+ begin
324
+ in_sequence do
325
+ let(:b) { object.fetch(:b) }
326
+ and_yield { yielder }
327
+ end
328
+ rescue KeyError
329
+ # Ignore
330
+ end
331
+
332
+ expect(test_instance).not_to have_received(:yielder)
333
+ end
334
+ end
335
+
336
+ context 'when #and_then succeeds' do
337
+ let(:and_then_result) { Success('and_then') }
338
+ before { allow(test_instance).to receive(:and_then_function).and_return(and_then_result) }
339
+
340
+ it 'continues the sequence' do
341
+ allow(test_instance).to receive(:another_step).and_return(arbitrary_success)
342
+
343
+ in_sequence do
344
+ and_then { and_then_function }
345
+ and_then { another_step }
346
+ and_yield { arbitrary_success }
347
+ end
348
+
349
+ expect(test_instance).to have_received(:another_step)
350
+ end
351
+ end
352
+
353
+ context 'when #and_then fails' do
354
+ let(:and_then_result) { Failure('and_then') }
355
+ before { allow(test_instance).to receive(:and_then_function).and_return(and_then_result) }
356
+
357
+ it 'does not continue the sequence' do
358
+ allow(test_instance).to receive(:another_step).and_return(arbitrary_success)
359
+
360
+ in_sequence do
361
+ and_then { and_then_function }
362
+ and_then { another_step }
363
+ and_yield { arbitrary_success }
364
+ end
365
+
366
+ expect(test_instance).not_to have_received(:another_step)
367
+ end
368
+
369
+ it 'returns the failure' do
370
+ result = in_sequence do
371
+ and_then { and_then_function }
372
+ and_yield { arbitrary_success }
373
+ end
374
+
375
+ expect(result).to eq(and_then_result)
376
+ end
377
+ end
378
+
379
+ context 'when #observe returns a failure' do
380
+ let(:observe_result) { Failure('observe') }
381
+ before { allow(test_instance).to receive(:observer).and_return(observe_result) }
382
+
383
+ it 'its return value is ignored and the sequence continues' do
384
+ allow(test_instance).to receive(:another_step).and_return(arbitrary_success)
385
+
386
+ in_sequence do
387
+ observe { observer }
388
+ and_then { another_step }
389
+ and_yield { arbitrary_success }
390
+ end
391
+
392
+ expect(test_instance).to have_received(:another_step)
393
+ end
394
+ end
395
+
396
+ it 'does not allow calling methods outside of the wrapped instance' do
397
+ expect do
398
+ in_sequence do
399
+ and_yield { top_level_test_method }
400
+ end
401
+ end.to raise_error(NameError)
402
+ end
403
+
404
+ context 'when including FunctionalLightService::Prelude' do
405
+ let(:test_class) { Class.new { include FunctionalLightService::Prelude } }
406
+
407
+ it '#in_sequence is available' do
408
+ expect do
409
+ in_sequence do
410
+ and_yield { arbitrary_success }
411
+ end
412
+ end.not_to raise_error
413
+ end
414
+ end
415
+
416
+ context 'readme example' do
417
+ let(:test_class) do
418
+ Class.new do
419
+ include FunctionalLightService::Prelude
420
+
421
+ # rubocop:disable Metrics/AbcSize
422
+ def call(input)
423
+ in_sequence do
424
+ get(:sanitized_input) { sanitize(input) }
425
+ and_then { validate(sanitized_input) }
426
+ get(:user) { get_user_from_db(sanitized_input) }
427
+ let(:name) { user.fetch(:name) }
428
+ observe { log('user name', name) }
429
+ get(:request) { build_request(sanitized_input, user) }
430
+ observe { log('sending request', request) }
431
+ get(:response) { send_request(request) }
432
+ observe { log('got response', response) }
433
+ and_yield { format_response(response) }
434
+ end
435
+ end
436
+ # rubocop:enable Metrics/AbcSize
437
+
438
+ def sanitize(input)
439
+ sanitized_input = input
440
+ Success(sanitized_input)
441
+ end
442
+
443
+ def validate(sanitized_input)
444
+ Success(sanitized_input)
445
+ end
446
+
447
+ def get_user_from_db(sanitized_input)
448
+ Success(:type => :admin, :id => sanitized_input.fetch(:id), :name => 'John')
449
+ end
450
+
451
+ def build_request(sanitized_input, user)
452
+ Success(:input => sanitized_input, :user => user)
453
+ end
454
+
455
+ def log(message, data)
456
+ # logger.info(message, data)
457
+ end
458
+
459
+ def send_request(_request)
460
+ Success(:status => 200)
461
+ end
462
+
463
+ def format_response(response)
464
+ Success(:response => response, :message => 'it worked')
465
+ end
466
+ end
467
+ end
468
+
469
+ it 'returns expected result' do
470
+ result = test_instance.call(:id => 1)
471
+
472
+ expect(result).to eq(Success(
473
+ :response => { :status => 200 },
474
+ :message => 'it worked'
475
+ ))
476
+ end
477
+
478
+ it 'logs expected values' do
479
+ allow(test_instance).to receive(:log).and_call_original
480
+
481
+ test_instance.call(:id => 1)
482
+
483
+ expect(test_instance).to have_received(:log)
484
+ .with('user name', 'John')
485
+ .ordered
486
+ expect(test_instance).to have_received(:log)
487
+ .with('sending request',
488
+ :input => { :id => 1 },
489
+ :user => { :type => :admin, :id => 1, :name => 'John' })
490
+ .ordered
491
+ expect(test_instance).to have_received(:log)
492
+ .with('got response', :status => 200)
493
+ .ordered
494
+ end
495
+ end
496
+
497
+ def in_sequence(&block)
498
+ test_instance.instance_eval do
499
+ in_sequence(&block)
500
+ end
501
+ end
502
+ end
503
+
504
+ def top_level_test_method
505
+ :empty
506
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: functional-light-service
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boscolo Michele
@@ -231,6 +231,7 @@ files:
231
231
  - lib/functional-light-service/functional/null.rb
232
232
  - lib/functional-light-service/functional/option.rb
233
233
  - lib/functional-light-service/functional/result.rb
234
+ - lib/functional-light-service/functional/sequencer.rb
234
235
  - lib/functional-light-service/localization_adapter.rb
235
236
  - lib/functional-light-service/organizer.rb
236
237
  - lib/functional-light-service/organizer/execute.rb
@@ -290,6 +291,7 @@ files:
290
291
  - spec/lib/deterministic/result/result_shared.rb
291
292
  - spec/lib/deterministic/result/success_spec.rb
292
293
  - spec/lib/deterministic/result_spec.rb
294
+ - spec/lib/deterministic/sequencer_spec.rb
293
295
  - spec/lib/edge_cases_spec.rb
294
296
  - spec/lib/enum_spec.rb
295
297
  - spec/lib/native_pattern_matching_spec.rb
@@ -332,7 +334,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
332
334
  - !ruby/object:Gem::Version
333
335
  version: '0'
334
336
  requirements: []
335
- rubygems_version: 3.6.9
337
+ rubygems_version: 4.0.3
336
338
  specification_version: 4
337
339
  summary: A service skeleton with an emphasis on simplicity with a pinch a functional
338
340
  programming