servactory 2.16.1 → 3.0.0.rc2

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -9
  3. data/config/locales/de.yml +134 -0
  4. data/config/locales/en.yml +15 -0
  5. data/config/locales/es.yml +134 -0
  6. data/config/locales/fr.yml +134 -0
  7. data/config/locales/it.yml +134 -0
  8. data/config/locales/ru.yml +15 -0
  9. data/lib/generators/README.md +45 -0
  10. data/lib/generators/servactory/base.rb +82 -0
  11. data/lib/generators/servactory/extension/USAGE +54 -0
  12. data/lib/generators/servactory/extension/extension_generator.rb +41 -0
  13. data/lib/generators/servactory/extension/templates/extension.rb.tt +62 -0
  14. data/lib/generators/servactory/install/USAGE +27 -0
  15. data/lib/generators/servactory/install/install_generator.rb +94 -0
  16. data/lib/generators/servactory/{templates/services/application_service/base.rb → install/templates/application_service/base.rb.tt} +29 -19
  17. data/lib/generators/servactory/{templates/services/application_service/exceptions.rb → install/templates/application_service/exceptions.rb.tt} +1 -1
  18. data/lib/generators/servactory/{templates/services/application_service/result.rb → install/templates/application_service/result.rb.tt} +1 -1
  19. data/lib/generators/servactory/rspec/USAGE +46 -0
  20. data/lib/generators/servactory/rspec/rspec_generator.rb +95 -0
  21. data/lib/generators/servactory/rspec/templates/service_spec.rb.tt +58 -0
  22. data/lib/generators/servactory/service/USAGE +51 -0
  23. data/lib/generators/servactory/service/service_generator.rb +56 -0
  24. data/lib/generators/servactory/service/templates/service.rb.tt +22 -0
  25. data/lib/servactory/actions/collection.rb +56 -1
  26. data/lib/servactory/actions/dsl.rb +11 -11
  27. data/lib/servactory/actions/tools/rules.rb +1 -1
  28. data/lib/servactory/actions/tools/runner.rb +111 -28
  29. data/lib/servactory/base.rb +1 -7
  30. data/lib/servactory/configuration/actions/aliases/collection.rb +5 -0
  31. data/lib/servactory/configuration/actions/rescue_handlers/collection.rb +5 -0
  32. data/lib/servactory/configuration/actions/shortcuts/collection.rb +5 -0
  33. data/lib/servactory/configuration/collection_mode/class_names_collection.rb +5 -0
  34. data/lib/servactory/configuration/config.rb +36 -0
  35. data/lib/servactory/configuration/configurable.rb +95 -0
  36. data/lib/servactory/configuration/dsl.rb +3 -27
  37. data/lib/servactory/configuration/factory.rb +20 -20
  38. data/lib/servactory/configuration/hash_mode/class_names_collection.rb +5 -0
  39. data/lib/servactory/configuration/option_helpers/option_helpers_collection.rb +5 -0
  40. data/lib/servactory/context/warehouse/inputs.rb +2 -2
  41. data/lib/servactory/context/workspace/inputs.rb +2 -2
  42. data/lib/servactory/context/workspace/internals.rb +2 -2
  43. data/lib/servactory/context/workspace/outputs.rb +2 -2
  44. data/lib/servactory/context/workspace.rb +11 -7
  45. data/lib/servactory/dsl.rb +10 -8
  46. data/lib/servactory/maintenance/attributes/tools/validation.rb +1 -1
  47. data/lib/servactory/result.rb +2 -2
  48. data/lib/servactory/test_kit/fake_type.rb +23 -0
  49. data/lib/servactory/test_kit/result.rb +45 -0
  50. data/lib/servactory/test_kit/rspec/helpers/argument_matchers.rb +97 -0
  51. data/lib/servactory/test_kit/rspec/helpers/concerns/error_messages.rb +179 -0
  52. data/lib/servactory/test_kit/rspec/helpers/concerns/service_class_validation.rb +74 -0
  53. data/lib/servactory/test_kit/rspec/helpers/fluent.rb +110 -0
  54. data/lib/servactory/test_kit/rspec/helpers/input_validator.rb +149 -0
  55. data/lib/servactory/test_kit/rspec/helpers/legacy.rb +228 -0
  56. data/lib/servactory/test_kit/rspec/helpers/mock_executor.rb +256 -0
  57. data/lib/servactory/test_kit/rspec/helpers/output_validator.rb +121 -0
  58. data/lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb +422 -0
  59. data/lib/servactory/test_kit/rspec/helpers/service_mock_config.rb +129 -0
  60. data/lib/servactory/test_kit/rspec/helpers.rb +51 -84
  61. data/lib/servactory/test_kit/rspec/matchers/base/attribute_matcher.rb +324 -0
  62. data/lib/servactory/test_kit/rspec/matchers/base/submatcher.rb +133 -0
  63. data/lib/servactory/test_kit/rspec/matchers/base/submatcher_context.rb +101 -0
  64. data/lib/servactory/test_kit/rspec/matchers/base/submatcher_registry.rb +205 -0
  65. data/lib/servactory/test_kit/rspec/matchers/concerns/attribute_data_access.rb +100 -0
  66. data/lib/servactory/test_kit/rspec/matchers/concerns/error_message_builder.rb +106 -0
  67. data/lib/servactory/test_kit/rspec/matchers/concerns/value_comparison.rb +97 -0
  68. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +89 -219
  69. data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +74 -166
  70. data/lib/servactory/test_kit/rspec/matchers/have_service_output_matcher.rb +238 -0
  71. data/lib/servactory/test_kit/rspec/matchers/result/be_failure_service_matcher.rb +257 -0
  72. data/lib/servactory/test_kit/rspec/matchers/result/be_success_service_matcher.rb +185 -0
  73. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/default_submatcher.rb +81 -0
  74. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/optional_submatcher.rb +62 -0
  75. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/required_submatcher.rb +93 -0
  76. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/valid_with_submatcher.rb +271 -0
  77. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/consists_of_submatcher.rb +85 -0
  78. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/inclusion_submatcher.rb +120 -0
  79. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/message_submatcher.rb +115 -0
  80. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/must_submatcher.rb +82 -0
  81. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/schema_submatcher.rb +102 -0
  82. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/target_submatcher.rb +125 -0
  83. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/types_submatcher.rb +77 -0
  84. data/lib/servactory/test_kit/rspec/matchers.rb +126 -285
  85. data/lib/servactory/test_kit/utils/faker.rb +62 -2
  86. data/lib/servactory/tool_kit/dynamic_options/consists_of.rb +166 -0
  87. data/lib/servactory/tool_kit/dynamic_options/format.rb +195 -8
  88. data/lib/servactory/tool_kit/dynamic_options/inclusion.rb +219 -17
  89. data/lib/servactory/tool_kit/dynamic_options/max.rb +143 -0
  90. data/lib/servactory/tool_kit/dynamic_options/min.rb +143 -0
  91. data/lib/servactory/tool_kit/dynamic_options/multiple_of.rb +157 -8
  92. data/lib/servactory/tool_kit/dynamic_options/must.rb +194 -0
  93. data/lib/servactory/tool_kit/dynamic_options/schema.rb +226 -2
  94. data/lib/servactory/tool_kit/dynamic_options/target.rb +248 -0
  95. data/lib/servactory/version.rb +4 -4
  96. data/lib/servactory.rb +6 -0
  97. metadata +57 -48
  98. data/lib/generators/servactory/install_generator.rb +0 -21
  99. data/lib/generators/servactory/rspec_generator.rb +0 -88
  100. data/lib/generators/servactory/service_generator.rb +0 -49
  101. data/lib/servactory/configuration/setup.rb +0 -97
  102. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +0 -68
  103. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +0 -73
  104. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/message_matcher.rb +0 -91
  105. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +0 -72
  106. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/schema_matcher.rb +0 -92
  107. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/types_matcher.rb +0 -72
  108. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/default_matcher.rb +0 -69
  109. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/optional_matcher.rb +0 -63
  110. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/required_matcher.rb +0 -81
  111. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb +0 -199
