Project

oval

0.0
No commit activity in last 3 years
No release in over 3 years
Validate options when passed to methods
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

 Project Readme

#Oval - Options Validator

Build Status Coverage Status Code Climate

####Table of Contents

  1. Overview
  2. Module Description
  3. Usage
    • Example 1: Declaring simple options
    • Example 2: Separating declaration from validation
  4. Reference
    • Declarators
    • API Reference
  5. Limitations

##Overview

Validate arguments and option hashes when passed to methods.

[Table of Contents]

##Module Description

This module implements simple to use data validators. It was initially thought to validate option hashes (so the name Oval stands for Options' Validator), but it appeared early that it's suitable to validate arbitrary parameters (variables).

The shape of acceptable data is described by a simple grammar. The validation is then carried out by a recursive-descent parser which matches actual values provided by caller to declarators that comprise the declaration of acceptable values.

A declaration consists of terminal and non-terminal declarators. Most Oval methods with ov_xxx names are non-terminal declarators. All other values (such as :symbol, 'string', nil, or Class) are terminals. Terminals use == operator to match the values provided by caller. Non-terminal use its own logic introducing more elaborate matching criteria (see for example ov_collection).

Oval raises Oval::DeclError if the declaration is not well-formed. This is raised from the point of declaration. Other, more common exception is the Oval::ValueError which is raised each time the validation fails. This one is raised from within a method which validates its arguments.

[Table of Contents]

##Usage

The usage is basically a two-step procedure. The first step is to declare data to be validated. This would create a validator object. The second step is to validate data using the previously constructed validator. For simple cases the entire construction may fit to a single line. Let's start with such a simple example.

###Example 1: Declaring Simple Options

The method foo in the following code accepts only {} and {:foo => value} as ops hash, and the value may be anything:

# Options validator
require 'oval'
class C
  extend Oval
  def self.foo(ops = {})
    Oval.validate(ops, ov_options[ :foo => ov_anything ], 'ops')
  end
end

What does it do? Just try it out:

C.foo # should pass
C.foo :foo => 10 # should pass
C.foo :foo => 10, :bar => 20 # Oval::ValueError "Invalid option :bar for ops. Allowed options are :foo"

Options are declared with ov_xxx declarators. The ov_options, for example, declares a hash of options. In ov_options all the allowed options should be listed inside of [] square brackets. Keys may be any values convertible to strings (i.e. a key given in declaration must respond_to? :to_s). Values are declared recursively using ov_xxx declarators or terminal declarators (any other ruby values).

In Example 1 we have declared options inside of a method for simplicity. This isn't an optimal technique. Usually options' declaration remains same for the entire lifetime of an application, so it is unnecessary to recreate the declaration each time function is called. In other words, we should move the declaration outside of the method, convert it to a singleton and only validate options inside of a function. For that purpose, the Example 1 could be modified to the following form

###Example 2: Separating declaration from validation

In this example we separate options declaration from the validation to reduce costs related to options declaration:

# Options validator
require 'oval'
class C
  extend Oval
  # create a singleton declaration ov
  def self.ov
    @ov ||= ov_options[ :foo => ov_anything ]
  end
  # use ov to validate ops
  def self.foo(ops = {})
    Oval.validate(ops, ov, 'ops')
  end
end

[Table of Contents]

##Reference

###Declarators

A declaration of data being validated consists entirely of what we call declarators. The grammar for defining acceptable data uses non-terminal and terminal declarators. Non-terminal declarators are most of the ov_xxx methods of Oval module, for example ov_options. General syntax for non-terminal declarator is ov_xxx[ args ], where args are declarator-specific arguments.

Terminal declarators include all the other ruby values, for example nil. They are matched exactly against data being validated, so if the data doesn't equal the given value an exception is raised.

In what follows, we'll document all the core declarators implemented in Oval.

###Index of Declarators

  • ov_anything
  • ov_collection
  • ov_instance_of
  • ov_kind_of
  • ov_match
  • ov_one_of
  • ov_options
  • ov_subclass_of

####ov_anything

  • Declaration

    ov_anything

    or

    ov_anything[]
  • Validation - permits any value

  • Example

    require 'oval'
    class C
      extend Oval
      def self.ov
        @oc = ov_options[ :bar => ov_anything ]
      end
      def self.foo(ops = {})
        Oval.validate(ops, ov, 'ops')
      end
    end
    C.foo() # should pass
    C.foo :bar => 10 # should pass
    C.foo :bar => nil # should pass
    C.foo :bar => 'bar' # should pass
    C.foo :foo => 10, :bar => 20 # Oval::ValueError "Invalid option :foo for ops. Allowed options are :bar"

[Table of Contents|Index of Declarators]

####ov_collection

  • Declaration

    ov_collection[ class_decl, item_decl ]
  • Validation - permits only collections of type class_decl with items matching item_decl declaration

  • Allowed values for class_decl are:

    • Hash or Array or any subclass of Hash or Array,
    • ov_subclass_of[klass] where klass is Hash or Array or a subclass of any of them.
  • Allowed values for item_decl:

    • if class_decl is Array-like, then any value is allowed as item_decl,
    • if class_decl is Hash-like, then item_decl should be a one-element Hash in form { key_decl => val_decl }.
  • Example

    require 'oval'
    class C
      extend Oval
      def self.ov_h
        ov_collection[ Hash, { ov_instance_of[Symbol] => ov_anything } ]
      end
      def self.ov_a
        ov_collection[ Array, ov_instance_of[String] ]
      end
      def self.foo(h, a)
        Oval.validate(h, ov_h, 'h')
        Oval.validate(a, ov_a, 'a')
      end
    end
    C.foo({:x => 10}, ['xxx']) # Should bass
    C.foo({:x => 10, :y => nil}, ['xxx', 'zzz']) # Should pass
    C.foo(10,['xxx']) # Oval::ValueError, "Invalid value Fixnum for h.class. Should be equal Hash"
    C.foo({:x => 10, 'y' => 20}, [ 'xxx' ]) # Oval::ValueError, 'Invalid object "y" of type String for h key. Should be an instance of Symbol'
    C.foo({:x => 10}, 20) # Invalid value Fixnum for a.class. Should be equal Array
    C.coo({:x => 10}, [ 'ten', 20 ]) # Oval::ValueError, "Invalid object 20 of type Fixnum for a[1]. Should be an instance of String"

[Table of Contents|Index of Declarators]

####ov_instance_of

  • Declaration

    ov_instance_of[klass]
  • Validation - permits only instances of a given class klass

  • Allowed values for klass - only class names, for example String, Hash, etc.

  • Example

    require 'oval'
    class C
      extend Oval
      def self.ov
        ov_instance_of[String]
      end
      def self.foo(s)
        Oval.validate(s, ov, 's')
      end
    end
    C.foo('bar') # Should pass
    C.foo(10) # Oval::ValueError, "Invalid object 10 for s. Should be an instance of String"

[Table of Contents|Index of Declarators]

####ov_kind_of

  • Declaration

    ov_kind_of[klass]
  • Validation - permits only values that are a kind of given class klass

  • Allowed values for klass - only class names, for example String, Hash, etc.

  • Example

    require 'oval'
    class C
      extend Oval
      def self.ov
        ov_kind_of[Numeric]
      end
      def self.foo(n)
        Oval.validate(n, ov, 'n')
      end
    end
    C.foo(10) # Should pass
    C.foo(10.0) # Should pass
    C.foo('10') # Oval::ValueError, 'Invalid object "10" of type String for n. Should be a kind of Numeric'

[Table of Contents|Index of Declarators]

####ov_match

  • Declaration

    ov_match[re]
  • Validation - permits only values matching regular expression re,

  • Allowed values for re - must be a kind of Regexp.

  • Example

    require 'oval'
    class C
      extend Oval
      def self.ov
        # Only valid identifiers are allowed as :bar option
        ov_match[/^[a-z_]\w+$/]
      end
      def self.foo(name)
        Oval.validate(name, ov, 'name')
      end
    end
    C.foo('var_23') # Should pass
    C.foo(10) # Oval::ValueError, "Invalid value 10 for name. Should match /^[a-z_]\\w+$/ but it's not even convertible to String"
    C.foo('10abc_') # Oval::ValueError, 'Invalid value "10abc_" for name. Should match /^[a-z_]\\w+$/'

[Table of Contents|Index of Declarators]

####ov_one_of

  • Declaration

    ov_one_of[decl1,decl2,...]
  • Validation - permits only values matching one of declarations decl, decl2, ...

  • Example

    require 'oval'
    class C
      extend Oval
      def self.ov
        ov_one_of[ ov_instance_of[String], ov_kind_of[Numeric], nil ]
      end
      def self.foo(x)
        Oval.validate(x, ov, 'x')
      end
    end
    C.foo('str')  # Should pass
    C.foo(10)     # Should pass
    C.foo(10.0)   # Should pass
    C.foo(nil)    # Should pass
    C.foo([])     # Oval::ValueError, "Invalid value [] for x. Should be an instance of String, be a kind of Numeric or be equal nil"

[Table of Contents|Index of Declarators]

####ov_options

  • Declaration

    ov_options[ optkey_decl1 => optval_decl1, ... ]
  • Validation - permits only declared options and their values.

  • Allowed values for optkey_declN - anything that is convertible to string (namely, anything that responds to to_s method).

  • Example:

    ov = ov_options[ 
      :bar => ov_anything,
      :geez => ov_instance_of[String],
      # ...
    ]
    def foo(ops = {})
      Oval.validate(ops, ov, 'ops')
    end 

[Table of Contents|Index of Declarators]

####ov_subclass_of

  • Declaration

    ov_subclass_of[klass]
  • Validation - permits only subclasses of klass

  • Allowed values for klass - only class names, for example String, Hash, etc.

  • Example

    require 'oval'
    class C
      extend Oval
      def self.ov
        ov_options[ :bar => ov_subclass_of[Numeric] ]
      end
      def self.foo(ops = {})
        Oval.validate(ops, ov, 'ops')
      end
    end
    C.foo :bar => Integer   # Should pass
    C.foo :bar => Fixnum    # Should pass
    C.foo([])               # Oval::ValueError, "Invalid options [] of type Array. Should be a Hash
    C.foo :foo => Fixnum    # Oval::ValueError, "Invalid option :foo for ops. Allowed options are :bar"
    C.foo :bar => 10        # Oval::ValueError, "Invalid class 10 for ops[:bar]. Should be subclass of Numeric"

###API Reference

API reference may be generated with

bundle exec rake yard

The generated documentation goes to doc/ directory. Note that this works only under ruby >= 1.9.

The API documentation is also available online.

[Table of Contents]

##Limitations

  • API documentation is currently very poor,

[Table of Contents]