Parameter Validation & Type Coercion for Sinatra
REST conventions take the guesswork out of designing and consuming web APIs. Simply GET
, POST
, PATCH
, or DELETE
resource endpoints, and you get what you'd expect.
However, when it comes to figuring out what parameters are expected... well, all bets are off.
This Sinatra extension takes a first step to solving this problem on the developer side
sinatra-param
allows you to declare, validate, and transform endpoint parameters as you would in frameworks like ActiveModel or DataMapper.
Use
sinatra-param
in combination withRack::PostBodyContentTypeParser
andRack::NestedParams
to automatically parameterize JSONPOST
bodies and nested parameters.
You can install sinatra-param
from the command line with the following:
$ gem install sinatra-param
Alternatively, you can specify sinatra-param
as a dependency in your Gemfile
and run $ bundle install
:
gem "sinatra-param", require: "sinatra/param"
require 'sinatra/base'
require 'sinatra/param'
require 'json'
class App < Sinatra::Base
helpers Sinatra::Param
before do
content_type :json
end
# GET /search?q=example
# GET /search?q=example&categories=news
# GET /search?q=example&sort=created_at&order=ASC
get '/search' do
param :q, String, required: true
param :categories, Array
param :sort, String, default: "title"
param :order, String, in: ["ASC", "DESC"], transform: :upcase, default: "ASC"
param :price, String, format: /[<\=>]\s*\$\d+/
one_of :q, :categories
{...}.to_json
end
end
By declaring parameter types, incoming parameters will automatically be transformed into an object of that type. For instance, if a param is Boolean
, values of '1'
, 'true'
, 't'
, 'yes'
, and 'y'
will be automatically transformed into true
.
String
Integer
Float
Boolean
("1/0", "true/false", "t/f", "yes/no", "y/n")Array
("1,2,3,4,5")Hash
(key1:value1,key2:value2)Date
,Time
, &DateTime
Encapsulate business logic in a consistent way with validations. If a parameter does not satisfy a particular condition, a 400
error is returned with a message explaining the failure.
required
blank
is
in
,within
,range
min
/max
format
Passing a default
option will provide a default value for a parameter if none is passed. A default
can defined as either a default or as a Proc
:
param :attribution, String, default: "©"
param :year, Integer, default: lambda { Time.now.year }
Use the transform
option to take even more of the business logic of parameter I/O out of your code. Anything that responds to to_proc
(including Proc
and symbols) will do.
param :order, String, in: ["ASC", "DESC"], transform: :upcase, default: "ASC"
param :offset, Integer, min: 0, transform: lambda {|n| n - (n % 10)}
Using one_of
, routes can specify two or more parameters to be mutually exclusive, and fail if more than one of those parameters is provided:
param :a, String
param :b, String
param :c, String
one_of :a, :b, :c
Using any_of
, a route can specify that at least one of two or more parameters are required, and fail if none of them are provided:
param :x, String
param :y, String
any_of :x, :y
By default, when a parameter precondition fails, Sinatra::Param
will halt 400
with an error message:
{
"message": "Invalid parameter, order",
"errors": {
"order": "Param must be within [\"ASC\", \"DESC\"]"
}
}
To change this, you can set :raise_sinatra_param_exceptions
to true
, and intercept Sinatra::Param::InvalidParameterError
with a Sinatra error do...end
block. (To make this work in development, set :show_exceptions
to false
and :raise_errors
to true
):
set :raise_sinatra_param_exceptions, true
error Sinatra::Param::InvalidParameterError do
{error: "#{env['sinatra.error'].param} is invalid"}.to_json
end
Custom exception handling can also be enabled on an individual parameter basis, by passing the raise
option:
param :order, String, in: ["ASC", "DESC"], raise: true
one_of :q, :categories, raise: true
Mattt Thompson (@mattt)
sinatra-param is released under an MIT license. See LICENSE for more information.