Skip to content

Commit

Permalink
Added param validator for an allowed list of values.
Browse files Browse the repository at this point in the history
  • Loading branch information
vickychijwani authored and dblock committed Sep 26, 2013
1 parent b0123a4 commit e7c44e0
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Next Release
#### Features

* [#475](https://github.com/intridea/grape/pull/475): Added support for the `:jsonapi`, `application/vnd.api+json` media type registered at http://jsonapi.org - [@bcm](https://github.com/bcm).
* [#471](https://github.com/intridea/grape/issues/471): Added parameter validator for a list of allowed values - [@vickychijwani](https://github.com/vickychijwani).
* Your contribution here.

0.6.0 (9/16/2013)
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ params do
requires :url
end
optional :audio do
requires :mp3
requires :format, type: Symbol, values: [:mp3, :wav, :aac, :ogg], default: :mp3
end
end
put ':id' do
Expand All @@ -354,6 +354,14 @@ params do
end
```

Parameters can be restricted to a specific set of values with the `:values` option.

```ruby
params do
requires :status, type: Symbol, values: [:not_started, :processing, :done]
end
```

Parameters can be nested using `group` or by calling `requires` or `optional` with a block.
In the above example, this means `params[:media][:url]` is required along with `params[:id]`,
and `params[:audio][:mp3]` is required only if `params[:audio]` is present.
Expand Down
1 change: 1 addition & 0 deletions lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ module Exceptions
autoload :UnknownValidator, 'grape/exceptions/unknown_validator'
autoload :UnknownOptions, 'grape/exceptions/unknown_options'
autoload :InvalidWithOptionForRepresent, 'grape/exceptions/invalid_with_option_for_represent'
autoload :IncompatibleOptionValues, 'grape/exceptions/incompatible_option_values'
end

module ErrorFormatter
Expand Down
13 changes: 13 additions & 0 deletions lib/grape/exceptions/incompatible_option_values.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# encoding: utf-8
module Grape
module Exceptions
class IncompatibleOptionValues < Base

def initialize(option1, value1, option2, value2)
super(message: compose_message("incompatible_option_values", option1: option1, value1: value1, option2: option2, value2: value2))
end

end

end
end
2 changes: 2 additions & 0 deletions lib/grape/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ en:
coerce: 'is invalid'
presence: 'is missing'
regexp: 'is invalid'
values: 'does not have a valid value'
missing_vendor_option:
problem: 'missing :vendor option.'
summary: 'when version using header, you must specify :vendor option. '
Expand All @@ -26,3 +27,4 @@ en:
resolution: 'available strategy for :using is :path, :header, :param'
unknown_validator: 'unknown validator: %{validator_type}'
unknown_options: 'unknown options: %{options}'
incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
14 changes: 14 additions & 0 deletions lib/grape/validations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,20 @@ def validates(attrs, validations)
doc_attrs[:default] = default
end

if values = validations[:values]
doc_attrs[:values] = values
end

# default value should be present in values array, if both exist
if default && values && (! values.include? default)
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
end

# type should be compatible with values array, if both exist
if coerce_type && values && values.any? { |v| not v.instance_of? coerce_type }
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
end

full_attrs = attrs.collect{ |name| { :name => name, :full_name => full_name(name)} }
@api.document_attribute(full_attrs, doc_attrs)

Expand Down
16 changes: 16 additions & 0 deletions lib/grape/validations/values.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Grape
module Validations
class ValuesValidator < Validator
def initialize(attrs, options, required, scope)
@values = options
super
end

def validate_param!(attr_name, params)
if params[attr_name] && (! @values.include? params[attr_name])
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message_key: :values
end
end
end
end
end
64 changes: 64 additions & 0 deletions spec/grape/validations/values_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require 'spec_helper'

describe Grape::Validations::ValuesValidator do

module ValidationsSpec
module ValuesValidatorSpec
class API < Grape::API
default_format :json

params do
requires :type, values: ['valid-type1', 'valid-type2', 'valid-type3']
end
get '/' do
{ type: params[:type] }
end

params do
optional :type, values: ['valid-type1', 'valid-type2', 'valid-type3'], default: 'valid-type2'
end
get '/default/valid' do
{ type: params[:type] }
end

end
end
end

def app
ValidationsSpec::ValuesValidatorSpec::API
end

it 'allows a valid value for a parameter' do
get("/", type: 'valid-type1')
last_response.status.should eq 200
last_response.body.should eq({ type: "valid-type1" }.to_json)
end

it 'does not allow an invalid value for a parameter' do
get("/", type: 'invalid-type')
last_response.status.should eq 400
last_response.body.should eq({ error: "type does not have a valid value" }.to_json)
end

it 'allows a valid default value' do
get("/default/valid")
last_response.status.should eq 200
last_response.body.should eq({ type: "valid-type2" }.to_json)
end

it 'raises IncompatibleOptionValues on an invalid default value' do
subject = Class.new(Grape::API)
expect {
subject.params { optional :type, values: ['valid-type1', 'valid-type2', 'valid-type3'], default: 'invalid-type' }
}.to raise_error Grape::Exceptions::IncompatibleOptionValues
end

it 'raises IncompatibleOptionValues when type is incompatible with values array' do
subject = Class.new(Grape::API)
expect {
subject.params { optional :type, values: ['valid-type1', 'valid-type2', 'valid-type3'], type: Symbol }
}.to raise_error Grape::Exceptions::IncompatibleOptionValues
end

end

0 comments on commit e7c44e0

Please sign in to comment.