servactory 3.0.0.rc3 → 3.0.0.rc4

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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/lib/servactory/context/warehouse/base.rb +20 -4
  3. data/lib/servactory/context/warehouse/crate.rb +39 -0
  4. data/lib/servactory/context/warehouse/inputs.rb +41 -10
  5. data/lib/servactory/context/warehouse/internals.rb +7 -2
  6. data/lib/servactory/context/warehouse/outputs.rb +7 -0
  7. data/lib/servactory/context/warehouse/setup.rb +53 -30
  8. data/lib/servactory/context/workspace/inputs.rb +6 -8
  9. data/lib/servactory/context/workspace/internals.rb +5 -7
  10. data/lib/servactory/context/workspace/outputs.rb +5 -7
  11. data/lib/servactory/inputs/input.rb +28 -18
  12. data/lib/servactory/inputs/tools/validation.rb +9 -9
  13. data/lib/servactory/inputs/validations/required.rb +51 -27
  14. data/lib/servactory/internals/internal.rb +20 -16
  15. data/lib/servactory/maintenance/attributes/tools/validation.rb +9 -10
  16. data/lib/servactory/maintenance/attributes/validations/concerns/error_builder.rb +52 -0
  17. data/lib/servactory/maintenance/attributes/validations/must.rb +164 -57
  18. data/lib/servactory/maintenance/attributes/validations/type.rb +77 -27
  19. data/lib/servactory/maintenance/validations/types.rb +18 -33
  20. data/lib/servactory/outputs/output.rb +18 -13
  21. data/lib/servactory/result.rb +266 -43
  22. data/lib/servactory/version.rb +1 -1
  23. metadata +3 -6
  24. data/lib/servactory/inputs/validations/base.rb +0 -21
  25. data/lib/servactory/inputs/validations/errors.rb +0 -17
  26. data/lib/servactory/maintenance/attributes/tools/check_errors.rb +0 -23
  27. data/lib/servactory/maintenance/attributes/validations/base.rb +0 -23
  28. data/lib/servactory/maintenance/attributes/validations/errors.rb +0 -19
@@ -1,24 +1,130 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Servactory
4
+ # Result object returned from Servactory service calls.
5
+ #
6
+ # ## Purpose
7
+ #
8
+ # Result provides a unified interface for handling service outcomes.
9
+ # Supports both success and failure states with dynamic output attribute access.
10
+ #
11
+ # ## Usage
12
+ #
13
+ # Results are typically obtained from service calls:
14
+ #
15
+ # ```ruby
16
+ # result = MyService.call(name: "John")
17
+ #
18
+ # if result.success?
19
+ # puts result.user # Access output attribute
20
+ # else
21
+ # puts result.error.message
22
+ # end
23
+ # ```
24
+ #
25
+ # ## Callback Chains
26
+ #
27
+ # Results support fluent callback chains:
28
+ #
29
+ # ```ruby
30
+ # MyService.call(arguments)
31
+ # .on_success { |outputs:| redirect_to(outputs[:user]) }
32
+ # .on_failure(:validation) { |exception:| render_errors(exception) }
33
+ # .on_failure { |exception:| log_error(exception) }
34
+ # ```
35
+ #
36
+ # ## Predicate Methods
37
+ #
38
+ # When `predicate_methods_enabled` is configured, outputs support
39
+ # boolean predicates:
40
+ #
41
+ # ```ruby
42
+ # result.active? # Equivalent to Utils.query_attribute(result.active)
43
+ # ```
4
44
  class Result
45
+ # Internal container for service output values.
46
+ #
47
+ # Provides dynamic method access to output values via method_missing.
48
+ # Stores outputs in hash and supports predicate methods when enabled.
5
49
  class Outputs
50
+ # Creates an Outputs container with given output values.
51
+ #
52
+ # @param outputs [Hash<Symbol, Object>] Output name-value pairs from warehouse
53
+ # @param predicate_methods_enabled [Boolean] Enable predicate methods (e.g., user?)
54
+ # @return [Outputs] New outputs container
6
55
  def initialize(outputs:, predicate_methods_enabled:)
