interactor-validation 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a2389fc324f275ef082e2d5b24174f8c753c88a5adc3d4ed6499670b6a1da11
4
- data.tar.gz: dc45f25929ca97a2a49dc4663e885dff8780d1dc54d4d11bb8555e39fd00c11f
3
+ metadata.gz: b8c04f34a47f6d887079d08a07958d91c19932a1b3c190faaa0ba4f1a85dbbf2
4
+ data.tar.gz: 167cbca80c87fe0519f3bab2a1e36c4d753366126e5587d3559907473d558812
5
5
  SHA512:
6
- metadata.gz: dfc83bceb6887d5b45338a0801ba61e9e978bbbd319fa5b43d754bc50cc18f863fda7d841e1dd8a749292bc9ffb998bb476df57b0e76091075723fc157281d2f
7
- data.tar.gz: f0ab78c0e9f364da7c343fd5799d020c8dd4e3139c7b859c95ae8f7d10470c24a6b1e519516b27b0eff06627549075bb8b1d1e125afde15f2cea2b88e91459d3
6
+ metadata.gz: 03a955748e4429b6e141a17292484ec49bf52c5a8af056c7436270f91a9a536c1e782a6ca7d3af38b4437736dd960491735eb233c4abe3599d96a604d9297966
7
+ data.tar.gz: 0ba87d4e8591ff0ba7cb260d10bda55c8d0526bb8de75cc66a7c81b0addf2d5355d4f9da9919e3ad76fe3052e43868d7af39eb5d57729319ed92500ad12c5e61
data/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2024-11-16
4
+
5
+ ### Security
6
+
7
+ - **CRITICAL:** Added ReDoS (Regular Expression Denial of Service) protection with configurable timeout
8
+ - **HIGH:** Fixed thread safety issues in validation rule registration using mutex locks
9
+ - Added memory protection with configurable maximum array size for nested validations
10
+ - Added regex pattern caching to prevent recompilation attacks
11
+
12
+ ### Bug Fixes
13
+
14
+ - Fixed numeric precision loss bug - now uses `to_i` for integers and `to_f` for floats
15
+ - Fixed ambiguous handling of `nil` vs missing hash keys in nested validation
16
+ - Improved boolean validation to properly distinguish between `nil`, `false`, and missing values
17
+
18
+ ### Performance Improvements
19
+
20
+ - Implemented regex pattern caching for up to 10x performance improvement on repeated validations
21
+ - Added configuration memoization during validation to reduce overhead
22
+ - Optimized string-to-numeric coercion to preserve integer precision
23
+
24
+ ### New Features
25
+
26
+ - Added `config.regex_timeout` - Configurable timeout for regex validation (default: 100ms)
27
+ - Added `config.max_array_size` - Maximum array size for nested validation (default: 1000)
28
+ - Added `config.enable_instrumentation` - ActiveSupport::Notifications integration for monitoring
29
+ - Added `config.cache_regex_patterns` - Enable/disable regex pattern caching (default: true)
30
+ - Created `ErrorCodes` module with constants for all error types
31
+ - Added comprehensive YARD documentation for public API methods
32
+
33
+ ### Documentation
34
+
35
+ - Added SECURITY.md with vulnerability reporting process and security best practices
36
+ - Added benchmark suite in `benchmark/validation_benchmark.rb`
37
+ - Enhanced inline documentation with YARD tags for all public methods
38
+ - Improved code organization by extracting error codes into separate module
39
+
40
+ ### Breaking Changes
41
+
42
+ None - this release is fully backward compatible with 0.1.x
43
+
44
+ ### Upgrade Notes
45
+
46
+ To take advantage of the new security features, no changes are required. However, you may want to configure:
47
+
48
+ ```ruby
49
+ Interactor::Validation.configure do |config|
50
+ config.regex_timeout = 0.05 # Stricter timeout for high-security contexts
51
+ config.max_array_size = 100 # Lower limit for your use case
52
+ config.enable_instrumentation = true # Monitor validation performance
53
+ end
54
+ ```
55
+
56
+ ## [0.1.1] - 2025-11-16
57
+
58
+ - Minor version bump for release preparation
59
+
3
60
  ## [0.1.0] - 2025-11-16
4
61
 
5
62
  - Initial release
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Interactor::Validation
2
2
 