@@ -3,31 +3,154 @@
3
3
  module Servactory
4
4
  module ToolKit
5
5
  module DynamicOptions
6
+ # Validates that numeric value is a multiple of a specified number.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # MultipleOf ensures that numeric values are evenly divisible by
11
+ # a specified divisor. This is useful for validating quantities,
12
+ # prices, or any values that must conform to specific increments.
13
+ #
14
+ # ## Usage
15
+ #
16
+ # This option is **NOT included by default**. Register it for each
17
+ # attribute type where you want to use it:
18
+ #
19
+ # ```ruby
20
+ # configuration do
21
+ # input_option_helpers([
22
+ # Servactory::ToolKit::DynamicOptions::MultipleOf.use
23
+ # ])
24
+ #
25
+ # internal_option_helpers([
26
+ # Servactory::ToolKit::DynamicOptions::MultipleOf.use
27
+ # ])
28
+ #
29
+ # output_option_helpers([
30
+ # Servactory::ToolKit::DynamicOptions::MultipleOf.use
31
+ # ])
32
+ # end
33
+ # ```
34
+ #
35
+ # Use in your service definition:
36
+ #
37
+ # ```ruby
38
+ # class ProcessOrderService < ApplicationService::Base
39
+ # input :quantity, type: Integer, multiple_of: 5
40
+ # input :price, type: Float, multiple_of: 0.25
41
+ # input :batch_size, type: Integer, multiple_of: 100
42
+ # end
43
+ # ```
44
+ #
45
+ # ## Simple Mode
46
+ #
47
+ # Specify divisor directly:
48
+ #
49
+ # ```ruby
50
+ # class ProcessOrderService < ApplicationService::Base
51
+ # input :quantity, type: Integer, multiple_of: 5
52
+ # input :price, type: Float, multiple_of: 0.25
53
+ # input :batch_size, type: Integer, multiple_of: 100
54
+ # end
55
+ # ```
56
+ #
57
+ # ## Advanced Mode
58
+ #
59
+ # Specify divisor with custom error message using a hash:
60
+ #
61
+ # With static message:
62
+ #
63
+ # ```ruby
64
+ # input :quantity, type: Integer, multiple_of: {
65
+ # is: 5,
66
+ # message: "Input `quantity` must be a multiple of 5"
67
+ # }
68
+ # ```
69
+ #
70
+ # With dynamic lambda message:
71
+ #
72
+ # ```ruby
73
+ # input :quantity, type: Integer, multiple_of: {
74
+ # is: 5,
75
+ # message: lambda do |input:, value:, option_value:, **|
76
+ # "Input `#{input.name}` must be divisible by #{option_value}, got #{value}"
77
+ # end
78
+ # }
79
+ # ```
80
+ #
81
+ # Lambda receives the following parameters:
82
+ # - For inputs: `input:, option_value:, value:, **`
83
+ # - For internals: `internal:, option_value:, value:, **`
84
+ # - For outputs: `output:, option_value:, value:, **`
85
+ #
86
+ # ## Supported Types
87
+ #
88
+ # - Integer
89
+ # - Float
90
+ # - Rational
91
+ # - BigDecimal
92
+ #
93
+ # ## Important Notes
94
+ #
95
+ # - Divisor must be a non-zero Numeric
96
+ # - Uses epsilon comparison for floating point precision
97
+ # - Returns false for non-numeric values
98
+ # - Provides specific error messages for blank and zero divisors
6
99
  class MultipleOf < Must
