functional-light-service 0.4.4 → 6.0.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/.github/workflows/project-build.yml +43 -11
- data/.rubocop.yml +101 -160
- data/AUDIT-functional-light-service.md +352 -0
- data/Appraisals +4 -0
- data/CHANGELOG.md +118 -0
- data/Gemfile +0 -2
- data/README.md +1544 -1426
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/audit/bench.rb +99 -0
- data/audit/verify_findings.rb +172 -0
- data/functional-light-service.gemspec +15 -16
- data/lib/functional-light-service/action.rb +97 -101
- data/lib/functional-light-service/configuration.rb +26 -24
- data/lib/functional-light-service/context/key_verifier.rb +124 -118
- data/lib/functional-light-service/context.rb +63 -20
- data/lib/functional-light-service/deprecations.rb +26 -0
- data/lib/functional-light-service/errors.rb +8 -6
- data/lib/functional-light-service/functional/enum.rb +286 -250
- data/lib/functional-light-service/functional/maybe.rb +21 -15
- data/lib/functional-light-service/functional/monad.rb +77 -66
- data/lib/functional-light-service/functional/null.rb +88 -74
- data/lib/functional-light-service/functional/option.rb +100 -97
- data/lib/functional-light-service/functional/result.rb +129 -116
- data/lib/functional-light-service/localization_adapter.rb +48 -47
- data/lib/functional-light-service/organizer/execute.rb +16 -14
- data/lib/functional-light-service/organizer/iterate.rb +30 -25
- data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
- data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
- data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
- data/lib/functional-light-service/organizer/with_callback.rb +28 -26
- data/lib/functional-light-service/organizer/with_reducer.rb +81 -71
- data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -105
- data/lib/functional-light-service/organizer.rb +114 -104
- data/lib/functional-light-service/testing/context_factory.rb +48 -42
- data/lib/functional-light-service/testing.rb +3 -1
- data/lib/functional-light-service/version.rb +5 -3
- data/lib/functional-light-service.rb +30 -28
- data/spec/acceptance/after_actions_spec.rb +87 -71
- data/spec/acceptance/before_actions_spec.rb +115 -98
- data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
- data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
- data/spec/acceptance/fail_spec.rb +52 -50
- data/spec/acceptance/message_localization_spec.rb +119 -118
- data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
- data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
- data/spec/acceptance/organizer/iterate_spec.rb +7 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +89 -83
- data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
- data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
- data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
- data/spec/acceptance/rollback_spec.rb +183 -132
- data/spec/action_expects_and_promises_spec.rb +97 -93
- data/spec/action_promised_keys_spec.rb +126 -122
- data/spec/action_spec.rb +8 -0
- data/spec/context_spec.rb +289 -197
- data/spec/examples/controller_spec.rb +63 -63
- data/spec/examples/validate_address_spec.rb +38 -37
- data/spec/lib/deterministic/currify_spec.rb +90 -88
- data/spec/lib/deterministic/null_spec.rb +6 -1
- data/spec/lib/deterministic/option_spec.rb +140 -133
- data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
- data/spec/lib/deterministic/result/result_shared.rb +3 -2
- data/spec/lib/deterministic/result_spec.rb +2 -2
- data/spec/lib/edge_cases_spec.rb +156 -0
- data/spec/lib/enum_spec.rb +1 -1
- data/spec/lib/native_pattern_matching_spec.rb +74 -0
- data/spec/organizer_spec.rb +115 -93
- data/spec/readme_spec.rb +45 -47
- data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
- data/spec/sample/calculates_tax_spec.rb +1 -1
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
- data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
- data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
- data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
- data/spec/spec_helper.rb +21 -13
- data/spec/test_doubles.rb +628 -564
- data/spec/testing/context_factory_spec.rb +21 -0
- metadata +49 -117
- data/.travis.yml +0 -24
- data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
- data/spec/acceptance/include_warning_spec.rb +0 -29
data/Rakefile
CHANGED
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.5.4
|
data/audit/bench.rb
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Micro-benchmark dei costi nascosti individuati nell'audit
|
|
2
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
3
|
+
require 'functional-light-service'
|
|
4
|
+
require 'benchmark/ips'
|
|
5
|
+
|
|
6
|
+
include FunctionalLightService::Prelude::Result
|
|
7
|
+
include FunctionalLightService::Prelude::Option
|
|
8
|
+
|
|
9
|
+
puts "Ruby #{RUBY_VERSION}, YJIT: #{defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled? ? 'on' : 'off'}"
|
|
10
|
+
puts
|
|
11
|
+
|
|
12
|
+
some = Some(42)
|
|
13
|
+
success = Success(42)
|
|
14
|
+
|
|
15
|
+
# 1) Costo del motore match (Option#value_or usa match) vs equivalente diretto
|
|
16
|
+
Benchmark.ips do |x|
|
|
17
|
+
x.report("Option#value_or (match engine)") { some.value_or(0) }
|
|
18
|
+
x.report("equivalente is_a? diretto") { some.is_a?(FunctionalLightService::Option::Some) ? some.value : 0 }
|
|
19
|
+
x.report("case/in nativo Ruby") do
|
|
20
|
+
case some
|
|
21
|
+
in FunctionalLightService::Option::Some then some.value
|
|
22
|
+
else 0
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
x.compare!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# 2) match esplicito con guard (Struct.new per chiamata) su Result#+
|
|
29
|
+
Benchmark.ips do |x|
|
|
30
|
+
other = Success(1)
|
|
31
|
+
x.report("Result#+ (match con guard -> Struct.new/call)") { success + other }
|
|
32
|
+
x.report("somma diretta is_a?") do
|
|
33
|
+
if success.is_a?(FunctionalLightService::Result::Success) && other.is_a?(FunctionalLightService::Result::Success)
|
|
34
|
+
FunctionalLightService::Result::Success.new(success.value + other.value)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
x.compare!
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 3) Result#map (bind, senza match) — per confronto: la parte "sana"
|
|
41
|
+
Benchmark.ips do |x|
|
|
42
|
+
f = ->(v) { Success(v + 1) }
|
|
43
|
+
x.report("Result#map via bind") { success.map(f) }
|
|
44
|
+
x.report("lambda diretta") { success.is_a?(FunctionalLightService::Result::Success) ? f.call(success.value) : success }
|
|
45
|
+
x.compare!
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 4) Overhead di Organizer.with: caller(1..1) + methods.include?(:call)
|
|
49
|
+
Benchmark.ips do |x|
|
|
50
|
+
klass = Class.new { def self.call; end }
|
|
51
|
+
x.report("caller(1..1) + methods.include?") do
|
|
52
|
+
c = caller(1..1).first
|
|
53
|
+
c =~ /`(.*)'/
|
|
54
|
+
klass.methods.include?(:call)
|
|
55
|
+
end
|
|
56
|
+
x.report("respond_to?(:call) soltanto") { klass.respond_to?(:call) }
|
|
57
|
+
x.compare!
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# 5) define_accessor_methods_for_keys: singleton methods per ogni context
|
|
61
|
+
Benchmark.ips do |x|
|
|
62
|
+
keys = %i[number total counter]
|
|
63
|
+
x.report("nuovo Context + define accessor per keys") do
|
|
64
|
+
ctx = FunctionalLightService::Context.make(:number => 1, :total => 2, :counter => 3)
|
|
65
|
+
ctx.define_accessor_methods_for_keys(keys)
|
|
66
|
+
end
|
|
67
|
+
x.report("nuovo Context senza accessor") do
|
|
68
|
+
FunctionalLightService::Context.make(:number => 1, :total => 2, :counter => 3)
|
|
69
|
+
end
|
|
70
|
+
x.compare!
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# 6) Context#[] con alias attivi (Hash#key reverse scan) vs Hash puro
|
|
74
|
+
Benchmark.ips do |x|
|
|
75
|
+
ctx = FunctionalLightService::Context.make(:a => 1, :b => 2, :c => 3)
|
|
76
|
+
ctx.assign_aliases(:a => :alfa)
|
|
77
|
+
h = { :a => 1, :b => 2, :c => 3 }
|
|
78
|
+
x.report("Context#[] (con lookup alias)") { ctx[:b] }
|
|
79
|
+
x.report("Hash#[] puro") { h[:b] }
|
|
80
|
+
x.compare!
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# 7) Organizer end-to-end: quota di overhead per call minimale
|
|
84
|
+
class BenchAdd
|
|
85
|
+
extend FunctionalLightService::Action
|
|
86
|
+
expects :number
|
|
87
|
+
executed { |ctx| ctx[:number] = ctx[:number] + 1 }
|
|
88
|
+
end
|
|
89
|
+
class BenchOrg
|
|
90
|
+
extend FunctionalLightService::Organizer
|
|
91
|
+
def self.call(n)
|
|
92
|
+
with(:number => n).reduce([BenchAdd])
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
Benchmark.ips do |x|
|
|
96
|
+
x.report("Organizer.call 1 action") { BenchOrg.call(1) }
|
|
97
|
+
x.report("lavoro utile equivalente") { { :number => 1 }.tap { |h| h[:number] += 1 } }
|
|
98
|
+
x.compare!
|
|
99
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Verifica dei finding dell'audit — eseguito contro lib/ reale
|
|
2
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
3
|
+
require 'functional-light-service'
|
|
4
|
+
|
|
5
|
+
results = []
|
|
6
|
+
def check(results, name)
|
|
7
|
+
ok, detail = yield
|
|
8
|
+
results << [name, ok, detail]
|
|
9
|
+
rescue => e
|
|
10
|
+
results << [name, :exception, "#{e.class}: #{e.message}"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# ---------- F1: before_actions perso alla seconda chiamata ----------
|
|
14
|
+
class AddOne
|
|
15
|
+
extend FunctionalLightService::Action
|
|
16
|
+
expects :number
|
|
17
|
+
promises :number
|
|
18
|
+
executed { |ctx| ctx.number = ctx.number + 1 }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Org1
|
|
22
|
+
extend FunctionalLightService::Organizer
|
|
23
|
+
before_actions ->(ctx) { ctx.number -= 10 if ctx.current_action == AddOne }
|
|
24
|
+
def self.call(number)
|
|
25
|
+
with(:number => number).reduce([AddOne])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
check(results, "F1 before_actions perso alla 2a chiamata") do
|
|
30
|
+
r1 = Org1.call(0).fetch(:number) # atteso: -10 + 1 = -9
|
|
31
|
+
r2 = Org1.call(0).fetch(:number) # se bug: hook perso -> 1
|
|
32
|
+
[r1 == -9 && r2 == 1, "prima call=#{r1}, seconda call=#{r2}"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# ---------- F2: Context#fetch — nessun KeyError e scrittura su read ----------
|
|
36
|
+
check(results, "F2a fetch(:missing) ritorna nil invece di KeyError e scrive la chiave") do
|
|
37
|
+
ctx = FunctionalLightService::Context.make({})
|
|
38
|
+
v = ctx.fetch(:missing)
|
|
39
|
+
[v.nil? && ctx.to_h.key?(:missing), "valore=#{v.inspect}, chiavi dopo fetch=#{ctx.keys.inspect}"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
check(results, "F2b fetch con default sovrascrive un valore falsy esistente") do
|
|
43
|
+
ctx = FunctionalLightService::Context.make(:flag => false)
|
|
44
|
+
v = ctx.fetch(:flag, true)
|
|
45
|
+
[v == true && ctx[:flag] == true, "fetch(:flag, true)=#{v.inspect}, ctx[:flag] ora=#{ctx[:flag].inspect} (era false)"]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# ---------- F3: alias asimmetrico lettura/scrittura ----------
|
|
49
|
+
check(results, "F3 scrittura su alias persa in lettura") do
|
|
50
|
+
ctx = FunctionalLightService::Context.make(:codice_fiscale => "ABC")
|
|
51
|
+
ctx.assign_aliases(:codice_fiscale => :cf)
|
|
52
|
+
ctx[:cf] = "NUOVO" # scrive la chiave :cf direttamente
|
|
53
|
+
read = ctx[:cf] # legge tradotto -> :codice_fiscale -> "ABC"
|
|
54
|
+
[read == "ABC", "dopo ctx[:cf]='NUOVO', ctx[:cf]=#{read.inspect}; hash=#{ctx.to_h.inspect}"]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# ---------- F4: accessor non definito per chiavi che collidono con metodi Hash ----------
|
|
58
|
+
class UsesSize
|
|
59
|
+
extend FunctionalLightService::Action
|
|
60
|
+
expects :size
|
|
61
|
+
executed { |ctx| ctx[:observed] = ctx.size } # l'utente si aspetta il valore di :size
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
check(results, "F4 expects :size -> accessor non definito, ritorna Hash#size") do
|
|
65
|
+
result = UsesSize.execute(:size => 999)
|
|
66
|
+
[result[:observed] != 999, "ctx.size dentro l'action=#{result[:observed].inspect} (atteso dal punto di vista utente: 999)"]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# ---------- F5: @ctx a livello di classe Action (retention + race) ----------
|
|
70
|
+
check(results, "F5 la classe Action trattiene l'ultimo context in @ctx") do
|
|
71
|
+
AddOne.execute(:number => 5)
|
|
72
|
+
held = AddOne.instance_variable_get(:@ctx)
|
|
73
|
+
[held.is_a?(FunctionalLightService::Context), "AddOne @ctx = #{held.inspect[0,80]}"]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# ---------- F6: fail! muta l'hash di opzioni del chiamante ----------
|
|
77
|
+
check(results, "F6 fail! cancella :error_code dall'hash del chiamante") do
|
|
78
|
+
ctx = FunctionalLightService::Context.make({})
|
|
79
|
+
opts = { :error_code => 500 }
|
|
80
|
+
ctx.fail!("boom", opts)
|
|
81
|
+
[!opts.key?(:error_code), "opts dopo fail! = #{opts.inspect}"]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# ---------- F7: Null#respond_to? con firma sbagliata ----------
|
|
85
|
+
check(results, "F7 Null#respond_to?(m, true) solleva ArgumentError") do
|
|
86
|
+
begin
|
|
87
|
+
Null.instance.respond_to?(:foo, true)
|
|
88
|
+
[false, "nessuna eccezione"]
|
|
89
|
+
rescue ArgumentError => e
|
|
90
|
+
[true, "ArgumentError: #{e.message}"]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# ---------- F8: Context#[] con aliases usa Hash#key (reverse lookup O(n)) ----------
|
|
95
|
+
check(results, "F8 lettura di chiave originale con molti alias resta corretta (sanity)") do
|
|
96
|
+
ctx = FunctionalLightService::Context.make(:a => 1)
|
|
97
|
+
ctx.assign_aliases(:a => :b)
|
|
98
|
+
[ctx[:b] == 1 && ctx[:a] == 1, "ctx[:a]=#{ctx[:a]}, ctx[:b]=#{ctx[:b]}"]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# ---------- F9: skip_remaining! dentro iterate viene resettato ad ogni item ----------
|
|
102
|
+
class SkipAll
|
|
103
|
+
extend FunctionalLightService::Action
|
|
104
|
+
executed { |ctx| ctx.skip_remaining!("basta") if ctx[:counter] == 2 }
|
|
105
|
+
end
|
|
106
|
+
class CollectAction
|
|
107
|
+
extend FunctionalLightService::Action
|
|
108
|
+
executed { |ctx| (ctx[:seen] ||= []) << ctx[:counter] }
|
|
109
|
+
end
|
|
110
|
+
class OrgIter
|
|
111
|
+
extend FunctionalLightService::Organizer
|
|
112
|
+
def self.call(ctx)
|
|
113
|
+
with(ctx).reduce([iterate(:counters, [SkipAll, CollectAction])])
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
check(results, "F9 skip_remaining! dentro iterate NON ferma l'iterazione") do
|
|
118
|
+
r = OrgIter.call(:counters => [1, 2, 3])
|
|
119
|
+
[r[:seen] == [1, 3], "seen=#{r[:seen].inspect} (2 saltato, ma 3 processato: lo skip e' stato resettato)"]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# ---------- F10: succeed! + message perso da reset_skip_remaining! in scoped_reduce ----------
|
|
123
|
+
check(results, "F10 reset_skip_remaining! azzera anche message/outcome") do
|
|
124
|
+
ctx = FunctionalLightService::Context.make({})
|
|
125
|
+
ctx.succeed!("fatto bene")
|
|
126
|
+
ctx.reset_skip_remaining!
|
|
127
|
+
[ctx.message == '', "message dopo reset=#{ctx.message.inspect}"]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# ---------- F11: Context#select/map degradano a Hash ----------
|
|
131
|
+
check(results, "F11 Context#select ritorna Hash, non Context (perde outcome)") do
|
|
132
|
+
ctx = FunctionalLightService::Context.make(:a => 1)
|
|
133
|
+
sel = ctx.select { |_k, _v| true }
|
|
134
|
+
[!sel.is_a?(FunctionalLightService::Context), "select.class=#{sel.class}"]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# ---------- F12: Some(nil) e' costruibile ----------
|
|
138
|
+
check(results, "F12 Some(nil) e' consentito (Option non valida il nil)") do
|
|
139
|
+
s = FunctionalLightService::Option::Some.new(nil)
|
|
140
|
+
[s.some? && s.value.nil?, "Some(nil).some?=#{s.some?}, value=nil"]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# ---------- F13: rollback con azione duplicata nella lista ----------
|
|
144
|
+
class RollA
|
|
145
|
+
extend FunctionalLightService::Action
|
|
146
|
+
executed { |ctx| (ctx[:trace] ||= []) << :a }
|
|
147
|
+
rolled_back { |ctx| (ctx[:rb] ||= []) << :a }
|
|
148
|
+
end
|
|
149
|
+
class RollB
|
|
150
|
+
extend FunctionalLightService::Action
|
|
151
|
+
executed { |ctx| (ctx[:trace] ||= []) << :b; ctx.fail_with_rollback!("ko") if ctx[:trace].count(:b) == 2 }
|
|
152
|
+
rolled_back { |ctx| (ctx[:rb] ||= []) << :b }
|
|
153
|
+
end
|
|
154
|
+
class OrgRoll
|
|
155
|
+
extend FunctionalLightService::Organizer
|
|
156
|
+
def self.call(ctx)
|
|
157
|
+
with(ctx).reduce([RollB, RollA, RollB]) # RollB duplicata; fallisce la SECONDA RollB
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
check(results, "F13 rollback con azione duplicata: index trova la 1a occorrenza -> rollback parziale") do
|
|
162
|
+
r = OrgRoll.call({})
|
|
163
|
+
# eseguite: B, A, B(fail). Rollback atteso: B, A, B. Con il bug: index(RollB)=0 -> solo [RollB].take(1) -> rollback solo B
|
|
164
|
+
[r[:rb] == [:b], "trace=#{r[:trace].inspect}, rollback eseguiti=#{r[:rb].inspect} (attesi: [:b, :a, :b])"]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
puts
|
|
168
|
+
results.each do |name, ok, detail|
|
|
169
|
+
status = ok == true ? "CONFERMATO" : (ok == :exception ? "ECCEZIONE " : "NON RIPRODOTTO")
|
|
170
|
+
puts "[#{status}] #{name}"
|
|
171
|
+
puts " -> #{detail}"
|
|
172
|
+
end
|
|
@@ -1,34 +1,33 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
1
|
require File.expand_path('../lib/functional-light-service/version', __FILE__)
|
|
3
2
|
|
|
4
3
|
Gem::Specification.new do |gem|
|
|
5
4
|
gem.authors = ["Boscolo Michele"]
|
|
6
5
|
gem.email = ["miboscol@gmail.com"]
|
|
7
|
-
gem.description = %q{
|
|
6
|
+
gem.description = %q{FunctionalLightService combines the Organizer/Action/Context pattern of LightService with functional programming constructs (Result/Option monads, pattern matching) inspired by Deterministic: complex workflows are organized into small single-purpose actions with functional error handling.}
|
|
8
7
|
gem.summary = %q{A service skeleton with an emphasis on simplicity with a pinch a functional programming}
|
|
9
8
|
gem.homepage = "https://github.com/sphynx79/functional-light-service"
|
|
10
9
|
gem.license = "MIT"
|
|
11
10
|
|
|
12
11
|
gem.files = `git ls-files`.split($\)
|
|
13
12
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
14
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
15
13
|
gem.name = "functional-light-service"
|
|
16
14
|
gem.require_paths = ["lib"]
|
|
17
15
|
gem.version = FunctionalLightService::VERSION
|
|
18
|
-
gem.required_ruby_version = '>=
|
|
16
|
+
gem.required_ruby_version = '>= 3.1.0'
|
|
19
17
|
|
|
20
|
-
gem.add_runtime_dependency("dry-inflector", "
|
|
18
|
+
gem.add_runtime_dependency("dry-inflector", ">= 0.2.1", "< 2")
|
|
21
19
|
gem.add_runtime_dependency("i18n", "~> 1.8", ">= 1.8.11")
|
|
22
|
-
|
|
20
|
+
# logger non e' piu una default gem da Ruby 3.5/4.0: senza questa
|
|
21
|
+
# dichiarazione `require 'logger'` fallisce sotto Bundler
|
|
22
|
+
gem.add_runtime_dependency("logger", "~> 1.5")
|
|
23
23
|
|
|
24
|
-
gem.add_development_dependency("
|
|
25
|
-
gem.add_development_dependency("
|
|
26
|
-
gem.add_development_dependency("
|
|
27
|
-
gem.add_development_dependency("simplecov", "~> 0
|
|
28
|
-
gem.add_development_dependency("
|
|
29
|
-
gem.add_development_dependency("rubocop", "~> 1.
|
|
30
|
-
gem.add_development_dependency("
|
|
31
|
-
gem.add_development_dependency("
|
|
32
|
-
gem.add_development_dependency("
|
|
33
|
-
gem.add_development_dependency("nokogiri", "~> 1.12.5")
|
|
24
|
+
gem.add_development_dependency("rake", "~> 13.0")
|
|
25
|
+
gem.add_development_dependency("rspec", "~> 3.13")
|
|
26
|
+
gem.add_development_dependency("simplecov", "~> 0.22")
|
|
27
|
+
gem.add_development_dependency("simplecov-cobertura", "~> 3.0")
|
|
28
|
+
gem.add_development_dependency("rubocop", "~> 1.75")
|
|
29
|
+
gem.add_development_dependency("rubocop-performance", "~> 1.20")
|
|
30
|
+
gem.add_development_dependency("pry", "~> 0.15")
|
|
31
|
+
gem.add_development_dependency("solargraph", "~> 0.50")
|
|
32
|
+
gem.add_development_dependency("benchmark-ips", "~> 2.13")
|
|
34
33
|
end
|
|
@@ -1,101 +1,97 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def
|
|
83
|
-
invoke_callbacks(context[:
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Action
|
|
5
|
+
def self.extended(base_class)
|
|
6
|
+
base_class.extend Macros
|
|
7
|
+
base_class.extend FunctionalLightService::Prelude::Result
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.included(base_class)
|
|
11
|
+
FunctionalLightService::Deprecations.warn(
|
|
12
|
+
"Including FunctionalLightService::Action is deprecated; " \
|
|
13
|
+
"use `extend FunctionalLightService::Action` instead"
|
|
14
|
+
)
|
|
15
|
+
base_class.extend Macros
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module Macros
|
|
19
|
+
def expects(*args)
|
|
20
|
+
expected_keys.concat(args)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def promises(*args)
|
|
24
|
+
promised_keys.concat(args)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def expected_keys
|
|
28
|
+
@expected_keys ||= []
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def promised_keys
|
|
32
|
+
@promised_keys ||= []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def executed
|
|
36
|
+
define_singleton_method :execute do |context = {}|
|
|
37
|
+
action_context = create_action_context(context)
|
|
38
|
+
return action_context if action_context.stop_processing?
|
|
39
|
+
|
|
40
|
+
# Store the action within the context
|
|
41
|
+
action_context.current_action = self
|
|
42
|
+
|
|
43
|
+
Context::KeyVerifier.verify_keys(action_context, self) do
|
|
44
|
+
action_context.define_accessor_methods_for_keys(all_keys)
|
|
45
|
+
|
|
46
|
+
catch(:jump_when_failed) do
|
|
47
|
+
call_before_action(action_context)
|
|
48
|
+
yield(action_context)
|
|
49
|
+
call_after_action(action_context)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def rolled_back
|
|
56
|
+
msg = "`rolled_back` macro can not be invoked again"
|
|
57
|
+
raise msg if respond_to?(:rollback)
|
|
58
|
+
|
|
59
|
+
define_singleton_method :rollback do |context = {}|
|
|
60
|
+
yield(context)
|
|
61
|
+
|
|
62
|
+
context
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def create_action_context(context)
|
|
69
|
+
return context if context.is_a? FunctionalLightService::Context
|
|
70
|
+
|
|
71
|
+
FunctionalLightService::Context.make(context)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def all_keys
|
|
75
|
+
expected_keys + promised_keys
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def call_before_action(context)
|
|
79
|
+
invoke_callbacks(context[:_before_actions], context)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def call_after_action(context)
|
|
83
|
+
invoke_callbacks(context[:_after_actions], context)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def invoke_callbacks(callbacks, context)
|
|
87
|
+
return context unless callbacks
|
|
88
|
+
|
|
89
|
+
callbacks.each do |cb|
|
|
90
|
+
cb.call(context)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
context
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
logger
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
class Configuration
|
|
5
|
+
class << self
|
|
6
|
+
attr_writer :logger, :localization_adapter
|
|
7
|
+
|
|
8
|
+
def logger
|
|
9
|
+
@logger = _default_logger unless instance_variable_defined?("@logger")
|
|
10
|
+
@logger
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def localization_adapter
|
|
14
|
+
@localization_adapter ||= LocalizationAdapter.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def _default_logger
|
|
20
|
+
logger = Logger.new(nil)
|
|
21
|
+
logger.level = Logger::WARN
|
|
22
|
+
logger
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|