0.0
No commit activity in last 3 years
No release in over 3 years
Yet another validation library
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 2.0
~> 10.0
~> 3.0
 Project Readme

St. Validation

https://badge.fury.io/rb/st_validation.svg https://travis-ci.org/Nondv/st_validation.rb.svg?branch=master

Incredibly simple and customisable validation DSL

is_valid_user = StValidation.build(
  id: Integer,
  name: String,
  age: ->(x) { x.is_a?(Integer) && (0..150).cover?(x) },
  favourite_food: [String],
  dog: Set[NilClass, { name: String, age: Integer, breed: Set[NilClass, String] }]
)

is_valid_user.call(
  id: 123,
  name: 'John',
  age: 18,
  favourite_food: %w[apples pies],
  dog: { name: 'Lucky', age: 2 }
)

# ===> true

Table of Contents <– :TOC: –>

  • Installation
  • Usage
    • Terms
    • Default syntax
      • Classes
      • Sets (unions)
      • Arrays
      • Hashes
        • When we don’t care about additional keys
      • Misc
        • Boolean
        • Maybe (optional values)
    • Tinkering DSL
      • Important note!
    • explain
    • Testing
  • Contributing
  • License

Installation

gem 'st_validation'

Usage

Terms

  • validator - proc-predicate or an object from `StValidation::AbstractValidator` family and used for validating an object. This is what you want to get from this gem in the end.
  • factory - refers to a `StValidation::ValidatorFactory` and transforms blueprints into validators by given set of transformations.
    • blueprint - a validator or something that can be transformed into a validator by factory.
    • transformation - function f(blueprint, factory) returning a blueprint. The core of the DSL itself.

Default syntax

The default syntax is not final. I’m still trying to figure out the best DSL to use. It needs to be minimal but yet practical and practicality is hard without being too complex.

To see some examples, read DSLs specs.

Current default DSL is described below. All of these are blueprints. Some of them are composable, e.g arrays and hashes.

Classes

By using a class as a blueprint the result validator will check if an object belongs to the class.

is_int = StValidation.build(Integer)
is_int.call(123) # ==> true
is_int.call('123') # ==> false

Sets (unions)

Checks if a value matches any provided blueprint.

is_str_or_int = Set[String, Integer]
is_str_or_int.call(123) # ==> true
is_str_or_int.call('123') # ==> true

Arrays

Arrays are defined via [<blueprint>]. The result validator checks if its every element matches blueprint. Note that array should be of exactly one element.

is_bool_array = StValidation.build([Set[TrueClass, FalseClass]])
is_bool_array.call(true) # ==> false
is_bool_array.call([]) # ==> true
is_bool_array.call([false]) # ==> true

Hashes

Quite naturally, hashes just check if every key matches a blueprint.

is_user = StValidation.build(
  id: Integer,
  email: String,
  info: { first_name: String,
          last_name: String }
)

When we don’t care about additional keys

There’s a HashSubsetValidator for that. It checks only provided keys.

is_user = StValidation::Validators::HashSubsetValidator.new(
  id: Integer,
  email: String,
  info: { first_name: String,
          last_name: String }
)

is_user.call(
  id: 123,
  email: 'user@example.com',
  info: { first_name: 'John', last_name: 'Doe' },
  phone: '+123456',
  notes: 'Loves beer'
)
# ==> true

Misc

Boolean

Ruby doesn’t have a class for bool value. Instead, it has TrueClass and FalseClass which we can use with in a set:

is_bool = Set[TrueClass, FalseClass]

Maybe (optional values)

Again, sets are to rescue:

maybe_int = Set[NilClass, Integer]

Tinkering DSL

The ultimate goal of the factory is to return a validator. In order to generate a validator from a blueprint is to transform it.

Factory instance has a collection of transformations. Each of them is applied to a blueprint until there’s no transformations done.

Let’s introduce some sugar syntax for booleans.

factory = StValidation.default_factory.with_extra_transformations(
  ->(bp, factory) { bp == :bool ? Set[TrueClass, FalseClass] : bp  }
)

is_user = factory.build(
  name: String,
  loves_beer: :bool
)

is_user.call(name: 'John Doe', loves_beer: true) # ==> true

Important note!

A blueprint goes through all transformations. The process stops when no transformation changed the blueprint.

Do not rely on order; it’s not guarantueed.

explain

For development purposes there’s a #explain method defined in StValidation::AbstractValidator. The purpose of it is to show why a value didn’t pass validation.

For your custom validators you should implement #generate_explanation(value) method.

validator = StValidation.build(
  id: Integer,
  email: String,
)

validator.explain(
  id: '123',
  email: 'user@example.com'
)
# ==> { id: 'Expected Integer got String' }

Testing

There’s a rspec matcher:

require 'st_validation/rspec'

RSpec.describe 'user hash' do
  it 'matches schema' do
    user = build_user_hash
    expect(user).to pass_st_validation(
      id: Integer,
      name: String,
      age: Set[NilClass, Integer]
    )
  end
end

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Nondv/st_validation.rb

License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).