7
- outputs.each_pair do |key, value|
8
- define_singleton_method(:"#{key}?") { Servactory::Utils.query_attribute(value) } if predicate_methods_enabled
9
- define_singleton_method(key) { value }
10
- end
56
+ @outputs = outputs
57
+ @predicate_methods_enabled = predicate_methods_enabled
11
58
  end
12
59
 
60
+ # Returns string representation for debugging.
61
+ #
62
+ # @return [String] Formatted output values
13
63
  def inspect
14
64
  "#<#{self.class.name} #{draw_result}>"
15
65
  end
16
66
 
67
+ # Delegates method calls to stored outputs.
68
+ #
69
+ # Supports both regular output access and predicate methods
70
+ # when predicate_methods_enabled is true.
71
+ #
72
+ # @param name [Symbol] Method name (output or predicate)
73
+ # @param args [Array] Method arguments (unused)
74
+ # @return [Object] Output value or predicate result
75
+ def method_missing(name, *args)
76
+ if name.to_s.end_with?("?")
77
+ base_name = name.to_s.chomp("?").to_sym
78
+ if @predicate_methods_enabled && @outputs.key?(base_name)
79
+ return Servactory::Utils.query_attribute(@outputs[base_name])
80
+ end
81
+ elsif @outputs.key?(name)
82
+ return @outputs[name]
83
+ end
84
+
85
+ super
86
+ end
87
+
88
+ # Checks if method corresponds to stored output.
89
+ #
90
+ # @param name [Symbol] Method name to check
91
+ # @param include_private [Boolean] Include private methods in check
92
+ # @return [Boolean] True if output exists for this method
93
+ def respond_to_missing?(name, include_private = false)
94
+ if name.to_s.end_with?("?")
95
+ base_name = name.to_s.chomp("?").to_sym
96
+ return true if @predicate_methods_enabled && @outputs.key?(base_name)
97
+ elsif @outputs.key?(name)
98
+ return true
99
+ end
100
+
101
+ super
102
+ end
103
+
17
104
  private
18
105
 
106
+ # Returns array of output attribute names.
107
+ #
108
+ # @return [Array<Symbol>] Output names without predicates
109
+ def output_names
110
+ @outputs.keys
111
+ end
112
+
113
+ # Returns array of predicate method names.
114
+ #
115
+ # @return [Array<Symbol>] Predicate names (e.g., [:user?, :token?])
116
+ def predicate_names
117
+ return [] unless @predicate_methods_enabled
118
+
119
+ @outputs.keys.map { |key| :"#{key}?" }
120
+ end
121
+
122
+ # Builds formatted string of output values.
123
+ #
124
+ # @return [String] Comma-separated output representations
19
125
  def draw_result
20
- methods(false).sort.map do |method_name|
21
- "@#{method_name}=#{send(method_name)}"
126
+ (output_names + predicate_names).sort.map do |method_name|
127
+ "@#{method_name}=#{public_send(method_name)}"
22
128
  end.join(", ")
23
129
  end
24
130
  end
@@ -30,99 +136,216 @@ module Servactory
30
136
  STATE_PREDICATE_NAMES = %i[success? failure?].freeze
31
137
  private_constant :STATE_PREDICATE_NAMES
32
138
 
139
+ # Creates a success result for the given context.
140
+ #
141
+ # @param context [Object] Service execution context with outputs
142
+ # @return [Result] Success result with outputs accessible
143
+ #
144
+ # @example
145
+ # Result.success_for(context: service_context)
33
146
  def self.success_for(context:)
34
- new(context:).send(:as_success)
147
+ new(context:, success: true)
35
148
  end
36
149
 
150
+ # Creates a failure result for the given context.
151
+ #
152
+ # @param context [Object] Service execution context
153
+ # @param exception [Exception] Failure exception with type and message
154
+ # @return [Result] Failure result with error accessible
155
+ #
156
+ # @example
157
+ # Result.failure_for(context: ctx, exception: error)
37
158
  def self.failure_for(context:, exception:)
38
- new(context:, exception:).send(:as_failure)
159
+ new(context:, exception:, success: false)
39
160
  end