100
+ # Creates a MultipleOf validator instance.
101
+ #
102
+ # @param option_name [Symbol] The option name (default: :multiple_of)
103
+ # @return [Servactory::Maintenance::Attributes::OptionHelper]
7
104
  def self.use(option_name = :multiple_of)
8
105
  new(option_name).must(:be_multiple_of)
9
106
  end
10
107
 
108
+ # Validates multiple_of condition for input attribute.
109
+ #
110
+ # @param input [Object] Input attribute object
111
+ # @param value [Object] Value to validate
112
+ # @param option [WorkOption] Multiple of configuration
113
+ # @return [Boolean] true if valid
11
114
  def condition_for_input_with(...)
12
115
  common_condition_with(...)
13
116
  end
14
117
 
118
+ # Validates multiple_of condition for internal attribute.
119
+ #
120
+ # @param internal [Object] Internal attribute object
121
+ # @param value [Object] Value to validate
122
+ # @param option [WorkOption] Multiple of configuration
123
+ # @return [Boolean] true if valid
15
124
  def condition_for_internal_with(...)
16
125
  common_condition_with(...)
17
126
  end
18
127
 
128
+ # Validates multiple_of condition for output attribute.
129
+ #
130
+ # @param output [Object] Output attribute object
131
+ # @param value [Object] Value to validate
132
+ # @param option [WorkOption] Multiple of configuration
133
+ # @return [Boolean] true if valid
19
134
  def condition_for_output_with(...)
