Skip to content

Commit

Permalink
Add length validator.
Browse files Browse the repository at this point in the history
This validator validates that the length of the parameter (if the parameter supports `#length` is within the specified limits).
  • Loading branch information
dhruvCW committed May 10, 2024
1 parent 0c424d2 commit 8af6c91
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 2 deletions.
3 changes: 3 additions & 0 deletions lib/grape/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ en:
values: 'does not have a valid value'
except_values: 'has a value not allowed'
same_as: 'is not the same as %{parameter}'
length_mismatch_both: 'is expected to have a length within %{min_length} and %{max_length}'
length_mismatch_min_only: 'is expected to have a length greater than %{min_length}'
length_mismatch_max_only: 'is expected to have a length less than %{max_length}'
missing_vendor_option:
problem: 'missing :vendor option'
summary: 'when version using header, you must specify :vendor option'
Expand Down
6 changes: 6 additions & 0 deletions lib/grape/validations/attributes_doc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def extract_details(validations)
details[:documentation] = documentation if documentation

details[:default] = validations[:default] if validations.key?(:default)

return unless validations.key?(:length)

length_validation = validations[:length]
details[:min_length] = length_validation[:min] if length_validation.key?(:min)
details[:max_length] = length_validation[:max] if length_validation.key?(:max)
end

def document(attrs)
Expand Down
35 changes: 35 additions & 0 deletions lib/grape/validations/validators/length_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Grape
module Validations
module Validators
class LengthValidator < Base
def initialize(attrs, options, required, scope, **opts)
@min_length = options[:min]
@max_length = options[:max]
super
end

def validate_param!(attr_name, params)
param = params[attr_name]
return unless param.respond_to?(:length)
return unless (@min_length && param.length < @min_length) || (@max_length && param.length > @max_length)

raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: build_message)
end

def build_message
if options_key?(:message)
@option[:message]
elsif @min_length && @max_length
format I18n.t(:length_mismatch_both, scope: 'grape.errors.messages'), min_length: @min_length, max_length: @max_length
elsif @min_length
format I18n.t(:length_mismatch_min_only, scope: 'grape.errors.messages'), min_length: @min_length
else
format I18n.t(:length_mismatch_max_only, scope: 'grape.errors.messages'), max_length: @max_length
end
end
end
end
end
end
7 changes: 5 additions & 2 deletions spec/grape/validations/attributes_doc_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
presence: true,
desc: 'Age of...',
documentation: 'Age is...',
default: 1
default: 1,
length: { min: 1, max: 13 }
}
end

Expand Down Expand Up @@ -77,7 +78,9 @@
documentation: validations[:documentation],
default: validations[:default],
type: 'Integer',
values: valid_values
values: valid_values,
min_length: validations[:length][:min],
max_length: validations[:length][:max]
}
end

Expand Down
127 changes: 127 additions & 0 deletions spec/grape/validations/validators/length_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# frozen_string_literal: true

describe Grape::Validations::Validators::LengthValidator do
let_it_be(:app) do
Class.new(Grape::API) do
params do
requires :list, length: { min: 2, max: 3 }
end
post 'with_min_max' do
end

params do
requires :list, type: [Integer], length: { min: 2 }
end
post 'with_min_only' do
end

params do
requires :list, type: [Integer], length: { max: 3 }
end
post 'with_max_only' do
end

params do
requires :list, type: Integer, length: { max: 3 }
end
post 'type_is_not_array' do
end

params do
requires :list, type: [Integer], length: { min: 2, message: 'not match' }
end
post '/custom-message' do
end
end
end

describe '/with_min_max' do
context 'when length is within limits' do
it do
post '/with_min_max', list: [1, 2]
expect(last_response.status).to eq(201)
expect(last_response.body).to eq('')
end
end

context 'when length is exceeded' do
it do
post '/with_min_max', list: [1, 2, 3, 4, 5]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('list is expected to have a length within 2 and 3')
end
end

context 'when length is less than minimum' do
it do
post '/with_min_max', list: [1]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('list is expected to have a length within 2 and 3')
end
end
end

describe '/with_max_only' do
context 'when length is less than limits' do
it do
post '/with_max_only', list: [1, 2]
expect(last_response.status).to eq(201)
expect(last_response.body).to eq('')
end
end

context 'when length is exceeded' do
it do
post '/with_max_only', list: [1, 2, 3, 4, 5]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('list is expected to have a length less than 3')
end
end
end

describe '/with_min_only' do
context 'when length is greater than limit' do
it do
post '/with_min_only', list: [1, 2]
expect(last_response.status).to eq(201)
expect(last_response.body).to eq('')
end
end

context 'when length is less than limit' do
it do
post '/with_min_only', list: [1]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('list is expected to have a length greater than 2')
end
end
end

describe '/type_is_not_array' do
context 'is no op' do
it do
post 'type_is_not_array', list: 12
expect(last_response.status).to eq(201)
expect(last_response.body).to eq('')
end
end
end

describe '/custom-message' do
context 'is within limits' do
it do
post '/custom-message', list: [1, 2, 3]
expect(last_response.status).to eq(201)
expect(last_response.body).to eq('')
end
end

context 'is outside limit' do
it do
post '/custom-message', list: [1]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('list not match')
end
end
end
end

0 comments on commit 8af6c91

Please sign in to comment.