servactory 2.16.0 → 3.0.0.rc1

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 (128) 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/stroma/dsl.rb +118 -0
  49. data/lib/servactory/stroma/entry.rb +32 -0
  50. data/lib/servactory/stroma/exceptions/base.rb +45 -0
  51. data/lib/servactory/stroma/exceptions/invalid_hook_type.rb +29 -0
  52. data/lib/servactory/stroma/exceptions/key_already_registered.rb +32 -0
  53. data/lib/servactory/stroma/exceptions/registry_frozen.rb +33 -0
  54. data/lib/servactory/stroma/exceptions/registry_not_finalized.rb +33 -0
  55. data/lib/servactory/stroma/exceptions/unknown_hook_target.rb +39 -0
  56. data/lib/servactory/stroma/hooks/applier.rb +63 -0
  57. data/lib/servactory/stroma/hooks/collection.rb +103 -0
  58. data/lib/servactory/stroma/hooks/factory.rb +80 -0
  59. data/lib/servactory/stroma/hooks/hook.rb +74 -0
  60. data/lib/servactory/stroma/registry.rb +94 -0
  61. data/lib/servactory/stroma/settings/collection.rb +90 -0
  62. data/lib/servactory/stroma/settings/registry_settings.rb +88 -0
  63. data/lib/servactory/stroma/settings/setting.rb +113 -0
  64. data/lib/servactory/stroma/state.rb +59 -0
  65. data/lib/servactory/test_kit/fake_type.rb +23 -0
  66. data/lib/servactory/test_kit/result.rb +45 -0
  67. data/lib/servactory/test_kit/rspec/helpers/argument_matchers.rb +97 -0
  68. data/lib/servactory/test_kit/rspec/helpers/concerns/error_messages.rb +179 -0
  69. data/lib/servactory/test_kit/rspec/helpers/concerns/service_class_validation.rb +74 -0
  70. data/lib/servactory/test_kit/rspec/helpers/fluent.rb +110 -0
  71. data/lib/servactory/test_kit/rspec/helpers/input_validator.rb +149 -0
  72. data/lib/servactory/test_kit/rspec/helpers/legacy.rb +228 -0
  73. data/lib/servactory/test_kit/rspec/helpers/mock_executor.rb +256 -0
  74. data/lib/servactory/test_kit/rspec/helpers/output_validator.rb +121 -0
  75. data/lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb +422 -0
  76. data/lib/servactory/test_kit/rspec/helpers/service_mock_config.rb +129 -0
  77. data/lib/servactory/test_kit/rspec/helpers.rb +51 -84
  78. data/lib/servactory/test_kit/rspec/matchers/base/attribute_matcher.rb +324 -0
  79. data/lib/servactory/test_kit/rspec/matchers/base/submatcher.rb +133 -0
  80. data/lib/servactory/test_kit/rspec/matchers/base/submatcher_context.rb +101 -0
  81. data/lib/servactory/test_kit/rspec/matchers/base/submatcher_registry.rb +205 -0
  82. data/lib/servactory/test_kit/rspec/matchers/concerns/attribute_data_access.rb +100 -0
  83. data/lib/servactory/test_kit/rspec/matchers/concerns/error_message_builder.rb +106 -0
  84. data/lib/servactory/test_kit/rspec/matchers/concerns/value_comparison.rb +97 -0
  85. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +89 -219
  86. data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +74 -166
  87. data/lib/servactory/test_kit/rspec/matchers/have_service_output_matcher.rb +238 -0
  88. data/lib/servactory/test_kit/rspec/matchers/result/be_failure_service_matcher.rb +257 -0
  89. data/lib/servactory/test_kit/rspec/matchers/result/be_success_service_matcher.rb +185 -0
  90. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/default_submatcher.rb +81 -0
  91. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/optional_submatcher.rb +62 -0
  92. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/required_submatcher.rb +93 -0
  93. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/valid_with_submatcher.rb +271 -0
  94. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/consists_of_submatcher.rb +85 -0
  95. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/inclusion_submatcher.rb +120 -0
  96. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/message_submatcher.rb +115 -0
  97. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/must_submatcher.rb +82 -0
  98. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/schema_submatcher.rb +102 -0
  99. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/target_submatcher.rb +125 -0
  100. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/types_submatcher.rb +77 -0
  101. data/lib/servactory/test_kit/rspec/matchers.rb +126 -285
  102. data/lib/servactory/test_kit/utils/faker.rb +62 -2
  103. data/lib/servactory/tool_kit/dynamic_options/consists_of.rb +166 -0
  104. data/lib/servactory/tool_kit/dynamic_options/format.rb +195 -8
  105. data/lib/servactory/tool_kit/dynamic_options/inclusion.rb +219 -17
  106. data/lib/servactory/tool_kit/dynamic_options/max.rb +143 -0
  107. data/lib/servactory/tool_kit/dynamic_options/min.rb +143 -0
  108. data/lib/servactory/tool_kit/dynamic_options/multiple_of.rb +157 -8
  109. data/lib/servactory/tool_kit/dynamic_options/must.rb +194 -0
  110. data/lib/servactory/tool_kit/dynamic_options/schema.rb +226 -2
  111. data/lib/servactory/tool_kit/dynamic_options/target.rb +252 -0
  112. data/lib/servactory/version.rb +3 -3
  113. data/lib/servactory.rb +4 -0
  114. metadata +73 -25
  115. data/lib/generators/servactory/install_generator.rb +0 -21
  116. data/lib/generators/servactory/rspec_generator.rb +0 -88
  117. data/lib/generators/servactory/service_generator.rb +0 -49
  118. data/lib/servactory/configuration/setup.rb +0 -97
  119. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +0 -68
  120. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +0 -73
  121. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/message_matcher.rb +0 -91
  122. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +0 -72
  123. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/schema_matcher.rb +0 -92
  124. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/types_matcher.rb +0 -72
  125. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/default_matcher.rb +0 -69
  126. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/optional_matcher.rb +0 -63
  127. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/required_matcher.rb +0 -81
  128. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb +0 -199