3
- Add Rails-style parameter validation to your [Interactor](https://github.com/collectiveidea/interactor) service objects with simple, declarative syntax.
3
+ Add declarative parameter validation to your [Interactor](https://github.com/collectiveidea/interactor) service objects with Rails-style syntax.
4
4
 
5
5
  ## Installation
6
6
 
@@ -27,124 +27,165 @@ class CreateUser
27
27
  end
28
28
  end
29
29
 
30
- # Success
31
- result = CreateUser.call(email: "dev@example.com", username: "developer", age: 25)
32
- result.success? # => true
33
- result.user # => #<User...>
34
-
35
- # Failure - automatic validation
30
+ # Validation runs automatically before call
36
31
  result = CreateUser.call(email: "", username: "ab", age: -5)
37
32
  result.failure? # => true
38
33
  result.errors # => [
39
- # { code: "EMAIL_IS_REQUIRED" },
40
- # { code: "USERNAME_BELOW_MIN_LENGTH_3" },
41
- # { code: "AGE_MUST_BE_GREATER_THAN_0" }
34
+ # { attribute: :email, type: :blank, message: "Email can't be blank" },
35
+ # { attribute: :username, type: :too_short, message: "Username is too short (minimum is 3 characters)" },
36
+ # { attribute: :age, type: :greater_than, message: "Age must be greater than 0" }
42
37
  # ]
43
38
  ```
44
39
 
45
- ## Features
46
-
47
- ### Parameter Declaration
40
+ ## Validation Types
48
41
 
49
- Use `params` to declare expected parameters - they're automatically delegated to context:
42
+ ### Presence
50
43
 
51
- ```ruby
52
- params :user_id, :action
53
-
54
- def call
55
- # Access directly instead of context.user_id
56
- user = User.find(user_id)
57
- user.perform(action)
58
- end
59
- ```
60
-
61
- ### Validation Rules
62
-
63
- All validations run **before** your `call` method. If validation fails, the interactor stops and returns structured errors.
64
-
65
- **Presence**
66
44
  ```ruby
67
45
  validates :name, presence: true
68
- # Error: { code: "NAME_IS_REQUIRED" }
46
+ # Error: { attribute: :name, type: :blank, message: "Name can't be blank" }
69
47
  ```
70
48
 
71
- **Format (Regex)**
49
+ ### Format (Regex)
50
+
72
51
  ```ruby
73
52
  validates :email, format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i }
74
- # Error: { code: "EMAIL_INVALID_FORMAT" }
53
+ # Error: { attribute: :email, type: :invalid, message: "Email is invalid" }
75
54
  ```
76
55
 
77
- **Length**
56
+ ### Length
57
+
78
58
  ```ruby
79
59
  validates :password, length: { minimum: 8, maximum: 128 }
80
60
  validates :code, length: { is: 6 }
81
- # Errors: { code: "PASSWORD_BELOW_MIN_LENGTH_8" }
82
- # { code: "CODE_INVALID_LENGTH_6" }
61
+ # Errors: { attribute: :password, type: :too_short, message: "Password is too short (minimum is 8 characters)" }
62
+ # { attribute: :code, type: :wrong_length, message: "Code is the wrong length (should be 6 characters)" }
83
63
  ```
84
64
 
85
- **Inclusion**
65
+ ### Inclusion
66
+
86
67
  ```ruby
87
68
  validates :status, inclusion: { in: %w[active pending inactive] }
88
- # Error: { code: "STATUS_NOT_IN_LIST" }
69
+ # Error: { attribute: :status, type: :inclusion, message: "Status is not included in the list" }
89
70
  ```
90
71
 
91
- **Numericality**
72
+ ### Numericality
73
+
92
74
  ```ruby
93
75
  validates :price, numericality: { greater_than_or_equal_to: 0 }
94
76
  validates :quantity, numericality: { greater_than: 0, less_than_or_equal_to: 100 }
95
- # Errors: { code: "PRICE_MUST_BE_GREATER_THAN_OR_EQUAL_TO_0" }
77
+ validates :count, numericality: true # Just check if numeric
78
+
79
+ # Available constraints:
80
+ # - greater_than, greater_than_or_equal_to
81
+ # - less_than, less_than_or_equal_to
82
+ # - equal_to
83
+ ```
84
+
85
+ ### Boolean
86
+
87
+ ```ruby
88
+ validates :is_active, boolean: true
89
+ # Ensures value is true or false (not truthy/falsy)
90
+ ```
91
+
92
+ ### Nested Validation
93
+
94
+ Validate nested hashes and arrays:
95
+
96
+ ```ruby
97
+ # Hash validation
98
+ params :user
99
+ validates :user do
100
+ attribute :name, presence: true
101
+ attribute :email, format: { with: /@/ }
102
+ attribute :age, numericality: { greater_than: 0 }
103
+ end
104
+
105
+ # Array validation
106
+ params :items
107
+ validates :items do
108
+ attribute :name, presence: true
109
+ attribute :price, numericality: { greater_than: 0 }
110
+ end
96
111
  ```
97
112
 
98
- ### Error Formats
113
+ ## Error Formats
99
114
 
100
- The gem supports two error formatting modes:
115
+ Choose between two error format modes:
101
116
 
102
- #### Code Mode (Default)
117
+ ### Default Mode (ActiveModel-style)
103
118
 
104
- Returns structured error codes ideal for APIs and i18n:
119
+ Human-readable errors with full context - ideal for forms and user-facing applications:
105
120
 
106
121
  ```ruby
122
+ # This is the default, no configuration needed
107
123
  result.errors # => [
108
- # { code: "EMAIL_IS_REQUIRED" },
109
- # { code: "USERNAME_BELOW_MIN_LENGTH_3" }
124
+ # { attribute: :email, type: :blank, message: "Email can't be blank" },
125
+ # { attribute: :username, type: :too_short, message: "Username is too short" }
110
126
  # ]
111
127
  ```
112
128
 
113
- #### Default Mode
129
+ ### Code Mode
114
130
 
115
- Returns ActiveModel-style errors with human-readable messages:
131
+ Structured error codes - ideal for APIs and internationalization:
116
132
 
117
133
  ```ruby
118
- Interactor::Validation.configure do |config|
119
- config.error_mode = :default
134
+ configure_validation do |config|
135
+ config.error_mode = :code
120
136
  end
121
137
 
122
138
  result.errors # => [
123
- # { attribute: :email, type: :blank, message: "Email can't be blank" },
124
- # { attribute: :username, type: :too_short, message: "Username is too short (minimum is 3 characters)" }
139
+ # { code: "EMAIL_IS_REQUIRED" },
140
+ # { code: "USERNAME_BELOW_MIN_LENGTH_3" }
125
141
  # ]
126
142
  ```
127
143
 
144
+ ### Custom Messages
145
+
146
+ Provide custom error messages for any validation:
147
+
148
+ ```ruby
149
+ # Works with both modes
150
+ validates :username, presence: { message: "Username is required" }
151
+ validates :email, format: { with: /@/, message: "Invalid email format" }
152
+
153
+ # In :code mode, custom messages become part of the code
154
+ configure_validation { |c| c.error_mode = :code }
155
+ validates :age, numericality: { greater_than: 0, message: "INVALID_AGE" }
156
+ # => { code: "AGE_INVALID_AGE" }
157
+ ```
158
+
128
159
  ## Configuration
129
160
 
130
161
  ### Global Configuration
131
162
 
132
- Configure validation behavior for all interactors:
163
+ Configure behavior for all interactors:
133
164
 
134
165
  ```ruby
135
166
  # config/initializers/interactor_validation.rb
136
167
  Interactor::Validation.configure do |config|
137
- # Error format mode: :code (default) or :default
138
- config.error_mode = :code
168
+ # Error format: :default (ActiveModel-style) or :code (structured codes)
169
+ config.error_mode = :default
139
170
 
140
- # Stop validation at first error (default: false)
171
+ # Stop at first error for better performance
141
172
  config.halt_on_first_error = false
173
+
174
+ # Security settings
175
+ config.regex_timeout = 0.1 # Regex timeout in seconds (ReDoS protection)
176
+ config.max_array_size = 1000 # Max array size for nested validation
177
+
178
+ # Performance settings
179
+ config.cache_regex_patterns = true
180
+
181
+ # Monitoring
182
+ config.enable_instrumentation = false
142
183
  end
143
184
  ```
144
185
 
145
186
  ### Per-Interactor Configuration
146
187
 
147
- Override global settings for specific interactors:
188
+ Override settings for specific interactors:
148
189
 
149
190
  ```ruby
150
191
  class CreateUser
@@ -152,7 +193,7 @@ class CreateUser
152
193
  include Interactor::Validation
153
194
 
154
195
  configure_validation do |config|
155
- config.error_mode = :default
196
+ config.error_mode = :code
156
197
  config.halt_on_first_error = true
157
198
  end
158
199
 
@@ -161,47 +202,37 @@ class CreateUser
161
202
  end
162
203
  ```
163
204
 
164
- ### Custom Error Messages
205
+ ## Advanced Features
165
206
 
166
- Provide custom error messages for any validation:
207
+ ### Parameter Declaration
167
208
 
168
- ```ruby
169
- # With :code mode
170
- configure_validation do |config|
171
- config.error_mode = :code
172
- end
209
+ Declare parameters for automatic delegation to context:
173
210
 
174
- validates :username, presence: { message: "CUSTOM_REQUIRED_ERROR" }
175
- validates :email, format: { with: /@/, message: "CUSTOM_FORMAT_ERROR" }
176
- # => { code: "USERNAME_CUSTOM_REQUIRED_ERROR" }
177
- # => { code: "EMAIL_CUSTOM_FORMAT_ERROR" }
211
+ ```ruby
212
+ params :user_id, :action
178
213
 
179
- # With :default mode
180
- configure_validation do |config|
181
- config.error_mode = :default
214
+ def call
215
+ # Access directly instead of context.user_id
216
+ user = User.find(user_id)
217
+ user.perform(action)
182
218
  end
183
-
184
- validates :bio, length: { maximum: 500, message: "is too long (max 500 chars)" }
185
- # => { attribute: :bio, type: :too_long, message: "is too long (max 500 chars)" }
186
219
  ```
187
220
 
188
- ### Advanced Usage
189
-
190
- #### Halt on First Error
221
+ ### Halt on First Error
191
222
 
192
- Improve performance by stopping validation at the first failure:
223
+ Improve performance by stopping validation early:
193
224
 
194
225
  ```ruby
195
226
  configure_validation do |config|
196
- config.halt_on_first_error = true # Stop at first error
227
+ config.halt_on_first_error = true
197
228
  end
198
229
 
199
230
  validates :field1, presence: true
200
231
  validates :field2, presence: true # Won't run if field1 fails
201
- validates :field3, presence: true # Won't run if field1 or field2 fails
232
+ validates :field3, presence: true # Won't run if earlier fields fail
202
233
  ```
203
234
 
204
- #### Integration with ActiveModel Validations
235
+ ### ActiveModel Integration
205
236
 
206
237
  Use ActiveModel's custom validation callbacks:
207
238
 
@@ -212,30 +243,90 @@ class CreateUser
212
243
 
213
244
  params :user_data
214
245
 
215
- validate :check_user_data_structure
246
+ validate :check_custom_logic
216
247
  validates :username, presence: true
217
248
 
218
- def check_user_data_structure
219
- errors.add(:user_data, "must be a Hash") unless user_data.is_a?(Hash)
249
+ private
250
+
251
+ def check_custom_logic
252
+ errors.add(:base, "Custom validation failed") unless custom_condition?
220
253
  end
221
254
  end
222
255
  ```
223
256
 
257
+ ### Performance Monitoring
258
+
259
+ Track validation performance in production:
260
+
261
+ ```ruby
262
+ config.enable_instrumentation = true
263
+
264
+ ActiveSupport::Notifications.subscribe('validate_params.interactor_validation') do |*args|
265
+ event = ActiveSupport::Notifications::Event.new(*args)
266
+ Rails.logger.info "Validation: #{event.duration}ms (#{event.payload[:interactor]})"
267
+ end
268
+ ```
269
+
270
+ ## Security
271
+
272
+ Built-in protection against common vulnerabilities:
273
+
274
+ ### ReDoS Protection
275
+
276
+ Automatic timeouts prevent Regular Expression Denial of Service attacks:
277
+
278
+ ```ruby
279
+ config.regex_timeout = 0.1 # 100ms default
280
+ ```
281
+
282
+ If a regex exceeds the timeout, validation fails safely instead of hanging.
283
+
284
+ ### Memory Protection
285
+
286
+ Array size limits prevent memory exhaustion:
287
+
288
+ ```ruby
289
+ config.max_array_size = 1000 # Default limit
290
+ ```
291
+
292
+ ### Thread Safety
293
+
294
+ All validation operations are thread-safe for use with Puma, Sidekiq, etc.
295
+
296
+ ### Best Practices
297
+
298
+ - Use simple regex patterns (avoid nested quantifiers)
299
+ - Sanitize error messages before displaying in HTML
300
+ - Set appropriate `max_array_size` limits for your use case
301
+ - Enable instrumentation to monitor performance
302
+ - Review [SECURITY.md](SECURITY.md) for detailed information
303
+
224
304
  ## Development
225
305
 
226
306
  ```bash
227
307
  bin/setup # Install dependencies
228
- bundle exec rspec # Run tests
308
+ bundle exec rspec # Run tests (231 examples)
229
309
  bundle exec rubocop # Lint code
230
310
  bin/console # Interactive console
231
311
  ```
232
312
 
313
+ ### Benchmarking
314
+
315
+ ```bash
316
+ bundle exec ruby benchmark/validation_benchmark.rb
317
+ ```
318
+
233
319
  ## Requirements
234
320
 
235
321
  - Ruby >= 3.2.0
236
322
  - Interactor ~> 3.0
237
323
  - ActiveModel >= 6.0
324
+ - ActiveSupport >= 6.0
238
325
 
239
326
  ## License
240
327
 
241
328
  MIT License - see [LICENSE.txt](LICENSE.txt)
329
+
330
+ ## Contributing
331
+
332
+ Issues and pull requests are welcome at [https://github.com/zyxzen/interactor-validation](https://github.com/zyxzen/interactor-validation)
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "benchmark/ips"
5
+ require "interactor"
6
+ require "interactor/validation"
7
+
8
+ # Benchmark different validation scenarios
9
+ puts "Interactor::Validation Performance Benchmarks"
10
+ puts "=" * 60
11
+
12
+ # Setup test interactors
13
+ class SimplePresenceInteractor
14
+ include Interactor
15
+ include Interactor::Validation
16
+
17
+ params :username, :email
18
+
19
+ validates :username, presence: true
20
+ validates :email, presence: true
21
+
22
+ def call; end
23
+ end
24
+
25
+ class ComplexValidationInteractor
26
+ include Interactor
27
+ include Interactor::Validation
28
+
29
+ params :username, :email, :age, :bio
30
+
31
+ validates :username, presence: true, length: { minimum: 3, maximum: 20 }
32
+ validates :email, presence: true, format: { with: /\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i }
33
+ validates :age, numericality: { greater_than: 0, less_than: 150 }
34
+ validates :bio, length: { maximum: 500 }
35
+
36
+ def call; end
37
+ end
38
+
39
+ class NestedValidationInteractor
40
+ include Interactor
41
+ include Interactor::Validation
42
+
43
+ params :user_data
44
+
45
+ validates :user_data do |v|
46
+ v.attribute :name, presence: true, length: { minimum: 2 }
47
+ v.attribute :email, presence: true, format: { with: /\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i }
48
+ v.attribute :age, numericality: { greater_than: 0 }
49
+ end
50
+
51
+ def call; end
52
+ end
53
+
54
+ class ArrayValidationInteractor
55
+ include Interactor
56
+ include Interactor::Validation
57
+
58
+ params :items
59
+
60
+ validates :items do |v|
61
+ v.attribute :name, presence: true
62
+ v.attribute :price, numericality: { greater_than: 0 }
63
+ end
64
+
65
+ def call; end
66
+ end
67
+
68
+ # Valid test data
69
+ valid_simple_data = { username: "john_doe", email: "john@example.com" }
70
+ valid_complex_data = {
71
+ username: "john_doe",
72
+ email: "john@example.com",
73
+ age: 25,
74
+ bio: "Software developer"
75
+ }
76
+ valid_nested_data = {
77
+ user_data: {
78
+ name: "John Doe",
79
+ email: "john@example.com",
80
+ age: 25
81
+ }
82
+ }
83
+ valid_array_data = {
84
+ items: [
85
+ { name: "Item 1", price: 10.99 },
86
+ { name: "Item 2", price: 20.50 },
87
+ { name: "Item 3", price: 15.75 }
88
+ ]
89
+ }
90
+
91
+ # Invalid test data
92
+ invalid_simple_data = { username: "", email: "" }
93
+
94
+ # Benchmark: Simple presence validation
95
+ puts "\n1. Simple Presence Validation (2 fields)"
96
+ Benchmark.ips do |x|
97
+ x.config(time: 5, warmup: 2)
98
+
99
+ x.report("valid data") do
100
+ SimplePresenceInteractor.call(valid_simple_data)
101
+ end
102
+
103
+ x.report("invalid data") do
104
+ result = SimplePresenceInteractor.call(invalid_simple_data)
105
+ result.failure?
106
+ end
107
+
108
+ x.compare!
109
+ end
110
+
111
+ # Benchmark: Complex multi-rule validation
112
+ puts "\n2. Complex Validation (4 fields, multiple rules)"
113
+ Benchmark.ips do |x|
114
+ x.config(time: 5, warmup: 2)
115
+
116
+ x.report("valid data") do
117
+ ComplexValidationInteractor.call(valid_complex_data)
118
+ end
119
+
120
+ x.compare!
121
+ end
122
+
123
+ # Benchmark: Nested validation
124
+ puts "\n3. Nested Hash Validation"
125
+ Benchmark.ips do |x|
126
+ x.config(time: 5, warmup: 2)
127
+
128
+ x.report("valid nested data") do
129
+ NestedValidationInteractor.call(valid_nested_data)
130
+ end
131
+
132
+ x.compare!
133
+ end
134
+
135
+ # Benchmark: Array validation
136
+ puts "\n4. Array Validation (3 items)"
137
+ Benchmark.ips do |x|
138
+ x.config(time: 5, warmup: 2)
139
+
140
+ x.report("valid array") do
141
+ ArrayValidationInteractor.call(valid_array_data)
142
+ end
143
+
144
+ x.compare!
145
+ end
146
+
147
+ # Benchmark: Regex caching impact
148
+ puts "\n5. Regex Pattern Caching (100 iterations)"
149
+ Benchmark.ips do |x|
150
+ x.config(time: 5, warmup: 2)
151
+
152
+ x.report("with caching") do
153
+ Interactor::Validation.configure { |c| c.cache_regex_patterns = true }
154
+ 100.times { ComplexValidationInteractor.call(valid_complex_data) }
155
+ end
156
+
157
+ x.report("without caching") do
158
+ Interactor::Validation.configure { |c| c.cache_regex_patterns = false }
159
+ 100.times { ComplexValidationInteractor.call(valid_complex_data) }
160
+ end
161
+
162
+ x.compare!
163
+ end
164
+
165
+ # Benchmark: Halt on first error
166
+ puts "\n6. Halt on First Error (invalid data, 4 fields)"
167
+ invalid_all_data = { username: "", email: "invalid", age: -1, bio: "a" * 600 }
168
+
169
+ Benchmark.ips do |x|
170
+ x.config(time: 5, warmup: 2)
171
+
172
+ x.report("halt enabled") do
173
+ Interactor::Validation.configure { |c| c.halt_on_first_error = true }
174
+ ComplexValidationInteractor.call(invalid_all_data)
175
+ end
176
+
177
+ x.report("halt disabled") do
178
+ Interactor::Validation.configure { |c| c.halt_on_first_error = false }
179
+ ComplexValidationInteractor.call(invalid_all_data)
180
+ end
181
+
182
+ x.compare!
183
+ end
184
+
185
+ # Benchmark: Error mode comparison
186
+ puts "\n7. Error Formatting Mode"
187
+ Benchmark.ips do |x|
188
+ x.config(time: 5, warmup: 2)
189
+
190
+ x.report("code mode") do
191
+ Interactor::Validation.configure { |c| c.error_mode = :code }
192
+ SimplePresenceInteractor.call(invalid_simple_data)
193
+ end
194
+
195
+ x.report("default mode") do
196
+ Interactor::Validation.configure { |c| c.error_mode = :default }
197
+ SimplePresenceInteractor.call(invalid_simple_data)
198
+ end
199
+
200
+ x.compare!
201
+ end
202
+
203
+ # Benchmark: Large array validation
204
+ puts "\n8. Large Array Validation (100 items)"
205
+ large_array_data = {
206
+ items: Array.new(100) { |i| { name: "Item #{i}", price: rand(1.0..100.0).round(2) } }
207
+ }
208
+
209
+ Benchmark.ips do |x|
210
+ x.config(time: 5, warmup: 2)
211
+
212
+ x.report("100 items") do
213
+ ArrayValidationInteractor.call(large_array_data)
214
+ end
215
+
216
+ x.compare!
217
+ end
218
+
219
+ # Reset configuration
220
+ Interactor::Validation.reset_configuration!
221
+
222
+ puts "\n#{"=" * 60}"
223
+ puts "Benchmarks complete!"
224
+ puts "\nTo run these benchmarks:"
225
+ puts " bundle exec ruby benchmark/validation_benchmark.rb"
226
+ puts "\nTo add benchmark-ips to your Gemfile:"
227
+ puts " gem 'benchmark-ips', '~> 2.0', group: :development"