Skip to content

Commit

Permalink
Add is: parameter to the length validator (#2485)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dakad authored Jul 30, 2024
1 parent 04e69ea commit 12dc739
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* [#2478](https://github.com/ruby-grape/grape/pull/2478): Fix rescue_from with invalid response - [@ericproulx](https://github.com/ericproulx).
* [#2480](https://github.com/ruby-grape/grape/pull/2480): Fix rescue_from ValidationErrors exception - [@numbata](https://github.com/numbata).
* [#2464](https://github.com/ruby-grape/grape/pull/2464): The `length` validator only takes effect for parameters with types that support `#length` method - [@OuYangJinTing](https://github.com/OuYangJinTing).
* [#2485](https://github.com/ruby-grape/grape/pull/2485): Add `is:` param to length validator - [@dakad](https://github.com/dakad).
* Your contribution here.

### 2.1.3 (2024-07-13)
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1714,10 +1714,11 @@ end

Parameters with types that support `#length` method can be restricted to have a specific length with the `:length` option.

The validator accepts `:min` or `:max` or both options to validate that the value of the parameter is within the given limits.
The validator accepts `:min` or `:max` or both options or only `:is` to validate that the value of the parameter is within the given limits.

```ruby
params do
requires :code, type: String, length: { is: 2 }
requires :str, type: String, length: { min: 3 }
requires :list, type: [Integer], length: { min: 3, max: 5 }
requires :hash, type: Hash, length: { max: 5 }
Expand Down Expand Up @@ -2045,6 +2046,7 @@ end

```ruby
params do
requires :code, type: String, length: { is: 2, message: 'code is expected to be exactly 2 characters long' }
requires :str, type: String, length: { min: 5, message: 'str is expected to be atleast 5 characters long' }
requires :list, type: [Integer], length: { min: 2, max: 3, message: 'list is expected to have between 2 and 3 elements' }
end
Expand Down
1 change: 1 addition & 0 deletions lib/grape/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ en:
except_values: 'has a value not allowed'
same_as: 'is not the same as %{parameter}'
length: 'is expected to have length within %{min} and %{max}'
length_is: 'is expected to have length exactly equal to %{is}'
length_min: 'is expected to have length greater than or equal to %{min}'
length_max: 'is expected to have length less than or equal to %{max}'
missing_vendor_option:
Expand Down
11 changes: 9 additions & 2 deletions lib/grape/validations/validators/length_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,25 @@ class LengthValidator < Base
def initialize(attrs, options, required, scope, **opts)
@min = options[:min]
@max = options[:max]
@is = options[:is]

super

raise ArgumentError, 'min must be an integer greater than or equal to zero' if !@min.nil? && (!@min.is_a?(Integer) || @min.negative?)
raise ArgumentError, 'max must be an integer greater than or equal to zero' if !@max.nil? && (!@max.is_a?(Integer) || @max.negative?)
raise ArgumentError, "min #{@min} cannot be greater than max #{@max}" if !@min.nil? && !@max.nil? && @min > @max

return if @is.nil?
raise ArgumentError, 'is must be an integer greater than zero' if !@is.is_a?(Integer) || !@is.positive?
raise ArgumentError, 'is cannot be combined with min or max' if !@min.nil? || !@max.nil?
end

def validate_param!(attr_name, params)
param = params[attr_name]

return unless param.respond_to?(:length)

return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max)
return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max) || (!@is.nil? && param.length != @is)

raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: build_message)
end
Expand All @@ -32,8 +37,10 @@ def build_message
format I18n.t(:length, scope: 'grape.errors.messages'), min: @min, max: @max
elsif @min
format I18n.t(:length_min, scope: 'grape.errors.messages'), min: @min
else
elsif @max
format I18n.t(:length_max, scope: 'grape.errors.messages'), max: @max
else
format I18n.t(:length_is, scope: 'grape.errors.messages'), is: @is
end
end
end
Expand Down
68 changes: 68 additions & 0 deletions spec/grape/validations/validators/length_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@
end
post '/custom-message' do
end

params do
requires :code, length: { is: 2 }
end
post 'is' do
end

params do
requires :code, length: { is: -2 }
end
post 'negative_is' do
end

params do
requires :code, length: { is: 2, max: 10 }
end
post 'is_with_max' do
end
end
end

Expand Down Expand Up @@ -298,4 +316,54 @@
end
end
end

describe '/is' do
context 'when length is exact' do
it do
post 'is', code: 'ZZ'
expect(last_response.status).to eq(201)
expect(last_response.body).to eq('')
end
end

context 'when length exceeds the limit' do
it do
post 'is', code: 'aze'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('code is expected to have length exactly equal to 2')
end
end

context 'when length is less than the limit' do
it do
post 'is', code: 'a'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('code is expected to have length exactly equal to 2')
end
end

context 'when length is zero' do
it do
post 'is', code: ''
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('code is expected to have length exactly equal to 2')
end
end
end

describe '/negative_is' do
context 'when `is` is negative' do
it do
expect { post 'negative_is', code: 'ZZ' }.to raise_error(ArgumentError, 'is must be an integer greater than zero')
end
end
end

describe '/is_with_max' do
context 'when `is` is combined with max' do
it do
expect { post 'is_with_max', code: 'ZZ' }.to raise_error(ArgumentError, 'is cannot be combined with min or max')
end
end
end
end

0 comments on commit 12dc739

Please sign in to comment.