Parameter Validation, Transformation, and Type Coercion for Sinatra applications.
sinatra-param adds useful helpers to your Sinatra application, allowing you to declare, validate, and transform URL endpoint parameters. By default, Sinatra route parameters are exposed to your application as String
s which may then be coerced to more useful types as needed. sinatra-param smooths over the rough edges and takes care of type coercion and parameter validation on your behalf.
- Provides
param
helper for defining, transforming, and validating parameters. - Provides
any_of
,one_of
, andall_or_none_of
helpers for advanced parameter validations. - Supports Ruby 2.7 and newer.
Before installing and using sinatra-param, you'll want to have Ruby 2.7 (or newer) installed. Using a Ruby version managment tool like rbenv, chruby, or rvm is recommended.
sinatra-param is developed using Ruby 2.7.8 and is tested against additional Ruby versions using GitHub Actions.
Add sinatra-param to your project's Gemfile
and run bundle install
:
source "https://rubygems.org"
gem "sinatra-param", github: "jgarber623/sinatra-param", tag: "v4.0.1"
The param
helper takes three arguments: name
(a Symbol
), type
(a Symbol
), and zero or more options
(a Hash
). options
may include a number of transformations, validations, or other configuration as mentioned in this documentation.
The name
argument should match the values you expect your Sinatra application's endpoints to receive (Sinatra's params
is an IndifferentHash whose keys may be referenced as either Symbol
s or String
s).
In your project's app.rb
file:
require "sinatra/base"
require "sinatra/json"
require "sinatra/param"
class App < Sinatra::Base
register Sinatra::Param
before do
content_type :json
end
# GET /search?user=@jgarber
# GET /search?user=@jgarber&attributes=first_name,email_address,url
get "/search" do
param :user, :string, required: true, format: %r{^@\w+}
param :attributes, :array, default: ["first_name", "last_name"]
json { status: "OK" }
end
# GET /articles?page=5
# GET /articles?page=5&order=desc
get "/articles" do
param :page, :integer, default: 1
param :order, :string, default: "ASC", in: ["ASC", "DESC"], transform: :upcase
json { status: "OK" }
end
# /photos?include_drafts=true
# /photos?taken_in=2015
get "/photos" do
param :include_drafts, :boolean, default: false
param :taken_in, :integer, within: Range.new(2000, 2019)
json { status: "OK" }
end
end
sinatra-param supports the following parameter types:
Type | Class | Matches | Options/Defaults |
---|---|---|---|
:string |
String |
foo , bar |
|
:array |
Array |
foo,bar,biz,baz |
delimiter: "," |
:boolean |
TrueClass |
true , yes , 1 |
|
FalseClass |
false , no , 0 |
||
:hash |
Hash |
foo:bar,biz:baz |
delimiter: "," |
separator: ":" |
|||
:integer |
Integer |
1 , 500 , 1000 |
|
:float |
Float |
38.89 , -77.03 |
By default, :array
parameters are comma-delimited. This behavior may be customized using the delimiter
option:
# GET /search?categories=foo|bar|biz|baz
get "/search" do
param :categories, :array, delimiter: "|"
puts categories # => ["foo", "bar", "biz", "baz"]
end
Similarly, :hash
parameters are comma-delimited and key/value pairs are colon-separated by default and may be customized using the delimiter
and separator
options:
# GET /search?coordinates=x:1,y:1
get "/search" do
param :coordinates, :hash
puts coordinates # => { x: "1", y: "1" }
end
# GET /search?coordinates=x_1|y_1
get "/search" do
param :coordinates, :hash, delimiter: "|", separator: "_"
puts coordinates # => { x: "1", y: "1" }
end
:hash
parameter types return values as String
s. Additional type coercion on elements in the Hash
should be handled by your application code.
sinatra-param supports the following parameter validations:
Name | Value Class | Usage |
---|---|---|
required |
TrueClass , FalseClass |
required: true |
format |
RegExp |
format: %r{^https?://} |
match |
Array , Float , etc.¹ |
match: "foo" |
in |
Array |
in: ["ASC", "DESC"] |
within |
Range |
within: (A..Z) |
min |
Float , Integer |
min: 100 |
max |
Float , Integer |
max: 10.5 |
minlength ² |
Integer |
minlength: 10 |
maxlength ² |
Integer |
maxlength: 10 |
Parameter validations are applied in the order in which they are passed to the param
helper:
param :url, :string, format: Regexp.new("^https?://"), required: true
In the example above, the format
validation will execute before the required
validation. This may be used intentionally to influence the order in which errors are raised.
¹ match
parameter validation values must be of the same Class as the parameter itself:
param :agree_to_terms, :string, match: "yes", required: true
² minlength
and maxlength
parameter validations may be applied to :array
, :hash
, and :string
parameter types and validates using each Class' length
method.
Parameter defaults may be set using the default
option:
param :taken_in, :integer, default: 2019
The default
option's value can be anything (but should probably match the parameter type) and may also be declared using a Proc
:
param :taken_in, :integer, default: -> { Time.now.year }
Transformations may be either a Symbol
(that maps to a method on the parameter type's underlying Class) or a Proc
:
param :order, :string, in: ["ASC", "DESC"], transform: :upcase
param :offset, :integer, transform: lambda { |n| n - (n % 10) }
Using the any_of
helper, you can specify that at least one of a set of parameters are required and fail if none of those parameters are provided:
param :a, :string
param :b, :string
param :c, :string
any_of :a, :b, :c
Using the one_of
helper, you can specify that only one of a set of parameters is required 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 the all_or_none_of
helper, you can specify that either none or all of a set of parameters is required and will fail if only some of those parameters are provided:
param :a, :string
param :b, :string
param :c, :string
all_or_none_of :a, :b, :c
By default, when a parameter condition fails, sinatra-param will halt
with a 400 HTTP error response code and an error message (as either text/plain
or application/json
).
In text/plain
:
Parameter foo value "bar" must match ^https?://
…and as application/json
:
{
"message": "Parameter foo value \"bar\" must match ^https?://"
}
The above would be returned to an end user in response to an HTTP request.
Use the message
option to specify a custom message when coercions or validations encounter an error:
# GET /search?order=BACKWARDS
get "/search"
param :order, :string, in: ["ASC", "DESC"], message: "Nice try, friend!"
# The rest of your route-specific code goes here…
end
A request to /search?order=BACKWARDS
would raise a Sinatra::Param::InvalidParameterError
with the message, Nice try, friend!
.
Under the covers, sinatra-param captures one of three types of user-level errors. Each is a subclass of Sinatra::Param::Error
(which itself is a subclass of Ruby's StandardError
):
Sinatra::Param::InvalidParameterError
Sinatra::Param::RequiredParameterError
(raised by theall_or_none_of
andany_of
helpers)Sinatra::Param::TooManyParametersError
(raised by theone_of
helper)
Additionally, Sinatra::Param::ArgumentError
is raised if implementation errors are encountered (e.g. mismatches between a parameter type and its default value).
If you'd prefer to handle these errors on your own, you can add the raise: true
option to any param
, one_of
, or any_of
declaration:
param :order, :string, in: ["ASC", "DESC"], raise: true
param :query, :string
param :categories, :array
one_of :query, :categories, raise: true
You may also globally configure exception handling by using :raise_sinatra_param_exceptions
:
class App < Sinatra::Base
configure do
set :raise_sinatra_param_exceptions, true
end
# The rest of your application code goes here…
end
For this method of exception handling to work in development, you may need to add the following configuration to your application:
class App < Sinatra::Base
configure do
set :raise_errors, true
set :show_exceptions, false
end
# The rest of your application code goes here…
end
This project is a fork of sinatra-param and wouldn't exist without Mattt. This fork is written and maintained by Jason Garber.
The following projects work well in conjunction with sinatra-param:
- sinatra-contrib (documentation)
- rack-contrib (
Rack::NestedParams
andRack::PostBodyContentTypeParser
in particular)
sinatra-param is freely available under the MIT License. Use it, learn from it, fork it, improve it, change it, tailor it to your needs.