20
135
  common_condition_with(...)
21
136
  end
22
137
 
138
+ # Common validation logic for all attribute types.
139
+ #
140
+ # @param value [Object] Value to validate
141
+ # @param option [WorkOption] Multiple of configuration
142
+ # @return [Boolean] true if value is multiple of divisor
23
143
  def common_condition_with(value:, option:, **) # rubocop:disable Naming/PredicateMethod
24
144
  case value
25
145
  when Integer, Float, Rational, BigDecimal
146
+ # Validate divisor is present and valid.
26
147
  return false if option.value.blank?
27
- return false unless [Numeric, Float, Rational, BigDecimal].any? { |c| option.value.is_a?(c) }
148
+ return false unless option.value.is_a?(Numeric)
28
149
  return false if option.value.zero?
29
150
 
30
- (value % option.value).zero?
151
+ # Calculate remainder with epsilon tolerance for floats.
152
+ remainder = value % option.value
153
+ remainder.zero? || remainder.abs < Float::EPSILON * [value.abs, option.value.abs].max
31
154
  else
32
155
  false
33
156
  end
@@ -35,13 +158,25 @@ module Servactory
35
158
 
36
159
  ########################################################################
37
160
 
161
+ # Generates error message for input validation failure.
162
+ #
163
+ # Selects appropriate message based on divisor state:
164
+ # - blank: divisor is nil or empty
165
+ # - divided_by_0: divisor is zero
166
+ # - default: standard not-multiple-of message
167
+ #
168
+ # @param service [Object] Service context
169
+ # @param input [Object] Input attribute
170
+ # @param value [Object] Failed value
171
+ # @param option_name [Symbol] Option name
172
+ # @param option_value [Object] Divisor value
173
+ # @return [String] Localized error message
38
174
  def message_for_input_with(service:, input:, value:, option_name:, option_value:, **) # rubocop:disable Metrics/MethodLength
39
175
  i18n_key = "inputs.validations.must.dynamic_options.multiple_of"
40
176
 
41
177
  i18n_key += if option_value.blank?
42
178
  ".blank"
43
- elsif [Numeric, Float, Rational, BigDecimal].any? { |c| option_value.is_a?(c) } &&
44
- option_value.zero?
179
+ elsif option_value.is_a?(Numeric) && option_value.zero?
45
180
  ".divided_by_0"
46
181
  else
47
182
  ".default"
@@ -56,13 +191,20 @@ module Servactory
56
191
  )
57
192
  end
58
193
 
194
+ # Generates error message for internal validation failure.
195
+ #
196
+ # @param service [Object] Service context
197
+ # @param internal [Object] Internal attribute
198
+ # @param value [Object] Failed value
199
+ # @param option_name [Symbol] Option name
200
+ # @param option_value [Object] Divisor value
201
+ # @return [String] Localized error message
59
202
  def message_for_internal_with(service:, internal:, value:, option_name:, option_value:, **) # rubocop:disable Metrics/MethodLength
60
203
  i18n_key = "internals.validations.must.dynamic_options.multiple_of"
61
204
 
62
205
  i18n_key += if option_value.blank?
63
206
  ".blank"
64
- elsif [Numeric, Float, Rational, BigDecimal].any? { |c| option_value.is_a?(c) } &&
65
- option_value.zero?
207
+ elsif option_value.is_a?(Numeric) && option_value.zero?
66
208
  ".divided_by_0"
67
209
  else
68
210
  ".default"
@@ -77,13 +219,20 @@ module Servactory
77
219
  )
78
220
  end
79
221
 
222
+ # Generates error message for output validation failure.
223
+ #
224
+ # @param service [Object] Service context
225
+ # @param output [Object] Output attribute
226
+ # @param value [Object] Failed value
227
+ # @param option_name [Symbol] Option name
228
+ # @param option_value [Object] Divisor value
229
+ # @return [String] Localized error message
80
230
  def message_for_output_with(service:, output:, value:, option_name:, option_value:, **) # rubocop:disable Metrics/MethodLength
81
231
  i18n_key = "outputs.validations.must.dynamic_options.multiple_of"
82
232
 
83
233
  i18n_key += if option_value.blank?
84
234
  ".blank"
85
- elsif [Numeric, Float, Rational, BigDecimal].any? { |c| option_value.is_a?(c) } &&
86
- option_value.zero?
235
+ elsif option_value.is_a?(Numeric) && option_value.zero?
87
236
  ".divided_by_0"
88
237
  else
89
238
  ".default"
@@ -3,16 +3,129 @@
3
3
  module Servactory
4
4
  module ToolKit
5
5
  module DynamicOptions
6
+ # Base class for creating custom dynamic validation options.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Must provides a foundation for implementing custom validation rules
11
+ # that can be applied to service inputs, internals, and outputs.
12
+ # It handles the complexity of option parsing, condition evaluation,
13
+ # and error message generation, allowing subclasses to focus on
14
+ # validation logic.
15
+ #
16
+ # ## Usage
17
+ #
18
+ # Create a custom validator by inheriting from Must:
19
+ #
20
+ # ```ruby
21
+ # class MyValidator < Servactory::ToolKit::DynamicOptions::Must
22
+ # def self.use(option_name = :my_option)
23
+ # new(option_name).must(:my_validation)
24
+ # end
25
+ #
26
+ # def condition_for_input_with(input:, value:, option:)
27
+ # # Return true if valid, false otherwise
28
+ # value.meets_criteria?(option.value)
29
+ # end
30
+ #
31
+ # def message_for_input_with(input:, value:, option_name:, option_value:, **)
32
+ # "Input `#{input.name}` must satisfy #{option_name}"
33
+ # end
34
+ #
35
+ # # Implement similar methods for internal and output...
36
+ # end
37
+ # ```
38
+ #
39
+ # Register in service configuration:
40
+ #
41
+ # ```ruby
42
+ # configuration do
43
+ # input_option_helpers([
44
+ # MyValidator.use
45
+ # ])
46
+ # end
47
+ # ```
48
+ #
49
+ # ## Simple Mode
50
+ #
51
+ # Custom validators support simple mode with direct value:
52
+ #
53
+ # ```ruby
54
+ # input :value, type: Integer, my_option: 10
55
+ # ```
56
+ #
57
+ # ## Advanced Mode
58
+ #
59
+ # Custom validators support advanced mode with custom messages:
60
+ #
61
+ # With static message:
62
+ #
63
+ # ```ruby
64
+ # input :value, type: Integer, my_option: {
65
+ # is: 10,
66
+ # message: "Custom validation failed"
67
+ # }
68
+ # ```
69
+ #
70
+ # With dynamic lambda message:
71
+ #
72
+ # ```ruby
73
+ # input :value, type: Integer, my_option: {
74
+ # is: 10,
75
+ # message: lambda do |input:, value:, option_value:, **|
76
+ # "Input `#{input.name}` failed validation with value `#{value}`"
77
+ # end
78
+ # }
79
+ # ```
80
+ #
81
+ # Lambda receives the following parameters:
82
+ # - For inputs: `input:, option_value:, value:, **`
83
+ # - For internals: `internal:, option_value:, value:, **`
84
+ # - For outputs: `output:, option_value:, value:, **`
85
+ #
86
+ # ## Architecture
87
+ #
88
+ # The class uses a two-phase validation approach:
89
+ # 1. **Condition phase**: `condition_for_*` methods return boolean
90
+ # 2. **Message phase**: `message_for_*` methods generate error text
91
+ #
92
+ # ## Important Notes
93
+ #
94
+ # - Subclasses must implement all six abstract methods
95
+ # - Option values support both simple mode (`option: value`) and
96
+ # advanced mode (`option: { is: value, message: "..." }`)
97
+ # - Custom messages can be strings or Procs for dynamic generation
6
98
  class Must # rubocop:disable Metrics/ClassLength
