functional-light-service 6.2.0 → 6.3.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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/lib/functional-light-service.rb +6 -36
  3. metadata +14 -307
  4. data/.github/workflows/project-build.yml +0 -71
  5. data/.gitignore +0 -24
  6. data/.rspec +0 -3
  7. data/.rubocop.yml +0 -101
  8. data/.solargraph.yml +0 -11
  9. data/AUDIT-functional-light-service.md +0 -352
  10. data/Appraisals +0 -7
  11. data/CHANGELOG.md +0 -292
  12. data/CODE_OF_CONDUCT.md +0 -22
  13. data/Gemfile +0 -4
  14. data/LICENSE +0 -22
  15. data/README.md +0 -1663
  16. data/Rakefile +0 -12
  17. data/VERSION +0 -1
  18. data/audit/bench.rb +0 -99
  19. data/audit/verify_findings.rb +0 -172
  20. data/functional-light-service.gemspec +0 -35
  21. data/gemfiles/dry_inflector_0_2_1.gemfile +0 -5
  22. data/gemfiles/i18n_1_8_11.gemfile +0 -5
  23. data/lib/functional-light-service/action.rb +0 -145
  24. data/lib/functional-light-service/configuration.rb +0 -36
  25. data/lib/functional-light-service/context/key_verifier.rb +0 -124
  26. data/lib/functional-light-service/context.rb +0 -208
  27. data/lib/functional-light-service/deprecations.rb +0 -26
  28. data/lib/functional-light-service/errors.rb +0 -9
  29. data/lib/functional-light-service/functional/enum.rb +0 -286
  30. data/lib/functional-light-service/functional/maybe.rb +0 -21
  31. data/lib/functional-light-service/functional/monad.rb +0 -77
  32. data/lib/functional-light-service/functional/null.rb +0 -88
  33. data/lib/functional-light-service/functional/option.rb +0 -100
  34. data/lib/functional-light-service/functional/result.rb +0 -129
  35. data/lib/functional-light-service/functional/sequencer.rb +0 -144
  36. data/lib/functional-light-service/i18n/localization_adapter.rb +0 -50
  37. data/lib/functional-light-service/localization_adapter.rb +0 -39
  38. data/lib/functional-light-service/localization_map.rb +0 -9
  39. data/lib/functional-light-service/organizer/execute.rb +0 -16
  40. data/lib/functional-light-service/organizer/iterate.rb +0 -30
  41. data/lib/functional-light-service/organizer/reduce_case.rb +0 -50
  42. data/lib/functional-light-service/organizer/reduce_if.rb +0 -19
  43. data/lib/functional-light-service/organizer/reduce_if_else.rb +0 -23
  44. data/lib/functional-light-service/organizer/reduce_until.rb +0 -22
  45. data/lib/functional-light-service/organizer/reduce_while.rb +0 -31
  46. data/lib/functional-light-service/organizer/scoped_reducable.rb +0 -15
  47. data/lib/functional-light-service/organizer/with_callback.rb +0 -28
  48. data/lib/functional-light-service/organizer/with_reducer.rb +0 -81
  49. data/lib/functional-light-service/organizer/with_reducer_factory.rb +0 -20
  50. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +0 -111
  51. data/lib/functional-light-service/organizer.rb +0 -129
  52. data/lib/functional-light-service/testing/context_factory.rb +0 -48
  53. data/lib/functional-light-service/testing.rb +0 -3
  54. data/lib/functional-light-service/version.rb +0 -5
  55. data/resources/fail_actions.png +0 -0
  56. data/resources/light-service.png +0 -0
  57. data/resources/organizer_and_actions.png +0 -0
  58. data/resources/skip_actions.png +0 -0
  59. data/spec/acceptance/add_numbers_spec.rb +0 -11
  60. data/spec/acceptance/after_actions_spec.rb +0 -87
  61. data/spec/acceptance/around_each_spec.rb +0 -19
  62. data/spec/acceptance/before_actions_spec.rb +0 -115
  63. data/spec/acceptance/custom_log_from_organizer_spec.rb +0 -61
  64. data/spec/acceptance/deprecation_warnings_spec.rb +0 -82
  65. data/spec/acceptance/fail_spec.rb +0 -52
  66. data/spec/acceptance/log_from_organizer_spec.rb +0 -154
  67. data/spec/acceptance/message_localization_spec.rb +0 -119
  68. data/spec/acceptance/organizer/add_aliases_spec.rb +0 -28
  69. data/spec/acceptance/organizer/add_to_context_spec.rb +0 -54
  70. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +0 -42
  71. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +0 -90
  72. data/spec/acceptance/organizer/execute_spec.rb +0 -67
  73. data/spec/acceptance/organizer/iterate_spec.rb +0 -44
  74. data/spec/acceptance/organizer/reduce_case_spec.rb +0 -65
  75. data/spec/acceptance/organizer/reduce_if_else_spec.rb +0 -60
  76. data/spec/acceptance/organizer/reduce_if_spec.rb +0 -89
  77. data/spec/acceptance/organizer/reduce_until_spec.rb +0 -49
  78. data/spec/acceptance/organizer/reduce_while_spec.rb +0 -96
  79. data/spec/acceptance/organizer/with_callback_spec.rb +0 -113
  80. data/spec/acceptance/organizer_entry_point_spec.rb +0 -35
  81. data/spec/acceptance/rollback_spec.rb +0 -183
  82. data/spec/acceptance/skip_all_remaining_spec.rb +0 -139
  83. data/spec/acceptance/testing/context_factory_spec.rb +0 -54
  84. data/spec/action_expected_keys_spec.rb +0 -63
  85. data/spec/action_expects_and_promises_spec.rb +0 -97
  86. data/spec/action_optional_expected_keys_spec.rb +0 -107
  87. data/spec/action_promised_keys_spec.rb +0 -126
  88. data/spec/action_spec.rb +0 -97
  89. data/spec/context/inspect_spec.rb +0 -52
  90. data/spec/context_spec.rb +0 -301
  91. data/spec/examples/amount_spec.rb +0 -77
  92. data/spec/examples/controller_spec.rb +0 -63
  93. data/spec/examples/validate_address_spec.rb +0 -38
  94. data/spec/i18n_localization_adapter_spec.rb +0 -83
  95. data/spec/lib/deterministic/class_mixin_spec.rb +0 -24
  96. data/spec/lib/deterministic/currify_spec.rb +0 -90
  97. data/spec/lib/deterministic/monad_axioms.rb +0 -46
  98. data/spec/lib/deterministic/monad_spec.rb +0 -47
  99. data/spec/lib/deterministic/null_spec.rb +0 -65
  100. data/spec/lib/deterministic/option_spec.rb +0 -140
  101. data/spec/lib/deterministic/result/failure_spec.rb +0 -65
  102. data/spec/lib/deterministic/result/result_map_spec.rb +0 -155
  103. data/spec/lib/deterministic/result/result_shared.rb +0 -25
  104. data/spec/lib/deterministic/result/success_spec.rb +0 -41
  105. data/spec/lib/deterministic/result_spec.rb +0 -63
  106. data/spec/lib/deterministic/sequencer_spec.rb +0 -506
  107. data/spec/lib/edge_cases_spec.rb +0 -156
  108. data/spec/lib/enum_spec.rb +0 -114
  109. data/spec/lib/native_pattern_matching_spec.rb +0 -74
  110. data/spec/localization_adapter_spec.rb +0 -66
  111. data/spec/organizer/with_reducer_spec.rb +0 -56
  112. data/spec/organizer_key_aliases_spec.rb +0 -29
  113. data/spec/organizer_spec.rb +0 -115
  114. data/spec/readme_spec.rb +0 -45
  115. data/spec/sample/calculates_order_tax_action_spec.rb +0 -16
  116. data/spec/sample/calculates_tax_spec.rb +0 -30
  117. data/spec/sample/looks_up_tax_percentage_action_spec.rb +0 -55
  118. data/spec/sample/provides_free_shipping_action_spec.rb +0 -25
  119. data/spec/sample/tax/calculates_order_tax_action.rb +0 -10
  120. data/spec/sample/tax/calculates_tax.rb +0 -11
  121. data/spec/sample/tax/looks_up_tax_percentage_action.rb +0 -28
  122. data/spec/sample/tax/provides_free_shipping_action.rb +0 -11
  123. data/spec/spec_helper.rb +0 -32
  124. data/spec/support.rb +0 -1
  125. data/spec/test_doubles.rb +0 -656
  126. data/spec/testing/context_factory/iterate_spec.rb +0 -39
  127. data/spec/testing/context_factory/reduce_if_spec.rb +0 -40
  128. data/spec/testing/context_factory/reduce_until_spec.rb +0 -40
  129. data/spec/testing/context_factory/with_callback_spec.rb +0 -38
  130. data/spec/testing/context_factory_spec.rb +0 -76
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- require "bundler/gem_tasks"
2
-
3
- require "rubygems"
4
- require "bundler/setup"
5
-
6
- require 'rspec/core/rake_task'
7
- require 'rubocop/rake_task'
8
- RuboCop::RakeTask.new
9
- RSpec::Core::RakeTask.new(:spec)
10
-
11
- task(:default).clear
12
- task :default => %i[spec rubocop]
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.5.4
data/audit/bench.rb DELETED
@@ -1,99 +0,0 @@
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
@@ -1,172 +0,0 @@
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,35 +0,0 @@
1
- require File.expand_path('../lib/functional-light-service/version', __FILE__)
2
-
3
- Gem::Specification.new do |gem|
4
- gem.authors = ["Boscolo Michele"]
5
- gem.email = ["miboscol@gmail.com"]
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.}
7
- gem.summary = %q{A service skeleton with an emphasis on simplicity with a pinch a functional programming}
8
- gem.homepage = "https://github.com/sphynx79/functional-light-service"
9
- gem.license = "MIT"
10
-
11
- gem.files = `git ls-files`.split($\)
12
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
- gem.name = "functional-light-service"
14
- gem.require_paths = ["lib"]
15
- gem.version = FunctionalLightService::VERSION
16
- gem.required_ruby_version = '>= 3.1.0'
17
-
18
- gem.add_runtime_dependency("dry-inflector", ">= 0.2.1", "< 2")
19
- # logger non e' piu una default gem da Ruby 3.5/4.0: senza questa
20
- # dichiarazione `require 'logger'` fallisce sotto Bundler
21
- gem.add_runtime_dependency("logger", "~> 1.5")
22
-
23
- # i18n e' opzionale a runtime: l'adapter I18n viene usato solo se
24
- # l'applicazione ospite carica la gem (vedi Configuration)
25
- gem.add_development_dependency("i18n", "~> 1.8", ">= 1.8.11")
26
- gem.add_development_dependency("rake", "~> 13.0")
27
- gem.add_development_dependency("rspec", "~> 3.13")
28
- gem.add_development_dependency("simplecov", "~> 0.22")
29
- gem.add_development_dependency("simplecov-cobertura", "~> 3.0")
30
- gem.add_development_dependency("rubocop", "~> 1.75")
31
- gem.add_development_dependency("rubocop-performance", "~> 1.20")
32
- gem.add_development_dependency("pry", "~> 0.15")
33
- gem.add_development_dependency("solargraph", "~> 0.50")
34
- gem.add_development_dependency("benchmark-ips", "~> 2.13")
35
- end
@@ -1,5 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "dry-inflector", "~> 0.2.1"
4
-
5
- gemspec :path => "../"
@@ -1,5 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "i18n", "~> 1.8.11"
4
-
5
- gemspec :path => "../"
@@ -1,145 +0,0 @@
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
- if expect_key_having_default?(args)
21
- available_defaults[args.first] = args.last[:default]
22
-
23
- args = [args.first]
24
- end
25
-
26
- expected_keys.concat(args)
27
- end
28
-
29
- def promises(*args)
30
- promised_keys.concat(args)
31
- end
32
-
33
- def expected_keys
34
- @expected_keys ||= []
35
- end
36
-
37
- def promised_keys
38
- @promised_keys ||= []
39
- end
40
-
41
- def executed
42
- define_singleton_method :execute do |context = {}|
43
- action_context = create_action_context(context)
44
- return action_context if action_context.stop_processing?
45
-
46
- # Store the action within the context
47
- action_context.current_action = self
48
-
49
- Context::KeyVerifier.verify_keys(action_context, self) do
50
- action_context.define_accessor_methods_for_keys(all_keys)
51
-
52
- catch(:jump_when_failed) do
53
- call_before_action(action_context)
54
- yield(action_context)
55
- call_after_action(action_context)
56
- end
57
- end
58
- end
59
- end
60
-
61
- def rolled_back
62
- msg = "`rolled_back` macro can not be invoked again"
63
- raise msg if respond_to?(:rollback)
64
-
65
- define_singleton_method :rollback do |context = {}|
66
- yield(context)
67
-
68
- context
69
- end
70
- end
71
-
72
- private
73
-
74
- def create_action_context(context)
75
- # I default vanno applicati anche quando l'action gira dentro un
76
- # organizer (il context è già un Context): prima dell'early return.
77
- # Il guard sull'ivar evita lavoro (e scritture lazy) nel percorso caldo
78
- apply_expects_defaults(context) if @available_defaults
79
-
80
- return context if context.is_a? FunctionalLightService::Context
81
-
82
- FunctionalLightService::Context.make(context)
83
- end
84
-
85
- def apply_expects_defaults(context)
86
- usable_defaults(context).each do |ctx_key, default|
87
- context[ctx_key] = extract_default(default, context)
88
- end
89
- end
90
-
91
- def available_defaults
92
- @available_defaults ||= {}
93
- end
94
-
95
- def expect_key_having_default?(key)
96
- return false unless key.size == 2 && key.last.is_a?(Hash)
97
- return true if key.last.key?(:default)
98
-
99
- bad_key = key.last.keys.first
100
- err_msg = "Specify defaults with a `default` key. You have #{bad_key}."
101
- raise UnusableExpectKeyDefaultError, err_msg
102
- end
103
-
104
- def missing_expected_keys(context)
105
- # context.key? risolve anche gli alias: `expected_keys - context.keys`
106
- # (upstream) darebbe falsi mancanti sulle chiavi aliasate
107
- expected_keys.reject { |key| context.key?(key) }
108
- end
109
-
110
- def usable_defaults(context)
111
- available_defaults.slice(
112
- *(missing_expected_keys(context) & available_defaults.keys)
113
- )
114
- end
115
-
116
- def extract_default(default, context)
117
- return default unless default.respond_to?(:call)
118
-
119
- default.call(context)
120
- end
121
-
122
- def all_keys
123
- expected_keys + promised_keys
124
- end
125
-
126
- def call_before_action(context)
127
- invoke_callbacks(context[:_before_actions], context)
128
- end
129
-
130
- def call_after_action(context)
131
- invoke_callbacks(context[:_after_actions], context)
132
- end
133
-
134
- def invoke_callbacks(callbacks, context)
135
- return context unless callbacks
136
-
137
- callbacks.each do |cb|
138
- cb.call(context)
139
- end
140
-
141
- context
142
- end
143
- end
144
- end
145
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FunctionalLightService
4
- class Configuration
5
- class << self
6
- attr_writer :logger, :localization_adapter, :locale
7
-
8
- def logger
9
- @logger = _default_logger unless instance_variable_defined?("@logger")
10
- @logger
11
- end
12
-
13
- def localization_adapter
14
- # La gem i18n non è una dipendenza: l'adapter I18n viene scelto solo
15
- # se la costante è già stata caricata dall'applicazione ospite
16
- @localization_adapter ||= if Module.const_defined?('I18n')
17
- FunctionalLightService::I18n::LocalizationAdapter.new
18
- else
19
- LocalizationAdapter.new
20
- end
21
- end
22
-
23
- def locale
24
- @locale ||= :en
25
- end
26
-
27
- private
28
-
29
- def _default_logger
30
- logger = Logger.new(nil)
31
- logger.level = Logger::WARN
32
- logger
33
- end
34
- end
35
- end
36
- end
@@ -1,124 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FunctionalLightService
4
- class Context
5
- class KeyVerifier
6
- attr_reader :context, :action
7
-
8
- def initialize(context, action)
9
- @context = context
10
- @action = action
11
- end
12
-
13
- def are_all_keys_in_context?(keys)
14
- not_found_keys = keys_not_found(keys)
15
- not_found_keys.none?
16
- end
17
-
18
- def keys_not_found(keys)
19
- keys ||= context.keys
20
- # context.key? risolve anche gli alias
21
- keys.reject { |key| context.key?(key) }
22
- end
23
-
24
- def format_keys(keys)
25
- keys.map { |k| ":#{k}" }.join(', ')
26
- end
27
-
28
- def error_message
29
- "#{type_name} #{format_keys(keys_not_found(keys))} " \
30
- "to be in the context during #{action}"
31
- end
32
-
33
- def throw_error_predicate(_keys)
34
- raise NotImplementedError, 'Sorry, you have to override length'
35
- end
36
-
37
- def verify
38
- return context if context.failure?
39
-
40
- if throw_error_predicate(keys)
41
- Configuration.logger.error error_message
42
- raise error_to_throw, error_message
43
- end
44
-
45
- context
46
- end
47
-
48
- def self.verify_keys(context, action, &block)
49
- ReservedKeysVerifier.new(context, action).verify
50
- ExpectedKeyVerifier.new(context, action).verify
51
-
52
- block.call
53
-
54
- PromisedKeyVerifier.new(context, action).verify
55
- end
56
- end
57
-
58
- class ExpectedKeyVerifier < KeyVerifier
59
- def type_name
60
- "expected"
61
- end
62
-
63
- def keys
64
- action.expected_keys
65
- end
66
-
67
- def error_to_throw
68
- ExpectedKeysNotInContextError
69
- end
70
-
71
- def throw_error_predicate(keys)
72
- !are_all_keys_in_context?(keys)
73
- end
74
- end
75
-
76
- class PromisedKeyVerifier < KeyVerifier
77
- def type_name
78
- "promised"
79
- end
80
-
81
- def keys
82
- action.promised_keys
83
- end
84
-
85
- def error_to_throw
86
- PromisedKeysNotInContextError
87
- end
88
-
89
- def throw_error_predicate(keys)
90
- !are_all_keys_in_context?(keys)
91
- end
92
- end
93
-
94
- class ReservedKeysVerifier < KeyVerifier
95
- def violated_keys
96
- (action.promised_keys + action.expected_keys) & reserved_keys
97
- end
98
-
99
- def error_message
100
- "promised or expected keys cannot be a " \
101
- "reserved key: [#{format_keys(violated_keys)}]"
102
- end
103
-
104
- def keys
105
- violated_keys
106
- end
107
-
108
- def error_to_throw
109
- ReservedKeysInContextError
110
- end
111
-
112
- def throw_error_predicate(keys)
113
- keys.any?
114
- end
115
-
116
- def reserved_keys
117
- # _aliases/_before_actions/_after_actions sono chiavi infrastrutturali
118
- # scritte da Organizer.with nel context
119
- %i[message error_code current_action organized_by
120
- _aliases _before_actions _after_actions].freeze
121
- end
122
- end
123
- end
124
- end