40
161
 
41
- def initialize(context:, exception: nil)
162
+ # Initializes a Result instance.
163
+ #
164
+ # @param context [Object] Service execution context
165
+ # @param exception [Exception, nil] Failure exception (nil for success)
166
+ # @param success [Boolean] Success state flag
167
+ def initialize(context:, exception: nil, success: true)
42
168
  @context = context
43
169
  @exception = exception
170
+ @success = success
171
+ end
172
+
173
+ # Returns whether the service call succeeded.
174
+ #
175
+ # @return [Boolean] True if successful
176
+ #
177
+ # @example
178
+ # result.success? # => true
179
+ def success?
180
+ @success
181
+ end
182
+
183
+ # Returns whether the service call failed.
184
+ #
185
+ # Supports filtering by failure type when called with argument.
186
+ #
187
+ # @param type [Symbol] Failure type to check (default: :all matches any failure)
188
+ # @return [Boolean] True if failed (optionally matching type)
189
+ #
190
+ # @example Check any failure
191
+ # result.failure? # => true
192
+ #
193
+ # @example Check specific failure type
194
+ # result.failure?(:validation) # => true
195
+ # result.failure?(:non_existent) # => false
196
+ def failure?(type = :all)
197
+ return false if @success
198
+
199
+ [:all, @exception&.type].include?(type)
44
200
  end
45
201
 
202
+ # Returns the failure exception.
203
+ #
204
+ # @return [Exception, nil] Exception for failures, nil for success
205
+ #
206
+ # @example
207
+ # result.error # => #<Servactory::Exceptions::Failure>
208
+ # result.error.message # => "Validation failed"
209
+ def error
210
+ @exception
211
+ end
212
+
213
+ # Converts outputs to hash.
214
+ #
215
+ # Excludes predicate methods from the result hash.
216
+ #
217
+ # @return [Hash<Symbol, Object>] Output name-value pairs
218
+ #
219
+ # @example
220
+ # result.to_h # => { user: #<User>, token: "abc123" }
46
221
  def to_h
47
- methods(false)
48
- .reject { |key| key.in?(STATE_PREDICATE_NAMES) || key.to_s.end_with?("?") }
49
- .to_h { |key| [key, public_send(key)] }
50
- .compact
222
+ outputs.send(:output_names).to_h { |key| [key, outputs.public_send(key)] }.compact
51
223
  end
52
224
 
225
+ # Returns string representation for debugging.
226
+ #
227
+ # @return [String] Formatted result state and outputs
53
228
  def inspect
54
229
  "#<#{self.class.name} #{draw_result}>"
55
230
  end
56
231
 
232
+ # Executes block if result is success.
233
+ #
234
+ # Supports method chaining for fluent API.
235
+ #
236
+ # @yield [outputs:] Block to execute on success
237
+ # @yieldparam outputs [Outputs] Container with output values
238
+ # @return [self] For chaining
239
+ #
240
+ # @example
241
+ # result.on_success { |outputs:| puts outputs.user.name }
57
242
  def on_success
58
243
  yield(outputs:) if success?
59
244
 
60
245
  self
61
246
  end
62
247
 
248
+ # Executes block if result is failure.
249
+ #
250
+ # Supports filtering by failure type and method chaining.
251
+ #
252
+ # @param type [Symbol] Failure type to match (default: :all matches any failure)
253
+ # @yield [outputs:, exception:] Block to execute on failure
254
+ # @yieldparam outputs [Outputs] Container with output values
255
+ # @yieldparam exception [Exception] Failure exception
256
+ # @return [self] For chaining
257
+ #
258
+ # @example Handle any failure
259
+ # result.on_failure { |exception:| log(exception.message) }
260
+ #
261
+ # @example Handle specific failure type
262
+ # result.on_failure(:validation) { |exception:| show_errors(exception) }
63
263
  def on_failure(type = :all)
64
264
  yield(outputs:, exception: @exception) if failure? && [:all, @exception&.type].include?(type)
65
265
 
66
266
  self
67
267
  end
68
268
 
