Skip to content

Workday/validate_my_routes

Repository files navigation

ValidateMyRoutes

Build Status Gem Version

Parameter Validation for Sinatra

REST specification defines how to consume web API, however it does not provide specification for parameters and API consumers are left with guessing how to use each specific endpoint.

This gem provides simple way to validate parameters for Sinatra routes.

Validation is done automatically before trying to execute route block, and if it fails, consumer receives response with status code 4** and validation failure message, explaining what was expected from parameters.

Installation

ValidateMyRoutes can be installed via RubyGems:

$ gem install validate_my_routes

Or compiling and installing it yourself as:

$ gem build validate_my_routes.gemspec
$ gem install validate_my_routes-VERSION.gem

Usage

Using ValidateMyRoutes is quite straightforward and it basically requires two steps:

  • register ValidateMyRoutes::Validatable extension in your Sinatra application
  • define parameters with validation

Example

require 'sinatra/base'
require 'validate_my_routes'

class MyApp < Sinatra::Base
  # register validate my routes Sinatra extension
  register ValidateMyRoutes::Validatable
  # add validation rules on class level
  extend ValidateMyRoutes::ValidationRules

  param_validation :pet_type, from_enum(%w(cat dog))
  get '/pet/:pet_type' do |pet_type|
    "Here is your pet: #{pet_type}"
  end
end

Rack::Handler::WEBrick.run MyApp.new

As a result consumer of such API will receive:

GET http://example.org/pet/dog
> HTTP/1.1 200 OK
> Here is your pet: dog

GET http://example.org/pet/bird
> HTTP/1.1 404 Not Found
> parameter <pet_type> was expected to have one of following values: <cat, dog>, but was <bird>

For more examples check out examples folder in the repository that contains plenty more of examples: here

Parameters validation

ValidateMyRoutes can validate parameters from the path by their name:

param_validation :order_id, of_type(Integer)

or validate all parameters for the route:

all_params_validation at_least_one_of(%i[order_type order_status])

Note: validating single parameter from path by name will validate all routes defined after such validation that contain defined name. i.e. if you define:

param_validation :id, of_type(Integer)
get('/pet/:id') { '' }
get('/order/:id') { '' }

ValidateMyRoutes will validate both routes. If order route should not have the same validation for id parameter make sure it has a different name (i.e. :order_id instead of :id)

Validation rules

Parameter validation is done by using validation rules:

  param_validation :order_id, of_type(Integer)
  get '/orders/:order_id' do
    ...
  end

In the example you can see of_type(Integer) validation rule to validate the type of parameter:

GET http://example.org/orders/5
> HTTP/1.1 200 OK
> Your order is waiting an aproval

GET http://example.org/orders/foo
> HTTP/1.1 404 Not Found
> was expected pet_id parameter to be of a type <Integer>, but was <foo>

The gem provides built-in validation rules for path parameters validation by name like of_type(Integer) (you can find the full list with examples in autogenerated documentation)

And there are built-in validation rules for all parameters validation for single route like required(:order_status) (you can find the full list with examples in autogenerated documentation)

The gem provides built-in validation rules for all parameters validation:

  • required(param_name) - fails if parameter was not provided
  • only_one_of(list_of_names) - mutually exclusive optional parameters
  • exactly_one_of(list_of_names) - mutually exclusive with at least one provided
  • at_least_one_of(list_of_names) - non-exclusive parameters with at least on provided

Rule combinators

To use multiple validation steps for single parameter ValidateMyRoutes provides combinators for rules, for example:

param_validation :id, of_type(Integer).and(eql('5'))

get '/pet/:id' do |id|
  "Pet id #{id} is Integer and is equal to '5'"
end

You can find the full list with examples in autogenerated documentation

Types transformations for further validation

Some validation rules require parameter value to be converted to some type. For example validation rule between(2, 10) expects parameter value to be Integer so it can be compared with 2 and 10.

For such case ValidateMyRoutes provides transformation rules:

  • value_as(TYPE, OTHER_RULE) - transforms parameter value to type TYPE and then performs validation of OTHER_RULE with transformed value. In case when convertion to specified type fails validation will fail with type validation failure
  • transform(TRANSFORMATION, OTHER_RULE) - transforms parameter value by calling TRANSFORMATION procedure with parameter value and performs validation of OTHER_RULE with transformed value
param_validation :order_id, value_as(Integer, greater_than(5))

and with custom transformation:

to_order = ->(order_type) { OrderTypesFactory.load(order_type) }
param_validation :order_type, transform(to_order, order_validator)

For more complete examples please check out here

Note: transformation is applied for parameter values only for validation. ValidateMyRoutes will not change the type or transform the value that will be passed in the route. For more information on why check out here

Conditional validation

In Sinatra, you can use conditions to determine what route to use depending on parameters value. ValidateMyRoutes provides a special rule conditional(RULE) to inform Sinatra to try another route with the same path if validation failed instead of returning validation error to the consumer.

Example:

all_params_validation conditional(required(:q))
get '/pet' do
  'request contains parameter <q> so it is a search request'
end

get '/pet' do
  'no <q> parameter provided so we want to get a list of all pets'
end

In this example, Sinatra will try validation for the first route, and if parameter q is not present, it will try second route because validation for the parameter q is conditional.

More information can be found in autogenerated documentation

Inline validation

It is possible to perform validation of any value when executing route code block. For this you need:

  • use ValidateMyRoutes::Validate.validate with validation rule and value you want to validate, following by a block with action to perform if validation fails

Example:

post '/some_route' do
  ValidateMyRoutes::Validate.validate(of_type(Integer), params['my_param']) do |msg|
    halt 400, "my_param validation failed with: #{msg}"
  end

  'my_param is valid'
end

More information can be found in autogenerated documentation

Custom validation rules

ValidateMyRoutes provides a DSL for creating validation rules for single parameter validation:

extend ValidateMyRoutes::ValidationRules

def_single_param_validator :custom_eql do |expected|
  validate { |actual, name| actual == expected }
end

Or for all parameters validation:

extend ValidateMyRoutes::ValidationRules

def_all_params_validator :custom_required do |expected|
  validate { |params| params.key? expected }
end

More information can be found in autogenerated documentation

Extra notes

Architecture notes can be found here

Changelog can be found here

Thoughts behind ValidateMyRoutes and why is it design in this way can be found here

About

Parameter Validation for Sinatra

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages