Skip to content

Parameter Validation, Transformation, and Type Coercion for Sinatra applications.

License

Notifications You must be signed in to change notification settings

jgarber623/sinatra-param

 
 

Repository files navigation

sinatra-param

Parameter Validation, Transformation, and Type Coercion for Sinatra applications.

Build

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 Strings 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.

Key Features

  • Provides param helper for defining, transforming, and validating parameters.
  • Provides any_of, one_of, and all_or_none_of helpers for advanced parameter validations.
  • Supports Ruby 2.7 and newer.

Table of Contents

Getting Started

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.

Installation

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"

Usage

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 Symbols or Strings).

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

Parameter Types

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 Strings. Additional type coercion on elements in the Hash should be handled by your application code.

Validations

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.

Validations Footnotes

¹ 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.

Defaults

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

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) }

Additional Helpers

any_of

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

one_of

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

all_or_none_of

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

Exception Handling

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.

Custom Messages

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!.

Exception Classes

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 the all_or_none_of and any_of helpers)
  • Sinatra::Param::TooManyParametersError (raised by the one_of helper)

Additionally, Sinatra::Param::ArgumentError is raised if implementation errors are encountered (e.g. mismatches between a parameter type and its default value).

Configuring Exception Handling

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

Acknowledgments

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:

License

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.

About

Parameter Validation, Transformation, and Type Coercion for Sinatra applications.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages

  • Ruby 100.0%