99
+ # Value object representing a parsed validation option.
100
+ #
101
+ # ## Purpose
102
+ #
103
+ # WorkOption normalizes the various ways an option can be specified
104
+ # (simple value, hash with `:is` key, hash with custom message)
105
+ # into a consistent interface for validators.
106
+ #
107
+ # ## Attributes
108
+ #
109
+ # - `name` - The option name symbol (e.g., `:min`, `:max`)
110
+ # - `value` - The actual validation value (e.g., `10` for `min: 10`)
111
+ # - `message` - Custom error message (String or Proc), or nil
112
+ # - `properties` - Additional hash properties passed with the option
7
113
  class WorkOption
8
114
  attr_reader :name,
9
115
  :value,
10
116
  :message,
11
117
  :properties
12
118
 
119
+ # Creates a new WorkOption by parsing option data.
120
+ #
121
+ # @param name [Symbol] The option name
122
+ # @param data [Object] Raw option value (can be scalar, Hash, etc.)
123
+ # @param body_key [Symbol] Key to extract value from Hash (default: :is)
124
+ # @param body_fallback [Object] Default value if data is empty
13
125
  def initialize(name, data, body_key:, body_fallback:)
14
126
  @name = name
15
127
 
128
+ # Extract the primary value from data.
16
129
  @value =
17
130
  if data.is_a?(Hash) && data.key?(body_key)
18
131
  data.delete(body_key)
@@ -20,17 +133,29 @@ module Servactory
20
133
  data.presence || body_fallback
21
134
  end
22
135
 
136
+ # Extract custom message if provided.
23
137
  @message = (data.is_a?(Hash) && data.key?(:message) ? data.delete(:message) : nil)
138
+
139
+ # Remaining hash properties become additional configuration.
24
140
  @properties = data.is_a?(Hash) ? data : {}
25
141
  end
26
142
  end
27
143
 
144
+ # Creates a new Must instance.
145
+ #
146
+ # @param option_name [Symbol] Name of the option (e.g., :min, :max)
147
+ # @param body_key [Symbol] Key for extracting value in advanced mode
148
+ # @param body_fallback [Object] Default value when none provided
28
149
  def initialize(option_name, body_key = :is, body_fallback = nil)
29
150
  @option_name = option_name
30
151
  @body_key = body_key
31
152
  @body_fallback = body_fallback
32
153
  end
33
154
 
155
+ # Creates an OptionHelper for registration with Servactory.
156
+ #
157
+ # @param name [Symbol] Internal validation name
158
+ # @return [Servactory::Maintenance::Attributes::OptionHelper]
34
159
  def must(name)
35
160
  Servactory::Maintenance::Attributes::OptionHelper.new(
36
161
  name: @option_name,
@@ -42,6 +167,10 @@ module Servactory
42
167
  )
43
168
  end
44
169
 
170
+ # Builds the equivalence lambda for option transformation.
171
+ #
172
+ # @param name [Symbol] Validation name
173
+ # @return [Proc] Lambda that transforms option data to must format
45
174
  def equivalent_with(name)
46
175
  lambda do |data|
47
176
  option = WorkOption.new(@option_name, data, body_key: @body_key, body_fallback: @body_fallback)
@@ -54,6 +183,10 @@ module Servactory
54
183
  end
55
184
  end
56
185
 
186
+ # Constructs the must content hash with value and message lambdas.
187
+ #
188
+ # @param option [WorkOption] Parsed option data
189
+ # @return [Hash] Hash with :is and :message keys
57
190
  def must_content_with(option)