@@ -3,60 +3,262 @@
3
3
  module Servactory
4
4
  module ToolKit
5
5
  module DynamicOptions
6
+ # Validates that attribute value is included in a specified set.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Inclusion provides set membership validation for attributes.
11
+ # It ensures that the value is present within a predefined list
12
+ # of acceptable values. This is similar to ActiveModel's inclusion
13
+ # validation and is useful for enum-like constraints.
14
+ #
15
+ # ## Usage
16
+ #
17
+ # This option is **included by default** for inputs, internals, and outputs.
18
+ # No registration required.
19
+ #
20
+ # Use in your service definition:
21
+ #
22
+ # ```ruby
23
+ # class CreateUserService < ApplicationService::Base
24
+ # input :role, type: String, inclusion: { in: %w[admin user guest] }
25
+ # input :status, type: Symbol, inclusion: { in: [:active, :inactive] }
26
+ # input :level, type: Integer, inclusion: { in: [1, 2, 3, 4, 5] }
27
+ # end
28
+ # ```
29
+ #
30
+ # ## Simple Mode
31
+ #
32
+ # Specify inclusion values directly:
33
+ #
34
+ # ```ruby
35
+ # class CreateUserService < ApplicationService::Base
36
+ # input :role, type: String, inclusion: %w[admin user guest]
37
+ # input :status, type: Symbol, inclusion: [:active, :inactive]
38
+ # input :level, type: Integer, inclusion: 1..10
39
+ # end
40
+ # ```
41
+ #
42
+ # ## Advanced Mode
43
+ #
44
+ # Specify inclusion with custom error message using a hash.
45
+ # Note: Advanced mode uses `:in` key (not `:is`).
46
+ #
47
+ # With static message:
48
+ #
49
+ # ```ruby
50
+ # input :role, type: String, inclusion: {
51
+ # in: %w[admin user guest],
52
+ # message: "Input `role` must be one of: admin, user, guest"
53
+ # }
54
+ # ```
55
+ #
56
+ # With dynamic lambda message:
57
+ #
58
+ # ```ruby
59
+ # input :role, type: String, inclusion: {
60
+ # in: %w[admin user guest],
61
+ # message: lambda do |input:, value:, option_value:, **|
62
+ # "Input `#{input.name}` has invalid value `#{value}`, allowed: #{option_value.join(', ')}"
63
+ # end
64
+ # }
65
+ # ```
66
+ #
67
+ # Lambda receives the following parameters:
68
+ # - For inputs: `input:, value:, option_value:, reason:, **`
69
+ # - For internals: `internal:, value:, option_value:, reason:, **`
70
+ # - For outputs: `output:, value:, option_value:, reason:, **`
71
+ #
72
+ # ## Validation Rules
73
+ #
74
+ # - Value must be present in the inclusion list
75
+ # - Supports arrays as inclusion sets
76
+ # - Optional inputs with nil value validate against default
77
+ # - Returns `:invalid_option` error if inclusion set is nil
78
+ #
79
+ # ## Important Notes
80
+ #
81
+ # - Use `inclusion: { in: [...] }` syntax for specifying allowed values
82
+ # - Single values are automatically wrapped in an array
83
+ # - For optional inputs with nil value, validates default if present
84
+ # - Range objects ARE supported natively: `(1..10)`, `(1..)`, `(..100)`
85
+ # - Mixed syntax supported: `[1..5, 10, 15..20]` checks value against all elements
6
86
  class Inclusion < Must