269
+ # Delegates method calls to outputs container.
270
+ #
271
+ # Provides access to output attributes as if they were
272
+ # defined on the Result instance itself.
273
+ #
274
+ # @param name [Symbol] Method name (output attribute)
275
+ # @param args [Array] Method arguments
276
+ # @param block [Proc] Optional block
277
+ # @return [Object] Output value
69
278
  def method_missing(name, *args, &block)
279
+ return outputs.public_send(name, *args, &block) if outputs.respond_to?(name)
280
+
70
281
  super
71
282
  rescue NoMethodError => e
72
283
  rescue_no_method_error_with(exception: e)
73
284
  end
74
285
 
75
- def respond_to_missing?(*)
76
- super
286
+ # Checks if method corresponds to output attribute.
287
+ #
288
+ # @param name [Symbol] Method name to check
289
+ # @param include_private [Boolean] Include private methods in check
290
+ # @return [Boolean] True if output exists
291
+ def respond_to_missing?(name, include_private = false)
292
+ outputs.respond_to?(name, include_private) || super
77
293
  end
78
294
 
79
295
  private
80
296
 
81
- def as_success
82
- define_singleton_method(:success?) { true }
83
- define_singleton_method(:failure?) { false }
84
-
85
- outputs.methods(false).each do |method_name|
86
- define_singleton_method(method_name) { outputs.send(method_name) }
87
- end
88
-
89
- self
90
- end
297
+ # Builds formatted string of result state and outputs.
298
+ #
299
+ # @return [String] Comma-separated result representations
300
+ def draw_result # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
301
+ result_parts = []
91
302
 
92
- def as_failure
93
- define_singleton_method(:error) { @exception }
303
+ result_parts << "@success?=#{success?}"
304
+ result_parts << "@failure?=#{failure?}"
305
+ result_parts << "@error=#{@exception}" if @exception
94
306
 
95
- define_singleton_method(:success?) { false }
96
-
97
- define_singleton_method(:failure?) do |type = :all|
98
- return true if [:all, @exception&.type].include?(type)
99
-
100
- false
307
+ outputs.send(:output_names).each do |method_name|
308
+ result_parts << "@#{method_name}=#{outputs.public_send(method_name)}"
101
309
  end
102
310
 
103
- outputs.methods(false).each do |method_name|
104
- define_singleton_method(method_name) { outputs.send(method_name) }
311
+ outputs.send(:predicate_names).each do |method_name|
312
+ result_parts << "@#{method_name}=#{outputs.public_send(method_name)}"
105
313
  end
106
314
 
107
- self
108
- end
109
-
110
- def draw_result
111
- methods(false).sort.map do |method_name|
112
- "@#{method_name}=#{send(method_name)}"
113
- end.join(", ")
315
+ result_parts.sort.join(", ")
114
316
  end
115
317
 
318
+ # Returns outputs container, lazily initialized.
319
+ #
320
+ # @return [Outputs] Outputs container with service values
116
321
  def outputs
117
322
  @outputs ||= Outputs.new(
118
- outputs: @context.send(:servactory_service_warehouse).outputs,
323
+ outputs: build_outputs_hash,
119
324
  predicate_methods_enabled:
120
325
  @context.is_a?(Servactory::TestKit::Result) || @context.config.predicate_methods_enabled
121
326
  )
122
327
  end
123
328
 
329
+ # Builds hash from warehouse outputs object.
330
+ #
331
+ # @return [Hash<Symbol, Object>] Output name-value pairs
332
+ def build_outputs_hash
333
+ hash = {}
334
+ @context.send(:servactory_service_warehouse).outputs.each_pair do |key, value|
335
+ hash[key] = value
336
+ end
337
+ hash
338
+ end
339
+
124
340
  ########################################################################
125
341
 
342
+ # Wraps NoMethodError with contextual failure.
343
+ #
344
+ # Converts undefined method errors to Servactory failure exceptions
345
+ # with localized error messages.
346
+ #
347
+ # @param exception [NoMethodError] Original exception
348
+ # @raise [Exception] Wrapped failure exception with context
126
349
  def rescue_no_method_error_with(exception:) # rubocop:disable Metrics/MethodLength
