Skip to content

Commit

Permalink
Add Grape::Util::Registry to Grape::Validations
Browse files Browse the repository at this point in the history
ContractScope validator has been moved to validations/validators and renamed properly
  • Loading branch information
ericproulx committed Dec 15, 2024
1 parent 81e68f1 commit 8aee8f9
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 73 deletions.
25 changes: 5 additions & 20 deletions lib/grape/validations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,14 @@

module Grape
module Validations
module_function

def validators
@validators ||= {}
end

# Register a new validator, so it can be used to validate parameters.
# @param short_name [String] all lower-case, no spaces
# @param klass [Class] the validator class. Should inherit from
# Grape::Validations::Validators::Base.
def register_validator(short_name, klass)
validators[short_name] = klass
end
extend Grape::Util::Registry

def deregister_validator(short_name)
validators.delete(short_name)
end
module_function

def require_validator(short_name)
str_name = short_name.to_s
validators.fetch(str_name) { Grape::Validations::Validators.const_get(:"#{str_name.camelize}Validator") }
rescue NameError
raise Grape::Exceptions::UnknownValidator, short_name
raise Grape::Exceptions::UnknownValidator, short_name unless registry.key?(short_name)

registry[short_name]
end
end
end
36 changes: 1 addition & 35 deletions lib/grape/validations/contract_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,46 +23,12 @@ def initialize(api, contract = nil, &block)
api.namespace_stackable(:contract_key_map, key_map)

validator_options = {
validator_class: Validator,
validator_class: Grape::Validations.require_validator(:contract_scope),
opts: { schema: contract, fail_fast: false }
}

api.namespace_stackable(:validations, validator_options)
end

class Validator < Grape::Validations::Validators::Base
attr_reader :schema

def initialize(_attrs, _options, _required, _scope, opts)
super
@schema = opts.fetch(:schema)
end

# Validates a given request.
# @param request [Grape::Request] the request currently being handled
# @raise [Grape::Exceptions::ValidationArrayErrors] if validation failed
# @return [void]
def validate(request)
res = schema.call(request.params)

if res.success?
request.params.deep_merge!(res.to_h)
return
end

raise Grape::Exceptions::ValidationArrayErrors.new(build_errors_from_messages(res.errors.messages))
end

private

def build_errors_from_messages(messages)
messages.map do |message|
full_name = message.path.first.to_s
full_name << "[#{message.path[1..].join('][')}]" if message.path.size > 1
Grape::Exceptions::Validation.new(params: [full_name], message: message.text)
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/grape/validations/validators/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def self.inherited(klass)
return if klass.name.blank?

short_validator_name = klass.name.demodulize.underscore.delete_suffix('_validator')
Validations.register_validator(short_validator_name, klass)
Validations.register(short_validator_name, klass)
end

def message(default_key = nil)
Expand Down
41 changes: 41 additions & 0 deletions lib/grape/validations/validators/contract_scope_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

module Grape
module Validations
module Validators
class ContractScopeValidator < Base
attr_reader :schema

def initialize(_attrs, _options, _required, _scope, opts)
super
@schema = opts.fetch(:schema)
end

# Validates a given request.
# @param request [Grape::Request] the request currently being handled
# @raise [Grape::Exceptions::ValidationArrayErrors] if validation failed
# @return [void]
def validate(request)
res = schema.call(request.params)

if res.success?
request.params.deep_merge!(res.to_h)
return
end

raise Grape::Exceptions::ValidationArrayErrors.new(build_errors_from_messages(res.errors.messages))
end

private

def build_errors_from_messages(messages)
messages.map do |message|
full_name = message.path.first.to_s
full_name << "[#{message.path[1..].join('][')}]" if message.path.size > 1
Grape::Exceptions::Validation.new(params: [full_name], message: message.text)
end
end
end
end
end
end
45 changes: 40 additions & 5 deletions spec/grape/api/custom_validations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ def validate_param!(attr_name, params)
end
let(:app) { subject }

before { stub_const('Grape::Validations::Validators::DefaultLengthValidator', default_length_validator) }
before do
stub_const('DefaultLengthValidator', default_length_validator)
described_class.register(:default_length, DefaultLengthValidator)
end

after do
described_class.deregister(:default_length)
end

it 'under 140 characters' do
get '/', text: 'abc'
Expand Down Expand Up @@ -77,7 +84,14 @@ def validate(request)
end
let(:app) { subject }

