Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add is: parameter to the length validator #2485

Merged
merged 7 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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