127
350
  raise exception if @context.blank? || @context.instance_of?(Servactory::TestKit::Result)
128
351
 
@@ -5,7 +5,7 @@ module Servactory
5
5
  MAJOR = 3
6
6
  MINOR = 0
7
7
  PATCH = 0
8
- PRE = "rc3"
8
+ PRE = "rc4"
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
11
11
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: servactory
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.rc3
4
+ version: 3.0.0.rc4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Sokolov
@@ -231,6 +231,7 @@ files:
231
231
  - lib/servactory/context/callable.rb
232
232
  - lib/servactory/context/dsl.rb
233
233
  - lib/servactory/context/warehouse/base.rb
234
+ - lib/servactory/context/warehouse/crate.rb
234
235
  - lib/servactory/context/warehouse/inputs.rb
235
236
  - lib/servactory/context/warehouse/internals.rb
236
237
  - lib/servactory/context/warehouse/outputs.rb
@@ -258,8 +259,6 @@ files:
258
259
  - lib/servactory/inputs/tools/validation.rb
259
260
  - lib/servactory/inputs/tools/warehouse.rb
260
261
  - lib/servactory/inputs/translator/required.rb
261
- - lib/servactory/inputs/validations/base.rb
262
- - lib/servactory/inputs/validations/errors.rb
263
262
  - lib/servactory/inputs/validations/required.rb
264
263
  - lib/servactory/inputs/workspace.rb
265
264
  - lib/servactory/internals/collection.rb
@@ -271,12 +270,10 @@ files:
271
270
  - lib/servactory/maintenance/attributes/option_helper.rb
272
271
  - lib/servactory/maintenance/attributes/options/registrar.rb
273
272
  - lib/servactory/maintenance/attributes/options_collection.rb
274
- - lib/servactory/maintenance/attributes/tools/check_errors.rb
275
273
  - lib/servactory/maintenance/attributes/tools/validation.rb
276
274
  - lib/servactory/maintenance/attributes/translator/must.rb
277
275
  - lib/servactory/maintenance/attributes/translator/type.rb
278
- - lib/servactory/maintenance/attributes/validations/base.rb
279
- - lib/servactory/maintenance/attributes/validations/errors.rb
276
+ - lib/servactory/maintenance/attributes/validations/concerns/error_builder.rb
280
277
  - lib/servactory/maintenance/attributes/validations/must.rb
281
278
  - lib/servactory/maintenance/attributes/validations/type.rb
282
279
  - lib/servactory/maintenance/validations/types.rb
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Inputs
5
- module Validations
6
- class Base
7
- private
8
-
9
- def add_error(message:, **attributes)
10
- message = message.call(**attributes) if message.is_a?(Proc)
11
-
12
- errors << message
13
- end
14
-
15
- def errors
16
- @errors ||= Errors.new
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Inputs
5
- module Validations
6
- class Errors
7
- extend Forwardable
8
-
9
- def_delegators :@collection, :<<, :to_a
10
-
11
- def initialize(*)
12
- @collection = Set.new
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Maintenance
5
- module Attributes
6
- module Tools
7
- class CheckErrors
8
- extend Forwardable
9
-
10
- def_delegators :@collection, :merge, :reject, :first, :empty?
11
-
12
- def initialize(collection = Set.new)
13
- @collection = collection
14
- end
15
-
16
- def not_blank
17
- CheckErrors.new(reject(&:blank?))
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Maintenance
5
- module Attributes
6
- module Validations
7
- class Base
8
- private
9
-
10
- def add_error(message:, **attributes)
11
- message = message.call(**attributes) if message.is_a?(Proc)
12
-
13
- errors << message
14
- end
15
-
16
- def errors
17
- @errors ||= Errors.new
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Maintenance
5
- module Attributes
6
- module Validations
7
- class Errors
8
- extend Forwardable
9
-
10
- def_delegators :@collection, :<<, :to_a, :empty?
11
-
12
- def initialize(*)
13
- @collection = Set.new
14
- end
15
- end
16
- end
17
- end
18
- end
19
- end