light-services 3.0.0 → 3.1.1
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/.rubocop.yml +7 -1
- data/CHANGELOG.md +21 -0
- data/CLAUDE.md +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +11 -11
- data/docs/{readme.md → README.md} +12 -11
- data/docs/{summary.md → SUMMARY.md} +11 -1
- data/docs/arguments.md +23 -0
- data/docs/concepts.md +19 -19
- data/docs/configuration.md +36 -0
- data/docs/errors.md +31 -1
- data/docs/outputs.md +23 -0
- data/docs/quickstart.md +1 -1
- data/docs/rubocop.md +285 -0
- data/docs/ruby-lsp.md +133 -0
- data/docs/steps.md +62 -8
- data/docs/testing.md +1 -1
- data/lib/light/services/base.rb +110 -7
- data/lib/light/services/base_with_context.rb +23 -1
- data/lib/light/services/callbacks.rb +293 -41
- data/lib/light/services/collection.rb +50 -2
- data/lib/light/services/concerns/execution.rb +3 -0
- data/lib/light/services/config.rb +83 -3
- data/lib/light/services/constants.rb +3 -0
- data/lib/light/services/dsl/arguments_dsl.rb +1 -0
- data/lib/light/services/dsl/outputs_dsl.rb +1 -0
- data/lib/light/services/dsl/validation.rb +30 -0
- data/lib/light/services/exceptions.rb +19 -1
- data/lib/light/services/message.rb +28 -3
- data/lib/light/services/messages.rb +74 -2
- data/lib/light/services/rubocop/cop/light_services/argument_type_required.rb +52 -0
- data/lib/light/services/rubocop/cop/light_services/condition_method_exists.rb +173 -0
- data/lib/light/services/rubocop/cop/light_services/deprecated_methods.rb +113 -0
- data/lib/light/services/rubocop/cop/light_services/dsl_order.rb +176 -0
- data/lib/light/services/rubocop/cop/light_services/missing_private_keyword.rb +102 -0
- data/lib/light/services/rubocop/cop/light_services/no_direct_instantiation.rb +66 -0
- data/lib/light/services/rubocop/cop/light_services/output_type_required.rb +52 -0
- data/lib/light/services/rubocop/cop/light_services/step_method_exists.rb +109 -0
- data/lib/light/services/rubocop.rb +12 -0
- data/lib/light/services/settings/field.rb +33 -5
- data/lib/light/services/settings/step.rb +23 -5
- data/lib/light/services/version.rb +1 -1
- data/lib/ruby_lsp/light_services/addon.rb +36 -0
- data/lib/ruby_lsp/light_services/definition.rb +132 -0
- data/lib/ruby_lsp/light_services/indexing_enhancement.rb +263 -0
- metadata +17 -3
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# This class allows running a service object with context (parent class and custom config)
|
|
4
3
|
module Light
|
|
5
4
|
module Services
|
|
5
|
+
# Wrapper for running a service with a parent context or custom configuration.
|
|
6
|
+
# Created via {Base.with} method.
|
|
7
|
+
#
|
|
8
|
+
# @example Running with parent service context
|
|
9
|
+
# ChildService.with(self).run(data: value)
|
|
10
|
+
#
|
|
11
|
+
# @example Running with custom configuration
|
|
12
|
+
# MyService.with(use_transactions: false).run(name: "test")
|
|
6
13
|
class BaseWithContext
|
|
14
|
+
# Initialize a new context wrapper.
|
|
15
|
+
#
|
|
16
|
+
# @param service_class [Class] the service class to run
|
|
17
|
+
# @param parent_service [Base, nil] parent service for error/warning propagation
|
|
18
|
+
# @param config [Hash] configuration overrides
|
|
19
|
+
# @raise [ArgTypeError] if parent_service is not a Base subclass
|
|
7
20
|
def initialize(service_class, parent_service, config)
|
|
8
21
|
@service_class = service_class
|
|
9
22
|
@config = config
|
|
@@ -14,10 +27,19 @@ module Light
|
|
|
14
27
|
raise Light::Services::ArgTypeError, "#{parent_service.class} - must be a subclass of Light::Services::Base"
|
|
15
28
|
end
|
|
16
29
|
|
|
30
|
+
# Run the service with the configured context.
|
|
31
|
+
#
|
|
32
|
+
# @param args [Hash] arguments to pass to the service
|
|
33
|
+
# @return [Base] the executed service instance
|
|
17
34
|
def run(args = {})
|
|
18
35
|
@service_class.new(extend_arguments(args), @config, @parent_service).tap(&:call)
|
|
19
36
|
end
|
|
20
37
|
|
|
38
|
+
# Run the service and raise an error if it fails.
|
|
39
|
+
#
|
|
40
|
+
# @param args [Hash] arguments to pass to the service
|
|
41
|
+
# @return [Base] the executed service instance
|
|
42
|
+
# @raise [Error] if the service fails
|
|
21
43
|
def run!(args = {})
|
|
22
44
|
@config[:raise_on_error] = true
|
|
23
45
|
run(args)
|
|
@@ -2,7 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
module Light
|
|
4
4
|
module Services
|
|
5
|
+
# Provides callback hooks for service and step lifecycle events.
|
|
6
|
+
#
|
|
7
|
+
# @example Service-level callbacks
|
|
8
|
+
# class MyService < Light::Services::Base
|
|
9
|
+
# before_service_run :log_start
|
|
10
|
+
# after_service_run { |service| Rails.logger.info("Done!") }
|
|
11
|
+
# on_service_success :send_notification
|
|
12
|
+
# on_service_failure :log_error
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# @example Step-level callbacks
|
|
16
|
+
# class MyService < Light::Services::Base
|
|
17
|
+
# before_step_run :log_step_start
|
|
18
|
+
# after_step_run { |service, step_name| puts "Finished #{step_name}" }
|
|
19
|
+
# on_step_failure :handle_step_error
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @example Around callbacks
|
|
23
|
+
# class MyService < Light::Services::Base
|
|
24
|
+
# around_service_run :with_timing
|
|
25
|
+
#
|
|
26
|
+
# private
|
|
27
|
+
#
|
|
28
|
+
# def with_timing(service)
|
|
29
|
+
# start = Time.now
|
|
30
|
+
# yield
|
|
31
|
+
# puts "Took #{Time.now - start}s"
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
5
34
|
module Callbacks
|
|
35
|
+
# Available callback events.
|
|
36
|
+
# @return [Array<Symbol>] list of callback event names
|
|
6
37
|
EVENTS = [
|
|
7
38
|
:before_step_run,
|
|
8
39
|
:after_step_run,
|
|
@@ -17,47 +48,12 @@ module Light
|
|
|
17
48
|
:on_service_failure,
|
|
18
49
|
].freeze
|
|
19
50
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
EVENTS.each do |event|
|
|
27
|
-
define_method(event) do |method_name = nil, &block|
|
|
28
|
-
callback = method_name || block
|
|
29
|
-
raise ArgumentError, "#{event} requires a method name (symbol) or a block" unless callback
|
|
30
|
-
|
|
31
|
-
unless callback.is_a?(Symbol) || callback.is_a?(Proc)
|
|
32
|
-
raise ArgumentError,
|
|
33
|
-
"#{event} callback must be a Symbol or Proc"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
callbacks_for(event) << callback
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Get all callbacks for a specific event (including inherited ones)
|
|
41
|
-
def callbacks_for(event)
|
|
42
|
-
@callbacks ||= {}
|
|
43
|
-
@callbacks[event] ||= []
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Get all callbacks including inherited ones
|
|
47
|
-
def all_callbacks_for(event)
|
|
48
|
-
if superclass.respond_to?(:all_callbacks_for)
|
|
49
|
-
inherited = superclass.all_callbacks_for(event)
|
|
50
|
-
else
|
|
51
|
-
inherited = []
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
inherited + callbacks_for(event)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Run callbacks for a given event
|
|
59
|
-
# For around callbacks, yields to the block
|
|
60
|
-
# For other callbacks, just executes them in order
|
|
51
|
+
# Run all callbacks for a given event.
|
|
52
|
+
#
|
|
53
|
+
# @param event [Symbol] the callback event name
|
|
54
|
+
# @param args [Array] arguments to pass to callbacks
|
|
55
|
+
# @yield for around callbacks, the block to wrap
|
|
56
|
+
# @return [void]
|
|
61
57
|
def run_callbacks(event, *args, &block)
|
|
62
58
|
callbacks = self.class.all_callbacks_for(event)
|
|
63
59
|
|
|
@@ -99,5 +95,261 @@ module Light
|
|
|
99
95
|
end
|
|
100
96
|
end
|
|
101
97
|
end
|
|
98
|
+
|
|
99
|
+
# Class methods for registering callbacks.
|
|
100
|
+
# Extend this module in your service class to get callback DSL methods.
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# class MyService < Light::Services::Base
|
|
104
|
+
# before_service_run :setup
|
|
105
|
+
# after_service_run :cleanup
|
|
106
|
+
# end
|
|
107
|
+
module CallbackDsl
|
|
108
|
+
# Registers a callback to run before each step executes.
|
|
109
|
+
#
|
|
110
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
111
|
+
# @yield [service, step_name] block to execute if no method name provided
|
|
112
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
113
|
+
# @yieldparam step_name [Symbol] the name of the step about to run
|
|
114
|
+
# @return [void]
|
|
115
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
116
|
+
#
|
|
117
|
+
# @example With method name
|
|
118
|
+
# before_step_run :log_step_start
|
|
119
|
+
#
|
|
120
|
+
# @example With block
|
|
121
|
+
# before_step_run { |service, step_name| puts "Starting #{step_name}" }
|
|
122
|
+
def before_step_run(method_name = nil, &block)
|
|
123
|
+
register_callback(:before_step_run, method_name, &block)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Registers a callback to run after each step executes.
|
|
127
|
+
#
|
|
128
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
129
|
+
# @yield [service, step_name] block to execute if no method name provided
|
|
130
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
131
|
+
# @yieldparam step_name [Symbol] the name of the step that just ran
|
|
132
|
+
# @return [void]
|
|
133
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
134
|
+
#
|
|
135
|
+
# @example With method name
|
|
136
|
+
# after_step_run :log_step_complete
|
|
137
|
+
#
|
|
138
|
+
# @example With block
|
|
139
|
+
# after_step_run { |service, step_name| puts "Finished #{step_name}" }
|
|
140
|
+
def after_step_run(method_name = nil, &block)
|
|
141
|
+
register_callback(:after_step_run, method_name, &block)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Registers an around callback that wraps each step execution.
|
|
145
|
+
# The callback must yield to execute the step.
|
|
146
|
+
#
|
|
147
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
148
|
+
# @yield [service, step_name] block to execute if no method name provided
|
|
149
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
150
|
+
# @yieldparam step_name [Symbol] the name of the step being wrapped
|
|
151
|
+
# @return [void]
|
|
152
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
153
|
+
#
|
|
154
|
+
# @example With method name
|
|
155
|
+
# around_step_run :with_step_timing
|
|
156
|
+
#
|
|
157
|
+
# def with_step_timing(service, step_name)
|
|
158
|
+
# start = Time.now
|
|
159
|
+
# yield
|
|
160
|
+
# puts "#{step_name} took #{Time.now - start}s"
|
|
161
|
+
# end
|
|
162
|
+
def around_step_run(method_name = nil, &block)
|
|
163
|
+
register_callback(:around_step_run, method_name, &block)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Registers a callback to run when a step completes successfully (without adding errors).
|
|
167
|
+
#
|
|
168
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
169
|
+
# @yield [service, step_name] block to execute if no method name provided
|
|
170
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
171
|
+
# @yieldparam step_name [Symbol] the name of the successful step
|
|
172
|
+
# @return [void]
|
|
173
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
174
|
+
#
|
|
175
|
+
# @example With method name
|
|
176
|
+
# on_step_success :track_step_success
|
|
177
|
+
#
|
|
178
|
+
# @example With block
|
|
179
|
+
# on_step_success { |service, step_name| Analytics.track("step.success", step: step_name) }
|
|
180
|
+
def on_step_success(method_name = nil, &block)
|
|
181
|
+
register_callback(:on_step_success, method_name, &block)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Registers a callback to run when a step fails (adds errors).
|
|
185
|
+
#
|
|
186
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
187
|
+
# @yield [service, step_name] block to execute if no method name provided
|
|
188
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
189
|
+
# @yieldparam step_name [Symbol] the name of the failed step
|
|
190
|
+
# @return [void]
|
|
191
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
192
|
+
#
|
|
193
|
+
# @example With method name
|
|
194
|
+
# on_step_failure :handle_step_error
|
|
195
|
+
#
|
|
196
|
+
# @example With block
|
|
197
|
+
# on_step_failure { |service, step_name| Rails.logger.error("Step #{step_name} failed") }
|
|
198
|
+
def on_step_failure(method_name = nil, &block)
|
|
199
|
+
register_callback(:on_step_failure, method_name, &block)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Registers a callback to run when a step raises an exception.
|
|
203
|
+
#
|
|
204
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
205
|
+
# @yield [service, step_name, exception] block to execute if no method name provided
|
|
206
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
207
|
+
# @yieldparam step_name [Symbol] the name of the crashed step
|
|
208
|
+
# @yieldparam exception [Exception] the exception that was raised
|
|
209
|
+
# @return [void]
|
|
210
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
211
|
+
#
|
|
212
|
+
# @example With method name
|
|
213
|
+
# on_step_crash :report_crash
|
|
214
|
+
#
|
|
215
|
+
# @example With block
|
|
216
|
+
# on_step_crash { |service, step_name, error| Sentry.capture_exception(error) }
|
|
217
|
+
def on_step_crash(method_name = nil, &block)
|
|
218
|
+
register_callback(:on_step_crash, method_name, &block)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Registers a callback to run before the service starts executing.
|
|
222
|
+
#
|
|
223
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
224
|
+
# @yield [service] block to execute if no method name provided
|
|
225
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
226
|
+
# @return [void]
|
|
227
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
228
|
+
#
|
|
229
|
+
# @example With method name
|
|
230
|
+
# before_service_run :log_start
|
|
231
|
+
#
|
|
232
|
+
# @example With block
|
|
233
|
+
# before_service_run { |service| Rails.logger.info("Starting #{service.class.name}") }
|
|
234
|
+
def before_service_run(method_name = nil, &block)
|
|
235
|
+
register_callback(:before_service_run, method_name, &block)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Registers a callback to run after the service completes (regardless of success/failure).
|
|
239
|
+
#
|
|
240
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
241
|
+
# @yield [service] block to execute if no method name provided
|
|
242
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
243
|
+
# @return [void]
|
|
244
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
245
|
+
#
|
|
246
|
+
# @example With method name
|
|
247
|
+
# after_service_run :cleanup
|
|
248
|
+
#
|
|
249
|
+
# @example With block
|
|
250
|
+
# after_service_run { |service| Rails.logger.info("Done!") }
|
|
251
|
+
def after_service_run(method_name = nil, &block)
|
|
252
|
+
register_callback(:after_service_run, method_name, &block)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Registers an around callback that wraps the entire service execution.
|
|
256
|
+
# The callback must yield to execute the service.
|
|
257
|
+
#
|
|
258
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
259
|
+
# @yield [service] block to execute if no method name provided
|
|
260
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
261
|
+
# @return [void]
|
|
262
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
263
|
+
#
|
|
264
|
+
# @example With method name
|
|
265
|
+
# around_service_run :with_timing
|
|
266
|
+
#
|
|
267
|
+
# def with_timing(service)
|
|
268
|
+
# start = Time.now
|
|
269
|
+
# yield
|
|
270
|
+
# puts "Took #{Time.now - start}s"
|
|
271
|
+
# end
|
|
272
|
+
def around_service_run(method_name = nil, &block)
|
|
273
|
+
register_callback(:around_service_run, method_name, &block)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Registers a callback to run when the service completes successfully (without errors).
|
|
277
|
+
#
|
|
278
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
279
|
+
# @yield [service] block to execute if no method name provided
|
|
280
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
281
|
+
# @return [void]
|
|
282
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
283
|
+
#
|
|
284
|
+
# @example With method name
|
|
285
|
+
# on_service_success :send_notification
|
|
286
|
+
#
|
|
287
|
+
# @example With block
|
|
288
|
+
# on_service_success { |service| NotificationMailer.success(service.user).deliver_later }
|
|
289
|
+
def on_service_success(method_name = nil, &block)
|
|
290
|
+
register_callback(:on_service_success, method_name, &block)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Registers a callback to run when the service completes with errors.
|
|
294
|
+
#
|
|
295
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
296
|
+
# @yield [service] block to execute if no method name provided
|
|
297
|
+
# @yieldparam service [Light::Services::Base] the service instance
|
|
298
|
+
# @return [void]
|
|
299
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
300
|
+
#
|
|
301
|
+
# @example With method name
|
|
302
|
+
# on_service_failure :log_error
|
|
303
|
+
#
|
|
304
|
+
# @example With block
|
|
305
|
+
# on_service_failure { |service| Rails.logger.error(service.errors.full_messages) }
|
|
306
|
+
def on_service_failure(method_name = nil, &block)
|
|
307
|
+
register_callback(:on_service_failure, method_name, &block)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Get callbacks defined in this class for a specific event.
|
|
311
|
+
#
|
|
312
|
+
# @param event [Symbol] the callback event name
|
|
313
|
+
# @return [Array<Symbol, Proc>] callbacks for this event
|
|
314
|
+
def callbacks_for(event)
|
|
315
|
+
@callbacks ||= {}
|
|
316
|
+
@callbacks[event] ||= []
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Get all callbacks for an event including inherited ones.
|
|
320
|
+
#
|
|
321
|
+
# @param event [Symbol] the callback event name
|
|
322
|
+
# @return [Array<Symbol, Proc>] all callbacks for this event
|
|
323
|
+
def all_callbacks_for(event)
|
|
324
|
+
if superclass.respond_to?(:all_callbacks_for)
|
|
325
|
+
inherited = superclass.all_callbacks_for(event)
|
|
326
|
+
else
|
|
327
|
+
inherited = []
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
inherited + callbacks_for(event)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
private
|
|
334
|
+
|
|
335
|
+
# Registers a callback for a given event.
|
|
336
|
+
#
|
|
337
|
+
# @param event [Symbol] the callback event name
|
|
338
|
+
# @param method_name [Symbol, nil] name of the instance method to call
|
|
339
|
+
# @yield block to execute if no method name provided
|
|
340
|
+
# @return [void]
|
|
341
|
+
# @raise [ArgumentError] if neither method name nor block is provided
|
|
342
|
+
# @api private
|
|
343
|
+
def register_callback(event, method_name = nil, &block)
|
|
344
|
+
callback = method_name || block
|
|
345
|
+
raise ArgumentError, "#{event} requires a method name (symbol) or a block" unless callback
|
|
346
|
+
|
|
347
|
+
unless callback.is_a?(Symbol) || callback.is_a?(Proc)
|
|
348
|
+
raise ArgumentError, "#{event} callback must be a Symbol or Proc"
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
callbacks_for(event) << callback
|
|
352
|
+
end
|
|
353
|
+
end
|
|
102
354
|
end
|
|
103
355
|
end
|
|
@@ -2,15 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "constants"
|
|
4
4
|
|
|
5
|
-
# Collection to store arguments and outputs values
|
|
6
5
|
module Light
|
|
7
6
|
module Services
|
|
7
|
+
# Collection module for storing argument and output values.
|
|
8
8
|
module Collection
|
|
9
|
+
# Storage for service arguments or outputs with type validation.
|
|
10
|
+
#
|
|
11
|
+
# @example Accessing values
|
|
12
|
+
# service.arguments[:name] # => "John"
|
|
13
|
+
# service.outputs[:user] # => #<User id: 1>
|
|
9
14
|
class Base
|
|
10
15
|
extend Forwardable
|
|
11
16
|
|
|
17
|
+
# @!method key?(key)
|
|
18
|
+
# Check if a key exists in the collection.
|
|
19
|
+
# @param key [Symbol] the key to check
|
|
20
|
+
# @return [Boolean] true if key exists
|
|
21
|
+
|
|
22
|
+
# @!method to_h
|
|
23
|
+
# Convert collection to a hash.
|
|
24
|
+
# @return [Hash] the stored values
|
|
12
25
|
def_delegators :@storage, :key?, :to_h
|
|
13
26
|
|
|
27
|
+
# Initialize a new collection.
|
|
28
|
+
#
|
|
29
|
+
# @param instance [Base] the service instance
|
|
30
|
+
# @param collection_type [String] "arguments" or "outputs"
|
|
31
|
+
# @param storage [Hash] initial values
|
|
32
|
+
# @raise [ArgTypeError] if storage is not a Hash
|
|
14
33
|
def initialize(instance, collection_type, storage = {})
|
|
15
34
|
validate_collection_type!(collection_type)
|
|
16
35
|
|
|
@@ -23,22 +42,43 @@ module Light
|
|
|
23
42
|
raise Light::Services::ArgTypeError, "#{instance.class} - #{collection_type} must be a Hash"
|
|
24
43
|
end
|
|
25
44
|
|
|
45
|
+
# Set a value in the collection.
|
|
46
|
+
#
|
|
47
|
+
# @param key [Symbol] the key to set
|
|
48
|
+
# @param value [Object] the value to store
|
|
49
|
+
# @return [Object] the stored value
|
|
26
50
|
def set(key, value)
|
|
27
51
|
@storage[key] = value
|
|
28
52
|
end
|
|
29
53
|
|
|
54
|
+
# Get a value from the collection.
|
|
55
|
+
#
|
|
56
|
+
# @param key [Symbol] the key to retrieve
|
|
57
|
+
# @return [Object, nil] the stored value or nil
|
|
30
58
|
def get(key)
|
|
31
59
|
@storage[key]
|
|
32
60
|
end
|
|
33
61
|
|
|
62
|
+
# Get a value using bracket notation.
|
|
63
|
+
#
|
|
64
|
+
# @param key [Symbol] the key to retrieve
|
|
65
|
+
# @return [Object, nil] the stored value or nil
|
|
34
66
|
def [](key)
|
|
35
67
|
get(key)
|
|
36
68
|
end
|
|
37
69
|
|
|
70
|
+
# Set a value using bracket notation.
|
|
71
|
+
#
|
|
72
|
+
# @param key [Symbol] the key to set
|
|
73
|
+
# @param value [Object] the value to store
|
|
74
|
+
# @return [Object] the stored value
|
|
38
75
|
def []=(key, value)
|
|
39
76
|
set(key, value)
|
|
40
77
|
end
|
|
41
78
|
|
|
79
|
+
# Load default values for fields that haven't been set.
|
|
80
|
+
#
|
|
81
|
+
# @return [void]
|
|
42
82
|
def load_defaults
|
|
43
83
|
settings_collection.each do |name, settings|
|
|
44
84
|
next if !settings.default_exists || key?(name)
|
|
@@ -51,6 +91,10 @@ module Light
|
|
|
51
91
|
end
|
|
52
92
|
end
|
|
53
93
|
|
|
94
|
+
# Validate all values against their type definitions.
|
|
95
|
+
#
|
|
96
|
+
# @return [void]
|
|
97
|
+
# @raise [ArgTypeError] if a value fails type validation
|
|
54
98
|
def validate!
|
|
55
99
|
settings_collection.each do |name, field|
|
|
56
100
|
next if field.optional && (!key?(name) || get(name).nil?)
|
|
@@ -62,7 +106,11 @@ module Light
|
|
|
62
106
|
end
|
|
63
107
|
end
|
|
64
108
|
|
|
65
|
-
# Extend
|
|
109
|
+
# Extend arguments hash with context values from this collection.
|
|
110
|
+
# Only applies to arguments collections.
|
|
111
|
+
#
|
|
112
|
+
# @param args [Hash] arguments hash to extend
|
|
113
|
+
# @return [Hash] the extended arguments hash
|
|
66
114
|
def extend_with_context(args)
|
|
67
115
|
return args unless @collection_type == CollectionTypes::ARGUMENTS
|
|
68
116
|
|
|
@@ -3,17 +3,73 @@
|
|
|
3
3
|
module Light
|
|
4
4
|
module Services
|
|
5
5
|
class << self
|
|
6
|
+
# Configure Light::Services with a block.
|
|
7
|
+
#
|
|
8
|
+
# @yield [Config] the configuration object
|
|
9
|
+
# @return [void]
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# Light::Services.configure do |config|
|
|
13
|
+
# config.require_type = true
|
|
14
|
+
# config.use_transactions = false
|
|
15
|
+
# end
|
|
6
16
|
def configure
|
|
7
17
|
yield config
|
|
8
18
|
end
|
|
9
19
|
|
|
20
|
+
# Get the global configuration object.
|
|
21
|
+
#
|
|
22
|
+
# @return [Config] the configuration instance
|
|
10
23
|
def config
|
|
11
24
|
@config ||= Config.new
|
|
12
25
|
end
|
|
13
26
|
end
|
|
14
27
|
|
|
28
|
+
# Configuration class for Light::Services global settings.
|
|
29
|
+
#
|
|
30
|
+
# @example Accessing configuration
|
|
31
|
+
# Light::Services.config.require_type # => true
|
|
32
|
+
#
|
|
33
|
+
# @example Modifying configuration
|
|
34
|
+
# Light::Services.config.use_transactions = false
|
|
15
35
|
class Config
|
|
36
|
+
# @return [Boolean] whether arguments and outputs must have a type specified
|
|
37
|
+
attr_reader :require_type
|
|
38
|
+
|
|
39
|
+
# @return [Boolean] whether to wrap service execution in a database transaction
|
|
40
|
+
attr_reader :use_transactions
|
|
41
|
+
|
|
42
|
+
# @return [Boolean] whether to copy errors to parent service in chain
|
|
43
|
+
attr_reader :load_errors
|
|
44
|
+
|
|
45
|
+
# @return [Boolean] whether to stop executing steps when an error is added
|
|
46
|
+
attr_reader :break_on_error
|
|
47
|
+
|
|
48
|
+
# @return [Boolean] whether to raise Light::Services::Error when service fails
|
|
49
|
+
attr_reader :raise_on_error
|
|
50
|
+
|
|
51
|
+
# @return [Boolean] whether to rollback the transaction when an error is added
|
|
52
|
+
attr_reader :rollback_on_error
|
|
53
|
+
|
|
54
|
+
# @return [Boolean] whether to copy warnings to parent service in chain
|
|
55
|
+
attr_reader :load_warnings
|
|
56
|
+
|
|
57
|
+
# @return [Boolean] whether to stop executing steps when a warning is added
|
|
58
|
+
attr_reader :break_on_warning
|
|
59
|
+
|
|
60
|
+
# @return [Boolean] whether to raise Light::Services::Error when service has warnings
|
|
61
|
+
attr_reader :raise_on_warning
|
|
62
|
+
|
|
63
|
+
# @return [Boolean] whether to rollback the transaction when a warning is added
|
|
64
|
+
attr_reader :rollback_on_warning
|
|
65
|
+
|
|
66
|
+
# @return [Hash{String => String}] custom type mappings for Ruby LSP addon.
|
|
67
|
+
# Maps dry-types or custom types to Ruby types for hover/completion.
|
|
68
|
+
# @example { "Types::UUID" => "String", "CustomTypes::Money" => "BigDecimal" }
|
|
69
|
+
attr_reader :ruby_lsp_type_mappings
|
|
70
|
+
|
|
16
71
|
DEFAULTS = {
|
|
72
|
+
require_type: true,
|
|
17
73
|
use_transactions: true,
|
|
18
74
|
|
|
19
75
|
load_errors: true,
|
|
@@ -25,22 +81,46 @@ module Light
|
|
|
25
81
|
break_on_warning: false,
|
|
26
82
|
raise_on_warning: false,
|
|
27
83
|
rollback_on_warning: false,
|
|
84
|
+
|
|
85
|
+
ruby_lsp_type_mappings: {}.freeze,
|
|
28
86
|
}.freeze
|
|
29
87
|
|
|
30
|
-
|
|
88
|
+
DEFAULTS.each_key do |name|
|
|
89
|
+
define_method(:"#{name}=") do |value|
|
|
90
|
+
instance_variable_set(:"@#{name}", value)
|
|
91
|
+
@to_h = nil # Invalidate memoized hash
|
|
92
|
+
end
|
|
93
|
+
end
|
|
31
94
|
|
|
95
|
+
# Initialize configuration with default values.
|
|
32
96
|
def initialize
|
|
33
97
|
reset_to_defaults!
|
|
34
98
|
end
|
|
35
99
|
|
|
100
|
+
# Reset all configuration options to their default values.
|
|
101
|
+
#
|
|
102
|
+
# @return [void]
|
|
36
103
|
def reset_to_defaults!
|
|
37
|
-
DEFAULTS.each
|
|
104
|
+
DEFAULTS.each do |key, value|
|
|
105
|
+
instance_variable_set(:"@#{key}", value)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
@to_h = nil # Invalidate memoized hash
|
|
38
109
|
end
|
|
39
110
|
|
|
111
|
+
# Convert configuration to a hash.
|
|
112
|
+
#
|
|
113
|
+
# @return [Hash{Symbol => Object}] all configuration options as a hash
|
|
40
114
|
def to_h
|
|
41
|
-
DEFAULTS.keys.to_h
|
|
115
|
+
@to_h ||= DEFAULTS.keys.to_h do |key|
|
|
116
|
+
[key, public_send(key)]
|
|
117
|
+
end
|
|
42
118
|
end
|
|
43
119
|
|
|
120
|
+
# Merge configuration with additional options.
|
|
121
|
+
#
|
|
122
|
+
# @param config [Hash] options to merge
|
|
123
|
+
# @return [Hash] merged configuration hash
|
|
44
124
|
def merge(config)
|
|
45
125
|
to_h.merge(config)
|
|
46
126
|
end
|
|
@@ -41,6 +41,7 @@ module Light
|
|
|
41
41
|
Validation.validate_symbol_name!(name, :argument, self)
|
|
42
42
|
Validation.validate_reserved_name!(name, :argument, self)
|
|
43
43
|
Validation.validate_name_conflicts!(name, :argument, self)
|
|
44
|
+
Validation.validate_type_required!(name, :argument, self, opts)
|
|
44
45
|
|
|
45
46
|
own_arguments[name] = Settings::Field.new(name, self, opts.merge(field_type: FieldTypes::ARGUMENT))
|
|
46
47
|
@arguments = nil # Clear memoized arguments since we're modifying them
|
|
@@ -37,6 +37,7 @@ module Light
|
|
|
37
37
|
Validation.validate_symbol_name!(name, :output, self)
|
|
38
38
|
Validation.validate_reserved_name!(name, :output, self)
|
|
39
39
|
Validation.validate_name_conflicts!(name, :output, self)
|
|
40
|
+
Validation.validate_type_required!(name, :output, self, opts)
|
|
40
41
|
|
|
41
42
|
own_outputs[name] = Settings::Field.new(name, self, opts.merge(field_type: FieldTypes::OUTPUT))
|
|
42
43
|
@outputs = nil # Clear memoized outputs since we're modifying them
|