Skip to content

Commit f87920f

Browse files
Instantiate validators at definition time
- Store validator instances in ParamsScope/ContractScope and have Endpoint#run_validators read them directly - Remove ValidatorFactory indirection and eagerly compute validator messages/options in constructors - Normalize Grape::Exceptions::Validation params handling and refactor validator specs to define routes per example group - Drop test-prof dependency and its spec config Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ddc3b8c commit f87920f

35 files changed

Lines changed: 2088 additions & 1558 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#### Features
44

55
* [#2656](https://github.com/ruby-grape/grape/pull/2656): Remove useless instance_variable_defined? checks - [@ericproulx](https://github.com/ericproulx).
6+
* [#2657](https://github.com/ruby-grape/grape/pull/2657): Instantiate validators at compile time - [@ericproulx](https://github.com/ericproulx).
67
* Your contribution here.
78

89
#### Fixes

Gemfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ group :test do
3232
gem 'rspec', '~> 3.13'
3333
gem 'simplecov', '~> 0.21', require: false
3434
gem 'simplecov-lcov', '~> 0.8', require: false
35-
gem 'test-prof', require: false
3635
end
3736

3837
platforms :jruby do

lib/grape/endpoint.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def run
176176
status 204
177177
else
178178
run_filters before_validations, :before_validation
179-
run_validators validations, request
179+
run_validators request: request
180180
run_filters after_validations, :after_validation
181181
response_object = execute
182182
end
@@ -205,10 +205,13 @@ def execute
205205
end
206206
end
207207

208-
def run_validators(validators, request)
208+
def run_validators(request:)
209+
validators = inheritable_setting.route[:saved_validations]
210+
return if validators.blank?
211+
209212
validation_errors = []
210213

211-
ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
214+
ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request:) do
212215
validators.each do |validator|
213216
validator.validate(request)
214217
rescue Grape::Exceptions::Validation => e

lib/grape/exceptions/validation.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
module Grape
44
module Exceptions
55
class Validation < Base
6-
attr_accessor :params, :message_key
6+
attr_reader :params, :message_key
77

88
def initialize(params:, message: nil, status: nil, headers: nil)
9-
@params = params
9+
@params = params.is_a?(Array) ? params : [params]
1010
if message
1111
@message_key = message if message.is_a?(Symbol)
1212
message = translate_message(message)

lib/grape/validations/contract_scope.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,10 @@ def initialize(api, contract = nil, &block)
2222

2323
api.inheritable_setting.namespace_stackable[:contract_key_map] = key_map
2424

25-
validator_options = {
26-
validator_class: Grape::Validations.require_validator(:contract_scope),
27-
opts: { schema: contract, fail_fast: false }
28-
}
25+
validator_class = Grape::Validations.require_validator(:contract_scope)
26+
validator_instance = validator_class.new(schema: contract)
2927

30-
api.inheritable_setting.namespace_stackable[:validations] = validator_options
28+
api.inheritable_setting.namespace_stackable[:validations] = validator_instance
3129
end
3230
end
3331
end

lib/grape/validations/params_scope.rb

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ def validates(attrs, validations)
357357
# Before we run the rest of the validators, let's handle
358358
# whatever coercion so that we are working with correctly
359359
# type casted values
360-
coerce_type validations, attrs, required, opts
360+
coerce_type validations.extract!(:coerce, :coerce_with, :coerce_message), attrs, required, opts
361361

362362
validations.each do |type, options|
363363
# Don't try to look up validators for documentation params that don't have one.
@@ -430,17 +430,14 @@ def check_coerce_with(validations)
430430
def coerce_type(validations, attrs, required, opts)
431431
check_coerce_with(validations)
432432

433-
return unless validations.key?(:coerce)
433+
return unless validations[:coerce]
434434

435435
coerce_options = {
436436
type: validations[:coerce],
437437
method: validations[:coerce_with],
438438
message: validations[:coerce_message]
439439
}
440440
validate('coerce', coerce_options, attrs, required, opts)
441-
validations.delete(:coerce_with)
442-
validations.delete(:coerce)
443-
validations.delete(:coerce_message)
444441
end
445442

446443
def guess_coerce_type(coerce_type, *values_list)
@@ -464,15 +461,15 @@ def check_incompatible_option_values(default, values, except_values)
464461
end
465462

466463
def validate(type, options, attrs, required, opts)
467-
validator_options = {
468-
attributes: attrs,
469-
options: options,
470-
required: required,
471-
params_scope: self,
472-
opts: opts,
473-
validator_class: Validations.require_validator(type)
474-
}
475-
@api.inheritable_setting.namespace_stackable[:validations] = validator_options
464+
validator_class = Validations.require_validator(type)
465+
validator_instance = validator_class.new(
466+
attrs,
467+
options,
468+
required,
469+
self,
470+
opts
471+
)
472+
@api.inheritable_setting.namespace_stackable[:validations] = validator_instance
476473
end
477474

478475
def validate_value_coercion(coerce_type, *values_list)

lib/grape/validations/validator_factory.rb

Lines changed: 0 additions & 15 deletions
This file was deleted.

lib/grape/validations/validators/all_or_none_of_validator.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ module Grape
44
module Validations
55
module Validators
66
class AllOrNoneOfValidator < MultipleParamsBase
7+
def initialize(attrs, options, required, scope, opts)
8+
super
9+
@exception_message = message(:all_or_none)
10+
end
11+
712
def validate_params!(params)
813
keys = keys_in_common(params)
914
return if keys.empty? || keys.length == all_keys.length
1015

11-
raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:all_or_none))
16+
raise Grape::Exceptions::Validation.new(params: all_keys, message: @exception_message)
1217
end
1318
end
1419
end

lib/grape/validations/validators/allow_blank_validator.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@ module Grape
44
module Validations
55
module Validators
66
class AllowBlankValidator < Base
7-
def validate_param!(attr_name, params)
8-
return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash)
7+
def initialize(attrs, options, required, scope, opts)
8+
super
9+
10+
@value = option_value
11+
@exception_message = message(:blank)
12+
end
913

10-
value = params[attr_name]
11-
value = value.scrub if value.respond_to?(:valid_encoding?) && !value.valid_encoding?
14+
def validate_param!(attr_name, params)
15+
return if @value || !hash_like?(params)
1216

17+
value = scrub(params[attr_name])
1318
return if value == false || value.present?
1419

15-
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:blank))
20+
raise Grape::Exceptions::Validation.new(params: @scope.full_name(attr_name), message: @exception_message)
1621
end
1722
end
1823
end

lib/grape/validations/validators/at_least_one_of_validator.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ module Grape
44
module Validations
55
module Validators
66
class AtLeastOneOfValidator < MultipleParamsBase
7+
def initialize(attrs, options, required, scope, opts)
8+
super
9+
@exception_message = message(:at_least_one)
10+
end
11+
712
def validate_params!(params)
8-
return unless keys_in_common(params).empty?
13+
return if keys_in_common(params).any?
914

10-
raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:at_least_one))
15+
raise Grape::Exceptions::Validation.new(params: all_keys, message: @exception_message)
1116
end
1217
end
1318
end

0 commit comments

Comments
 (0)