Skip to content

Commit 0383e38

Browse files
committed
Add params validation and method wrapping logic
Adds the validation logic that integrates with dry-schema: - validate_params instance method that validates input against the schema - params method accepts either a schema class or block for defining validation rules - Method wrapping via create_validator_for that prepends validation to wrapped methods - Handles both positional hash and keyword argument calling styles - Integration with PrependManager to apply validation before StepsMethodPrepender - Returns Failure[:invalid_params, errors] when validation fails - Adds dry-schema development dependency to Gemfile
1 parent 8e3d14b commit 0383e38

File tree

2 files changed

+111
-3
lines changed

2 files changed

+111
-3
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ end
2222

2323
group :development, :test do
2424
gem "activerecord"
25+
gem "dry-schema"
2526
gem "rom-sql"
2627
gem "sequel"
2728
gem "sqlite3"

lib/dry/operation/extensions/params.rb

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,106 @@ 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
2449
module ClassMethods
2550
# Define a params schema for validating operation inputs
2651
#
27-
# The block is passed to `Dry::Schema.Params` to define the validation rules.
52+
# Accepts either a schema class or a block for defining validation rules.
2853
#
54+
# @param schema_class [Class, nil] A dry-schema class to use for validation
2955
# @yield Block for defining the schema using dry-schema DSL
3056
# @return [Dry::Schema::Params] The compiled schema
31-
def params(&block)
32-
@_params_schema = Dry::Schema.Params(&block)
57+
#
58+
# @example Using a block
59+
# params do
60+
# required(:name).filled(:string)
61+
# end
62+
#
63+
# @example Using a schema class
64+
# class UserSchema < Dry::Schema::Params
65+
# define do
66+
# required(:name).filled(:string)
67+
# end
68+
# end
69+
#
70+
# params UserSchema
71+
def params(schema_class = nil, &block)
72+
@_params_schema = if schema_class
73+
schema_class.is_a?(Class) ? schema_class.new : schema_class
74+
elsif block
75+
Dry::Schema.Params(&block)
76+
else
77+
raise ArgumentError, "params requires either a schema class or a block"
78+
end
79+
_apply_params_validation
3380
end
3481

3582
# @api private
3683
def _params_schema
3784
@_params_schema
3885
end
3986

87+
# @api private
88+
def _params_validated_methods
89+
@_params_validated_methods ||= []
90+
end
91+
92+
# @api private
93+
def _apply_params_validation
94+
methods_to_wrap = instance_variable_get(:@_prepend_manager)
95+
&.instance_variable_get(:@methods_to_prepend) || []
96+
97+
methods_to_wrap.each do |method_name|
98+
next if _params_validated_methods.include?(method_name)
99+
next unless instance_methods.include?(method_name)
100+
101+
prepend(Params.create_validator_for(method_name))
102+
_params_validated_methods << method_name
103+
end
104+
end
105+
106+
# @api private
107+
def method_added(method_name)
108+
if @_params_schema
109+
methods_to_wrap = instance_variable_get(:@_prepend_manager)
110+
&.instance_variable_get(:@methods_to_prepend) || []
111+
112+
if methods_to_wrap.include?(method_name) && !_params_validated_methods.include?(method_name)
113+
prepend(Params.create_validator_for(method_name))
114+
_params_validated_methods << method_name
115+
end
116+
end
117+
118+
super
119+
end
120+
40121
# @api private
41122
def inherited(subclass)
42123
super
@@ -45,6 +126,32 @@ def inherited(subclass)
45126
end
46127
end
47128
end
129+
130+
# @api private
131+
# Creates a module that wraps a method to add params validation
132+
def self.create_validator_for(method_name)
133+
Module.new do
134+
define_method(method_name) do |input = {}, *rest, **kwargs, &block|
135+
use_kwargs = input.empty? && !kwargs.empty? && rest.empty?
136+
actual_input = use_kwargs ? kwargs : input
137+
138+
validation_result = validate_params(actual_input)
139+
140+
case validation_result
141+
when Dry::Monads::Success
142+
validated_input = validation_result.value!
143+
144+
if use_kwargs
145+
super(**validated_input, &block)
146+
else
147+
super(validated_input, *rest, **kwargs, &block)
148+
end
149+
when Dry::Monads::Failure
150+
throw_failure(validation_result)
151+
end
152+
end
153+
end
154+
end
48155
end
49156
end
50157
end

0 commit comments

Comments
 (0)