before { stub_const('Grape::Validations::Validators::InBodyValidator', in_body_validator) }
before do
stub_const('InBodyValidator', in_body_validator)
described_class.register(:in_body, InBodyValidator)
end

after do
described_class.deregister(:in_body)
end

it 'allows field in body' do
get '/', text: 'abc'
Expand Down Expand Up @@ -113,7 +127,14 @@ def validate_param!(attr_name, _params)
end
let(:app) { subject }

before { stub_const('Grape::Validations::Validators::WithMessageKeyValidator', message_key_validator) }
before do
stub_const('WithMessageKeyValidator', message_key_validator)
described_class.register(:with_message_key, WithMessageKeyValidator)
end

after do
described_class.deregister(:with_message_key)
end

it 'fails with message' do
get '/', text: 'foobar'
Expand Down Expand Up @@ -159,7 +180,14 @@ def access_header
let(:app) { subject }
let(:x_access_token_header) { 'x-access-token' }

before { stub_const('Grape::Validations::Validators::AdminValidator', admin_validator) }
before do
stub_const('AdminValidator', admin_validator)
described_class.register(:admin, AdminValidator)
end

after do
described_class.deregister(:admin)
end

it 'fail when non-admin user sets an admin field' do
get '/', admin_field: 'tester', non_admin_field: 'toaster'
Expand Down Expand Up @@ -218,7 +246,14 @@ def validate_param!(_attr_name, _params)
end
end

before { stub_const('Grape::Validations::Validators::InstanceValidatorValidator', validator_type) }
before do
stub_const('InstanceValidatorValidator', validator_type)
described_class.register(:instance_validator, InstanceValidatorValidator)
end

after do
described_class.deregister(:instance_validator)
end

it 'passes validation every time' do
expect(validator_type).to receive(:new).twice.and_call_original
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

describe Grape::Validations::Validators::ContractScopeValidator do
describe '.inherits' do
subject { described_class }

it { is_expected.to be < Grape::Validations::Validators::Base }
end
end
24 changes: 20 additions & 4 deletions spec/grape/validations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,8 @@ def validate_param!(attr_name, params)
end

before do
stub_const('Grape::Validations::Validators::DateRangeValidator', date_range_validator)
stub_const('DateRangeValidator', date_range_validator)
described_class.register(:date_range, DateRangeValidator)
subject.params do
optional :date_range, date_range: true, type: Hash do
requires :from, type: Integer
Expand All @@ -515,6 +516,10 @@ def validate_param!(attr_name, params)
end
end

after do
described_class.deregister(:date_range)
end

context 'which is optional' do
it "doesn't throw an error if the validation passes" do
get '/optional', date_range: { from: 1, to: 2 }
Expand Down Expand Up @@ -1186,7 +1191,14 @@ def validate_param!(attr_name, params)
end
end

before { stub_const('Grape::Validations::Validators::CustomvalidatorValidator', custom_validator) }
before do
stub_const('CustomvalidatorValidator', custom_validator)
described_class.register(:customvalidator, CustomvalidatorValidator)
end

after do
described_class.deregister(:customvalidator)
end

context 'when using optional with a custom validator' do
before do
Expand Down Expand Up @@ -1338,8 +1350,8 @@ def validate_param!(attr_name, params)
end

before do
stub_const('Grape::Validations::Validators::CustomvalidatorWithOptionsValidator', custom_validator_with_options)

stub_const('CustomvalidatorWithOptionsValidator', custom_validator_with_options)
described_class.register(:customvalidator_with_options, CustomvalidatorWithOptionsValidator)
subject.params do
optional :custom, customvalidator_with_options: { text: 'im custom with options', message: 'is not custom with options!' }
end
Expand All @@ -1348,6 +1360,10 @@ def validate_param!(attr_name, params)
end
end

after do
described_class.deregister(:customvalidator_with_options)
end

it 'validates param with custom validator with options' do
get '/optional_custom', custom: 'im custom with options'
expect(last_response.status).to eq(200)
Expand Down
8 changes: 0 additions & 8 deletions spec/integration/dry_validation/dry_validation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,4 @@
end
end
end

describe Grape::Validations::ContractScope::Validator do
describe '.inherits' do
subject { described_class }

it { is_expected.to be < Grape::Validations::Validators::Base }
end
end
end

0 comments on commit 8aee8f9

Please sign in to comment.