@@ -18,6 +18,31 @@ module Extensions
1818 module Params
1919 def self . included ( klass )
2020 klass . extend ( ClassMethods )
21+ klass . prepend ( InstanceMethods )
22+ end
23+
24+ # Instance methods for params validation
25+ module InstanceMethods
26+ include Dry ::Monads ::Result ::Mixin
27+
28+ # Validates input against the params schema
29+ #
30+ # @param input [Hash] The input to validate
31+ # @return [Dry::Monads::Result] Success with validated params or Failure with errors
32+ # @api private
33+ def validate_params ( input )
34+ schema = self . class . _params_schema
35+
36+ return Success ( input ) unless schema
37+
38+ result = schema . call ( input )
39+
40+ if result . success?
41+ Success ( result . to_h )
42+ else
43+ Failure [ :invalid_params , result . errors . to_h ]
44+ end
45+ end
2146 end
2247
2348 # Class methods added to the operation class
@@ -30,13 +55,48 @@ module ClassMethods
3055 # @return [Dry::Schema::Params] The compiled schema
3156 def params ( &block )
3257 @_params_schema = Dry ::Schema . Params ( &block )
58+ _apply_params_validation
3359 end
3460
3561 # @api private
3662 def _params_schema
3763 @_params_schema
3864 end
3965
66+ # @api private
67+ def _params_validated_methods
68+ @_params_validated_methods ||= [ ]
69+ end
70+
71+ # @api private
72+ def _apply_params_validation
73+ methods_to_wrap = instance_variable_get ( :@_prepend_manager )
74+ &.instance_variable_get ( :@methods_to_prepend ) || [ ]
75+
76+ methods_to_wrap . each do |method_name |
77+ next if _params_validated_methods . include? ( method_name )
78+ next unless instance_methods . include? ( method_name )
79+
80+ prepend ( Params . create_validator_for ( method_name ) )
81+ _params_validated_methods << method_name
82+ end
83+ end
84+
85+ # @api private
86+ def method_added ( method_name )
87+ if @_params_schema
88+ methods_to_wrap = instance_variable_get ( :@_prepend_manager )
89+ &.instance_variable_get ( :@methods_to_prepend ) || [ ]
90+
91+ if methods_to_wrap . include? ( method_name ) && !_params_validated_methods . include? ( method_name )
92+ prepend ( Params . create_validator_for ( method_name ) )
93+ _params_validated_methods << method_name
94+ end
95+ end
96+
97+ super
98+ end
99+
40100 # @api private
41101 def inherited ( subclass )
42102 super
@@ -45,6 +105,34 @@ def inherited(subclass)
45105 end
46106 end
47107 end
108+
109+ # @api private
110+ # Creates a module that wraps a method to add params validation
111+ def self . create_validator_for ( method_name )
112+ Module . new do
113+ define_method ( method_name ) do |input = { } , *rest , **kwargs , &block |
114+ # Handle Ruby 3+ keyword argument behavior
115+ use_kwargs = input . empty? && !kwargs . empty? && rest . empty?
116+ actual_input = use_kwargs ? kwargs : input
117+
118+ validation_result = validate_params ( actual_input )
119+
120+ case validation_result
121+ when Dry ::Monads ::Success
122+ validated_input = validation_result . value!
123+
124+ # Pass validated params in the same format as received
125+ if use_kwargs
126+ super ( **validated_input , &block )
127+ else
128+ super ( validated_input , *rest , **kwargs , &block )
129+ end
130+ when Dry ::Monads ::Failure
131+ throw_failure ( validation_result )
132+ end
133+ end
134+ end
135+ end
48136 end
49137 end
50138 end
0 commit comments