87
+ # Creates an Inclusion validator instance.
88
+ #
89
+ # @param option_name [Symbol] The option name (default: :inclusion)
90
+ # @return [Servactory::Maintenance::Attributes::OptionHelper]
7
91
  def self.use(option_name = :inclusion)
8
92
  instance = new(option_name, :in)
9
93
  instance.must(:be_inclusion)
10
94
  end
11
95
 
12
- def condition_for_input_with(input:, value:, option:) # rubocop:disable Naming/PredicateMethod
96
+ # Validates inclusion condition for input attribute.
97
+ #
98
+ # @param input [Object] Input attribute object
99
+ # @param value [Object] Value to validate
100
+ # @param option [WorkOption] Inclusion configuration
101
+ # @return [Boolean, Array] true if valid, or [false, reason]
102
+ def condition_for_input_with(input:, value:, option:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
103
+ return [false, :invalid_option] if option.value.nil?
104
+
105
+ inclusion_values = normalize_inclusion_values(option.value)
106
+
107
+ # Required inputs or optional with non-nil value.
13
108
  if input.required? || (input.optional? && !value.nil?) # rubocop:disable Style/IfUnlessModifier
14
- return option.value.include?(value)
109
+ return value_in_inclusion?(inclusion_values, value)
15
110
  end
16
111
 
17
- if input.optional? && value.nil? && !input.default.nil? # rubocop:disable Style/IfUnlessModifier
18
- return option.value.include?(input.default)
112
+ # Optional with nil value but has default.
113
+ if input.optional? && value.nil? && !input.default.nil?
114
+ return value_in_inclusion?(inclusion_values, input.default)
19
115
  end
20
116
 
21
117
  true
22
118
  end
23
119
 
24
- def condition_for_internal_with(value:, option:, **) # rubocop:disable Naming/PredicateMethod
25
- option.value.include?(value)
120
+ # Validates inclusion condition for internal attribute.
121
+ #
122
+ # @param value [Object] Value to validate
123
+ # @param option [WorkOption] Inclusion configuration
124
+ # @return [Boolean, Array] true if valid, or [false, reason]
125
+ def condition_for_internal_with(value:, option:, **)
126
+ return [false, :invalid_option] if option.value.nil?
127
+
128
+ inclusion_values = normalize_inclusion_values(option.value)
129
+ value_in_inclusion?(inclusion_values, value)
26
130
  end
27
131
 
28
- def condition_for_output_with(value:, option:, **) # rubocop:disable Naming/PredicateMethod
29
- option.value.include?(value)
132
+ # Validates inclusion condition for output attribute.
133
+ #
134
+ # @param value [Object] Value to validate
135
+ # @param option [WorkOption] Inclusion configuration
136
+ # @return [Boolean, Array] true if valid, or [false, reason]
137
+ def condition_for_output_with(value:, option:, **)
138
+ return [false, :invalid_option] if option.value.nil?
139
+
140
+ inclusion_values = normalize_inclusion_values(option.value)
141
+ value_in_inclusion?(inclusion_values, value)
30
142
  end
31
143
 
32
144
  ########################################################################
33
145
 
34
- def message_for_input_with(service:, input:, value:, option_value:, **)
146
+ # Generates error message for input validation failure.
147
+ #
148
+ # @param service [Object] Service context
149
+ # @param input [Object] Input attribute
150
+ # @param value [Object] Failed value
151
+ # @param option_name [Symbol] Option name
152
+ # @param option_value [Object] Allowed values
153
+ # @param reason [Symbol] Failure reason
154
+ # @return [String] Localized error message
155
+ def message_for_input_with(service:, input:, value:, option_name:, option_value:, reason:, **)
156
+ i18n_key = "inputs.validations.must.dynamic_options.inclusion"
157
+ i18n_key += reason.present? ? ".#{reason}" : ".default"
158
+
35
159
  service.translate(
36
- "inputs.validations.must.dynamic_options.inclusion.default",
160
+ i18n_key,
37
161
  input_name: input.name,
38
162
  value: value.inspect,
39
- input_inclusion: option_value.inspect
163
+ input_inclusion: option_value.inspect,
164
+ option_name:
40
165
  )
41
166
  end
42
167
 
43
- def message_for_internal_with(service:, internal:, value:, option_value:, **)
168
+ # Generates error message for internal validation failure.
169
+ #
170
+ # @param service [Object] Service context
171
+ # @param internal [Object] Internal attribute
172
+ # @param value [Object] Failed value
173
+ # @param option_name [Symbol] Option name
174
+ # @param option_value [Object] Allowed values
175
+ # @param reason [Symbol] Failure reason
176
+ # @return [String] Localized error message
177
+ def message_for_internal_with(service:, internal:, value:, option_name:, option_value:, reason:, **)
178
+ i18n_key = "internals.validations.must.dynamic_options.inclusion"
179
+ i18n_key += reason.present? ? ".#{reason}" : ".default"
180
+
44
181
  service.translate(
45
- "internals.validations.must.dynamic_options.inclusion.default",
182
+ i18n_key,
46
183
  internal_name: internal.name,
47
184
  value: value.inspect,
48
- internal_inclusion: option_value.inspect
185
+ internal_inclusion: option_value.inspect,
186
+ option_name:
49
187
  )
50
188
  end
51
189
 
52
- def message_for_output_with(service:, output:, value:, option_value:, **)
190
+ # Generates error message for output validation failure.
191
+ #
192
+ # @param service [Object] Service context
193
+ # @param output [Object] Output attribute
194
+ # @param value [Object] Failed value
195
+ # @param option_name [Symbol] Option name
196
+ # @param option_value [Object] Allowed values
197
+ # @param reason [Symbol] Failure reason
198
+ # @return [String] Localized error message
199
+ def message_for_output_with(service:, output:, value:, option_name:, option_value:, reason:, **)
200
+ i18n_key = "outputs.validations.must.dynamic_options.inclusion"
201
+ i18n_key += reason.present? ? ".#{reason}" : ".default"
202
+
53
203
  service.translate(
54
- "outputs.validations.must.dynamic_options.inclusion.default",
204
+ i18n_key,
55
205
  output_name: output.name,
56
206
  value: value.inspect,
57
- output_inclusion: option_value.inspect
207
+ output_inclusion: option_value.inspect,
208
+ option_name:
58
209
  )
59
210
  end
211
+
212
+ private
213
+
214
+ # Normalizes inclusion values, preserving Range objects.
215
+ #
216
+ # @param option_value [Range, Array, Object] Inclusion value(s)
217
+ # @return [Range, Array] Range preserved as-is, others normalized to array
218
+ def normalize_inclusion_values(option_value)
219
+ case option_value
220
+ when Range, Array then option_value
221
+ else [option_value]
222
+ end
223
+ end
224
+
225
+ # Checks if value is included in the normalized inclusion values.
226
+ #
227
+ # @param inclusion_values [Range, Array] Normalized inclusion set
228
+ # @param value [Object] Value to check
229
+ # @return [Boolean] true if value is in inclusion set
230
+ def value_in_inclusion?(inclusion_values, value)
231
+ case inclusion_values
232
+ when Range
233
+ range_covers?(inclusion_values, value)
234
+ when Array
235
+ inclusion_values.any? { |element| element_matches?(element, value) }
236
+ else
237
+ inclusion_values == value
238
+ end
239
+ end
240
+
241
+ # Checks if value matches a single element (Range or scalar).
242
+ #
243
+ # @param element [Range, Object] Element to match against
244
+ # @param value [Object] Value to check
245
+ # @return [Boolean] true if matches
246
+ def element_matches?(element, value)
247
+ element.is_a?(Range) ? range_covers?(element, value) : element == value
248
+ end
249
+
250
+ # Safely checks if Range covers the value.
251
+ #
252
+ # @param range [Range] Range to check against
253
+ # @param value [Object] Value to check
254
+ # @return [Boolean] true if range covers value, false on type errors
255
+ def range_covers?(range, value)
256
+ return false if value.nil?
257
+
258
+ range.cover?(value)
259
+ rescue ArgumentError, TypeError
260
+ false
261
+ end
60
262
  end
61
263
  end
62
264
  end
@@ -3,28 +3,147 @@
3
3
  module Servactory
4
4
  module ToolKit
5
5
  module DynamicOptions
6
+ # Validates that attribute value does not exceed a maximum limit.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Max provides upper bound validation for numeric values and
11
+ # size-based validation for collections and strings. It ensures
12
+ # that values stay within acceptable limits.
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::Max.use
23
+ # ])
24
+ #
25
+ # internal_option_helpers([
26
+ # Servactory::ToolKit::DynamicOptions::Max.use
27
+ # ])
28
+ #
29
+ # output_option_helpers([
30
+ # Servactory::ToolKit::DynamicOptions::Max.use
31
+ # ])
32
+ # end
33
+ # ```
34
+ #
35
+ # Use in your service definition:
36
+ #
37
+ # ```ruby
38
+ # class ProcessDataService < ApplicationService::Base
39
+ # input :count, type: Integer, max: 100
40
+ # input :name, type: String, max: 255
41
+ # input :items, type: Array, max: 50
42
+ # end
43
+ # ```
44
+ #
45
+ # ## Simple Mode
46
+ #
47
+ # Specify maximum value directly:
48
+ #
49
+ # ```ruby
50
+ # class ProcessDataService < ApplicationService::Base
51
+ # input :count, type: Integer, max: 100
52
+ # input :name, type: String, max: 255
53
+ # input :items, type: Array, max: 50
54
+ # end
55
+ # ```
56
+ #
57
+ # ## Advanced Mode
58
+ #
59
+ # Specify maximum with custom error message using a hash:
60
+ #
61
+ # With static message:
62
+ #
63
+ # ```ruby
64
+ # input :count, type: Integer, max: {
65
+ # is: 100,
66
+ # message: "Input `count` must not exceed 100"
67
+ # }
68
+ # ```
69
+ #
70
+ # With dynamic lambda message:
71
+ #
72
+ # ```ruby
73
+ # input :count, type: Integer, max: {
74
+ # is: 100,
75
+ # message: lambda do |input:, value:, option_value:, **|
76
+ # "Input `#{input.name}` must be <= #{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
+ # ## Validation Rules
87
+ #
88
+ # - For Integer: value must be <= max
89
+ # - For String/Array/Hash: size must be <= max
90
+ # - Objects must respond to `:size` method for size-based validation
91
+ #
92
+ # ## Important Notes
93
+ #
94
+ # - Returns false if value doesn't support size comparison
95
+ # - Combines well with `:min` for range validation
6
96
  class Max < Must
97
+ # Creates a Max validator instance.
98
+ #
99
+ # @param option_name [Symbol] The option name (default: :max)
100
+ # @return [Servactory::Maintenance::Attributes::OptionHelper]
7
101
  def self.use(option_name = :max)
8
102
  new(option_name).must(:be_less_than_or_equal_to)
9
103
  end
10
104
 
105
+ # Validates max condition for input attribute.
106
+ #
107
+ # @param input [Object] Input attribute object
108
+ # @param value [Object] Value to validate
109
+ # @param option [WorkOption] Max configuration
110
+ # @return [Boolean] true if valid
11
111
  def condition_for_input_with(...)
12
112
  common_condition_with(...)
13
113
  end
14
114
 
115
+ # Validates max condition for internal attribute.
116
+ #
117
+ # @param internal [Object] Internal attribute object
118
+ # @param value [Object] Value to validate
119
+ # @param option [WorkOption] Max configuration
120
+ # @return [Boolean] true if valid
15
121
  def condition_for_internal_with(...)
16
122
  common_condition_with(...)
17
123
  end
18
124
 
125
+ # Validates max condition for output attribute.
126
+ #
127
+ # @param output [Object] Output attribute object
128
+ # @param value [Object] Value to validate
129
+ # @param option [WorkOption] Max configuration
130
+ # @return [Boolean] true if valid
19
131
  def condition_for_output_with(...)
20
132
  common_condition_with(...)
21
133
  end
22
134
 
135
+ # Common validation logic for all attribute types.
136
+ #
137
+ # @param value [Object] Value to validate
138
+ # @param option [WorkOption] Max configuration
139
+ # @return [Boolean] true if value <= max
23
140
  def common_condition_with(value:, option:, **) # rubocop:disable Naming/PredicateMethod
24
141
  case value
25
142
  when Integer
143
+ # Direct numeric comparison.
26
144
  value <= option.value
27
145
  else
146
+ # Size-based comparison for collections and strings.
28
147
  return false unless value.respond_to?(:size)
29
148
 
30
149
  value.size <= option.value
@@ -33,6 +152,14 @@ module Servactory
33
152
 
34
153
  ########################################################################
35
154
 
155
+ # Generates error message for input validation failure.
156
+ #
157
+ # @param service [Object] Service context
158
+ # @param input [Object] Input attribute
159
+ # @param value [Object] Failed value
160
+ # @param option_name [Symbol] Option name
161
+ # @param option_value [Object] Maximum limit
162
+ # @return [String] Localized error message
36
163
  def message_for_input_with(service:, input:, value:, option_name:, option_value:, **)
37
164
  service.translate(
38
165
  "inputs.validations.must.dynamic_options.max.default",
@@ -43,6 +170,14 @@ module Servactory
43
170
  )
44
171
  end
45
172
 
173
+ # Generates error message for internal validation failure.
174
+ #
175
+ # @param service [Object] Service context
176
+ # @param internal [Object] Internal attribute
177
+ # @param value [Object] Failed value
178
+ # @param option_name [Symbol] Option name
179
+ # @param option_value [Object] Maximum limit
180
+ # @return [String] Localized error message
46
181
  def message_for_internal_with(service:, internal:, value:, option_name:, option_value:, **)
47
182
  service.translate(
48
183
  "internals.validations.must.dynamic_options.max.default",
@@ -53,6 +188,14 @@ module Servactory
53
188
  )
54
189
  end
55
190
 
191
+ # Generates error message for output validation failure.
192
+ #
193
+ # @param service [Object] Service context
194
+ # @param output [Object] Output attribute
195
+ # @param value [Object] Failed value
196
+ # @param option_name [Symbol] Option name
197
+ # @param option_value [Object] Maximum limit
198
+ # @return [String] Localized error message
56
199
  def message_for_output_with(service:, output:, value:, option_name:, option_value:, **)
57
200
  service.translate(
58
201
  "outputs.validations.must.dynamic_options.max.default",
@@ -3,28 +3,147 @@
3
3
  module Servactory
4
4
  module ToolKit
5
5
  module DynamicOptions
6
+ # Validates that attribute value meets a minimum threshold.
7
+ #
8
+ # ## Purpose
9
+ #
10
+ # Min provides lower bound validation for numeric values and
11
+ # size-based validation for collections and strings. It ensures
12
+ # that values meet minimum requirements.
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::Min.use
23
+ # ])
24
+ #
25
+ # internal_option_helpers([
26
+ # Servactory::ToolKit::DynamicOptions::Min.use
27
+ # ])
28
+ #
29
+ # output_option_helpers([
30
+ # Servactory::ToolKit::DynamicOptions::Min.use
31
+ # ])
32
+ # end
33
+ # ```
34
+ #
35
+ # Use in your service definition:
36
+ #
37
+ # ```ruby
38
+ # class ProcessDataService < ApplicationService::Base
39
+ # input :age, type: Integer, min: 18
40
+ # input :password, type: String, min: 8
41
+ # input :tags, type: Array, min: 1
42
+ # end
43
+ # ```
44
+ #
45
+ # ## Simple Mode
46
+ #
47
+ # Specify minimum value directly:
48
+ #
49
+ # ```ruby
50
+ # class ProcessDataService < ApplicationService::Base
51
+ # input :age, type: Integer, min: 18
52
+ # input :password, type: String, min: 8
53
+ # input :tags, type: Array, min: 1
54
+ # end
55
+ # ```
56
+ #
57
+ # ## Advanced Mode
58
+ #
59
+ # Specify minimum with custom error message using a hash:
60
+ #
61
+ # With static message:
62
+ #
63
+ # ```ruby
64
+ # input :age, type: Integer, min: {
65
+ # is: 18,
66
+ # message: "Input `age` must be at least 18"
67
+ # }
68
+ # ```
69
+ #
70
+ # With dynamic lambda message:
71
+ #
72
+ # ```ruby
73
+ # input :age, type: Integer, min: {
74
+ # is: 18,
75
+ # message: lambda do |input:, value:, option_value:, **|
76
+ # "Input `#{input.name}` must be >= #{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
+ # ## Validation Rules
87
+ #
88
+ # - For Integer: value must be >= min
89
+ # - For String/Array/Hash: size must be >= min
90
+ # - Objects must respond to `:size` method for size-based validation
91
+ #
92
+ # ## Important Notes
93
+ #
94
+ # - Returns false if value doesn't support size comparison
95
+ # - Combines well with `:max` for range validation
6
96
  class Min < Must
97
+ # Creates a Min validator instance.
98
+ #
99
+ # @param option_name [Symbol] The option name (default: :min)
100
+ # @return [Servactory::Maintenance::Attributes::OptionHelper]
7
101
  def self.use(option_name = :min)
8
102
  new(option_name).must(:be_greater_than_or_equal_to)
9
103
  end
10
104
 
105
+ # Validates min condition for input attribute.
106
+ #
107
+ # @param input [Object] Input attribute object
108
+ # @param value [Object] Value to validate
109
+ # @param option [WorkOption] Min configuration
110
+ # @return [Boolean] true if valid
11
111
  def condition_for_input_with(...)
12
112
  common_condition_with(...)
13
113
  end
14
114
 
115
+ # Validates min condition for internal attribute.
116
+ #
117
+ # @param internal [Object] Internal attribute object
118
+ # @param value [Object] Value to validate
119
+ # @param option [WorkOption] Min configuration
120
+ # @return [Boolean] true if valid
15
121
  def condition_for_internal_with(...)
16
122
  common_condition_with(...)
17
123
  end
18
124
 
125
+ # Validates min condition for output attribute.
126
+ #
127
+ # @param output [Object] Output attribute object
128
+ # @param value [Object] Value to validate
129
+ # @param option [WorkOption] Min configuration
130
+ # @return [Boolean] true if valid
19
131
  def condition_for_output_with(...)
20
132
  common_condition_with(...)
21
133
  end
22
134
 
135
+ # Common validation logic for all attribute types.
136
+ #
137
+ # @param value [Object] Value to validate
138
+ # @param option [WorkOption] Min configuration
139
+ # @return [Boolean] true if value >= min
23
140
  def common_condition_with(value:, option:, **) # rubocop:disable Naming/PredicateMethod
24
141
  case value
25
142
  when Integer
143
+ # Direct numeric comparison.
26
144
  value >= option.value
27
145
  else
146
+ # Size-based comparison for collections and strings.
28
147
  return false unless value.respond_to?(:size)
29
148
 
30
149
  value.size >= option.value
@@ -33,6 +152,14 @@ module Servactory
33
152
 
34
153
  ########################################################################
35
154
 
155
+ # Generates error message for input validation failure.
156
+ #
157
+ # @param service [Object] Service context
158
+ # @param input [Object] Input attribute
159
+ # @param value [Object] Failed value
160
+ # @param option_name [Symbol] Option name
161
+ # @param option_value [Object] Minimum limit
162
+ # @return [String] Localized error message
36
163
  def message_for_input_with(service:, input:, value:, option_name:, option_value:, **)
37
164
  service.translate(
38
165
  "inputs.validations.must.dynamic_options.min.default",
@@ -43,6 +170,14 @@ module Servactory
43
170
  )
44
171
  end
45
172
 
173
+ # Generates error message for internal validation failure.
174
+ #
175
+ # @param service [Object] Service context
176
+ # @param internal [Object] Internal attribute
177
+ # @param value [Object] Failed value
178
+ # @param option_name [Symbol] Option name
179
+ # @param option_value [Object] Minimum limit
180
+ # @return [String] Localized error message
46
181
  def message_for_internal_with(service:, internal:, value:, option_name:, option_value:, **)
47
182
  service.translate(
48
183
  "internals.validations.must.dynamic_options.min.default",
@@ -53,6 +188,14 @@ module Servactory
53
188
  )
54
189
  end
55
190
 
191
+ # Generates error message for output validation failure.
192
+ #
193
+ # @param service [Object] Service context
194
+ # @param output [Object] Output attribute
195
+ # @param value [Object] Failed value
196
+ # @param option_name [Symbol] Option name
197
+ # @param option_value [Object] Minimum limit
198
+ # @return [String] Localized error message
56
199
  def message_for_output_with(service:, output:, value:, option_name:, option_value:, **)
57
200
  service.translate(
58
201
  "outputs.validations.must.dynamic_options.min.default",