diff --git a/README.md b/README.md index d5eaf51..f49ab2e 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,14 @@ param :order, String, in: ["ASC", "DESC"], raise: true one_of :q, :categories, raise: true ``` +## Nested Params + +Passing a `scope` option will provide validation for nested params, e.g. for GET parameter `q[order_id_eq]=4`: + +```ruby +param :order_id_eq, Integer, scope: :q, min: 1, max: 2147483647 +``` + ## Contact Mattt Thompson ([@mattt](http://twitter.com/mattt)) diff --git a/lib/sinatra/param.rb b/lib/sinatra/param.rb index 7762b84..73ae28e 100644 --- a/lib/sinatra/param.rb +++ b/lib/sinatra/param.rb @@ -13,28 +13,31 @@ class InvalidParameterError < StandardError def param(name, type, options = {}) name = name.to_s + params = self.params # local variable with same neme as method. - return unless params.member?(name) or options[:default] or options[:required] + # Nested params + scope = options[:scope] + params = params[scope] if scope && params[scope].is_a?(Hash) - begin - params[name] = coerce(params[name], type, options) - params[name] = (options[:default].call if options[:default].respond_to?(:call)) || options[:default] if params[name].nil? and options[:default] - params[name] = options[:transform].to_proc.call(params[name]) if params[name] and options[:transform] - validate!(params[name], options) - rescue InvalidParameterError => exception - if options[:raise] or (settings.raise_sinatra_param_exceptions rescue false) - exception.param, exception.options = name, options - raise exception - end + return unless params.member?(name) or options[:default] or options[:required] - error = exception.to_s + params[name] = coerce(params[name], type, options) + params[name] = (options[:default].call if options[:default].respond_to?(:call)) || options[:default] if params[name].nil? and options[:default] + params[name] = options[:transform].to_proc.call(params[name]) if params[name] and options[:transform] + validate!(params[name], options) + rescue InvalidParameterError => exception + if options[:raise] or (settings.raise_sinatra_param_exceptions rescue false) + exception.param, exception.options = name, options + raise exception + end - if content_type and content_type.match(mime_type(:json)) - error = {message: error, errors: {name => exception.message}}.to_json - end + error = exception.to_s - halt 400, error + if content_type and content_type.match(mime_type(:json)) + error = {message: error, errors: {name => exception.message}}.to_json end + + halt 400, error end def one_of(*args) diff --git a/spec/dummy/app.rb b/spec/dummy/app.rb index f577123..27d61ec 100644 --- a/spec/dummy/app.rb +++ b/spec/dummy/app.rb @@ -175,6 +175,11 @@ class App < Sinatra::Base params.to_json end + get '/validation/scoped/max' do + param :arg, Integer, scope: :q, max: 20 + params.to_json + end + get '/validation/min_length' do param :arg, String, min_length: 5 params.to_json diff --git a/spec/parameter_raise_spec.rb b/spec/parameter_raise_spec.rb index 997b063..d4a1cfe 100644 --- a/spec/parameter_raise_spec.rb +++ b/spec/parameter_raise_spec.rb @@ -5,7 +5,7 @@ it 'should raise error when option is specified' do expect { get('/raise/validation/required') - }.to raise_error + }.to raise_error Sinatra::Param::InvalidParameterError end end @@ -13,13 +13,13 @@ params = {a: 1, b: 2, c: 3} expect { get('/raise/one_of/3', params) - }.to raise_error + }.to raise_error Sinatra::Param::InvalidParameterError end it 'should raise error when no parameters are specified' do params = {} expect { get('/raise/any_of', params) - }.to raise_error + }.to raise_error Sinatra::Param::InvalidParameterError end end diff --git a/spec/parameter_validations_spec.rb b/spec/parameter_validations_spec.rb index 46114e4..d2cbf73 100644 --- a/spec/parameter_validations_spec.rb +++ b/spec/parameter_validations_spec.rb @@ -160,6 +160,21 @@ expect(response.status).to eq(200) end end + + context 'nested params' do + it 'returns 400 on requests with a value larger than max' do + get('/validation/scoped/max', q: { arg: 100 }) do |response| + expect(response.status).to eq(400) + expect(JSON.parse(response.body)['message']).to eq("Parameter cannot be greater than 20") + end + end + + it 'returns 200 on requests with a value smaller than max' do + get('/validation/scoped/max', q: { arg: 2 }) do |response| + expect(response.status).to eq(200) + end + end + end end describe 'min_length' do