58
191
  {
59
192
  is: must_content_value_with(option),
@@ -63,6 +196,10 @@ module Servactory
63
196
 
64
197
  ########################################################################
65
198
 
199
+ # Builds the validation condition lambda.
200
+ #
201
+ # @param option [WorkOption] Parsed option data
202
+ # @return [Proc] Lambda that evaluates validation condition
66
203
  def must_content_value_with(option)
67
204
  lambda do |value:, input: nil, internal: nil, output: nil|
68
205
  if input.present? && input.input?
@@ -75,6 +212,15 @@ module Servactory
75
212
  end
76
213
  end
77
214
 
215
+ # Builds the error message lambda.
216
+ #
217
+ # Handles three message sources:
218
+ # 1. Custom Proc message (called with context)
219
+ # 2. Custom String message (returned as-is)
220
+ # 3. Default message from subclass implementation
221
+ #
222
+ # @param option [WorkOption] Parsed option data
223
+ # @return [Proc] Lambda that generates error message
78
224
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
79
225
  def must_content_message_with(option)
80
226
  is_option_message_present = option.message.present?
@@ -132,26 +278,74 @@ module Servactory
132
278
 
133
279
  ########################################################################
134
280
 
281
+ # Validates condition for input attribute.
282
+ #
283
+ # @abstract Subclasses must implement this method
284
+ # @param input [Object] Input attribute object
285
+ # @param value [Object] Current value being validated
286
+ # @param option [WorkOption] Validation option configuration
287
+ # @return [Boolean] true if valid, false otherwise
288
+ # @raise [RuntimeError] If not implemented in subclass
135
289
  def condition_for_input_with(**)
136
290
  raise "Need to implement `condition_for_input_with(**attributes)` method"
137
291
  end
138
292
 
293
+ # Validates condition for internal attribute.
294
+ #
295
+ # @abstract Subclasses must implement this method
296
+ # @param internal [Object] Internal attribute object
297
+ # @param value [Object] Current value being validated
298
+ # @param option [WorkOption] Validation option configuration
299
+ # @return [Boolean] true if valid, false otherwise
300
+ # @raise [RuntimeError] If not implemented in subclass
139
301
  def condition_for_internal_with(**)
140
302
  raise "Need to implement `condition_for_internal_with(**attributes)` method"
141
303
  end
142
304
 
305
+ # Validates condition for output attribute.
306
+ #
307
+ # @abstract Subclasses must implement this method
308
+ # @param output [Object] Output attribute object
309
+ # @param value [Object] Current value being validated
310
+ # @param option [WorkOption] Validation option configuration
311
+ # @return [Boolean] true if valid, false otherwise
312
+ # @raise [RuntimeError] If not implemented in subclass
143
313
  def condition_for_output_with(**)
144
314
  raise "Need to implement `condition_for_output_with(**attributes)` method"
145
315
  end
146
316
 
317
+ # Generates error message for input validation failure.
318
+ #
319
+ # @abstract Subclasses must implement this method
320
+ # @param input [Object] Input attribute object
321
+ # @param option_name [Symbol] Name of the failed option
322
+ # @param option_value [Object] Expected value
323
+ # @return [String] Human-readable error message
324
+ # @raise [RuntimeError] If not implemented in subclass
147
325
  def message_for_input_with(**)
148
326
  raise "Need to implement `message_for_input_with(**attributes)` method"
149
327
  end
150
328
 
329
+ # Generates error message for internal validation failure.
330
+ #
331
+ # @abstract Subclasses must implement this method
332
+ # @param internal [Object] Internal attribute object
333
+ # @param option_name [Symbol] Name of the failed option
334
+ # @param option_value [Object] Expected value
335
+ # @return [String] Human-readable error message
336
+ # @raise [RuntimeError] If not implemented in subclass
151
337
  def message_for_internal_with(**)
152
338
  raise "Need to implement `message_for_internal_with(**attributes)` method"
153
339
  end
154
340
 
341
+ # Generates error message for output validation failure.
342
+ #
343
+ # @abstract Subclasses must implement this method
344
+ # @param output [Object] Output attribute object
345
+ # @param option_name [Symbol] Name of the failed option
346
+ # @param option_value [Object] Expected value
347
+ # @return [String] Human-readable error message
348
+ # @raise [RuntimeError] If not implemented in subclass
155
349
  def message_for_output_with(**)
156
350
  raise "Need to implement `message_for_output_with(**attributes)` method"
157
351
  end