interactor-validation 0.3.9 → 0.4.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.
@@ -216,9 +216,6 @@ Benchmark.ips do |x|
216
216
  x.compare!
217
217
  end
218
218
 
219
- # Reset configuration
220
- Interactor::Validation.reset_configuration!
221
-
222
219
  puts "\n#{"=" * 60}"
223
220
  puts "Benchmarks complete!"
224
221
  puts "\nTo run these benchmarks:"
@@ -4,32 +4,12 @@ module Interactor
4
4
  module Validation
5
5
  # Configuration class for interactor validation behavior
6
6
  class Configuration
7
- attr_accessor :halt, :regex_timeout, :max_array_size,
8
- :enable_instrumentation, :cache_regex_patterns,
9
- :skip_validate
10
- attr_reader :error_mode
7
+ attr_accessor :skip_validate, :mode, :halt
11
8
 
12
- # Backward compatibility alias for halt_on_first_error
13
- alias halt_on_first_error halt
14
- alias halt_on_first_error= halt=
15
-
16
- # Available error modes:
17
- # - :default - Uses ActiveModel-style human-readable messages [DEFAULT]
18
- # - :code - Returns structured error codes (e.g., USERNAME_IS_REQUIRED)
19
9
  def initialize
20
- @error_mode = :default
21
- @halt = false
22
- @regex_timeout = 0.1 # 100ms timeout for regex matching (ReDoS protection)
23
- @max_array_size = 1000 # Maximum array size for nested validation (memory protection)
24
- @enable_instrumentation = false # ActiveSupport::Notifications instrumentation
25
- @cache_regex_patterns = true # Cache compiled regex patterns for performance
26
10
  @skip_validate = true # Skip validate! hook if validate_params! has errors
27
- end
28
-
29
- def error_mode=(mode)
30
- raise ArgumentError, "Invalid error_mode: #{mode}. Must be :default or :code" unless %i[default code].include?(mode)
31
-
32
- @error_mode = mode
11
+ @mode = :default # Error message format mode (:default or :code)
12
+ @halt = false # Stop validation on first error
33
13
  end
34
14
  end
35
15
 
@@ -43,10 +23,6 @@ module Interactor
43
23
  def configure
44
24
  yield(configuration)
45
25
  end
46
-
47
- def reset_configuration!
48
- @configuration = Configuration.new
49
- end
50
26
  end
51
27
  end
52
28
  end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactor
4
+ module Validation
5
+ # Minimal core extensions - no external dependencies
6
+ module CoreExt
7
+ # Simple class attribute implementation with inheritance support
8
+ def class_attribute(*names)
9
+ names.each do |name|
10
+ ivar_name = "@#{name}"
11
+
12
+ # Class-level reader - checks own value, then parent
13
+ define_singleton_method(name) do
14
+ if instance_variable_defined?(ivar_name)
15
+ instance_variable_get(ivar_name)
16
+ elsif superclass.respond_to?(name)
17
+ # When reading from parent, ensure we get our own copy first
18
+ parent_value = superclass.public_send(name)
19
+ # Deep copy parent value if it hasn't been set on this class yet
20
+ if parent_value && !instance_variable_defined?(ivar_name)
21
+ copied_value = deep_copy(parent_value)
22
+ instance_variable_set(ivar_name, copied_value)
23
+ copied_value
24
+ else
25
+ parent_value
26
+ end
27
+ end
28
+ end
29
+
30
+ # Class-level writer
31
+ define_singleton_method("#{name}=") do |val|
32
+ instance_variable_set(ivar_name, val)
33
+ end
34
+
35
+ # Instance-level reader delegates to class
36
+ define_method(name) { self.class.public_send(name) }
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def deep_copy(value)
43
+ case value
44
+ when Hash
45
+ value.transform_values { |v| deep_copy(v) }
46
+ when Array
47
+ value.map { |v| deep_copy(v) }
48
+ else
49
+ # For immutable objects (Symbol, Integer, etc.) or simple objects, return as-is
50
+ value.duplicable? ? value.dup : value
51
+ end
52
+ end
53
+
54
+ # Simple delegation
55
+ def delegate(*methods, to:)
56
+ methods.each do |method|
57
+ define_method(method) { |*args, &block| public_send(to).public_send(method, *args, &block) }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # Minimal Object extensions
65
+ class Object
66
+ def present?
67
+ !blank?
68
+ end
69
+
70
+ def blank?
71
+ respond_to?(:empty?) ? empty? : false
72
+ end
73
+ end
74
+
75
+ class NilClass
76
+ def blank?
77
+ true
78
+ end
79
+ end
80
+
81
+ class FalseClass
82
+ def blank?
83
+ true
84
+ end
85
+ end
86
+
87
+ class String
88
+ def humanize
89
+ tr("_.", " ").sub(/\A./, &:upcase)
90
+ end
91
+ end
92
+
93
+ class Symbol
94
+ def humanize
95
+ to_s.humanize
96
+ end
97
+
98
+ def duplicable?
99
+ false
100
+ end
101
+ end
102
+
103
+ # Make immutable classes non-duplicable
104
+ [NilClass, FalseClass, TrueClass, Symbol, Numeric].each do |klass|
105
+ klass.class_eval do
106
+ def duplicable?
107
+ false
108
+ end
109
+ end
110
+ end
111
+
112
+ class Object
113
+ def duplicable?
114
+ true
115
+ end
116
+ end
117
+
118
+ class Module
119
+ include Interactor::Validation::CoreExt
120
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactor
4
+ module Validation
5
+ # Minimal error collection
6
+ class Errors
7
+ include Enumerable
8
+
9
+ Error = Struct.new(:attribute, :type, :message, :options, keyword_init: true)
10
+
11
+ def initialize(halt_checker: nil)
12
+ @errors = []
13
+ @halt_checker = halt_checker
14
+ end
15
+
16
+ def add(attribute, type = :invalid, message: nil, **options)
17
+ @errors << Error.new(
18
+ attribute: attribute,
19
+ type: type,
20
+ message: message || type.to_s,
21
+ options: options
22
+ )
23
+
24
+ # Raise HaltValidation if halt is configured
25
+ raise HaltValidation if @halt_checker&.call
26
+ end
27
+
28
+ def empty?
29
+ @errors.empty?
30
+ end
31
+
32
+ def any?
33
+ !empty?
34
+ end
35
+
36
+ def clear
37
+ @errors.clear
38
+ end
39
+
40
+ def each(&)
41
+ @errors.each(&)
42
+ end
43
+
44
+ def to_a
45
+ @errors.dup
46
+ end
47
+ end
48
+ end
49
+ end
@@ -3,26 +3,16 @@
3
3
  module Interactor
4
4
  module Validation
5
5
  module Params
6
- extend ActiveSupport::Concern
7
-
8
- included do
9
- class_attribute :_declared_params, instance_writer: false, default: []
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ base.class_attribute :_declared_params
9
+ base._declared_params = []
10
10
  end
11
11
 
12
- class_methods do
13
- # Declares parameters that will be delegated from context
14
- # and registered for validation
15
- #
16
- # @param param_names [Array<Symbol>] the parameter names to declare
17
- # @example
18
- # params :username, :password
12
+ module ClassMethods
19
13
  def params(*param_names)
20
14
  param_names.each do |param_name|
21
- # Ensure we're working with a copy to avoid modifying parent's array
22
- current_params = _declared_params.dup
23
- self._declared_params = current_params + [param_name] unless current_params.include?(param_name)
24
-
25
- # Delegate to context for easy access
15
+ _declared_params << param_name unless _declared_params.include?(param_name)
26
16
  delegate param_name, to: :context
27
17
  end
28
18
  end